NSByteStore
Inherits From: NSObject
Conforms To: NSObject (NSObject)
Declared In: Foundation/NSByteStore.h
Class Description
An NSByteStore object manages a single memory-based heap. Use NSByteStore to allocate storage in data-intensive applications. Its main feature is transaction management, which makes compound operations atomic and ensures data integrity.
You address the blocks of storage that an NSByteStore manages through unsigned integers called block numbers. To gain access to the contents of a block, you first must open the block for reading or writing. When you open a block, the NSByteStore resolves the block number into a pointer. While a block is open, you can address its contents using the pointer and can safely assume that the block won't move. Once you close the block, however, the NSByteStore is free to move it in order to compact storage; so the pointer may become invalid.
The contents of an NSByteStore are relocatable to and from other instances of NSByteStore and its subclasses. Although the address of a block becomes invalid when the block is relocated, its block number remains constant. Since block numbers are indirect references to data, it's possible to retrieve the contents of an NSByteStore without invalidating block number-based referential data structures residing in the NSByteStore, like linked lists or trees. This makes it easy to copy complex structures or to quickly save them to a file.
A subclass of NSByteStore, NSByteStoreFile, stores data in a file so that you can retain data created and changed by your application. For more information, see its class description.
Transactions
NSByteStore implements transactions, allowing several operations to be grouped together in such a way that either all of them take effect, or none of them do. Transactions help to ensure semantic integrity by making compound operations atomic, and they provide a convenient way to undo a series of changes. If you use NSByteStoreFile, the use of transactions also ensures data integrity against process and system crashes. This means that if a system loses power, the NSByteStoreFile's contents can be recovered intact on power up, in the state they were in after the last transaction that actually finished.
Transactions are either enabled or disabled for an object. Most likely, you will want to disable transactions for NSByteStores (unless you want the undo capability) and enable them for NSByteStoreFiles. When transactions are enabled, NSByteStore copies blocks that your application opens for writing. Thus, updates are slower when transactions are enabled. If you are using NSByteStore directly, its contents are always destroyed by a system crash, so the only advantage to using transactions is the undo capability. If you are using NSByteStoreFile, enabling transactions may save some of the changes made before a system crash. Therefore, you should always use transactions with NSByteStoreFile except if it contains data that can be easily reconstructed, such as an index.
Using Transactions
A single transaction begins with a startTransaction message and ends with either a commitTransaction or abortTransaction message. startTransaction enables transactions if they are disabled. Sending commitTransaction means you want the changes made by this transaction to take effect. abortTransaction means you want to cancel the changes made by this transaction.
You can check whether transactions have been enabled with areTransactionsEnabled. You may want to do this if your code is invoked by higher level methods that determine the transaction management policy for the application. For example, NSByteStore uses areTransactionsEnabled to determine whether or not to invoke startTransaction before responding to an empty message.
You can nest transactions. The first startTransaction message (or the first message that opens a block after enableTransactions) starts transaction 1. If you send startTransaction again before ending transaction 1, it begins transaction 2, which is nested inside transaction 1. The nestingLevel method returns the current nesting level of transactions. startTransaction also returns the nesting level as the transaction's ID.
The trick with nesting transactions is: the changes a transaction makes aren't really made until the nesting level returns to 0. In other words, changes don't actually take effect until the top-level transaction is committed. This means that any blocks that any of the transactions have opened for writing will not be available until the all of the transactions are finished. So, if you start a transaction at nesting level 2, make some changes to blocks 3, 5, and 7, and then you send commitTransaction, all that commitTransaction really does is set the nesting level to 1 and tell transaction 1 about the changes to blocks 3, 5, and 7. If you then send commitTransaction at transaction 1, commitTransaction sets the nesting level to 0. Because the nesting level is now 0, the changes can take place. Blocks 3, 5, and 7 are overwritten with the changes made during transaction 2 and are made available. If instead you decide to abort transaction 1 (by sending abortTransaction), the changes transaction 2 made to blocks 3, 5, and 7 are cancelled, as well as any changes transaction 1 made to any blocks. In this way, the parent of a transaction can undo changes made by their children, but the children cannot undo the changes made by their parents.
Note that if your code makes changes outside any transaction while transactions are enabled, an enclosing transaction is started automatically. The next invocation of startTransaction, if any, before an intervening abort or commit, simply picks up this enclosing transaction and reports a nesting level of 1. Thus, if nesting isn't needed, your code can simply enable transactions initially with a pair of startTransaction/commitTransaction messages, and thereafter use only commitTransaction to mark transaction boundaries. New transactions implicitly begin with the first modification following each commit.
Any modifications that haven't been committed are aborted when an NSByteStore is freed.
Opening Blocks for Reading or Writing
When you open a block for reading or writing, that block is unavailable until you specify that you are finished.
When you are finished reading a block, you send closeBlock:. Any method that accesses information about a block opens it for reading. This means not only does readBlock:range: open a block for reading, but so does sizeOfBlock:, which returns the block's size. The copyBlock: method opens the block for reading, but it also closes it when finished (unless you already had that block opened for reading). Even if you commit a transaction before you send closeBlock:, the block remains open for reading.
Any method that changes a block's contents opens the block for writing. This means not only does openBlock:range: open a block for writing, but so do the methods copyBytes:toBlock:range:, createBlockOfSize:, and freeBlock:. You indicate that you are finished with a block you have open for writing by having its changes take effect. Closing the block with closeBlock: does not make your changes take effect, even if transactions are disabled. Regardless of whether transactions are enabled or disabled, you must send commitTransaction to have your changes actually be made.
If transactions are disabled, commitTransaction commits all the changes made to blocks since that last commitTransaction or abortTransaction message was sent. abortTransaction cancels all the changes made since the last commitTransaction.
Creating an NSByteStore