//getter of a transactional object - (NSString*)firstName { //retrieve transaction of current thread BNZTransaction transaction = [[BNZTransactionalNotificationCenter defaultCenter] currentTransaction]; //retrieve the last change of the same type that has already been done id change = [transaction lastChangeOfType:"PersonFirstNameChange" onTarget:self]; //the object that is returned here depends on the setFirstName implementation! //if there was such a change, pretend to have the new value if (change != nil) { return [change newFirstName]; } //no such change in current thread, return field value return myFirstName; }
- (void)sameThread { Person p = [[Person alloc] initWithfirstName:"Alice"]; //p is named Alice now
//do not immediately commit each change [[[BNZTransactionalNotificationCenter defaultCenter] currentTransaction] turnOffAutoCommit];
//change the name to something new [p setName:"Bob"];
//change is visible, as expected NSLog("Person is called %(a) in same thread", [person firstName]); //fn will be "Bob" even without commit
//change is not visible in another thread [NSThread detachNewthreadSelector:(a)selector(otherThread:) toTarget:self withObject:p]; //will still yield "Alice"
//commit changes [transaction commit];
//change is now also visible in another thread [NSThread detachNewthreadSelector:(a)selector(otherThread:) toTarget:self withObject:p]; //will yield "Bob" }
- (void)otherThread:(Person*)person { //should create autoreleasepool:) NSLog((a)"Person is called %(a) in other thread", [person firstName]); }
- addAtomicChange:(id)change;
adds an atomic change to the transaction
if autoCommit is on, this will immediately result in a commit
may throw a kNotAnActiveTransactionException exception if
not in ACTIVE or MARKED_ROLLBACK state
Result: returns self
Name Description change an atomic change
- addTransactionToWaitFor:(BNZTransaction*)transaction;
Result: reeturns self
Name Description transaction a transaction this transaction should wait for
- (NSArray*)changes;
currently, all changes are stored in the
order in which they were added. On a commit, all changes are applied
in the same order. So if you first set a property, say, to
"foo" and then to "bar", the commit will result in first
setting it to "foo" and then to "bar" even though the
first change may be ommitted.
However, this may change in
future releases so that only one change of the same type
on the same target can be part of the transaction. Then,
the AtomicChange implementation may need to care for
addivitve changes themselves (like adding objects to a collection
one at a time => each add would then find its predecessing change
and include its addings to the own addings). But as mentioned, this is
an idea for a future release and does not work this way currently.
- (NSArray*)changesOfTypes:(NSArray*)types onTarget:(id)target;
this method allows to filter the change set for changes of certain
types. this allows, for example, BNZAtomicChange implementations to find
corresponding changes such as "addObject" and "removeObject" changes for arrays
Result: an array containing changes of one of the given types. Order of changes is preserved
Name Description types an array of changeTypes (any objects, most often Strings, however) that should be included in the result target the target object for the changes. if nil, all changes of the given types are returned
- (NSArray*)changesForObserver:(id)observer inCallback:(SEL)callback;
Within a transaction, several (unrelated) atomic changes
can be included. Before and after commit all objects are called
that registered for at least one change (of a certain type and/or on a certain object).
These objects also can inspect the changes in the transaction they did not
register for.
However, if they are only interested in changes they originally registered for
in a certain callback, this method returns filters the change set to return only
those.
This method can be used to filter out the real changes of interest.
e.g.:
registered selector "handleChangesMethod:" as hasCommittedCallback for "FooChange" and "BarChange" changes.
in "handleChangesMethod:" one can then call
NSArray* changes = [transaction changesForObserver:self inCallback:
Result: an array containing all changes in the transaction change set for wich the registrations apply
Name Description observer an object that registered for receiving change notifications callback a selector for which the observer registered.
- (BOOL)commit;
commit all the changes of this transaction.
will inform transaction observers about the upcoming
commit, commit all changes, and inform them again when
committed.
Before a commit, observers can prevent the whole commit by
returning NO or throwing an exception in their callback.
this method returns YES if the commit was successfull and
NO if the transaction was rolled back.
will call rollback for sure if transaction is in state
MARKED_ROLLBACK.
may throw a kNotAnActiveTransactionException exception if
not in ACTIVE or PREPARED state
may throw a kNoTransactionForCurrentThreadException if
the transaction thread is not equal to the caller thread.
might call prepare if not already in PREPARED state
will call clearToCommit of BNZTransactionalNotificationCenter. This method
blocks the current thread until all currently committing transactions that
have an intersecting change set have finished.
see hasIntersectingChangeSet: for details
- (NSException*)commitException;
if a commit has been interrupted by an exception either
within the notification phase of a transaction observer or
within the commit by an AtomicChange, this will return the exception
(commit will have returned NO, then).
- (BOOL)hasIntersectingChangeSet:(BNZTransaction*)transaction;
change sets intersect, if there exist at least one change object
per transaction that have the same type and the same target.
If this is the case, the commit of the transactions can not be intermingled;
instead, the earlier transaction has to finish commit (or rollback) before
the later transaction can start.
This is done to be sure that each change set is committed as a whole before
a conflicting change set is committed. Imagine two transactions:
init: p.firstName ="Carter", p.lastName="Dole", address.street="4th Ave"
A changes property p.firstName to "Bob" and address.street to "5th Ave"
B changes p.firstName to "Alice" and p.lastName to "Johnson"
The transactions intersect because both change p.firstName. If they would be
committed in parallel, it may happen, that a third thread reads
p.firstName "Bob" p.lastName="Johnson", address.street="4th Ave" which is an
intermingled state of A (first name, street) and B (last name).
When executing one after another, possible states are
"Carter", "Dole", "4th Ave" (initial)
"Bob", "Dole", "5th Ave" (after Transaction A)
"Alice", "Johnson", "5th Ave" (after Transaction B)
In this case: The order of Transaction A and B is not defined.
So even though it is somehow random if the person will be "Bob" or "Alice"
afterwards (depending on which transaction commits first), the states
themselfe are at least consistent.
In short: the change of the later transaction wins.
Note: there may be other strategies that could be incorporated later. Such as:
the earlier transaction is committed, the later fails.
- (id)initWithThread:(NSThread*)thread;
BNZTransaction objects shall not be created directly. Instead,
[BNZTransactionalNotificationCenter currentTransaction] will create a transaction
for the current thread if there is none.
Result: a transaction object
Name Description thread the thread this transaction is associated with
- (BOOL)isAutoCommit;
initially, every transaction is set to autocommit YES this means, that a call to addAtomicChange: immediately results in a commit (i.e., every atomic change is committed on its own)
Result: the current value of autoCommit- (id)lastChangeOfType:(id)type onTarget:(id)target;
return the change of the given type on the given target that has been added at last. Mainly used, if changes of the same type overwrite each other and only the last change of that type therefore counts. e.g., PersonFirstNameChange: only the last setFirstName: is important, so in the getter firstName, the object can simply ask for the last change of the PersonFirstNameChange
Result: the BNZAtomicChange object of the given type on the given target that has been added latest, if any. nil, if no such change was found
Name Description type the type of the change that should be returned target the target of the change that should be returned
Result: the number of transactions in transactionsToWaitFor list- (int)numberOfTransactionsToWaitFor;
- prepare;
this method prepares the transaction for a commit or a rollback.
It is called automatically when needed; however, a caller may want to prepare
the transaction manually (long) before the commit. After the transaction is
prepared, no additional changes can be added to the transaction.
Preparing the transaction currently only fills the observations array with
BNZObservation objects that registered for being notified
of one or more of the changes contained in this transaction.
may throw a kNotAnActiveTransactionException exception if
not in ACTIVE or MARKED_ROLLBACK state
- removeTransactionToWaitFor:(BNZTransaction*)transaction;
Result: returns self
Name Description transaction the transaction to remove
- (void)rollback;
rolls back the transaction. Iterates all atomic changes
in the change set and calls rollback.
may throw a kNoTransactionForCurrentThreadException if
the transaction thread is not equal to the caller thread.
may throw a kNotAnActiveTransactionException exception if
not in ACTIVE, PREPARED, COMMITTING, or MARKED_ROLLBACK state
will call prepare if not already done
- setRollbackOnly;
when calling this method, the state of the transaction becomes
MARKED_ROLLBACK. The transaction can be used as an active one, however, the
only possible outcome of calling commit is a rollback.
This is helpful if you want to invalidate a transaction (e.g., you discovered an
erroneous state) without affecting the code that comes later until the commit.
e.g.:
- (void)do { BNZTransaction transaction = ...; //transaction with no auto commit [foo setBar:"bar"]; //clearly works
\\illegal state discovered, may even be inside some other method if (cond) { [transaction setRollbackOnly]; }
[bar setFoo:"foo"]; //still works, even though it is clear that transaction will be rolled back [transaction commit]; //will roll back!! }
- (enum BNZTransactionStatus)status;
see the documentation for the BNZTransactionStatus enum for details
Result: the current statusResult: the thread- (NSThread*)thread;
- (NSConditionLock*)threadLock;
the commit of a transaction may be halted until previous transactions are finished. This lock allows the BNZTransactionalNotification center to do so. see the [BNZTransactionalNotification clearToCommit] for details
Result: an NSConditionLock that can be used exclusively for this transaction- turnOffAutoCommit;
calling this method will turn off the autoCommit behavior
Note that once this method is called, there is no way to turn autoCommit
on again, because this does not make sense. (after committing or rolling back,
however, the next transaction will have autoCommit on again).
may throw a kNotAnActiveTransactionException exception if
not in ACTIVE or MARKED_ROLLBACK state
- (BOOL)waitsForTransaction:(BNZTransaction*)transaction;
Result: a BOOL indicating wheather the given transaction is in the transactionsToWaitFor list
Name Description transaction the transaction in question
(Last Updated 8/31/2006)