Granularity status of an NSBlockOperation - objective-c

I have extended NSOperationQueue to allow adding NSBlockOperation with a specific NSString as identifier.
The identifier value is held in a NSMutableArray serving as a registry. This is how I implement the registry.
-(void)addOperation:(NSOperation *)operation withID:(NSString*)operationID
{
#synchronized(self.queueReference)
{
[self.queueReference addObject:operationID]; // <-- just a mutable array
}
[operation setCompletionBlock:^(){
#synchronized(self.queueReference) {
[self.queueReference removeObject:operationID];
}
}];
[self addOperation:operation];
}
Basically I am adding a completion block which is cleaning the registry when that particular operation has finished.
However, while this works, I am in need to add more granularity to the queue.
I only use the queue with block operation, and during the execution of the block I may send different NSNotification to the listener depending how the execution went.
What I was trying to achieve:
A caller try to add a particular NSBlockOperation with identifier to queue. If queue already has such identifier just don't add block, and the calling class set itself as listener.
What is missing ? Checking for the identifier is not enough, there may be case when the NSBlockOperation already dispatched the NSNotification but the completion block has not yet being called.
So the caller class ask the queue, which is saying the identifier exists in registry, and caller wrongly set itself for listening to a notification that will never arrive because it's already being sent.
The scenario would be instead: caller ask the queue, which is saying 'identifier is in registry' but NSNotification is sent. And the caller put NSBlockOperation to queue.
The check of registry is made by means of a simple method:
-(BOOL)hasOperationWithID:(NSString*)operationID
{
#synchronized(self.queueReference)
{
return [self.queueReference containsObject:operationID];
}
}
but at this point I have not much idea on how to extend such method. The code I am working on is kind of 'academic', it does not serve any particular purpose, it is just me trying to experiment. Therefore I have great flexibility within the code. But this is quite new subject to me, so please be as much specific as possible of any downside of suggested implementation.

It looks like your current system has three fundamental events:
Operation is added to the queue
Operation sends notification while executing
Operation completion block is called
Unless the queue itself explicitly listens for any NSNotifications that might be sent by the blocks, it has no way of knowing whether they have happened yet. But even if it does listen, the ordering in which observers of NSNotifications are called is non-deterministic. In other words, even if the queue listens for the notification and interlocks its callback with enqueue/dequeue operations, it could (and eventually would) still be too late for another client to start listening for that NSNotification, and you would falsely reject an operation.
Consider this alternative: Instead of using the completion block to manage the identifier list, use the notification itself -- have the queue handle sending the notifications. Put differently, let's get rid of the third event and have the notification sending do double duty for identifier list maintenance. The simplest way I came up with to do this looked like:
Header:
//
// SONotifyingOperationQueue.h
// NotifyingOpQueue
//
typedef void (^SOSendNotificationBlock)(NSDictionary* userInfo);
typedef void (^SONotifyingBlock)(SOSendNotificationBlock sendNotificationBlock);
#interface SONotifyingOperationQueue : NSOperationQueue
- (BOOL)addOperationForBlock:(SONotifyingBlock)block withNotificationName:(NSString*)notificationName;
#end
Implementation
//
// SONotifyingOperationQueue.m
// NotifyingOpQueue
//
#import "SONotifyingOperationQueue.h"
#implementation SONotifyingOperationQueue
{
NSMutableSet* _names;
}
- (BOOL)addOperationForBlock: (SONotifyingBlock)block withNotificationName: (NSString*)notificationName
{
notificationName = [[notificationName copy] autorelease];
BOOL shouldAdd = NO;
#synchronized(self)
{
_names = _names ? : [[NSMutableSet alloc] init];
if (![_names containsObject: notificationName])
{
[_names addObject: notificationName];
shouldAdd = YES;
}
}
if (shouldAdd)
{
NSBlockOperation* blockOp = [[[NSBlockOperation alloc] init] autorelease];
__block SONotifyingOperationQueue* blockSelf = self;
SOSendNotificationBlock notificationBlock = ^(NSDictionary* userInfo){
#synchronized(blockSelf)
{
[blockSelf->_names removeObject: notificationName];
// Sending the notification from inside the #synchronized makes it atomic
// with respect to enqueue operations, meaning there can never be a missed
// notification that could have been received.
[[NSNotificationCenter defaultCenter] postNotificationName: notificationName object: blockSelf userInfo: userInfo];
}
};
dispatch_block_t executionBlock = ^{
block(notificationBlock);
};
[blockOp addExecutionBlock: executionBlock];
[self addOperation: blockOp];
}
return shouldAdd;
}
- (void)dealloc
{
[_names release];
[super dealloc];
}
#end
This approach makes several changes to your original approach. First, the API here adds blocks and not NSOperations. You could do the same thing with an NSOperation subclass, but it would be more code, and wouldn't change the overall pattern. It also merges the notion of the identifier and the notification name. If an operation could send multiple, different NSNotifications, this won't work without modification, but again, the overall pattern would be the same. The important feature of this pattern is that your id/name check is now interlocked with the notification sending itself, providing a strong guarantee that if someone goes to add a new block/operation to the queue, and another operation with the same id/name hasn't fired its notification yet, the new operation won't be added, but if the notification has been fired, then it will be added, even if the preceding block hasn't yet completed.
If having the NSOperation object was somehow important here, you could also have the method here return the operation it creates for the supplied block.
HTH.

Related

Is there a good way to init an ObjC object on a background dispatch_queue?

I have an ObjC class RuleSet that needs quite heavy initialization (parses a big JSON file, does lots of querying into several OS services, applies heuristics, and last, it builds a set of complicated NSPredicates, that can later be used for fast evaluation.
My program (except for this object) receives high-priority events from MacOS (EndpointSecurity framework) and has to respond to these within very narrow deadlines - based on the fast evaluation of my current RuleSet instance.
I handle those events in a special (highest priority) concurrent dispatch_queue I created.
However... if I receive some external trigger, to change my RuleSet - and I need to build the new RuleSet instance before applying it, I want the code creating it not to know about the internal structure of dispatch queues, and simply call
RuleSet *newRules = [[RuleSet alloc] initWithJSON:newJSONPath];
[myEventHandlingEngine setRuleSet:newRuleSet];
I'd like the RuleSet init to be able to somehow defer the work to another, low-priority serial dispatch_queue, maybe in parts. I don't mind blocking the caller until I'm done (this heavy init shouldn't take more than a second...). still, it's important that I do it on a queue that has lower priority - so that incoming events will be handled first.
Now I tried to write things like
- (instancetype) initWithJSON:newJSONPath:(NSString *)jsonFilePath
{
dispatch_sync([RuleSet setupQueue], ^{
if ( nil == (self = [super init]) ) {
self = nil;
return self;
}
// heavy work here
});
return self;
}
but that of course won't even compile because 'self' isn't a __block variable, and I'm not sure it can be...
Any advice on how to go about this?
You should try using a dispatchGroup to handle that situation :
- (instancetype) initWithJSON:newJSONPath:(NSString *)jsonFilePath
{
if ( nil == (self = [super init]) ) {
self = nil; //This seems useless since self is already nil ?
return self;
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, [OITPreventionPolicy policySetupQueue], ^{
// Execute your heavy work here
});
// Wait for the group to finish its work (you could replace 'forever' by a timeout)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return self;
}
The call to dispatch_group_wait will block the thread in which the object is created (so it cannot be the main thread).

Chaining Completion Blocks

I have two instances of different classes who both need to add a completion block to a particular operation. I'll try to explain the problem generically rather than having to explain everything my app is attempting to do.
A view controller is calling into an instance of a resource manager class for it to save a resource. The resource manager then calls into the class of the resource to be saved to get a network operation for the save.
The instance of the resource creates the operation and gives it a completion block that will affect the state of the resource when it fires.
This is where my problem is - the resource class also needs to add a completion block to this operation in order for the view controller to be informed of the success or failure of the save.
Here's a snippit of the save method on the manager:
-(void)save:resource withCompletion:completion
{
.
.
.
NSOperation *operation = [resource operationForSave];
NSOperation __weak *weakOperation = operation;
void(^__weak resourceCompletion)(void)= operation.completionBlock;
[operation setCompletionBlock:^{
if (resourceCompletion) {
resourceCompletion();
}
if (completion) {
if (weakOperation.error) {
completion(NO, operation.error);
}
else {
completion(YES, nil);
}
}
}];
.
.
.
// add the operation to a network operation queue
}
While I think this will technically work, I'm not crazy about it. It feels pretty funky. I would prefer to have one block encapsulating the second block, but this isn't possible because the view controller and the resource are creating their own completion blocks, and the manager class is the one that has to smash them together.
Is there a more elegant way to chain these two completion blocks together in this situation, or is my current method of creating a block to contain the original two blocks the best I'm going to get?
Any input would be great appreciated.
The code you posted will probably not work. When you replace the operation's completion block with your own block, you're probably removing the only strong reference to the original completion block (set by the resource). So your resourceCompletion variable, being weak, will become nil by the time setCompletionBlock: returns.
Just making resourceCompletion strong should fix the problem. But if you want to do it in a cleaner way, modify the operationForSave message (on the resource) to take a completion block itself:
__block NSNetworkOperation *operation = [resource operationForSaveWithCompletion:^{
NSError *error = operation.error;
completion(error == nil, error);
// Break the retain cycle between this block and the operation object.
operation = nil;
}];
And make it the job of the resource's own internal completion block to call the completion block you provide.
If you don't want to or can't modify the resource's API, you can still simplify your code by eliminating the weak references:
__block NSNetworkOperation *operation = [resource operationForSave];
__block void (^priorCompletion)(void) = operation.completionBlock;
operation.completionBlock = ^{
if (priorCompletion) {
priorCompletion);
// Break possible retain cycle.
priorCompletion = nil;
}
NSError *error = operation.error;
completion(error == nil, error);
// Break the retain cycle between this block and the operation object.
operation = nil;
};
Also, I sincerely hope you don't really have a class named NSNetworkOperation, because Apple reserves the NS prefix (and all other two-letter prefixes) for its own use.

Is Objective-C's NSMutableArray thread-safe?

I've been trying to fix this crash for almost a week. The application crashes without any exception or stack-trace. The application does not crash in any way while running through instruments in zombie mode.
I have a method that gets called on a different thread.
The solution that fixed the crash was replacing
[self.mutableArray removeAllObjects];
with
dispatch_async(dispatch_get_main_queue(), ^{
[self.searchResult removeAllObjects];
});
I thought it might be a timing issue, so I tried to synchronize it, but it still crashed:
#synchronized(self)
{
[self.searchResult removeAllObjects];
}
Here is the code
- (void)populateItems
{
// Cancel if already exists
[self.searchThread cancel];
self.searchThread = [[NSThread alloc] initWithTarget:self
selector:#selector(populateItemsinBackground)
object:nil];
[self.searchThread start];
}
- (void)populateItemsinBackground
{
#autoreleasepool
{
if ([[NSThread currentThread] isCancelled])
[NSThread exit];
[self.mutableArray removeAllObjects];
// Populate data here into mutable array
for (loop here)
{
if ([[NSThread currentThread] isCancelled])
[NSThread exit];
// Add items to mutableArray
}
}
}
Is this problem with NSMutableArray not being thread-safe?
No.
It is not thread safe and if you need to modify your mutable array from another thread you should use NSLock to ensure everything goes as planned:
NSLock *arrayLock = [[NSLock alloc] init];
[...]
[arrayLock lock]; // NSMutableArray isn't thread-safe
[myMutableArray addObject:#"something"];
[myMutableArray removeObjectAtIndex:5];
[arrayLock unlock];
As others already said, NSMutableArray is not thread safe. In case anyone want to achieve more than removeAllObject in a thread-safe environment, I will give another solution using GCD besides the one using lock. What you have to do is to synchronize the read/update(replace/remove) actions.
First get the global concurrent queue:
dispatch_queue_t concurrent_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
For read:
- (id)objectAtIndex:(NSUInteger)index {
__block id obj;
dispatch_sync(self.concurrent_queue, ^{
obj = [self.searchResult objectAtIndex:index];
});
return obj;
}
For insert:
- (void)insertObject:(id)obj atIndex:(NSUInteger)index {
dispatch_barrier_async(self.concurrent_queue, ^{
[self.searchResult insertObject:obj atIndex:index];
});
}
From Apple Doc about dispatch_barrier_async:
When the barrier block reaches the front of a private concurrent queue, it is not executed immediately. Instead, the queue waits until its currently executing blocks finish executing. At that point, the barrier block executes by itself. Any blocks submitted after the barrier block are not executed until the barrier block completes.
Similar for remove:
- (void)removeObjectAtIndex:(NSUInteger)index {
dispatch_barrier_async(self.concurrent_queue, ^{
[self.searchResult removeObjectAtIndex:index];
});
}
EDIT: Actually I found another simpler way today to synchronize access to a resource by using a serial queue provided by GCD.
From Apple Doc Concurrency Programming Guide > Dispatch Queues:
Serial queues are useful when you want your tasks to execute in a specific order. A serial queue executes only one task at a time and always pulls tasks from the head of the queue. You might use a serial queue instead of a lock to protect a shared resource or mutable data structure. Unlike a lock, a serial queue ensures that tasks are executed in a predictable order. And as long as you submit your tasks to a serial queue asynchronously, the queue can never deadlock.
Create your serial queue:
dispatch_queue_t myQueue = dispatch_queue_create("com.example.MyQueue", NULL);
Dispatch tasks async to the serial queue:
dispatch_async(myQueue, ^{
obj = [self.searchResult objectAtIndex:index];
});
dispatch_async(myQueue, ^{
[self.searchResult removeObjectAtIndex:index];
});
Hope it helps!
As well as NSLock can also use #synchronized(condition-object) you just have to make sure every access of the array is wrapped in a #synchronized with the same object acting as the condition-object , if you only want to modify the contents of the same array instance then you can use the array itself as the condition-object, other wise you will have to use something else you know will not go away, the parent object, i.e self, is a good choice because it will always be the same one for the same array.
atomic in #property attributes will only make setting the array thread safe not modifying the contents, i.e. self.mutableArray = ... is thread safe but [self.mutableArray removeObject:] is not.
__weak typeof(self)weakSelf = self;
#synchronized (weakSelf.mutableArray) {
[weakSelf.mutableArray removeAllObjects];
}
Since serial queues were mentioned: With a mutable array, just asking "is it thread safe" isn't enough. For example, making sure that removeAllObjects doesn't crash is all good and fine, but if another thread tries to process the array at the same time, it will either process the array before or after all elements are removed, and you really have to think what the behaviour should be.
Creating one class + object that is responsible for this array, creating a serial queue for it, and doing all operations through the class on that serial queue is the easiest way to get things right without making your brain hurt through synchronisation problems.
All the NSMutablexxx classes are not thread-safe. Operations including get,insert,remove,add and replace should be used with NSLock.This is a list of thread-safe and thread-unsafe classes given by apple: Thread Safety Summary
Almost NSMutable classes object is not thread safe.

NSOperation & Singleton: Correct concurency design

I need an advice from you guys on the design of my app here, basically I would like to know if it will work as I expect ? As the multi-threading is quite tricky thing I would like to hear from you.
Basically my task is very simple -I've SomeBigSingletonClass - big singleton class, which has two methods someMethodOne and someMethodTwo
These methods should be invoked periodically (timer based) and in separate threads.
But there should be only one instance of each thread at the moment, e.g. there should be only one running someMethodOne at any time and the same for someMethodTwo.
What I've tried
GCD - Did implementation with GCD but it lacks very important feature, it does not provide means to check if there is any running task at the moment, i.e. I was not able to check if there is only one running instance of let say someMethodOne method.
NSThread - It does provide good functionality but I'm pretty sure that new high level technologies like NSOperation and GCD will make it more simple to maintain my code. So I decided to give-up with NSThread.
My Solution with NSOperation
How I plan to implement the two thread invokation
#implementation SomeBigSingletonClass
- (id)init
{
...
// queue is an iVar
queue = [[NSOperationQueue alloc] init];
// As I'll have maximum two running threads
[queue setMaxConcurrentOperationCount:2];
...
}
+ (SomeBigSingletonClass *)sharedInstance
{
static SomeBigSingletonClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[SomeBigSingletonClass alloc] init];
});
return sharedInstance;
}
- (void)someMethodOne
{
SomeMethodOneOperation *one = [[SomeMethodOneOperation alloc] init];
[queue addOperation:one];
}
- (void)someMethodTwo
{
SomeMethodTwoOperation *two = [[SomeMethodOneOperation alloc] init];
[queue addOperation:two];
}
#end
And finally my NSOperation inherited class will look like this
#implementation SomeMethodOneOperation
- (id)init
{
if (![super init]) return nil;
return self;
}
- (void)main {
// Check if the operation is not running
if (![self isExecuting]) {
[[SomeBigSingletonClass sharedInstance] doMethodOneStuff];
}
}
#end
And the same for SomeMethodTwoOperation operation class.
If you are using NSOperation, you can achieve what you want be creating your own NSOperationQueue and setting numberOfConcurrentOperations to 1.
You could have also maybe used an #synchronized scope with your class as your lock object.
EDIT: clarification---
What I am proposing:
Queue A (1 concurrent operation--used to perform SomeMethodOneOperation SomeMethodTwoOperation once at a time)
Queue B (n concurrent operations--used for general background operation performing)
EDIT 2: Updated code illustrating approach to run maximum operation one and operation two, with max one each of operation one and operation two executing at any given time.
-(void)enqueueMethodOne
{
static NSOperationQueue * methodOneQueue = nil ;
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
queue = [ [ NSOperationQueue alloc ] init ] ;
queue = 1 ;
});
[ queue addOperation:[ NSBlockOperation blockOperationWithBlock:^{
... do method one ...
} ] ];
}
-(void)enqueueMethodTwo
{
static NSOperationQueue * queue = nil ;
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
queue = [ [ NSOperationQueue alloc ] init ] ;
queue = 1 ;
});
[ queue addOperation:[ NSBlockOperation blockOperationWithBlock:^{
... do method two ...
} ] ];
}
EDIT 3:
per our discussion:
I pointed out that isExecuting is a member variable and refers only to the state of the operation being queried, not if any instance of that class is executing
therefore Deimus' solution won't work to keep multiple instances of operation one running simultaneously for example
Sorry, I'm late to the party. If your methods are called back based on timers, and you want them to execute concurrently with respect to one another, but synchronous with respect to themselves, might I suggest using GCD timers.
Basically, you have two timers, one which executes methodOne, and the other executes methodTwo. Since you pass blocks to the GCD timers, you don't even have to use methods, especially if you want to make sure other code does not call those methods when they are not supposed to run.
If you schedule the timers onto a concurrent queue, then both timers could possibly be running at the same time on different threads. However, the timer itself will only run when it is scheduled. Here is an example I just hacked up... you can easily use it with a singleton...
First, a helper function to create a timer that takes a block which will be called when the timer fires. The block passes the object, so it can be referenced by the block without creating a retain cycle. If we use self as the parameter name, the code in the block can look just like other code...
static dispatch_source_t setupTimer(Foo *fooIn, NSTimeInterval timeout, void (^block)(Foo * self)) {
// Create a timer that uses the default concurrent queue.
// Thus, we can create multiple timers that can run concurrently.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
uint64_t timeoutNanoSeconds = timeout * NSEC_PER_SEC;
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, timeoutNanoSeconds),
timeoutNanoSeconds,
0);
// Prevent reference cycle
__weak Foo *weakFoo = fooIn;
dispatch_source_set_event_handler(timer, ^{
// It is possible that the timer is running in another thread while Foo is being
// destroyed, so make sure it is still there.
Foo *strongFoo = weakFoo;
if (strongFoo) block(strongFoo);
});
return timer;
}
Now, the basic class implementation. If you don't want to expose methodOne and methodTwo, there is no reason to even create them, especially if they are simple, as you can just put that code directly in the block.
#implementation Foo {
dispatch_source_t timer1_;
dispatch_source_t timer2_;
}
- (void)methodOne {
NSLog(#"methodOne");
}
- (void)methodTwo {
NSLog(#"methodTwo");
}
- (id)initWithTimeout1:(NSTimeInterval)timeout1 timeout2:(NSTimeInterval)timeout2 {
if (self = [super init]) {
timer1_ = setupTimer(self, timeout1, ^(Foo *self) {
// Do "methodOne" work in this block... or call it.
[self methodOne];
});
timer2_ = setupTimer(self, timeout2, ^(Foo *self) {
// Do "methodOne" work in this block... or call it.
[self methodTwo];
});
dispatch_resume(timer1_);
dispatch_resume(timer2_);
}
return self;
}
- (void)dealloc {
dispatch_source_cancel(timer2_);
dispatch_release(timer2_);
dispatch_source_cancel(timer1_);
dispatch_release(timer1_);
}
#end
EDIT
In response to the comments (with more detail to hopefully explain why the block will not be executed concurrently, and why missed timers are coalesced into one).
You do not need to check for it being run multiple times. Straight from the documentation...
Dispatch sources are not reentrant. Any events received while the
dispatch source is suspended or while the event handler block is
currently executing are coalesced and delivered after the dispatch
source is resumed or the event handler block has returned.
That means when a GCD dispatch_source timer block is dispatched, it will not be dispatched again until the one that is already running completes. You do nothing, and the library itself will make sure the block is not executed multiple times concurrently.
If that block takes longer than the timer interval, then the "next" timer call will wait until the one that is running completes. Also, all the events that would have been delivered are coalesced into one single event.
You can call
unsigned numEventsFired = dispatch_source_get_data(timer);
from within your handler to get the number of events that have fired since the last time the handler was executed (e.g., if your handler ran through 4 timer firings, this would be 4 - but you would still get all this firings in this one event -- you would not receive separate events for them).
For example, let's say your interval timer is 1 second, and your timer happens to take 5 seconds to run. That timer will not fire again until the current block is done. Furthermore, all those timers will be coalesced into one, so you will get one call into your block, not 5.
Now, having said all that, I should caution you about what I think may be a bug. Now, I rarely lay bugs at the feet of library code, but this one is repeatable, and seems to go against the documentation. So, if it's not a bug, it's an undocumented feature. However, it is easy to get around.
When using timers, I have noticed that coalesced timers will most certainly be coalesced. That means, if your timer handler is running, and 5 timers fired while it was running, the block will be called immediately, representing those missed 5 events. However, as soon as that one is done, the block will be executed again, just once, no matter how many timer events were missed before.
It's easy to identify these, though, because dispatch_source_get_data(timer) will return 0, which means that no timer events have fired since the last time the block was called.
Thus, I have grown accustomed to adding this code as the first line of my timer handlers...
if (dispatch_source_get_data(timer) == 0) return;

Self destructing singleton design pattern iOS

I recently came across an issue in which I only wanted one instance of a particular object to exist, and exist for only the brief period of time it needed to perform a specific operation. Its operation was asynchronous so ARC would dealloc it at the end of the run loop if I didn't hold a reference to it. If I did hang onto it I would need delegate callbacks or notifications to know when it was done to release it.
The object needed to download several images and other data and cache it to disk. I didn't want it to waste memory when it wasn't caching items since the cache limit was around 24 hours. I also didn't need feedback of any kind from it; I wanted it to perform it's task and be done with itself.
I came up with a design pattern I liked quite nicely. I've used it in a few other projects since then, and was curios if it was a well known and analyzed pattern that I'm just not aware of (self-destructing singleton???). I'd like to know so I can be made aware of any potential pitfalls I'm not currently seeing.
I'm also very interested to hear any input you guys might have about why this is a bad design.
The Design Goes Like This (this is ARC, but non-arc can work too if you release the singleton through a class method):
A global static object (not really a singleton because it doesn't live the entire time)
static MySelfDestructingClass* singleton;
A single public class method
+ (void)downloadAndCacheDataIfNeeded
{
//Force synchronized access
#synchronized(singleton){
//We are already doing something, return
if(singleton){
return;
}
NSDate* lastCacheDate = [[NSUserDefaults standardDefaults] objectForKey:kKeyForLastUpdate];
if([[NSDate date] timeIntervalSinceDate:lastCacheDate] > kCacheLimit){
//Our cache is out of date, we need to update
singleton = [[self alloc] init];
[singleton downloadAndCache];
}
}
}
Now our instance methods, we need our object alive so the request can come back:
- (void)downloadAndCache
{
//This would probably be a delegate, but for simplicity of this example it's a notification
[[NSNotificationCenter defaultCenter] addObserver:self forNotificationWithName:NotificationSomeRequestDidSucceed selector:#selector(someCustomRequestDidSucceed:withData:) object:nil];
[SomeCustomRequest makeRequestWithURL:#"http://www.someURL.com"];
}
- (void)someCustomRequestDidSucceed:(SomeCustomRequest *)request withData:(NSDictionary *)dictionary
{
//Do whatever we need to in order to save our data, or fire off image download requests etc...
....
//Set our lastUpdated time in NSUserDefaults
[[NSUserDefaults standardDefaults] setObject:[NSDate date] forKey:kKeyForLastUpdate];
//Remove our observer
[NSNotificationCenter defaultCenter] removeObserver:self name:NotificationSomeRequestDidSucceed object:nil];
//Release ourselves (ok not really, but tell arc we can be released)
singleton = nil;
}
This way all I have to do anywhere else in the application is:
[MySelfDestructingClass downloadAndCacheDataIfNeeded];
Now this object will download things if it needs to and release itself when it's done, or not create itself at all. It also won't go and start downloading the data twice.
I'm aware this design has limitations with extendibility and functionality, but for an instance like this, and the other ones I've used it for, I've found it quite useful.
This pretty common using blocks. Consider something similar (though I would probably handle multiple invocations differently...)
void ExecuteWithMySingleSelfDestructingObject(void(^block)(MySelfDestructingClass *object)) {
static MySelfDestructingClass* singleton;
#synchronized(singleton) {
if (singleton) {
// To get past the synchronization primitive, this must be a recursive call.
}
// Whatever other conditions you want to have (like your date check)
singleton = [[MySelfDestructingClass] alloc] init];
#try { block(singleton); }
#finally { singleton = nil; }
}
}
Note double exception handling (try/finally plus what #synchronized does - may want to change that...
Then do whatever you want with the block...
ExecuteWithMySingleSelfDestructingObject(^(MySelfDestructingClass *object){
// Do whatever I want with the singleton instance that has
// been given to me as <object>
});
Of course, it could be a class method...
+ (void)performBlock:(void(^)(MySelfDestructingClass *object))block {
static MySelfDestructingClass* singleton;
#synchronized(singleton) {
if (singleton) {
// To get past the synchronization primitive, this must be a recursive call.
}
// Whatever other conditions you want to have (like your date check)
singleton = [[self] alloc] init];
#try { block(singleton); }
#finally { singleton = nil; }
}
}
[MySelfDestructingClass performBlock:^(MySelfDestructingClass *object){
// Do whatever I want with the singleton instance that has
// been given to me as <object>
}];
I hope that makes sense (I typed it free-hand, so syntax may vary, but you should get the idea).