Copyright ã1994 by NeXT Computer, Inc.  All Rights Reserved.

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

+ (NSByteStore *)byteStore	Returns a new NSByteStore with transactions disabled.

Managing the NSByteStore

- (unsigned)count	Returns the number of blocks in the NSByteStore at transaction level 0. That is, if you have created or freed some blocks but those changes have not been committed at transaction level 0, count will not reflect those changes.

- (void)empty	Frees all blocks of memory in the NSByteStore. If transactions are enabled, this method starts and commits a new transaction. 

- (void)getBlocks:(unsigned *)blocks	Returns in blocks a C-style array of block numbers at transaction level 0. The caller must free the returned array.

- (unsigned)rootBlock	Returns the number of the root block, which by convention is used as a table of contents or a directory. 

Creating, Copying, and Freeing Blocks

- (unsigned)createBlockOfSize:(unsigned)size 	Returns a block number for a new block of size bytes with the contents initialized to zero. Creating a block with size 0 is allowed. 

- (unsigned)copyBlock:(unsigned)aBlock 	Returns a block number for a new block whose size and 
range:(NSRange)range		contents are identical to the memory region in block aBlock specified by range.

- (void)freeBlock:(unsigned)aBlock	Removes and frees the block aBlock.

Opening and Closing Blocks

- (void *)openBlock:(unsigned)aBlock 	Opens for writing the memory region in block aBlock 
range:(NSRange)range 		specified by range. A pointer to the region is returned.

- (const void *)readBlock:(unsigned)aBlock 	Opens for reading the memory region in block aBlock. 
range:(NSRange)range 		specified by range. A pointer to the region is returned.

- (void)closeBlock:(unsigned)aBlock	Closes the block aBlock.

Managing Block Sizes

- (void)resizeBlock:(unsigned)aBlock 	Resizes the block aBlock to size bytes. This 
toSize:(unsigned)size		method may change the location of the block as well.

- (unsigned)sizeOfBlock:(unsigned)aBlock	Returns the size in bytes of the block aBlock.

Using Transactions

- (unsigned)startTransaction	Begins a new transaction, enabling transactions if necessary, for the current context. This transaction will be aborted or committed before all other outstanding transactions. Returns a number that both identifies the new transaction and indicates the number of transactions outstanding.

- (void)abortTransaction	Reverts the NSByteStore to the state it was in before the last startTransaction message or the last commitTransaction message. Any blocks that had been opened are made available to other store contexts.

- (void)commitTransaction	Commits all changes made to blocks opened since the last startTransaction or the last commitTransaction and closes those blocks. If transactions are disabled or the nesting level becomes 0, this method makes all of the changed blocks available to other contexts. 

- (BOOL)areTransactionsEnabled	Returns YES if transactions are enabled for the NSByteStore, NO if not. Transactions are enabled by the method startTransaction.

- (unsigned)nestingLevel	Returns the number of transactions pending against the NSByteStore. 

- (unsigned)changeCount	Returns the number of changes made to the NSByteStore's contents since it was initialized. This number equals the number of commitTransaction and abortTransaction messages the NSByteStore has received.

Changing the Contents

- (void)copyBytes:(const void *)newData 	Copies the series of bytes pointed to by newData into the 
toBlock:(unsigned)aBlock 		memory region in block aBlock specified by range. This 
range:(NSRange)range		method will expand the block's size if the data will not fit in the location specified by range. 

- (NSData *)contentsAsData	Creates a virtual memory image of the NSByteStore. 

- (void)replaceContentsWithData:(NSData *)data 	Replaces the contents of the NSByteStore with virtual memory image data. This method ignores and erases any pending writes to the NSByteStore.