The Guinea Pig in the Cocoa Mine

Underground Cocoa Experiments

Findings: Your Lab Notebook, Reinvented

I am very excited to announce the launch of Findings, a Mac app for scientists that aims to replace the paper lab notebook that is still in use in most research labs. You can sign up on the web site to get notified when Findings is ready. And be sure to share the URL with all your scientist friends or relatives. You can also follow us on twitter, retweet us, or check our Facebook page.

When doing science and running experiments, it is crucial to keep track of what one is doing, to be able to later reproduce the results, assemble and publish them. This is what lab notebooks are for. There is something great about paper, and the freedom and flexibility it affords. But in 2014, paper starts to show its limits in other areas where computers have taken over: storing results, analysing data, searching, replicating, sharing, preserving, and more.

Findings ambition is simple: make your computer a better tool than paper to run your experiments and keep your lab records.

For now, we are running a private beta (enroll with the same sign-up page at findingsapp.com). We plan to release version 1.0 very soon.

When a product is just starting, there is a unique opportunity for early adopters to shape its future and to influence its path. We need this feedback to reach our goals and truly reinvent the lab notebook.

App Purchases Twice as Likely Among iOS 7 Users

Brent Simmons:

“People who don’t upgrade their OS are also the kind of people who don’t buy apps”

This rings true to most developers. It likely depends on the type of customers you have (and thus the type of app), but the corrolary to that observation is that you should always lean towards dropping support for older OS versions. Brent Simmons made the case for OS X applications, but this should be applicable to iOS apps as well.

However, based on actual data, David Smith came to a different conclusion. He compared the iOS versions of two groups of users of one of his app, My Recipe Book:

  • Group A: all the users of his app
  • Group B: the last 1000 users who purchased his app

He found that the iOS distributions for these two groups are basically the same. His conclusion is that there is no correlation between purchasing his app and upgrading the OS.

I do not understand the reasoning here, as I fail to see how the iOS distribution between those 2 groups of users could be different. Both groups are purchasers of his app, since this is a paid app with no in-app purchases. I don’t see why the purchasing and upgrade habits of the users who purchased it last year would be different from the habits of more recent users.

It is possible I am misunderstanding David Smith’s data, but to me, the more relevant comparison would be between these two groups:

  • Group A: all the users of his recipe app
  • Group C: all the iPhone/iPad/iPod Touch users

The iOS distribution for all iOS devices is published by Apple. If anything, it underestimates older iOSes, since it relies on App Store analytics from the previous 7 days. When plotting Apple’s numbers (from February 23rd) against David Smith’s numbers, one gets a very different picture. While 18% of all iOS devices are still on iOS 5 or iOS 6, only 10% of the users of his app are. This means that iOS 7 users are twice as likely to purchase his app compared to the slow updaters still on older OSes (the exact ratio is: 90 / 82 x 18 / 10 ~ 1.98). The ratio between slow and fast updaters is likely even higher, since some iOS 7 users are in fact slow updaters who just acquired a new iOS device.

I conclude that Brent’s conjecture is in fact supported by David’s data.

NSMapTable and Zeroing Weak References

In Apple’s own words, NSMapTable is a mutable collection modeled after NSDictionary that provides different options (see also overview from Mike Ash, NSHipster and objc.io). The major option is to have keys and/or values held “weakly” in a manner that entries are removed when one of the objects is reclaimed. This behavior relies on zeroing weak references, which were first introduced with garbage collection, and then later adapted to ARC starting with Mountain Lion 10.8.

Zeroing weak references are magic in that they turn to nil automatically when the object they point to is deallocated. For instance, a zeroing weak reference held by variable foo could have the value 0x60000001bf10, pointing to an instance of NSString. But the instant that object is deallocated, the value of foo changes to 0x000000000000.

The use of zeroing weak references in combination with NSMapTable has the potential to simplify object management by providing automatic removal of unused entries. Unfortunately, NSMapTable does not fulfill all its promises.

The Promises of NSMapTable

Let’s imagine a hypothetical FooNotificationCenter class, that provides the following API:

1
2
3
4
@interface FooNotificationCenter : NSObject
- (void)addObserver:(id)obs selector:(SEL)sel name:(NSString *)name;
- (void)postNotificationName:(NSString *)name;
@end

Using two NSMapTable instances, a very simple implementation of FooNotificationCenter could look something like this, where the tables retain the selector and name, but only hold weakly onto the observer:

As long as the observer is not deallocated, both tables will have the selector and name entries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (id)init
{
    self = [super init];
    if (self != nil)
    {
        _selectorTable = [NSMapTable weakToStrongObjectsMapTable];
        _nameTable = [NSMapTable strongToWeakObjectsMapTable];
    }
    return self;
}

- (void)addObserver:(id)observer
           selector:(SEL)selector
               name:(NSString *)name
{
  NSString *selString = NSStringFromSelector(selector);
  [_selectorTable addObject:selString forKey:observer];
  [_nameTable addObject:observer forKey:name];
}

- (void)postNotificationName:(NSString *)name
{
  id observer = [_nameTable objectForKey:name];
  if (observer == nil)
      return;
  SEL selector = [_selectorTable objectForKey:observer];
  if (selector)
      [observer performSelector:selector withObject:nil];
}

// This highly-flawed code is released under the least friendly
// open-source license possible, to ensure you don't use it.

When the observer is deallocated, the corresponding references will be zeroed out in the NSMapTables, the entries will “disappear” automatically, and the name and selector objects will be deallocated. There is no need for the observer to explicitly remove itself from our FooNotificationCenter. It will happen automatically when the observer is deallocated… at least in theory.

NSMapTable entries disappear when the observer is deallocated and zeroed out

Probing NSMapTable

I recently struggled with unexpected behaviors in NSMapTable and decided to take a closer look. I came to the unfortunate conclusion that NSMapTable has undocumented limitations in its handling of zeroing weak references (at least under ARC, though I expect similar results with manual memory management).

To see NSMapTable in action, I wrote a simple test application that exercises NSMapTable, published on github. The application manages an instance of NSMapTable. Here is what it looks like (if you run the app, use the tooltips to get more info on the interface elements):

NSMapTable Test App

When clicking the ‘Add Entries’ button, the NSMapTable is fed the requested number of keys and objects, created freshly using the special PARMarker class. PARMarker is a simple NSObject subclass that keeps track of the calls to init and dealloc. Addition of the entries (one PARMarker for the key, and one for the object) is done within an autorelease pool, so that markers not retained by the NSMapTable instance are deallocated when the pool dies. The code is very simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (IBAction)addEntriesToMapTable:(id)sender
{
    @autoreleasepool
    {
        NSUInteger addCount = _entriesToAddField.integerValue;
        for (NSUInteger i = 0; i < addCount; i++)
        {
            PARMarker *key = [[PARMarker alloc] init];
            PARMarker *object = [[PARMarker alloc] init];
            [self.mapTable setObject:object forKey:key];
        }
    }
    [self refreshUI];
}

Let’s try the app. We will first add 10 entries in a strong-to-strong NSMapTable. After addition, the NSMapTable has a value of 10 for its count property, and has 10 keys and 10 objects. There were 20 markers created (10 keys and 10 values). None of the markers are deallocated: they are all retained by the NSMapTable instance. So far, so good:

Adding 10 entries to a strong-to-strong NSMapTable

Weak to Strong

Now, let’s get in trouble. For this, we will use an instance of NSMapTable with weak-to-strong references, and add 1 entry. Because the key is autoreleased just after being added to the table, there should be no entry at all in our NSMapTable instance. Here is what we get instead:

Adding 1 entry to a weak-to-strong NSMapTable

Let’s start with the good news. The ‘Key count’ and ‘Object count’ of the map table are both at 0. These numbers are the values returned by keyEnumerator.allObjects.count and objectEnumerator.allObjects.count. This indicates that the added entry was removed from the NSMapTable when the key was autoreleased. The corresponding object has disappeared from NSMapTable as soon as the key was deallocated. Or has it really?

The first sign of trouble is the count property of NSMapTable, which returns 1. Somehow, the NSMapTable still considers that it has 1 entry. More troubling, the object corresponding to the entry that should be gone, is in fact still alive! Out of the 2 initial FDMarker instances, only one is deallocated (the key), and the other one is still alive (the object).This is very troublesome, as this essentially means the object can’t be reached anymore, and is “leaking”.

Technically, it’s not leaking, since it’s properly released when NSMapTable is deallocated. But if the NSMapTable is never used again, and just kept alive, the object will also remain alive forever (there is no garbage-collector-like activity here). A number of operations can trigger a “purge”, though, and the object will eventually be released if the NSMapTable is used. For instance, a call to removeAllObjects will trigger such a purge.

In addition, if we keep adding more entries, the object will eventually be released and deallocated. For instance, if we add 8 more entries, 1 by 1, the number of markers still alive is now 6, meaning that 3 out of 9 of the added objects were purged. But we still have 6 objects dangling:

Adding 9 entries to a weak-to-strong NSMapTable

This completely defeats the purpose of [NSMapTable weakToStrongObjectsMapTable]. After lots of Googling, and to my great surprise, I eventually realized this behavior is (sort of) documented, as part of the Moutain Lion Release Notes, in a way that directly contradicts the promise of NSMapTable official documentation:

However, weak-to-strong NSMapTables are not currently recommended, as the strong values for weak keys which get zero’d out do not get cleared away (and released) until/unless the map table resizes itself.

I wish this had been documented in the NSMapTable documentation (to report the issue like I did, please use the ‘Provide Feedback’ button in the Xcode documentation window, or use this link directly).

Strong to weak

The NSMapTable documentation or the OS X release notes do not include any warning for strong-to-weak NSMapTables, but that does not mean we should not be looking for more troubles. Let’s use a strong-to-weak NSMapTable, and add 1000 entries. Again, as soon as the entries are added, our code exits an autorelease pool, but this time, the objects (not the keys) are deallocated and zeroed. This should result in the removal of all the NSMapTable entries, followed by deallocation of the keys. Let’s see:

Adding 1000 entries to a strong-to-weak NSMapTable

Definitely more troubles. Out of the 1000 keys initially retained by NSMapTable, only 489 have been deallocated, and 511 are still alive.

Let’s keep digging and add more entries by pressing the ‘Add Entries’ button another 64 times. After adding 65,000 key/value pairs in 1000 increments, we find that 32,255 objects are still alive and help onto by the map table. This means 32,255 out of the 65,000 objects are still alive, while all the keys are deallocated (and zero-ed out):

Adding 65000 entries to a strong-to-weak NSMapTable

Heck, let’s add another 1000 entries. When getting to 132,000 key/value pairs, the number of “live” objects drops to 487. It looks like something happened internally (resizing?) and the NSMapTable purged most of the retained keys:

Adding 66000 entries to a strong-to-weak NSMapTable

As pointed out by Michael Tsai, the expected behavior of strong-to-weak NSMapTable is not very well documented, and subject to interpretation. He expected that keys should be retained until explicitely removed with removeObjectForKey: (though in this case, I would expect the keyEnumerator to still include those keys). I would personally have expected a behavior symmetrical to the weak-to-strong NSMapTable, where zeroing of the object would remove the entry from the table and release the key. Based on what I am seeing, the latter is probably what NSMapTable is supposed to do. What happens in practice, though, is worse than any of those two possibilities: the key is retained for a while and released eventually.

One more tidbit of information: even though the non-released keys are not listed in the keyEnumerator, you can still call removeObjectForKey: with those, which will in fact purge the corresponding key. You can try this in the test app using the action “Remove Live Keys” in the popup menu at the bottom.

Conclusions

No matter how one interprets the NSMapTable documentation, it is in practice not as magic as one would like it to be. Both weak-to-strong and strong-to-weak NSMapTables have problems dealing with zeroing weak references. The behavior is dependent on the type of table, its current size, and its past activity. In both cases, the behavior is likely deterministic, but it depends on the internal implementation of NSMapTable. It is impossible to predict and should be considered undefined.

There are two obvious consequences to the leaking of entries with zeroing weak references in NSMapTable:

  • Memory leak. This is only significant if the map table is used with large objects and/or a large number of objects, as the internal purging appears to be less frequent with larger sizes, and as the number of objects allowed to stay alive becomes larger. Even with small number of objects, unexpected retain cycles that weak references are supposed to break, may also form, and may remain stuck for a very long time if the map table is not used very much.
  • Performance issue. If the strongly-referenced objects have any significant activity (e.g. respond to notifications, write to files, etc…), they will not only participate in the memory footprint but also remain active much longer than expected. For instance, they might be registered for notifications, for filesystem events, etc…, running code that is no longer needed (maybe even corrupting your application state in an unpredictable way).

The only real workaround is to have APIs for explicit removal of entries in the classes that use NSMapTables internally. The API clients of these classes should then add explicit calls in their dealloc method. For FooNotificationCenter, this would mean adding one more method, that would need to balance any use of addObserver:selector:name::

1
2
3
4
@interface FooNotificationCenter (Removal)
// use this or risk leaking the observer for a while 
- (void)removeObserver:(id)obs;
@end

This is a shame. Similar to how ARC has taken memory management out of the hands of developers, and reduced the number of associated bugs, the combination of NSMapTable and zeroing weak references should further reduce the code needed to clean up more complex object relationships, and to further reduce the memory consumption and resource usage of existing apps.

As Michal Tsai wonders, “NSMapTable is an important building block, so it’s a mystery why Apple hasn’t gotten it right yet”. I also think his interpretation is correct: without weak-deallocation notifications, the owners of zeroed references are unaware of the change. The zeroing of a reference is done by writing a bunch of 0s at the correct memory address(es), corresponding to the variable(s) that referenced the deallocated object. This process has to be as efficient as possible and be thread-safe. Any extraneous work to keep track of the object graphs or maintain a callback table, would need to happen upon deallocation of every weak reference, which could have a significant impact on performance. Is Apple ready to make that happen?

The implementation of NSMapTable, though, does not have to be dependent on the general implementation of zeroing weak references that Objective C has in place. Changing the Objective C runtime just to improve NSMapTable may be overkill. Instead, it seems a proper implementation of NSMapTable could be done using an alternative approach to zeroing weak references, like MAZeroingWeakRef. We are left waiting for Apple to fix NSMapTable, or implementing our own version (assuming it can even be done reliably).

Timers and Dispatch Queues

There are many similarities between dispatch queues and run loops, and many situations where both options can be considered. In practice, dispatch queues are more compelling: they are easier to set up and maintain, while offering better performance. That said, there is still one thing that run loops make easier to manage: timers. With a bit of work, and with the help of PARDispatchQueue, one can have the best of both worlds.

With run loops, you can easily fire a timer, or cancel it if you change your mind:

1
2
3
4
5
6
7
8
9
10
11
// if a timer is already scheduled, cancel it
[saveTimer invalidate];

// save in 5 seconds
// maybe this timer will also be canceled before it fires,
// amd the 'save' will be delayed further
saveTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                             target:self
                                           selector:@selector(save)
                                           userInfo:nil
                                            repeats:NO];

Timers can also be used indirectly with the performSelector:... APIs:

1
2
3
4
5
6
7
8
9
10
11
// if a 'save' call is already scheduled, cancel it
[NSObject cancelPreviousPerformRequestsWithTarget:self
                                         selector:@selector(save:)
                                           object:nil];

// save in 5 seconds
// maybe this call will also be canceled before it fires,
// and the 'save' will be delayed further
[self performSelector:@selector(save:)
           withObject:nil
           afterDelay:5.0];

However, timers and delayed execution only work as expected if you have a run loop set up on the current thread. In practice, I have found that using a run loop on anything other than the main thread is a painful experience.

Fortunately, timers and delayed execution are also possible in the world of Grand Central Dispatch. For delayed execution, one can use the dispatch_after function. Timers are also part of the APIs, as a particular type of dispatch_source_t, which can be created using the dispatch_source_set_timer function. The APIs are a bit intimidating, but thanks to Mike Ash, I did not have to fight too hard to decipher how to use them: the source code for MABGTimer is a great starting point, and it offers a familiar Objective C interface for managing a timer in a dispatch queue.

Coalesce and Delay

With MABGTimer, Mike introduces a simple concept, which I have found to be very powerful in many common situations. It’s timer behavior:

1
2
3
4
5
typedef NS_ENUM(NSInteger, MABGTimerBehavior)
{
    MABGTimerCoalesce,
    MABGTimerDelay
};

The MABGTimerCoalesce behavior means that subsequent calls to a charged timer can only reduce the time until it fires. In other words, once the firing date is set, it cannot be delayed further (but it can be shortened). Conversely, the MABGTimerDelay behavior means that subsequent calls will potentially replace the scheduled time previously set, and thus extend it further.

Coalesce and delay

To illustrate where those behaviors can be useful, let’s consider the indexing of documents with SearchKit. When new documents are added to an index, the data is first stored in memory, and only saved to disk when the index is “flushed”. A naive approach is then to flush after every addition. However, flushing too often results in high I/O activity and has a very significant impact on performance when a large number of documents need to be indexed. Here is a visual representation of that solution:

Flush every time

It would be equally dangerous to wait until all the documents are indexed before flushing, as memory usage will increase, the final flush will take forever, and there is a risk we will lose all the data if for some reason, the flush is not done. A good compromise for batch indexing is thus to flush every minute. The problem with this approach is that it can leave a large delay after adding just one document. This means the index will not be saved to disk for a while, data loss could happen, and the user may perceive a delay when searching (since a flush has to be performed before any search anyway):

Flush every minute

A good strategy is thus to flush after 5 seconds of inactivity following the last document addition, or else, flush every minute as long as documents are being added to the index:

Flush with delay and coalesce

This approach fits well with MABGTimer behaviors. We can first use the MABGTimerDelay behavior to have a flush scheduled 5 seconds after a document is added to the index: the flush time will keep being pushed as more documents are added to the queue. We then also use the MABGTimerCoalesce behavior to have a flush scheduled 1 minute after a document is added to the index: the flush will happen after 1 minute even if more document additions are scheduled. Whichever timer fires first, it cancels the other one. The resulting behavior looks like this:

Flush with timers

Alas, despite all the cleverness of Mike Ash’s design, the above scheme quickly hit the limits of MABGTimer and its APIs. The MABGTimer class is really a thin wrapper for a dispatch queue, and it can only be used with one timer on one dispatch queue.

Hence PARDispatchQueue.

PARDispatchQueue

Since I had a dispatch queue wrapper of my own, that I was using for other purposes as well, I decided to simply steal most of MABGTimer code and integrate it in PARDispatchQueue (it is BSD-licensed and I am thus only using the word “steal” for dramatic effects). The result is available on GitHub.

The APIs for timers in PARDispatchQueue are very simple:

1
2
3
4
5
6
- (BOOL)scheduleTimerWithName:(NSString *)name
                 timeInterval:(NSTimeInterval)delay
                     behavior:(PARTimerBehavior)behavior
                        block:(PARDispatchBlock)block;
- (void)cancelTimerWithName:(NSString *)name;
- (void)cancelAllTimers;

The queue can have multiple timers, which can be safely scheduled and canceled at any point. The timers are simply tracked by their name as an NSString object. I find that scheduling and canceling timers with this design is in fact even easier than on run loops.

In the case of the SearchKit example, we would create a queue to serialize access to an instance of SKIndexRef (the SearchKit APIs are mostly thread-safe, but there are still some aspects that are better handled via a serial queue, in addition to the use of timers here):

1
 indexQueue = [PARDispatchQueue dispatchQueueWithLabel:@"skindex"];

Here is what we do when adding a document:

1
2
3
4
5
6
7
8
- (void)addDocument:(NSString *)identifier content:(NSString *)content
{
    // ... prepare `document` and `string` ...
    SKIndexAddDocumentWithText(indexRef, document, string, 1);
    CFRelease(string);
    CFRelease(document);
    [self flushWhenNeeded];
}

The implementation of flushWhenNeeded is where the timers appear:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)flushWhenNeeded
{
    [self.indexQueue scheduleTimerWithName:@"flush_coalesce"
                              timeInterval:60.0
                                  behavior:FDTimerBehaviorCoalesce
                                     block:^{ [self _flush]; }];

    [self.indexQueue scheduleTimerWithName:@"flush_delay"
                              timeInterval:5.0
                                  behavior:FDTimerBehaviorDelay
                                     block:^{ [self _flush]; }];
}

Finally, the _flush method actually does the job. Note that it is called from within the queue itself. With this design, only one of the 2 timers will actually be fired, whichever comes first:

1
2
3
4
5
6
- (void)_flush
{
    SKIndexFlush(self.indexQueue);
    [self.indexQueue cancelTimerWithName:@"flush_coalesce"];
    [self.indexQueue cancelTimerWithName:@"flush_delay"];
}

PARDispatchQueue Timers in the UI

Such timers are not just useful when dealing with worker queues like in the SearchKit example. They are also useful in UI-related code.

Let’s consider another example related to search, but that now affects the user interface. When a user types in a search field, we need to start a new search and display the results as they come in. For a better user experience, we would like the following behavior:

  • perform the search in a background thread
  • do not initiate the search immediately, in case the user is still typing; instead, wait 0.2 seconds after the last keystroke before starting the actual search
  • if the search is still running after 0.5 seconds, display a spinning cursor, and keep it spinning until the search is finished (if the query is fast and takes less than 0.5 seconds, no need to add extra UI clutter with a short-lived spinning indicator)

Using the classic run loop timers, it is possible to make the above work, but it is a bit tricky and requires multiple methods and callbacks.

Here is what the delegate callback for the search field looks like with PARDispatchQueue. In this code, I also use a searchIndex object that can return search results using a block-based method call query:resultBlock: (the implementation of which is not shown):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- (IBAction)searchFieldDidChange:(id)sender
{
    NSString *query = self.searchField.stringValue;

    // Ignore empty queries
    if ([query length] == 0)
    {
        [self showAll];
        return;
    }

    // Use a serial queue, so only one query will run at a time
    // Start searching when the user seems to be done typing (after 0.2 seconds)
    [self.searchQueue scheduleTimerWithName:@"query"
                               timeInterval:0.2
                                   behavior:FDTimerBehaviorDelay
                                      block:^
      {
          // Spinning indicator will start if the query is still running after 0.5 secs
          NSString *sessionUUID = [[NSUUID UUID] UUIDString];
          FDDispatchQueue *mainQueue = [FDDispatchQueue mainDispatchQueue]
          [mainQueue scheduleTimerWithName:sessionUUID
                              timeInterval:0.5
                                  behavior:FDTimerBehaviorDelay
                                     block:^{ [self showSearchIndicator]; }];

          // Run query
          // Results are returned in batches every 0.1 seconds
          [self.searchIndex query:query batchTimeInterval:0.1 resultBlock:^BOOL(NSSet *results, BOOL hasMore)
           {
               __block BOOL shouldContinue = YES;
               [mainQueue dispatchSynchronously:^
                {
                    NSString *currentQuery = self.searchField.stringValue;
                    // We will bail out if the query has changed in the meantime
                    shouldContinue = [query isEqualToString:currentQuery];
                    [self showResults:results];
                }];
               return shouldContinue;
           }];

          // Stop spinning indicator (and maybe it was never started in the first place)
          [mainQueue cancelTimerWithName:sessionUUID];
          [mainQueue dispatchAsynchronously:^{ [self hideSearchIndicator]; }];
      }];
}

Blocks make it easy to have all the code in one place, and the timer APIs of PARDispatchQueue make it easy to add subtle behavior to the UI with a minimal amount of fuss. The end result is less code, easier to read and easier to maintain.

Avoiding Deadlocks With Dispatch Queues

I have started to use dispatch queues more seriously in the past couple of years. To make the best use of dispatch queues, one has to really think differently about the design of classes, the design of their APIs and the overall architecture of the program. It took a while for me to grasp, but it is liberating at many different levels. I now feel I write better code, and feel more confident writing multi-threaded code (best of all, without having to actually manage threads).

I still find myself thinking about dispatch queues through other concepts I am more familiar with, though. For instance, serial queues are great for serializing access to a group of connected resources, like a CoreData stack. In this case, I still tend to think of a serial dispatch queue as a run loop in which you can just push any work that need to be done on those resources. Of course, dispatch queues would probably be insulted to be called run loops, and rightly so. For starters, they are very efficient and much easier to use.

That said, there is still one thing that run loops make easier to manage: timers. But with the help of PARDispatchQueue, I now have the best of both worlds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@interface PARDispatchQueue : NSObject

// Creating Queues
+ (PARDispatchQueue *)globalDispatchQueue;
+ (PARDispatchQueue *)mainDispatchQueue;
+ (PARDispatchQueue *)dispatchQueueWithLabel:(NSString *)label;


// Dispatching Blocks
- (void)dispatchSynchronously:(PARDispatchBlock)block;
- (void)dispatchAsynchronously:(PARDispatchBlock)block;

// Managing Timers
- (BOOL)scheduleTimerWithName:(NSString *)name timeInterval:(NSTimeInterval)delay behavior:(FDTimerBehavior)behavior block:(FDDispatchBlock)block;
- (void)cancelTimerWithName:(NSString *)name;
- (void)cancelAllTimers;

// Deadlock Behaviors
@property (readonly) FDDeadlockBehavior deadlockBehavior;

typedef NS_ENUM(NSInteger, FDDeadlockBehavior)
{
    FDDeadlockBehaviorExecute,
    FDDeadlockBehaviorSkip,
    FDDeadlockBehaviorLog,
    FDDeadlockBehaviorAssert,
    FDDeadlockBehaviorBlock
};

// FDDeadlockBehaviorExecute: do not add the block to the queue, execute inline (default)
// FDDeadlockBehaviorSkip:    do not add the block to the queue, drop it silently
// FDDeadlockBehaviorLog:     do not add the block to the queue, log to console
// FDDeadlockBehaviorAssert:  do not add the block to the queue, raise an exception
// FDDeadlockBehaviorBlock:   add the block to the queue, and be damned



@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface PARStore : NSObject

// Accessing Store Content
- (id)propertyListValueForKey:(NSString *)key;
- (void)setPropertyListValue:(id)plist forKey:(NSString *)key;
- (NSDictionary *)allRelevantValues;
- (void)setEntriesFromDictionary:(NSDictionary *)dictionary;
- (void)runTransaction:(FDDispatchBlock)block;

@end

// CoreData APIs
- (void)performBlock:(void (^)())block
- (void)performBlockAndWait:(void (^)())block;

// ...
[context performBlockAndWait:^{
    [context performBlockAndWait:^{
        [context save:NULL];
    }];
}];
1
2
3
4
for (int i = 0; i < 10; i++)
{
    printf("%i", i);
}