Copyright ©1995 by NeXT Computer, Inc. All Rights Reserved.
|The IXBlockAndStoreAccess protocol defines methods for initializing and freeing store clients. A store client is any object that uses data in an IXStore to provide higher level services. IXBlockAndStoreAccess defines methods based on IXStore block handles for creating store clients, for initializing store clients from existing data, and for destroying store clients. A related protocol, IXNameAndFileAccess, defines methods for accessing store clients by name instead of by handles.
A store client is different from most Objective C objects in that it uses data which can outlive it, but which is considered an integral part of the store client itself. Unlike objects unarchived from an Interface Builder nib file, which retain no connection to that file, a store client uses its store to actively manage a persistent component of its state.
A store client can be initialized from scratch, or it can be initialized from data created by a previous instance; the second type of initialization is called reconstituting or opening a store client. When a store client is freed, only its run-time state is destroyed; the data in the store remains intact, ready to be used by a later store client instance. A store client can also completely destroy itself by removing its data from the store in addition to freeing its run-time data.
When a store client is initialized from scratch, it's given an IXStore in which to keep its persistent data. One of the first things it does is create a block in that IXStore. This "boot block" can contain the handles of other blocks, making it a single point of entry for reconstituting later instances. A later instance can use that single block to retrieve all of the persistent data created by the original instance.
Temporary Store Clients
In addition to the methods in this protocol, you may find it convenient to implement a simple init method that initializes a store client for temporary use by creating an IXStore private to that instance, and which that instance will free when it receives a free message. In such a case, of course, the store client will essentially be like most other objects; its state won't be persistent, but will be freed when it is.
Closing a Store
Before a store is closed (that is, before the IXStore object is sent a free message), all of the store clients should be properly cleaned up and freed. This involves freeing the store clients, sending either abortTransaction or commitTransaction if needed to the IXStore until all transactions are completed, and finally, freeing the IXStore object.
It's important to complete all transactions before freeing the store, since a store client may actually be working with the store. If the store is actually an IXStoreFile, changes made since the store file was opened aren't flushed when the IXStoreFile is freed; pending transactions have to be explicitly completed beforehand, or they're all effectively aborted.
|Initializing and freeing a client||initInStore:|
|Retrieving the block and store||getBlock:andStore:|
|+ freeFromBlock:(unsigned int)aHandle inStore:(IXStore *)aStore|
|Removes from aStore all storage for the client whose boot block is identified by aHandle. Normally, your code would have to instantiate a client for the data in the block identified by aHandle and send it a freeFromStore message. This method provides a convenient way to remove an object from an IXStore without your code having to allocate and initialize it. Returns self.
One way to implement this method is to create an instance of the client class, reconstitute it from aHandle, and free it from the store. Here's a simple example, without any error handling:
+ freeFromBlock:(unsigned int)aHandle inStore:(IXStore *)aStore
[[[self alloc] initFromBlock:aHandle inStore:aStore]
|Classes whose instances normally perform a lot of time-consuming initialization should implement a lightweight initialization method, which prepares the instance only to access its storage for efficient removal from its IXStore.
If your store client class only creates a single block in its IXStore, you can implement this method by simply freeing that block:
+ freeFromBlock:(unsigned int)aHandle inStore:(IXStore *)aStore
|See also: freeFromStore|
|Removes the receiver's storage from its IXStore and frees the run-time object. A store client's free method simply frees the run-time object without affecting any data in the IXStore. Returns nil.
See also: + freeFromBlock:inStore:, free (Object)
|getBlock:(unsigned int *)aHandle andStore:(IXStore **)aStore|
|Returns by reference the handle of the receiver's boot block, and its IXStore. Also returns self.
Since a store client needs to record its boot block handle and its IXStore to function properly, implementing this method is simply a matter of putting those values into aHandle and aStore.
Note: Though note part of the IXBlockAndStoreAccess protocol, a store method is defined by many store clients, providing a convenient shorthand for sending transaction management messages to the client's store:
[[aClient store] commitTransaction];
|initFromBlock:(unsigned int)aHandle inStore:(IXStore *)aStore|
|Initializes the receiver using existing data from the boot block identified by aHandle in aStore. That block should have been created by a previous invocation of the initInStore: method on the original instance of the store client. The receiver isn't required to be of the same class as the original creator of the store data, but it must be able to make sense of that data. Returns self if successful, or nil if the receiver can't be initialized (for example, if aHandle doesn't exist in aStore).
A block handle of zero should be interpreted as a request for the creation of a new store client. This allows the store client class' implementation of the initInStore: method to simply send initFromBlock:inStore: to self with a block handle of zero.
To implement this method, simply access the data in aHandle to set up a usable state for the client instance. This may involve opening other blocks whose handles are stored in the boot block. For example IXBTree implements this method to read the block at aHandle and to check that the contents of that block form the root node of a BTree.
Note: While a store client instance exists, it's considered to own the associated state in the IXStore. Your code should never use this method a second time with a specific boot block unless it's known for certain that any previous instance using that data has been freed (or that both instances will be using the storage for read-only access). If a second store client is initialized from the same block as an active client, the data associated with it will probably be corrupted, since there is no means provided in the Indexing Kit for synchronizing changes made by the two instances.
See also: initInStore:
|Initializes the receiver, creating a new boot block in store. After initialization, the boot block can be used to hold the receiver's data. That block's handle can be retrieved with getBlock:andStore:. Returns self if successful, or nil if the receiver can't initialize itself.
To implement this method, simply create a block in aStore, record its handle as the boot block, and store whatever initialization values your client may need there. If your client needs to use several blocks within aStore, it can also create those, and store their handles in its boot block. This allows a later instance to retrieve those blocks when it receives an initFromBlock:inStore: message. For example, IXBTree implements this method by creating a block and formatting it as the root node of a BTree; it creates more blocks only as it needs them.
This method is usually implemented to send initFromBlock:inStore: to self with a block handle of zero, since a block handle of zero is interpreted as a request to create a new store client.
See also: initFromBlock:inStore: