a +view controller manages a screenful of content. +This has since changed to
a view controller manages a self-contained +unit of content. +Why didn't Apple want us to build our own tab bar controllers and +navigation controllers? Or put more precisely, what is the problem with: + + [viewControllerA.view addSubView:viewControllerB.view] + + + +The UIWindow, which serves as an app's root view, is where rotation and +initial layout messages originate from. In the +illustration above, the child view controller, whose view was inserted +in the root view controller view hierarchy, is excluded from those events. +View event methods like `viewWillAppear:` will not get called. + +The custom view controller containers built before iOS 5 would keep a +reference to the child view controllers and manually relay all the +view event methods called on the parent view controller, which is +pretty difficult to get right. + +## An Example + +When you were a kid playing in the sand, did your parents ever tell you that if you kept digging with your little shovel you would end up in China? +Mine did, and I made a small demo app called *Tunnel* to test this claim. +You can clone the [GitHub +repo](https://github.com/RickiG/view-controller-containment) and run the +app, which will make it easier to understand the example code. +*(Spoiler: digging through the earth from western Denmark lands you somewhere in the South Pacific Ocean.)* + + + +To find the antipode, as the opposite location is called, move the little guy with the shovel around and the map will tell you where your exit location is. Tap the radar button and the map will flip to reveal the name of the location. + +There are two map view controllers on screen. Each of them has to deal with dragging, annotation, and updating the map. +Flipping these over reveals two new view controllers which reverse geocode the locations. +All the view controllers are contained inside a parent view controller, which holds their views, and ensures that layout and rotation behaves as expected. + +The root view controller has two container views. These are added to +make it easier to layout and animate the views of child view +controllers, as we will see later on. + + - (void)viewDidLoad + { + [super viewDidLoad]; + + //Setup controllers + _startMapViewController = [RGMapViewController new]; + [_startMapViewController setAnnotationImagePath:@"man"]; + [self addChildViewController:_startMapViewController]; // 1 + [topContainer addSubview:_startMapViewController.view]; // 2 + [_startMapViewController didMoveToParentViewController:self]; // 3 + [_startMapViewController addObserver:self + forKeyPath:@"currentLocation" + options:NSKeyValueObservingOptionNew + context:NULL]; + + _startGeoViewController = [RGGeoInfoViewController new]; // 4 + } + +The `_startMapViewController`, which displays the starting position, is instantiated and set up with the annotation image. + +1. The `_startMapViewcontroller` is added as a child of the root view + controller. This automatically calls +the method `willMoveToParentViewController:` on the child. +2. The child's view is added as a subview of the first container view. +3. The child is notified that it now has a parent view controller. +4. The child view controller that does the geocoding is instantiated, + but not inserted in any view or controller hierarchy yet. + + + +## Layout + +The root view controller defines the two container views, +which determine the size of the child view controllers. +The child view controllers do not know which container they will be +added to, and therefore have to be flexible in size: + + - (void) loadView + { + mapView = [MKMapView new]; + mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight; + [mapView setDelegate:self]; + [mapView setMapType:MKMapTypeHybrid]; + + self.view = mapView; + } + +Now they will layout using the bounds of their super view. This increases the reusability of the child view controller; if we were to push it on the stack of a navigation controller, it would still layout correctly. + +## Transitions + +Apple has made the view controller containment API so fine-grained that +it is possible to construct and animate any containment scenario we can +think of. +Apple also provides a block-based convenience method for exchanging two controller views on the screen. +The method +`transitionFromViewController:toViewController:(...)` +takes care of a lot of the details for us: + + - (void) flipFromViewController:(UIViewController*) fromController + toViewController:(UIViewController*) toController + withDirection:(UIViewAnimationOptions) direction + { + toController.view.frame = fromController.view.bounds; // 1 + [self addChildViewController:toController]; // + [fromController willMoveToParentViewController:nil]; // + + [self transitionFromViewController:fromController + toViewController:toController + duration:0.2 + options:direction | UIViewAnimationOptionCurveEaseIn + animations:nil + completion:^(BOOL finished) { + + [toController didMoveToParentViewController:self]; // 2 + [fromController removeFromParentViewController]; // 3 + }]; + } + +1. Before the animation we add the `toController` as a child and we + inform the `fromController` that it will be removed. If the fromController's view is part of the container's view hierarchy, this is where `viewWillDisapear:` is called. +2. `toController` is informed of its new parent, and appropriate view + event methods will be called. +3. The `fromController` is removed. + +This convenience method for view controller transitions automatically swaps out the old view controller's view for the new one. However, if you implement your own transition and you wish to only display one view at a time, you have to call `removeFromSuperview` on the old view and `addSubview:` for the new view yourself. Getting the sequence of method calls wrong will most likely result in an `UIViewControllerHierarchyInconsistency` warning. For example, this will +happen if you call `didMoveToParentViewController:` before you added the view. + +In order to be able to use the `UIViewAnimationOptionTransitionFlipFromTop` animation, we had to add the children's views to our view containers instead of to the root view controller's view. Otherwise the animation would result in the entire root view flipping over. + + +## Communication + +View controllers should be reusable and self-contained entities. Child view controllers are no exception to this rule of thumb. In order to achieve this, the parent view controller should only be concerned with two tasks: laying out the child view controller's root view, and communicating with the child view controller through its exposed API. It should never modify the child's view tree or other internal state directly. + +Child view controllers should contain the necessary logic to manage their view trees themselves -- don't treat them as dumb views. This will result in a clearer separation of concerns and better reusability. + +In the Tunnel example app, the parent view controller observes a property called `currentLocation` on the map view controllers: + + [_startMapViewController addObserver:self + forKeyPath:@"currentLocation" + options:NSKeyValueObservingOptionNew + context:NULL]; + +When this property changes in response to moving the little guy with the shovel around on the map, the parent view controller communicates the antipode of the new location to the other map: + + [oppositeController updateAnnotationLocation:[newLocation antipode]]; + +Likewise, when you tap the radar button, the parent view controller sets the locations to be reverse geocoded on the new child view controllers: + + [_startGeoViewController setLocation:_startMapViewController.currentLocation]; + [_targetGeoViewController setLocation:_targetMapViewController.currentLocation]; + +Independent of the technique you choose to communicate from child to parent view controllers (KVO, notifications, or the delegate pattern), the goal always stays the same: the child view controllers should be independent and reusable. In our example we could push one of the child view controllers on a navigation stack, but the communication would still work through the same API. diff --git a/2013-06-07-index.markdown b/2013-06-07-index.markdown new file mode 100644 index 0000000..869478d --- /dev/null +++ b/2013-06-07-index.markdown @@ -0,0 +1,6 @@ +--- +layout: toc +category: "1" +date: "2013-06-07 12:00" +tags: toc +--- diff --git a/2013-06-07-introduction.markdown b/2013-06-07-introduction.markdown new file mode 100644 index 0000000..80daa96 --- /dev/null +++ b/2013-06-07-introduction.markdown @@ -0,0 +1,21 @@ +--- +layout: post +title: "Introduction" +category: "1" +date: "2013-06-07 12:00" +tags: editorial +--- + +Welcome to the first edition of objc.io, a periodical about best practices and advanced techniques in Objective-C! + +objc.io was founded in Berlin by [Chris Eidhof](https://twitter.com/chriseidhof), [Daniel Eggert](https://twitter.com/danielboedewadt), and [Florian Kugler](https://twitter.com/floriankugler). We started objc.io to create a regular platform for in-depth technical topics relevant to all iOS and OS X developers. + +Each edition of objc.io has a focus on one particular subject, with multiple articles covering different aspects of that topic. The subject of this first edition is *Lighter View Controllers*, and it contains four articles – three from the founding team and one from [Ricki Gregersen](https://twitter.com/rickigregersen), whom we would like to welcome as our first guest writer! + +It's a common problem in the code base of iOS apps that view controllers get out of hand, because they do too much. By factoring out reusable code, they are easier to understand, easier to maintain and easier to test. This issue will focus on best practices and techniques how you can keep view controllers clean. + +We will look at how to make view controllers primarily coordinating objects by factoring out view and model code, as well as introducing other controller objects in addition to view controllers. Furthermore, we will look at splitting up view controllers using the view controller containment mechanism, and finally, discuss how to test clean view controllers. + +In upcoming editions we will have more articles from great guest writers of the Objective-C community; [Loren Brichter](https://twitter.com/lorenb), [Peter Steinberger](https://twitter.com/steipete), [Brent Simmons](https://twitter.com/brentsimmons), and [Ole Begemann](https://twitter.com/olebegemann) have committed to writing in the future. [Contact us](mailto:mail@objc.io) if you have an idea for an interesting topic and you would like to contribute an article about it to objc.io. + +Chris, Daniel, and Florian. diff --git a/2013-06-07-lighter-view-controllers.md b/2013-06-07-lighter-view-controllers.md new file mode 100644 index 0000000..3041588 --- /dev/null +++ b/2013-06-07-lighter-view-controllers.md @@ -0,0 +1,241 @@ +--- +layout: post +title: "Lighter View Controllers" +category: "1" +date: "2013-06-07 11:00:00" +author: "Chris Eidhof" +tags: article +--- + +View controllers are often the biggest files in iOS +projects, and they often contain way more code than necessary. Almost always, +view controllers are the least reusable part of the code. We will look +at techniques to slim down your view controllers, make code reusable, and move code to more appropriate places. + +The [example project](https://github.com/objcio/issue-1-lighter-view-controllers) for this issue is on GitHub. + +## Separate Out Data Source and Other Protocols + +One of the most powerful techniques to slim down your view controller is +to take the `UITableViewDataSource` part of your code, and move it to +its own class. If you do this more than once, you will start to see +patterns and create reusable classes for this. + +For example, in our example project, there is a class +`PhotosViewController` which had the following methods: + + # pragma mark Pragma + + - (Photo*)photoAtIndexPath:(NSIndexPath*)indexPath { + return photos[(NSUInteger)indexPath.row]; + } + + - (NSInteger)tableView:(UITableView*)tableView + numberOfRowsInSection:(NSInteger)section { + return photos.count; + } + + - (UITableViewCell*)tableView:(UITableView*)tableView + cellForRowAtIndexPath:(NSIndexPath*)indexPath { + PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier + forIndexPath:indexPath]; + Photo* photo = [self photoAtIndexPath:indexPath]; + cell.label.text = photo.name; + return cell; + } + + +A lot of this code has to do with arrays, and some of it is +specific to the photos that the view controller manages. So let's try to move the array-related code into its [own +class](https://github.com/objcio/issue-1-lighter-view-controllers/blob/master/PhotoData/ArrayDataSource.h). +We use a block for configuring the cell, but it might as well be +a delegate, depending on your use-case and taste. + + @implementation ArrayDataSource + + - (id)itemAtIndexPath:(NSIndexPath*)indexPath { + return items[(NSUInteger)indexPath.row]; + } + + - (NSInteger)tableView:(UITableView*)tableView + numberOfRowsInSection:(NSInteger)section { + return items.count; + } + + - (UITableViewCell*)tableView:(UITableView*)tableView + cellForRowAtIndexPath:(NSIndexPath*)indexPath { + id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier + forIndexPath:indexPath]; + id item = [self itemAtIndexPath:indexPath]; + configureCellBlock(cell,item); + return cell; + } + + @end + +The three methods that were in your view controller can go, and instead +you can create an instance of this object and set it as the table view's +data source. + + void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) { + cell.label.text = photo.name; + }; + photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos + cellIdentifier:PhotoCellIdentifier + configureCellBlock:configureCell]; + self.tableView.dataSource = photosArrayDataSource; + +Now you don't have to worry about mapping an index path to a +position in the array, and every time you want to display an +array in a table view you can reuse this code. You can also implement +additional methods such as +`tableView:commitEditingStyle:forRowAtIndexPath:` and share that code among +all your table view controllers. + +The nice thing is that we can [test this class](/issue-1/testing-view-controllers.html#testing-datasource) separately, +and never have to worry about writing it again. The same principle +applies if you use something else other than arrays. + +In one of the applications we were working on this year, +we made heavy use of Core Data. We created a similar class, but instead +of being backed by an array, it is backed by a +fetched results controller. It implements all the logic for +animating the updates, doing section headers, and deletion. You can +then create an instance of this object and feed it a fetch request and a +block for configuring the cell, and the rest will be taken care of. + +Furthermore, this approach extends to other protocols as well. One +obvious candidate is `UICollectionViewDataSource`. This gives you +tremendous flexibility; if, at some point during the development, you +decide to have a `UICollectionView` instead of a `UITableView`, you +hardly have to change anything in your view controller. You could even +make your data source support both protocols. + +## Move Domain Logic into the Model + + +Here is an example of code in view controller (from another project) that is supposed to find a list of active +priorities for a user: + + - (void)loadPriorities { + NSDate* now = [NSDate date]; + NSString* formatString = @"startDate <= %@ AND endDate >= %@"; + NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now]; + NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate]; + self.priorities = [priorities allObjects]; + } + +However, it is much cleaner to move this code to a category on the `User` class. Then +it looks like this in `View Controller.m`: + + - (void)loadPriorities { + self.priorities = [user currentPriorities]; + } + +and in `User+Extensions.m`: + + + - (NSArray*)currentPriorities { + NSDate* now = [NSDate date]; + NSString* formatString = @"startDate <= %@ AND endDate >= %@"; + NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now]; + return [[self.priorities filteredSetUsingPredicate:predicate] allObjects]; + } + +Some code cannot be easily moved into a model object but is still +clearly associated with model code, and for this, we can use a `Store`: + +## Creating the Store Class + +In the first version of our example application, we had some code to +load data from a file and parse it. This code was in the view +controller: + + - (void)readArchive { + NSBundle* bundle = [NSBundle bundleForClass:[self class]]; + NSURL *archiveURL = [bundle URLForResource:@"photodata" + withExtension:@"bin"]; + NSAssert(archiveURL != nil, @"Unable to find archive in bundle."); + NSData *data = [NSData dataWithContentsOfURL:archiveURL + options:0 + error:NULL]; + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; + _users = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"users"]; + _photos = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"photos"]; + [unarchiver finishDecoding]; + } + +The view controller should not have to know about this. +We created a *Store* object that does just this. By separating it +out, we can reuse that code, test it separately and keep our view +controller small. The store can take care of data loading, caching, +and setting up the database stack. This store is also often called a +*service layer* or a *repository*. + + +## Move Web Service Logic to the Model Layer + +This is very similar to the topic above: don't do web service logic in +your view controller. Instead, encapsulate this in a different class. +Your view controller can then call methods on this class with a callback +handler (for example, a completion block). +The nice thing is that you can do all your caching and error handling in +this class too. + +## Move View Code into the View Layer + +Building complicated view hierarchies shouldn't be done in view +controllers. Either use interface builder, or encapsulate views into +their own `UIView` subclasses. For example, if you build your own date +picker control, it makes more sense to put this into a +`DatePickerView` class than creating the whole thing in the view +controller. Again, this increases reusability and simplicity. + +If you like Interface Builder, then you can also do this in +Interface Builder. Some people assume you can only use this for view +controllers, but you can also load separate nib files with your custom +views. In our example app, we created a [`PhotoCell.xib`](https://github.com/objcio/issue-1-lighter-view-controllers/blob/master/PhotoData/PhotoCell.xib) that +contains the layout for a photo cell: + + + +As you can see, we created properties on the view (we don't use the +File's Owner object in this xib) and connect them to specific subviews. +This technique is also very handy for other custom views. + +## Communication + +One of the other things that happen a lot in view controllers is +communication with other view controllers, the model, and the views. +While this is exactly what a controller should do, it is also something +we'd like to achieve with as minimal code as possible. + +There are a lot of well-explained techniques for communication between +your view controllers and your model objects (such as KVO and fetched +results controllers), however, communication between view controllers is +often a bit less clear. + +We often have the problem where one view controller has some state and +communicates with multiple other view controllers. Often, it then makes +sense to put this state into a separate object and pass it around the +view controllers, which then all observe and modify that state. The +advantage is that it's all in one place, and we don't end up entangled +in nested delegate callbacks. This is a complex subject, and we might +dedicate a whole issue to this in the future. + +## Conclusion + +We've seen some techniques for creating smaller view controllers. We +don't strive to apply these techniques wherever possible, as we have only +one goal: to write maintainable code. By knowing these patterns, we have better +chances of taking unwieldy view controllers and making them clearer. + +### Further Reading + +* [View Controller Programming Guide for iOS](http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html) +* [Cocoa Core Competencies: Controller Object](http://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ControllerObject.html) +* [Writing high quality view controllers](http://subjective-objective-c.blogspot.de/2011/08/writing-high-quality-view-controller.html) +* [Stack Overflow: Model View Controller Store](http://programmers.stackexchange.com/questions/184396/mvcs-model-view-controller-store) +* [Unburdened View Controllers](https://speakerdeck.com/trianglecocoa/unburdened-viewcontrollers-by-jay-thrash) +* [Stack Overflow: How to avoid big and clumsy `UITableViewControllers` on iOS](http://programmers.stackexchange.com/questions/177668/how-to-avoid-big-and-clumsy-uitableviewcontroller-on-ios) + diff --git a/2013-06-07-table-views.md b/2013-06-07-table-views.md new file mode 100644 index 0000000..795a96b --- /dev/null +++ b/2013-06-07-table-views.md @@ -0,0 +1,223 @@ +--- +layout: post +title: "Clean table view code" +category: "1" +date: "2013-06-07 10:00:00" +author: "Florian Kugler" +tags: article +--- + +Table views are an extremely versatile building block for iOS apps. Therefore, a lot of code is directly or indirectly related to table view tasks, including supplying data, updating the table view, controlling its behavior, and reacting to selections, to name just a few. In this article, we will present techniques to keep this code clean and well-structured. + +## UITableViewController vs. UIViewController + +Apple provides `UITableViewController` as dedicated view controller class for table views. Table view controllers implement a handful of very useful features which can help you to avoid writing the same boilerplate code over and over. On the flip side, table view controllers are restricted to managing exactly one table view, which is presented full screen. However, in many cases, this is all you need, and if it's not, there are ways to work around this, as we will show below. + +### Features of Table View Controllers + +Table view controllers help you with loading the table view's data +when it is shown for the first time. More specifically, it helps with toggling the table view's +editing mode, with reacting to the keyboard notifications, and +with a few small tasks like flashing the scroll indicator and clearing +the selection. In order for these features to work, it is important that +you call super on any view event methods (such as `viewWillAppear:` and `viewDidAppear:`) that you may override in your custom subclass. + +Table view controllers have one unique selling point over standard view controllers, and that's the support for Apple's implementation for "pull to refresh." At the moment, the only documented way of using `UIRefreshControl` is within a table view controller. There are ways to make it work in other contexts, but these could easily not work with the next iOS update. + +The sum of all these elements provides much of the standard table view interface behavior as Apple has defined it. If your app conforms to these standards, it is a good idea to stick with table view controllers in order to avoid writing boilerplate code. + +### Limitations of Table View Controllers + +The view property of table view controllers always has to be set to a table view. If you decide later on that you want to show something else on the screen aside from the table view (e.g. a map), you are out of luck if you don't want to rely on awkward hacks. + +If you have defined your interface in code or using a .xib file, then it is pretty easy to transition to a standard view controller. If you're using storyboards, then this process involves a few more steps. With storyboards, you cannot change a table view controller to a standard view controller without recreating it. This means you have to copy all the contents over to the new view controller and wire everything up again. + +Finally, you need to add back the features of table view controller that you lost in this transition. Most of them are simple single-line statements in `viewWillAppear` or `viewDidAppear`. Toggling the editing state requires implementing an action method which flips the table view's `editing` property. The most work lies in recreating the keyboard support. + +Before you go down this route though, here is an easy alternative that has the additional benefit of separating concerns: + +### Child View Controllers + +Instead of getting rid of the table view controller entirely, you could +also add it as a child view controller to another view controller (see +the [article about view controller containment](/issue-1/containment-view-controller.html) in this issue). Then the table view controller continues to manage only the table view and the parent view controller can take care of whatever additional interface elements you might need. + + + - (void)addPhotoDetailsTableView + { + DetailsViewController *details = [[DetailsViewController alloc] init]; + details.photo = self.photo; + details.delegate = self; + [self addChildViewController:details]; + CGRect frame = self.view.bounds; + frame.origin.y = 110; + details.view.frame = frame; + [self.view addSubview:details.view]; + [details didMoveToParentViewController:self]; + } + +If you use this solution you have to create a communication channel from +the child to the parent view controller. For example, if the user selects a cell in the table view, the parent view controller needs to know about this in order to push another view controller. Depending on the use case, often the cleanest way to do this is to define a delegate protocol for the table view controller, which you then implement on the parent view controller. + + @protocol DetailsViewControllerDelegate + - (void)didSelectPhotoAttributeWithKey:(NSString *)key; + @end + + @interface PhotoViewController ()
If you always read or write a file’s contents from start to +finish, streams provide a simple interface for doing so +asynchronously.. + +Whether you use streams or not, the general pattern for reading a file +line-by-line is as follows: + +1. Have an intermediate buffer that you append to while not finding a newline +2. Read a chunk from the stream +3. For each newline found in the chunk, take the intermediate buffer, + append data from the stream up to (and including) the newline, and output that +4. Append the remaining bytes to the intermediate buffer +5. Go back to 2 until the stream closes + +To put this into practice, we created a [sample application](https://github.com/objcio/issue-2-background-file-io) with a +`Reader` class that does just this. The interface is very simple: + + @interface Reader : NSObject + - (void)enumerateLines:(void (^)(NSString*))block + completion:(void (^)())completion; + - (id)initWithFileAtPath:(NSString*)path; + @end + +Note that this is not a subclass of `NSOperation`. Like URL connections, +input streams deliver +their events using a run loop. Therefore, we will use the main run loop +again for event delivery, and then dispatch the processing of the data +onto a background operation queue. + + - (void)enumerateLines:(void (^)(NSString*))block + completion:(void (^)())completion + { + if (self.queue == nil) { + self.queue = [[NSOperationQueue alloc] init]; + self.queue.maxConcurrentOperationCount = 1; + } + self.callback = block; + self.completion = completion; + self.inputStream = [NSInputStream inputStreamWithURL:self.fileURL]; + self.inputStream.delegate = self; + [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] + forMode:NSDefaultRunLoopMode]; + [self.inputStream open]; + } + +Now the input stream will send us delegate messages (on the main +thread), and we do the processing on the operation +queue by adding a block operation: + + - (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode + { + switch (eventCode) { + ... + case NSStreamEventHasBytesAvailable: { + NSMutableData *buffer = [NSMutableData dataWithLength:4 * 1024]; + NSUInteger length = [self.inputStream read:[buffer mutableBytes] + maxLength:[buffer length]]; + if (0 < length) { + [buffer setLength:length]; + __weak id weakSelf = self; + [self.queue addOperationWithBlock:^{ + [weakSelf processDataChunk:buffer]; + }]; + } + break; + } + ... + } + } + +Processing a data chunk looks at the current buffered data and appends +the newly streamed chunk. It then breaks that into components, separated +by newlines, and emits each line. The remainder gets stored again: + + - (void)processDataChunk:(NSMutableData *)buffer; + { + if (self.remainder != nil) { + [self.remainder appendData:buffer]; + } else { + self.remainder = buffer; + } + [self.remainder obj_enumerateComponentsSeparatedBy:self.delimiter + usingBlock:^(NSData* component, BOOL last) { + if (!last) { + [self emitLineWithData:component]; + } else if (0 < [component length]) { + self.remainder = [component mutableCopy]; + } else { + self.remainder = nil; + } + }]; + } + +If you run the sample app, you will see that the app stays very +responsive, and the memory stays very low (in our test runs, the heap +size stayed under 800 KB, regardless of the file size). For processing +large files chunk by chunk, this technique is probably what you want. + +Further reading: + +* [File System Programming Guide: Techniques for Reading and Writing Files Without File Coordinators](http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/TechniquesforReadingandWritingCustomFiles.html) +* [StackOverflow: How to read data from NSFileHandle line by line?](http://stackoverflow.com/questions/3707427/how-to-read-data-from-nsfilehandle-line-by-line) + +## Conclusion + +In the examples above we demonstrated how to perform common tasks +asynchronously in the background. In all of these solutions, we tried +to keep our code simple, because it's very easy to make mistakes with +concurrent programming without noticing. + +Oftentimes you might get away with just doing your work on the main thread, +and when you can, it'll make your life a lot easier. But if you find performance bottlenecks, put these tasks into the background using the +simplest approach possible. + +The pattern we showed in the examples above is a safe choice for other +tasks as well. Receive events or data on the main queue, then +use a background operation queue to perform the actual work before getting +back onto the main queue to deliver the results. diff --git a/2013-07-07-concurrency-apis-and-pitfalls.md b/2013-07-07-concurrency-apis-and-pitfalls.md new file mode 100644 index 0000000..ca4bf1d --- /dev/null +++ b/2013-07-07-concurrency-apis-and-pitfalls.md @@ -0,0 +1,396 @@ +--- +layout: post +title: "Concurrent Programming: APIs and Challenges" +category: "2" +date: "2013-07-07 10:00:00" +author: "Florian Kugler" +tags: article +--- + +{% include links-2.md %} + +[Concurrency](http://en.wikipedia.org/wiki/Concurrency_%28computer_science%29) describes the concept of running several tasks at the same time. This can either happen in a [time-shared](http://en.wikipedia.org/wiki/Preemption_%28computing%29) manner on a single CPU core, or truly in parallel if multiple CPU cores are available. + +OS X and iOS provide several different APIs to enable concurrent programming. Each of these APIs has different capabilities and limitations, making them suitable for different tasks. They also sit on very different levels of abstraction. We have the possibility to operate very close to the metal, but this also comes with great responsibility to get things right. + +Concurrent programming is a very difficult subject with many intricate problems and pitfalls, and it's easy to forget this while using APIs like Grand Central Dispatch or `NSOperationQueue`. This article will first give an overview of the different concurrency APIs on OS X and iOS, and then dive deeper into the inherent challenges of concurrent programming, which are independent of the specific API you use. + + +## Concurrency APIs on OS X and iOS + +Apple's mobile and desktop operating systems provide the same APIs for concurrent programming. In this article we are going to take a look at `pthread` and `NSThread`, Grand Central Dispatch, `NSOperationQueue`, and `NSRunLoop`. Technically, run loops are the odd ones out in this list, because they don't enable true parallelism. But they are related closely enough to the topic that it's worth having a closer look. + +We'll start with the lower-level APIs and move our way up to the higher-level ones. We chose this route because the higher-level APIs are built on top of the lower-level APIs. However, when choosing an API for your use case, you should consider them in the exact opposite order: choose the highest level abstraction that gets the job done and keep your concurrency model very simple. + +If you're wondering why we are so persistent recommending high-level abstractions and very simple concurrency code, you should read the second part of this article, [challenges of concurrent programming](#challenges), as well as [Peter Steinberger's thread safety article][400]. + + +### Threads + +[Threads](http://en.wikipedia.org/wiki/Thread_%28computing%29) are subunits of processes, which can be scheduled independently by the operating system scheduler. Virtually all concurrency APIs are built on top of threads under the hood -- that's true for both Grand Central Dispatch and operation queues. + +Multiple threads can be executed at the same time on a single CPU core (or at least perceived as at the same time). The operating system assigns small slices of computing time to each thread, so that it seems to the user as if multiple tasks are executed at the same time. If multiple CPU cores are available, then multiple threads can be executed truly in parallel, therefore lessening the total time needed for a certain workload. + +You can use the [CPU strategy view](http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/AnalysingCPUUsageinYourOSXApp/AnalysingCPUUsageinYourOSXApp.html) in Instruments to get some insight of how your code or the framework code you're using gets scheduled for execution on multiple CPU cores. + +The important thing to keep in mind is that you have no control over where and when your code gets scheduled, and when and for how long its execution will be paused in order for other tasks to take their turn. This kind of thread scheduling is a very powerful technique. However, it also comes with great complexity, which we will investigate later on. + +Leaving this complexity aside for a moment, you can either use the [POSIX thread](http://en.wikipedia.org/wiki/POSIX_Threads) API, or the Objective-C wrapper around this API, `NSThread`, to create your own threads. Here's a small sample that finds the minimum and maximum in a set of 1 million numbers using `pthread`. It spawns off 4 threads that run in parallel. It should be obvious from this example why you wouldn't want to use pthreads directly. + + + struct threadInfo { + uint32_t * inputValues; + size_t count; + }; + + struct threadResult { + uint32_t min; + uint32_t max; + }; + + void * findMinAndMax(void *arg) + { + struct threadInfo const * const info = (struct threadInfo *) arg; + uint32_t min = UINT32_MAX; + uint32_t max = 0; + for (size_t i = 0; i < info->count; ++i) { + uint32_t v = info->inputValues[i]; + min = MIN(min, v); + max = MAX(max, v); + } + free(arg); + struct threadResult * const result = (struct threadResult *) malloc(sizeof(*result)); + result->min = min; + result->max = max; + return result; + } + + int main(int argc, const char * argv[]) + { + size_t const count = 1000000; + uint32_t inputValues[count]; + + // Fill input values with random numbers: + for (size_t i = 0; i < count; ++i) { + inputValues[i] = arc4random(); + } + + // Spawn 4 threads to find the minimum and maximum: + size_t const threadCount = 4; + pthread_t tid[threadCount]; + for (size_t i = 0; i < threadCount; ++i) { + struct threadInfo * const info = (struct threadInfo *) malloc(sizeof(*info)); + size_t offset = (count / threadCount) * i; + info->inputValues = inputValues + offset; + info->count = MIN(count - offset, count / threadCount); + int err = pthread_create(tid + i, NULL, &findMinAndMax, info); + NSCAssert(err == 0, @"pthread_create() failed: %d", err); + } + // Wait for the threads to exit: + struct threadResult * results[threadCount]; + for (size_t i = 0; i < threadCount; ++i) { + int err = pthread_join(tid[i], (void **) &(results[i])); + NSCAssert(err == 0, @"pthread_join() failed: %d", err); + } + // Find the min and max: + uint32_t min = UINT32_MAX; + uint32_t max = 0; + for (size_t i = 0; i < threadCount; ++i) { + min = MIN(min, results[i]->min); + max = MAX(max, results[i]->max); + free(results[i]); + results[i] = NULL; + } + + NSLog(@"min = %u", min); + NSLog(@"max = %u", max); + return 0; + } + + +`NSThread` is a simple Objective-C wrapper around pthreads. This makes the code look more familiar in a Cocoa environment. For example, you can define a thread as a subclass of NSThread, which encapsulates the code you want to run in the background. For the previous example, we could define an `NSThread` subclass like this: + + @interface FindMinMaxThread : NSThread + @property (nonatomic) NSUInteger min; + @property (nonatomic) NSUInteger max; + - (instancetype)initWithNumbers:(NSArray *)numbers; + @end + + @implementation FindMinMaxThread { + NSArray *_numbers; + } + + - (instancetype)initWithNumbers:(NSArray *)numbers + { + self = [super init]; + if (self) { + _numbers = numbers; + } + return self; + } + + - (void)main + { + NSUInteger min; + NSUInteger max; + // process the data + self.min = min; + self.max = max; + } + @end + +To start new threads, we need to create new thread objects and call their `start` methods: + + NSSet *threads = [NSMutableSet set]; + NSUInteger numberCount = self.numbers.count; + NSUInteger threadCount = 4; + for (NSUInteger i = 0; i < threadCount; i++) { + NSUInteger offset = (count / threadCount) * i; + NSUInteger count = MIN(numberCount - offset, numberCount / threadCount); + NSRange range = NSMakeRange(offset, count); + NSArray *subset = [self.numbers subarrayWithRange:range]; + FindMinMaxThread *thread = [[FindMinMaxThread alloc] initWithNumbers:subset]; + [threads addObject:thread]; + [thread start]; + } + +Now we could observe the threads' `isFinished` property to detect when all our newly spawned threads have finished before evaluating the result. We will leave this exercise to the interested reader though. The main point is that working directly with threads, using either the `pthread` or the `NSThread` APIs, is a relatively clunky experience and doesn't fit our mental model of coding very well. + +One problem that can arise from directly using threads is that the number of active threads increases exponentially if both your code and underlying framework code spawn their own threads. This is actually a quite common problem in big projects. For example, if you create eight threads to take advantage of eight CPU cores, and the framework code you call into from these threads does the same (as it doesn't know about the threads you already created), you can quickly end up with dozens or even hundreds of threads. Each part of the code involved acted responsibly in itself; nevertheless, the end result is problematic. Threads don't come for free. Each thread ties up memory and kernel resources. + +Next up, we'll discuss two queue-based concurrency APIs: Grand Central Dispatch and operation queues. They alleviate this problem by centrally managing a [thread pool](http://en.wikipedia.org/wiki/Thread_pool_pattern) that everybody uses collaboratively. + + +### Grand Central Dispatch + +Grand Central Dispatch (GCD) was introduced in OS X 10.6 and iOS 4 in order to make it easier for developers to take advantage of the increasing numbers of CPU cores in consumer devices. We will go into more detail about GCD in our [article about low-level concurrency APIs][300]. + +With GCD you don't interact with threads directly anymore. Instead you add blocks of code to queues, and GCD manages a [thread pool](http://en.wikipedia.org/wiki/Thread_pool_pattern) behind the scenes. GCD decides on which particular thread your code blocks are going to be executed on, and it manages these threads according to the available system resources. This alleviates the problem of too many threads being created, because the threads are now centrally managed and abstracted away from application developers. + +The other important change with GCD is that you as a developer think about work items in a queue rather than threads. This new mental model of concurrency is easier to work with. + +GCD exposes five different queues: the main queue running on the main thread, three background queues with different priorities, and one background queue with an even lower priority, which is I/O throttled. Furthermore, you can create custom queues, which can either be serial or concurrent queues. While custom queues are a powerful abstraction, all blocks you schedule on them will ultimately trickle down to one of the system's global queues and its thread pool(s). + + + +Making use of several queues with different priorities sounds pretty straightforward at first. However, we strongly recommend that you use the default priority queue in almost all cases. Scheduling tasks on queues with different priorities can quickly result in unexpected behavior if these tasks access shared resources. This can lead as far as causing your whole program to come to a grinding halt because some low-priority tasks are blocking a high-priority task from executing. You can read more about this phenomenon, called priority inversion, [below](#priority-inversion). + +Although GCD is a low-level C API, it's pretty straightforward to use. This makes it easy to forget that all caveats and pitfalls of concurrent programming still apply while dispatching blocks onto GCD queues. Please make sure to read about the [challenges of concurrent programming](#challenges) below, in order to be aware of the potential problems. Furthermore, we have an excellent [walkthrough of the GCD API][300] in this issue that contains many in-depth explanations and valuable hints. + + +### Operation Queues + +Operation queues are a Cocoa abstraction of the queue model exposed by GCD. While GCD offers more low-level control, operation queues implement several convenient features on top of it, which often makes it the best and safest choice for application developers. + +The `NSOperationQueue` class has two different types of queues: the main queue and custom queues. The main queue runs on the main thread, and custom queues are processed in the background. In any case, the tasks which are processed by these queues are represented as subclasses of `NSOperation`. + +You can define your own operations in two ways: either by overriding `main`, or by overriding `start`. The former is very simple to do, but gives you less flexibility. In return, the state properties like `isExecuting` and `isFinished` are managed for you, simply by assuming that the operation is finished when `main` returns. + + @implementation YourOperation + - (void)main + { + // do your work here ... + } + @end + +If you want more control and to maybe execute an asynchronous task within the operation, you can override `start`: + + @implementation YourOperation + - (void)start + { + self.isExecuting = YES; + self.isFinished = NO; + // start your work, which calls finished once it's done ... + } + + - (void)finished + { + self.isExecuting = NO; + self.isFinished = YES; + } + @end + +Notice that in this case, you have to manage the operation's state manually. In order for an operation queue to be able to pick up a such a change, the state properties have to be implemented in a KVO-compliant way. So make sure to send proper KVO messages in case you don't set them via default accessor methods. + +In order to benefit from the cancelation feature exposed by operation queues, you should regularly check the `isCancelled` property for longer-running operations: + + - (void)main + { + while (notDone && !self.isCancelled) { + // do your processing + } + } + +Once you have defined your operation class, it's very easy to add an operation to a queue: + + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + YourOperation *operation = [[YourOperation alloc] init]; + [queue addOperation:operation]; + +Alternatively, you can also add blocks to operation queues. This comes in handy, e.g. if you want to schedule one-off tasks on the main queue: + + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + // do something... + }]; + +While this is a very convenient way of scheduling work onto a queue, defining your own NSOperation subclasses can be very helpful during debugging. If you override the operation's `description` method, you can easily identify all the operations currently scheduled in a certain queue. + +Beyond the basics of scheduling operations or blocks, operation queues offer some features which would be non-trivial to get right in GCD. For example, you can easily control how many operations of a certain queue may be executed concurrently with the `maxConcurrentOperationCount` property. Setting it to one gives you a serial queue, which is great for isolation purposes. + +Another convenient feature is the sorting of operations within a queue according to their priorities. This is not the same as GCD's queue priorities. It solely influences the execution order of all operations scheduled in one queue. If you need more control over the sequence of execution beyond the five standard priorities, you can specify dependencies between operations like this: + + [intermediateOperation addDependency:operation1]; + [intermediateOperation addDependency:operation2]; + [finishedOperation addDependency:intermediateOperation]; + +This simple code guarantees that `operation1` and `operation2` will be executed before `intermediateOperation`, which, in turn, will be executed before `finishedOperation`. Operation dependencies are a very powerful mechanism to specify a well-defined execution order. This lets you create things like operation groups, which are guaranteed to be executed before the dependent operation, or serial operations within an otherwise concurrent queue. + +By the very nature of abstractions, operation queues come with a small performance hit compared to using the GCD API. However, in almost all cases, this impact is negligible and operation queues are the tool of choice. + + +### Run Loops + +Run loops are not technically a concurrency mechanism like GCD or operation queues, because they don't enable the parallel execution of tasks. However, run loops tie in directly with the execution of tasks on the main dispatch/operation queue and they provide a mechanism to execute code asynchronously. + +Run loops can be a lot easier to use than operation queues or GCD, because you don't have to deal with the complexity of concurrency and still get to do things asynchronously. + +A run loop is always bound to one particular thread. The main run loop associated with the main thread has a central role in each Cocoa and CocoaTouch application, because it handles UI events, timers, and other kernel events. Whenever you schedule a timer, use a `NSURLConnection`. or call `performSelector:withObject:afterDelay:`, the run loop is used behind the scenes in order to perform these asynchronous tasks. + +Whenever you use a method which relies on the run loop, it is important to remember that run loops can be run in different modes. Each mode defines a set of events the run loop is going to react to. This is a clever way to temporarily prioritize certain tasks over others in the main run loop. + +A typical example of this is scrolling on iOS. While you're scrolling, the run loop is not running in its default mode, and therefore, it's not going to react to, for example, a timer you have scheduled before. Once scrolling stops, the run loop returns to the default mode and the events which have been queued up are executed. If you want a timer to fire during scrolling, you need to add it to the run loop in the `NSRunLoopCommonModes` mode. + +The main thread always has the main run loop set up and running. Other threads though don't have a run loop configured by default. You can set up a run loop for other threads too, but you will rarely need to do this. Most of the time it is much easier to use the main run loop. If you need to do heavier work that you don't want to execute on the main thread, you can still dispatch it onto another queue after your code is called from the main run loop. Chris has some good examples of this pattern in his article about [common background practices][200]. + +If you really need to set up a run loop on another thread, don't forget to add at least one input source to it. If a run loop has no input sources configured, every attempt to run it will exit immediately. + + + + +## Challenges of Concurrent Programming + +Writing concurrent programs comes with many pitfalls. As soon as you're doing more than the most basic things, it becomes difficult to oversee all the different states in the interplay of multiple tasks being executed in parallel. Problems can occur in a non-deterministic way, which makes it even more difficult to debug concurrent code. + +There is a prominent example for unforeseen behavior of concurrent programs: In 1995, NASA sent the Pathfinder mission to Mars. Not too long after a successful landing on our red neighboring planet, the mission almost [came to an abrupt end](http://research.microsoft.com/en-us/um/people/mbj/Mars_Pathfinder/Mars_Pathfinder.html). The Mars rover kept rebooting for unknown reasons -- it suffered from a phenomenon called *priority inversion*, where a low-priority thread kept blocking a high-priority one. We are going to explore this particular issue in more detail below. But this example should demonstrate that even with vast resources and lots of engineering talent available, concurrency can come back to bite you in many ways. + + + + +### Sharing of Resources + +The root of many concurrency related evils is the access of shared resources from multiple threads. A resource can be a property or an object, memory in general, a network device, a file, etc. Anything you share between multiple threads is a potential point of conflict, and you have to take safety measures to prevent these kind of conflicts. + +In order to demonstrate the problem, let's look at a simple example of a resource in the form of an integer property which you're using as a counter. Let's say we have two threads running in parallel, A and B, and both try to increment the counter at the same time. The problem is that what you write as one statement in C or Objective-C is mostly not just one machine instruction for the CPU. To increment our counter, the current value has to be read from memory. Then the value is incremented by one and finally written back to memory. + +Imagine the hazards that can happen if both threads try to do this simultaneously. For example, thread A and thread B both read the value of the counter from memory; let's say it is `17`. Then thread A increments the counter by one and writes the resulting `18` back to memory. At the same time, thread B also increments the counter by one and writes a `18` back to memory, just after thread A. At this point the data has become corrupted, because the counter holds an `18` after it was incremented twice from a `17`. + + + +This problem is called a [race condition](http://en.wikipedia.org/wiki/Race_conditions#Software) and can always happen if multiple threads access a shared resource without making sure that one thread is finished operating on a resource before another one begins accessing it. If you're not only writing a simple integer but a more complex structure to memory, it might even happen that a second thread tries to read from this memory while you're in the midst of writing it, therefore seeing half new and half old or uninitialized data. In order to prevent this, multiple threads need to access shared resources in a mutually exclusive way. + +In reality, the situation is even more complicated than this, because modern CPUs change the sequence of reads and writes to memory for optimization purposes ([Out-of-order execution](http://en.wikipedia.org/wiki/Out-of-order_execution)). + + + +### Mutual Exclusion + +[Mutual exclusive](http://en.wikipedia.org/wiki/Mutex) access means that only one thread at a time gets access to a certain resource. In order to ensure this, each thread that wants to access a resource first needs to acquire a [*mutex* lock](http://en.wikipedia.org/wiki/Lock_%28computer_science%29) on it. Once it has finished its operation, it releases the lock, so that other threads get a chance to access it. + + + +In addition to ensuring mutual exclusive access, locks must also handle the problem caused by out-of-order execution. If you cannot rely on the CPU accessing the memory in the sequence defined by your program instructions, guaranteeing mutually exclusive access alone is not enough. To work around this side effect of CPU optimization strategies, [memory barriers](http://en.wikipedia.org/wiki/Memory_barrier) are used. Setting a memory barrier makes sure that no out-of-order execution takes place across the barrier. + +Of course the implementation of a mutex lock in itself needs to be race-condition free. This is a non-trivial undertaking and requires use of special instructions on modern CPUs. You can read more about atomic operations in Daniel's [low-level concurrency techniques][300] article. + +Objective-C properties come with language level support for locking in the form of declaring them as atomic. In fact, properties are even atomic by default. Declaring a property as atomic results in implicit locking/unlocking around each access of this property. It might be tempting to just declare all properties as atomic, just in case. However, locking comes at a cost. + +Acquiring a lock on a resource always comes with a performance cost. Acquiring and releasing a lock needs to be race-condition free, which is non-trivial on multi-core systems. And when acquiring a lock, the thread might have to wait because some other thread already holds the lock. In this case, that thread will sleep and has to be notified when the other thread relinquishes the lock. All of these operations are expensive and complicated. + +There are different kinds of locks. Some locks are very cheap when there's no lock contention but perform poorly under contention. Other locks are more expensive at a base level, but degrade better under contention ([Lock contention](http://en.wikipedia.org/wiki/Lock_%28computer_science%29#Granularity) is the situation when one or more threads try to take a lock that has already been taken). + +There is a trade-off to be made here: acquiring and releasing locks comes at a price (lock overhead). Therefore you want to make sure you're not constantly entering and exiting [critical sections](http://en.wikipedia.org/wiki/Critical_section) (i.e. acquiring and releasing locks). At the same time, if you acquire a lock for too large of a region of code, you run the risk of lock contention where other threads are often unable to do work because they're waiting to acquire a lock. It's not an easy task to solve. + +It is quite common to see code which is supposed to run concurrently, but which actually results in only one thread being active at a time, because of the way locks for shared resources are set up. It's often non-trivial to predict how your code will get scheduled on multiple cores. You can use Instrument's [CPU strategy view](http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/AnalysingCPUUsageinYourOSXApp/AnalysingCPUUsageinYourOSXApp.html) to get a better idea of whether you're efficiently using the available CPU cores or not. + + + + +### Dead Locks + +Mutex locks solve the problem of race conditions, but unfortunately they also introduce a new problem ([amongst others](http://en.wikipedia.org/wiki/Lock_%28computer_science%29#The_problems_with_locks)) at the same time: [dead locks](http://en.wikipedia.org/wiki/Deadlock). A dead lock occurs when multiple threads are waiting on each other to finish and get stuck. + + + +Consider the following example code, which swaps the values of two variables: + + void swap(A, B) + { + lock(lockA); + lock(lockB); + int a = A; + int b = B; + A = b; + B = a; + unlock(lockB); + unlock(lockA); + } + +This works quite well most of the time. But when by chance two threads call it at the same time with opposite variables + + swap(X, Y); // thread 1 + swap(Y, X); // thread 2 + +we can end up in a dead lock. Thread 1 acquires a lock on X, thread 2 acquires a lock on Y. Now they're both waiting for the other lock, but will never be able to acquire it. + +Again, the more resources you share between threads and the more locks you take, the greater your risk of running into a dead lock situation. This is one more reason to keep things as simple as possible and to share as few resources as possible between threads. Make sure to also read the section about [doing things asynchronously][301] in the [low-level concurrency APIs][300] article. + + +### Starvation + +Just when you thought that there are enough problems to think of, a new one comes around the corner. Locking shared resources can result in the [readers-writers problem](http://en.wikipedia.org/wiki/Readers-writers_problem). In many cases, it would be wasteful to restrict reading access to a resource to one access at a time. Therefore, taking a reading lock is allowed as long as there is no writing lock on the resource. In this situation, a thread that is waiting to acquire a write lock can be starved by more read locks occurring in the meantime. + +In order to solve this issue, more clever solutions than a simple read/write lock are necessary, e.g. giving [writers preference](http://en.wikipedia.org/wiki/Readers–writer_lock) or using the [read-copy-update](http://en.wikipedia.org/wiki/Read-copy-update) algorithm. Daniel shows in his [low-level concurrency techniques][302] article how to implement a multiple reader/single writer pattern with GCD which doesn't suffer from writer starvation. + + + + +### Priority Inversion + +We started this section with the example of NASA's Pathfinder rover on Mars suffering from a concurrency problem. Now we will have a closer look why Pathfinder almost failed, and why your application can suffer from the same problem, called [priority inversion](http://en.wikipedia.org/wiki/Priority_inversion). + +Priority inversion describes a condition where a lower priority task blocks a higher priority task from executing, effectively inverting task priorities. Since GCD exposes background queues with different priorities, including one which even is I/O throttled, it's good to know about this possibility. + +The problem can occur when you have a high-priority and a low-priority task share a common resource. When the low-priority task takes a lock to the common resource, it is supposed to finish off quickly in order to release its lock and to let the high-priority task execute without significant delays. Since the high-priority task is blocked from running as long as the low-priority task has the lock, there is a window of opportunity for medium-priority tasks to run and to preempt the low-priority task, because the medium-priority tasks have now the highest priority of all currently runnable tasks. At this moment, the medium-priority tasks hinder the low-priority task from releasing its lock, therefore effectively gaining priority over the still waiting, high-priority tasks. + + + +In your own code, things might not be as dramatic as the rebooting that occurred in the Mars rover, as priority inversion happens quite often in a less severe manner. + +In general, don't use different priorities. Often you will end up with high-priority code waiting on low-priority code to finish. When you're using GCD, always use the default priority queue (directly, or as a target queue). If you're using different priorities, more likely than not, it's actually going to make things worse. + +The lesson from this is that using multiple queues with different priorities sounds good on paper, but it adds even more complexity and unpredictability to concurrent programs. And if you ever run into a weird problem where your high-priority tasks seem to get stuck for no reason, maybe you will remember this article and the problem called priority inversion, which even the NASA engineers encountered. + + +## Conclusion + +We hope to have demonstrated the complexity of concurrent programming and its problems, no matter how straightforward an API may look. The resulting behavior quickly gets very difficult to oversee, and debugging these kind of problems is often very hard. + +On the other hand, concurrency is a powerful tool to take advantage of the computing power of modern multicore CPUs. The key is to keep your concurrency model as simple as possible, so that you can limit the amount of locking necessary. + +A safe pattern we recommend is this: pull out the data you want to work on the main thread, then use an operation queue to do the actual work in the background, and finally get back onto the main queue to deliver the result of your background work. This way, you don't need to do any locking yourself, which greatly reduces the chances for mistakes. + + + + + + + + + + + + + + + + diff --git a/2013-07-07-editorial.markdown b/2013-07-07-editorial.markdown new file mode 100644 index 0000000..7c332be --- /dev/null +++ b/2013-07-07-editorial.markdown @@ -0,0 +1,25 @@ +--- +layout: post +title: "Editorial" +category: "2" +date: "2013-07-07 11:00:00" +tags: editorial +--- + +Welcome to objc.io issue #2! + +First of all, we would like to thank everyone for the overwhelming response to our first issue -- we couldn't have hoped for a better start. + +In this second issue we are going to dive deep into the subject of concurrent programming. These days, we have multiple CPU cores at our disposal, even on mobile devices. If we get concurrency right, it can give our applications a big performance boost. + +Unfortunately, concurrent programming inherently has many intricate problems and pitfalls, no matter how simple the API makes it seem. We hope that the articles in this issue will give you a deeper understanding of the subject and help you navigate around the many potential pitfalls. + +For this issue we are very glad to have [Peter Steinberger](https://twitter.com/steipete) and [Tobias Kräntzer](http://twitter.com/anagrom_ataf) as guest authors. Peter shares his experiences with regard to concurrent programming from his well known [PSPDFKit](http://pspdfkit.com/) library, and Tobias writes about testing asynchronous code. + +If you have a topic in mind that you would like to contribute to objc.io in the future, please [get it in touch](mailto:mail@objc.io) with us. + +Lastly, we have good news for all of you who asked for an RSS feed: you can now subscribe to objc.io at [objc.io/feed.xml](/feed.xml). + +Happy reading! + +Chris, Daniel, and Florian. diff --git a/2013-07-07-index.markdown b/2013-07-07-index.markdown new file mode 100644 index 0000000..a1bcdc0 --- /dev/null +++ b/2013-07-07-index.markdown @@ -0,0 +1,6 @@ +--- +layout: toc +category: "2" +date: "2013-07-07 12:00" +tags: toc +--- diff --git a/2013-07-07-low-level-concurrency-apis.mdown b/2013-07-07-low-level-concurrency-apis.mdown new file mode 100644 index 0000000..03a53b1 --- /dev/null +++ b/2013-07-07-low-level-concurrency-apis.mdown @@ -0,0 +1,746 @@ +--- +layout: post +title: "Low-Level Concurrency APIs" +category: "2" +date: "2013-07-07 8:00:00" +author: "Daniel Eggert" +tags: article +--- + +{% include links-2.md %} + + +In this article we'll talk about some low-level APIs available on both iOS and OS X. Except for `dispatch_once`, we generally discourage using any of this. + +But we wanted to show what's available under the covers. These low-level APIs provide a huge amount of flexibility, yet with that flexibility comes a lot of complexity and responsibility. The higher-level APIs and patterns that we mention in our [article about common background practices][200] let you focus on your task at hand and save you a lot of trouble. And generally, the higher-level APIs will provide better performance unless you can afford the time and effort to tweak and debug code that uses lower-level APIs. + +Knowing how things work further down the software stack is good, though. We hope this article will give you a better understanding of the platform, and at the same time, will make you appreciate the higher-level APIs more. + +First, we'll go through most of the bits and pieces that make up *Grand Central Dispatch*. It's been around for some years and Apple keeps adding to it and improving it. Apple has open sourced it, which means it's available for other platforms, too. Finally, we'll take a look at [atomic operations](#atomic_operations) -- another set of low-level building blocks. + + +Probably the best book ever written about concurrent programming is *M. Ben-Ari*: "Principles of Concurrent Programming", [ISBN 0-13-701078-8](https://en.wikipedia.org/wiki/Special:BookSources/0-13-701078-8). If you're doing anything with concurrent programming, you need to read this. It's more than 30 years old, and is still unsurpassed. Its concise writing, excellent examples, and exercises take you through the fundamental building blocks of concurrent programming. It's out of print, but there are still some copies floating around. There's a new version called "Principles of Concurrent and Distributed Programming", [ISBN 0-321-31283-X](https://en.wikipedia.org/wiki/Special:BookSources/0-321-31283-X) that seems to cover much of the same, but I haven't read it myself. + + + +## Once Upon a Time… + +Probably the most widely used and misused feature in GCD is `dispatch_once`. Correctly used, it will look like this: + + + (UIColor *)boringColor; + { + static UIColor *color; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f]; + }); + return color; + } + +The block will only get run once. And during successive calls, the check is very performant. You can use this to initialize global data such as singletons. Beware that using `dispatch_once_t` makes testing very difficult -- singletons and testing don't go well together. + +Make sure that the `onceToken` is declared `static` or has global scope. Anything else causes undefined behavior. In other words: Do **not** put a `dispatch_once_t` as a member variable into an object or the like. + +Back in ancient times (i.e. a few years ago), people would use `pthread_once`, which you should never use again, as `dispatch_once` is easier to use and less error-prone. + +## Delaying / After + +Another common companion is `dispatch_after`. It lets you do work *a little bit later*. It's powerful, but beware: You can easily get into a lot of trouble. Common usage looks like this: + + - (void)foo + { + double delayInSeconds = 2.0; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [self bar]; + }); + } + +This looks awesome at first sight. There are a few drawbacks, though. We can't (directly) cancel the block we're submitting to `dispatch_after`. It will run. + +Another thing to note is there's a problematic tendency where people use `dispatch_after` to work around timing bugs they have in their code. Some code gets run too early and you may not know why, so you put it inside a `dispatch_after`. Now everything works. But some weeks later it stops working and since you have no clear indication of in which order your code runs, debugging turns into a nightmare. Don't do this. Most of the time, you're better of putting your code into the right place. If inside `-viewWillAppear` is too early, perhaps `-viewDidAppear` is the right place. + +You'll save yourself a lot of trouble by creating direct calls (analogous to `-viewDidAppear`) in your own code instead of relying on `dispatch_after`. + +If you need something to run at a specific point in time, `dispatch_after` may be the right thing, though. Be sure to check out `NSTimer`, too. That API is a tiny bit more cumbersome, but it allows you to cancel the firing of the timer. + + +## Queues + +One of the basic building blocks of GCD is queues. Below, we'll give a few examples on how you can put them to use. When using queues, you'll do yourself a favor when giving them a good label. While debugging, this label is displayed within Xcode (and lldb), and will help you understand what your app is up to: + + - (id)init; + { + self = [super init]; + if (self != nil) { + NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self]; + self.isolationQueue = dispatch_queue_create([label UTF8String], 0); + + label = [NSString stringWithFormat:@"%@.work.%p", [self class], self]; + self.workQueue = dispatch_queue_create([label UTF8String], 0); + } + return self; + } + +Queues can be either *concurrent* or *serial*. By default, they're serial, which means that only a single block runs at any given time. That's how isolation queues work, which we'll get to in a bit. Queues can also be concurrent, which allows multiple blocks to run at the same time. + +GCD queues use threads internally. GCD manages these threads, and thus when using GCD you don't need to create threads yourself. But the important takeaway is that GCD presents to you, the user of the API, a very different abstraction level. When you use GCD to do concurrent work, you don't think in terms of threads, but instead in terms of queues and work items (blocks submitted to queues). While down below, there're still threads, GCD's abstraction level lends itself way better to how you're usually writing code. + +The queues and work items also solve a common problem of consecutive fanning out: If we're using threads directly, and want to do something concurrently, we may split our work up into 100 smaller work items, and then create threads according to the number of available CPU cores, let's say eight. We send the work items to those eight threads. As we process those items, we might call some function as part of our work. The person who wrote that function also wanted to use concurrency, and hence also creates eight threads when you call the function. Now you have 8 x 8 = 64 threads, even though you only have eight cores -- i.e. only 12% of the threads can actually run at any point in time while the other 88% are not doing anything. With GCD you don't have this problem, and GCD can even adjust the number of threads when the system turns off cores to save power. + +GCD creates a so-called [thread pool](http://en.wikipedia.org/wiki/Thread_pool_pattern) that roughly matches the number of cores. Remember that threads don't come for free. Each thread ties up memory and kernel resources. There's one problem though: If you're submitting a block to GCD, and that code blocks the thread, this thread is no longer available at that point in time to do other work -- it's blocked. In order to keep processing work items (blocks) on the queues, GCD has to create a new thread and add it to the pool. + +If your code is blocking many threads, that can become quite problematic. First off, threads consume resources, but moreover, it's expensive to create them. And it takes some time. And during that time, GCD can't process work items at full speed. There are quite a few things that can cause a thread to block, but the most common are I/O related, i.e. reading and writing from / to a file or the network. You should not do that on a GCD queue in a blocking manner for this very reason. Take a look at the [Input / Output section](#input_output) below for information about how to do I/O in a way that plays nicely with GCD. + + +### Target Queue + +You can set a **target queue** for any queue that you create. This can be very powerful. And it helps debugging. + +It is generally considered good style for a class to create its own queue instead of using a global queue. This way you can set the name of that queue, which eases debugging a lot -- Xcode lets you see the names of all queues in the Debug Navigator, or if you're using `lldb` directly, `(lldb) thread list` will print out the queue names. Once you're using a lot of async stuff, this is very valuable help. + +Using a private queue also enforces encapsulation. It's your queue; you get to decide how to use it. + +By default, a newly created queue forwards into the default priority global queue. We'll talk more about priorities in a bit. + +You can change which queue your queue forwards into -- you can set your queue's target queue. This way you can chain multiple queues together. Your class `Foo` has a queue which forwards into the queue of class `Bar` which forwards into a global queue. + +This can be very useful when you use a queue for isolation (which we'll also talk about). `Foo` has an isolation queue, and by forwarding into `Bar`'s isolation queue, it will automatically be thread-safe with respect to the resources that `Bar`'s isolation queue protects. + +Make sure to make your private queue concurrent if you want multiple blocks to run on it. And note that if a queue's target queue is serial (i.e. non-concurrent), it effectively turns into a serial queue as well. + + +### Priorities + +You change the priority of your own queue by setting its target queue to be one of the global queues. But you should refrain from the temptation to do so. + +In most cases, changing the priority is not going to do what you intend. What may seem straightforward is actually a very complex problem. You'll very easily run into what is known as [Priority Inversion](http://en.wikipedia.org/wiki/Priority_inversion). Our [article about concurrency APIs and pitfalls][102] has more info on this problem which almost bricked NASA's Pathfinder rover on Mars. + +Furthermore, you need to be particularly careful with the `DISPATCH_QUEUE_PRIORITY_BACKGROUND` queue. Don't use it unless you understand what *throttled I/O* and *background status as per setpriority(2)* mean. Otherwise the system might end up putting your app to a grinding halt. It is mostly intended for doing I/O in a way such that it doesn't interfere with other parts of the system doing I/O. But combined with priority inversion, it can easily become a dangerous cocktail. + + +## Isolation + +Isolation queues are one of the most common patterns in GCD queue usage. There are two variations. + +### Protecting a Resource + +The most common scenario in multi-threaded programming is that you have a resource that only one thread is allowed to access at a time. + +Our [article about concurrency techniques][103] talks a bit more about what *resource* means in concurrent programming. It's often a piece of memory or an object that only one thread must access at a time. + +Let's say we need to access a `NSMutableDictionary` from multiple threads (queues). We would do something like this: + + - (void)setCount:(NSUInteger)count forKey:(NSString *)key + { + key = [key copy]; + dispatch_async(self.isolationQueue, ^(){ + if (count == 0) { + [self.counts removeObjectForKey:key]; + } else { + self.counts[key] = @(count); + } + }); + } + + - (NSUInteger)countForKey:(NSString *)key; + { + __block NSUInteger count; + dispatch_sync(self.isolationQueue, ^(){ + NSNumber *n = self.counts[key]; + count = [n unsignedIntegerValue]; + }); + return count; + } + +With this, only one thread will access the `NSMutableDictionary` instance. + +Note four things: + + 1. Don't use this code. First read about [multiple readers, single writer](#multiple-readers-single-writer) and also about [contention](#contention). + + 2. We're using `async` when storing a value. This is important. We don't want to and don't need to block the current thread for the *write* to complete. When reading, we're using `sync` since we need to return the result. + + 3. According to the method interface, `-setCount:forKey:` takes an `NSString`, which we're passing onto `dispatch_async`. The caller is free to pass in an `NSMutableString` and can modify it after the method returns, but before the block executes. Hence we *have* to copy the string to guarantee that the method works correctly. If the passed-in string isn't mutable (i.e. a normal `NSString`) the call to `-copy` is basically a no-op. + + 4. The `isolationQueue` needs to have been created with a `dispatch_queue_attr_t` of `DISPATCH_QUEUE_SERIAL` (or `0`). + + + +### One Resource, Multiple Readers, and a Single Writer + +We can improve upon the above example. GCD has concurrent queues on which multiple threads can run. We can safely read from the `NSMutableDictionary` on multiple threads as long as we don't mutate it at the same time. When we need to change the dictionary, we dispatch the block with a *barrier*. Such a block runs once all previously scheduled blocks have completed and before any following blocks are run. + +We'll create the queue with: + + self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT); + +and then change the setter like this: + + - (void)setCount:(NSUInteger)count forKey:(NSString *)key + { + key = [key copy]; + dispatch_barrier_async(self.isolationQueue, ^(){ + if (count == 0) { + [self.counts removeObjectForKey:key]; + } else { + self.counts[key] = @(count); + } + }); + } + +When you use concurrent queues, make sure that all *barrier* calls are *async*. If you're using `dispatch_barrier_sync` you'll quite likely get yourself (or rather: your code) into a deadlock. Writes *need* a barrier, and *can* be async. + + + +### A Word on Contention + +First off, a word of warning here: The resource we're protecting in this simple example is an `NSMutableDictionary`. This serves the purpose of an example very well. But in real code, it is important to put the isolation at the right complexity level. + +If you're accessing the `NSMutableDictionary` very frequently, you'll be running into what's known as lock contention. Lock contention is in no way specific to GCD or queues. Any form of locking mechanism will have the same problem -- different locking mechanisms in different ways. + +All calls to `dispatch_async`, `dispatch_sync`, etc. need to perform some form of locking -- making sure that only one thread or specific threads run a given block. GCD can avoid locks to some extent and use scheduling instead, but at the end of the day the problem just shifts. The underlying problem remains: if you have a **lot** of threads hitting the same lock or queue at the same time, you'll see performance hits. Performance can degrade severely. + +You need to isolate at the right complexity level. When you see performance degrade, it's a clear sign of a design problem in your code. There are two costs that you need to balance. First is the cost of being inside a critical section for so long that other threads are blocked from entering a critical section, and second is the cost of constantly entering and leaving critical sections. In the world of GCD, the first cost is describing the fact that if a block runs on your isolation queue, it may potentially block other code from running on your isolation queue. The second cost describes the fact that calling `dispatch_async` and `dispatch_sync`, while highly optimized, don't come for free. + +Sadly, there can be no general rule of what the right balance is: You need to measure and adjust. Spin up Instruments and see what your app is up to. + +If we look at the example code above, our critical code section is only doing very simple things. That may or may not be good, depending on how it's used. + +In your own code, consider if you're better off by protecting with an isolation queue on a higher level. For example, instead of the class `Foo` having an isolation queue and it itself protecting access to its `NSMutableDictionary`, you could have the class `Bar` that uses `Foo` have an isolation queue which protects all use of the class `Foo`. In other words: you would change `Foo` so that it is no longer thread-safe, (no isolation queue) and then inside `Bar`, use an isolation queue to make sure that only one thread is using `Foo` at any point in time. + + + + +### Going Fully Asynchronous + +Let's sidetrack a bit here. As you've seen above, you can dispatch a block, a work unit, both synchronously and asynchronously. A very common problem that we talk about in our [article about concurrency APIs and pitfalls][104] is [dead locks](http://en.wikipedia.org/wiki/Deadlock). It's quite easy to run into the problem with GCD with synchronous dispatching. The trivial case is: + + dispatch_queue_t queueA; // assume we have this + dispatch_sync(queueA, ^(){ + dispatch_sync(queueA, ^(){ + foo(); + }); + }); + +Once we hit the second `dispatch_sync` we'll deadlock: We can't dispatch onto queueA, because someone (the current thread) is already on that queue and is never going to leave it. But there are more subtle ways: + + dispatch_queue_t queueA; // assume we have this + dispatch_queue_t queueB; // assume we have this + + dispatch_sync(queueA, ^(){ + foo(); + }); + + void foo(void) + { + dispatch_sync(queueB, ^(){ + bar(); + }); + } + + void bar(void) + { + dispatch_sync(queueA, ^(){ + baz(); + }); + } + +Each call to `dispatch_sync()` on its own looks good, but in combination, they'll deadlock. + +This is all inherent to being synchronous. If we use asynchronous dispatching such as + + dispatch_queue_t queueA; // assume we have this + dispatch_async(queueA, ^(){ + dispatch_async(queueA, ^(){ + foo(); + }); + }); + +things will work just fine. *Asynchronous calls will not deadlock*. It is therefore very desirable to use asynchronous calls whenever possible. Instead of writing a function or method that returns a value (and hence has to be synchronous), we use a method that calls a result block asynchronously. That way we're less likely to run into problems with deadlocks. + +The downside of asynchronous calls is that they're difficult to debug. When we stop the code in the debugger, there's no meaningful backtrace to look at. + +Keep both of these in mind. Deadlocks are generally the tougher problem to deal with. + + +### How to Write a Good Async API + +If you're designing an API for others to use (or even for yourself), there are a few good practices to keep in mind. + +As we just mentioned, you should prefer asynchronous API. When you create an API, you can get called in ways that are outside your control, and if your code could deadlock, it will. + +You should write your functions or methods so that the function calls `dispatch_async()`. Don't make the caller call it. The caller should be able to call into your function or method. + +If your function or method has a result, deliver it asynchronously through a callback handler. The API should be such that your function or method takes both a result block and a queue to deliver the result on. The caller of your function should not have to dispatch onto the queue themself. The reason for this is quite simple: Almost all the time, the caller needs to be on a certain queue, and this way the code is easier to read. And your function will (should) call `dispatch_async()` anyhow to run the callback handler, so it might as well do that on the queue that the caller needs it to be on. + +If you're writing a class, it is probably a good option to let the user of the class set a queue that all callbacks will be delivered on. Your code would look like this: + + - (void)processImage:(UIImage *)image completionHandler:(void(^)(BOOL success))handler; + { + dispatch_async(self.isolationQueue, ^(void){ + // do actual processing here + dispatch_async(self.resultQueue, ^(void){ + handler(YES); + }); + }); + } + +If you write your classes this way, it's easy to make classes work together. If class A uses class B, it will set the callback queue of B to be its own isolation queue. + + +## Iterative Execution + +If you're doing some number crunching and the problem at hand can be dissected in smaller parts of identical nature, `dispatch_apply` can be very useful. + +If you have some code like this + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + // Do something with x and y here + } + } + +you may be able to speed it up by simply changing it to + + dispatch_apply(height, dispatch_get_global_queue(0, 0), ^(size_t y) { + for (size_t x = 0; x < width; x += 2) { + // Do something with x and y here + } + }); + +How well this works depends a lot on exactly what you're doing inside that loop. + +The work done by the block must be non trivial, otherwise the overhead is too big. Unless the code is bound by computational bandwidth, it is critical that the memory that each work unit needs to read from and write to fits nicely into the cache size. This can have dramatic effects on performance. Code bound by critical sections may not perform well at all. Going into detail about these problems is outside the scope of this article. Using `dispatch_apply` may help performance, yet performance optimization is a complex topic on its own. Wikipedia has an article about [Memory-bound function](https://en.wikipedia.org/wiki/Memory_bound). Memory access speed changes dramatically between L2, L3, and main memory. Seeing a performance drop of 10x is not uncommon when your data access pattern doesn’t fit within the cache size. + + +## Groups + +Quite often, you'll find yourself chaining asynchronous blocks together to perform a given task. Some of these may even run in parallel. Now, if you want to run some code once this task is complete, i.e. all blocks have completed, "groups" are the right tool. Here's a sample: + + dispatch_group_t group = dispatch_group_create(); + + dispatch_queue_t queue = dispatch_get_global_queue(0, 0); + dispatch_group_async(group, queue, ^(){ + // Do something that takes a while + [self doSomeFoo]; + dispatch_group_async(group, dispatch_get_main_queue(), ^(){ + self.foo = 42; + }); + }); + dispatch_group_async(group, queue, ^(){ + // Do something else that takes a while + [self doSomeBar]; + dispatch_group_async(group, dispatch_get_main_queue(), ^(){ + self.bar = 1; + }); + }); + + // This block will run once everything above is done: + dispatch_group_notify(group, dispatch_get_main_queue(), ^(){ + NSLog(@"foo: %d", self.foo); + NSLog(@"bar: %d", self.bar); + }); + +The important thing to note is that all of this is entirely non-blocking. At no point are we telling the current thread to wait until something else is done. Quite contrary, we're simply enqueuing multiple blocks. Since this code pattern doesn't block, it's not going to cause a deadlock. + +Also note how we're switching between different queues in this small and simple example. + + + +### Using dispatch_group_t with Existing API + +Once you've added groups to your tool belt, you'll be wondering why most async API doesn't take a `dispatch_group_t` as an optional argument. But there's no reason to despair: It's easy to add that yourself, although you have to be more careful to make sure your code is *balanced*. + +We can, for example, add it to Core Data's `-performBlock:` API like this: + + - (void)withGroup:(dispatch_group_t)group performBlock:(dispatch_block_t)block + { + if (group == NULL) { + [self performBlock:block]; + } else { + dispatch_group_enter(group); + [self performBlock:^(){ + block(); + dispatch_group_leave(group); + }]; + } + } + +This allows us to use `dispatch_group_notify` to run a block when a set of operations on Core Data (possibly combined with other blocks) completes. + +We can obviously do the same for `NSURLConnection`: + + + (void)withGroup:(dispatch_group_t)group + sendAsynchronousRequest:(NSURLRequest *)request + queue:(NSOperationQueue *)queue + completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler + { + if (group == NULL) { + [self sendAsynchronousRequest:request + queue:queue + completionHandler:handler]; + } else { + dispatch_group_enter(group); + [self sendAsynchronousRequest:request + queue:queue + completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){ + handler(response, data, error); + dispatch_group_leave(group); + }]; + } + } + +In order for this to work, you need to make sure that + * `dispatch_group_enter()` is guaranteed to run before `dispatch_group_leave()` + * Calls to `dispatch_group_enter()` and `dispatch_group_leave()` are always balanced (even when errors happen) + + +## Sources + +One of the lesser-known features of GCD is the event sources `dispatch_source_t`. + +Just like most of GCD, this is pretty low-level stuff. When you need it, it can be extremely useful, though. Some of it is extremely esoteric, and we'll just touch upon a few of the uses. A lot of this isn't very useful on iOS where you can't launch processes (hence no point in watching them) and you can't write outside your app bundle (hence no need to watch files), etc. + +GCD sources are implemented in an extremely resource-efficient way. + +### Watching Processes + +If some process is running and you want to know when it exits, GCD has you covered. You can also use it to check when that process forks, i.e. spawns child processes or a signal was delivered to the process (e.g. `SIGTERM`). + + NSRunningApplication *mail = [NSRunningApplication + runningApplicationsWithBundleIdentifier:@"com.apple.mail"]; + if (mail == nil) { + return; + } + pid_t const pid = mail.processIdentifier; + self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, + DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT); + dispatch_source_set_event_handler(self.source, ^(){ + NSLog(@"Mail quit."); + }); + dispatch_resume(self.source); + +This will print **Mail quit.** when the Mail.app exits. + +Note that you must call `dispatch_resume()` before any events will be delivered to your event handler. + + + + +### Watching Files + +The possibilities seem near endless. You can watch a file directly for changes, and the source's event handler will get called when a change happens. + +You can also use this to watch a directory, i.e. create a *watch folder*: + + + NSURL *directoryURL; // assume this is set to a directory + int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY); + if (fd < 0) { + char buffer[80]; + strerror_r(errno, buffer, sizeof(buffer)); + NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno); + return; + } + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, + DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT); + dispatch_source_set_event_handler(source, ^(){ + unsigned long const data = dispatch_source_get_data(source); + if (data & DISPATCH_VNODE_WRITE) { + NSLog(@"The directory changed."); + } + if (data & DISPATCH_VNODE_DELETE) { + NSLog(@"The directory has been deleted."); + } + }); + dispatch_source_set_cancel_handler(source, ^(){ + close(fd); + }); + self.source = source; + dispatch_resume(self.source); + +You should probably always add `DISPATCH_VNODE_DELETE` to check if the file or directory has been deleted -- and then stop monitoring it. + + +### Timers + +In most cases, `NSTimer` is your go-to place for timer events. GCD's version is lower-level. It gives you more control -- use that carefully. + +It is extremely important to point out that specifying a low *leeway* value for GCD timers interferes with the OS's attempt to conserve power. You'll be burning more battery if you unnecessarily specify a low leeway value. + +Here we're setting up a timer to fire every 5 seconds and allow for a leeway of 1/10 of a second: + + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, 0, DISPATCH_TARGET_QUEUE_DEFAULT); + dispatch_source_set_event_handler(source, ^(){ + NSLog(@"Time flies."); + }); + dispatch_time_t start + dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC, + 100ull * NSEC_PER_MSEC); + self.source = source; + dispatch_resume(self.source); + + +### Canceling + +All sources allow you to add a *cancel handler*. This can be useful to clean up any resources you've created for the event source, such as closing file descriptors. GCD guarantees that all calls to the event handler have completed before the cancel handler gets called. + +See the use of `dispatch_source_set_cancel_handler()` in the above [example for a watch folder](#watching_files). + + + + +## Input / Output + +Writing code that performs well while doing heavy I/O is extremely tricky. GCD has a few tricks up its sleeve to help. Without going into too much detail, we'll shortly touch upon what these problems are, and how GCD approaches them. + +Traditionally when you were reading data from a network socket, you'd be either do a blocking read, i.e. parking a thread until data became available, or you'd have to repeatedly poll. Both approaches are wasteful and don't scale. However, `kqueue` solved the polling by posting an event once data became available, and GCD uses the same approach, although more elegantly. When writing data to a socket, the identical problems exist, where you have to either perform a blocking write, or wait for the socket to be able to accept data. + +The second problem when performing I/O is that data arrives in small chunks; when reading from a network, the chunk size is typically around 1.5k bytes due to the MTU -- [maximum transmission unit](https://en.wikipedia.org/wiki/Maximum_transmission_unit). This can be anything, though. Once this data arrives, you're often interested in data that spans multiple chunks, and traditionally you would concatenate the data into one larger buffer and then process that. Let's say (contrived example), you receive these eight chunks + + 0: HTTP/1.1 200 OK\r\nDate: Mon, 23 May 2005 22:38 + 1: :34 GMT\r\nServer: Apache/1.3.3.7 (Unix) (Red-H + 2: at/Linux)\r\nLast-Modified: Wed, 08 Jan 2003 23 + 3: :11:55 GMT\r\nEtag: "3f80f-1b6-3e1cb03b"\r\nCon + 4: tent-Type: text/html; charset=UTF-8\r\nContent- + 5: Length: 131\r\nConnection: close\r\n\r\n\r + 6: \n\r\n
select displayName, key from items where UTTypeConformsTo(uti, ?) order by 2;+ +SQLite doesn’t know anything about UTTypes. But you can add Core functions as a block — see `-[FMDatabase makeFunctionNamed:maximumArguments:withBlock:]`. + +You could instead do a larger query, and then evaluate each object — but that’s a bunch more work. Better to do the filtering at the SQL level instead of after turning table rows into objects. + +## Finally + +You really should use Core Data. I’m not kidding. + +I’ve been using SQLite and FMDB for a long time, and I get a particular thrill out of going the extra mile (or two or ten) and getting exceptional performance. + +But remember that devices are getting faster. And also remember that anybody else who looks at your code is going to expect Core Data, which he or she already knows — someone else isn't going to know how your database code works. + +So please treat this entire article as a madman’s yelling about the detailed and crazy world he’s created for himself — and locked himself into. + +Just shake your head a little sadly, and please enjoy the awesome Core Data articles in this issue. + +Up next for me, after checking out the custom SQLite functions feature Gus pointed out, is investigating SQLite’s [full-text search extension](http://www.sqlite.org/fts3.html). There’s always more to learn. diff --git a/2013-09-09-core-data-fetch-requests.md b/2013-09-09-core-data-fetch-requests.md new file mode 100644 index 0000000..bfee08f --- /dev/null +++ b/2013-09-09-core-data-fetch-requests.md @@ -0,0 +1,296 @@ +--- +layout: post +title: Fetch Requests +category: "4" +date: "2013-09-09 06:00:00" +author: "Daniel Eggert" +tags: article +--- + +{% include links-4.md %} + +A way to get objects out of the store is to use an `NSFetchRequest`. Note, though, that one of the most common mistakes is to fetch data when you don't need to. Make sure you read and understand [Getting to Objects][320]. Most of the time, traversing relationships is more efficient, and using an `NSFetchRequest` is often expensive. + +There are usually two reasons to perform a fetch with an `NSFetchRequest`: (1) You need to search your entire object graph for objects that match specific predicates. Or (2), you want to display all your objects, e.g. in a table view. There's a third, less-common scenario, where you're traversing relationships but want to pre-fetch more efficiently. We'll briefly dive into that, too. But let us first look at the main two reasons, which are more common and each have their own set of complexities. + +## The Basics + +We won't cover the basics here, since the Xcode Documentation on Core Data called [Fetching Managed Objects](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html) covers a lot of ground already. We'll dive right into some more specialized aspects. + +## Searching the Object Graph + +In our [sample with transportation data](https://github.com/objcio/issue-4-importing-and-fetching), we have 12,800 stops and almost 3,000,000 stop times that are interrelated. If we want to find stop times with a departure time between 8:00 and 8:30 for stops close to 52° 29' 57.30" North, +13° 25' 5.40" East, we don't want to load all 12,800 *stop* objects and all three million *stop time* objects into the context and then loop through them. If we did, we'd have to spend a huge amount of time to simply load all objects into memory and then a fairly large amount of memory to hold all of these in memory. Instead what we want to do is have SQLite narrow down the set of objects that we're pulling into memory. + + +### Geo-Location Predicate + +Let's start out small and create a fetch request for stops close to 52° 29' 57.30" North, +13° 25' 5.40" East. First we create the fetch request: + + NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[Stop entityName]] + +We're using the `+entityName` method that we mention in [Florian's data model article][250]. Next, we need to limit the results to just those close to our point. + +We'll simply use a (not quite) square region around our point of interest. The actual math is [a bit complex](https://en.wikipedia.org/wiki/Geographical_distance), because the Earth happens to be somewhat similar to an ellipsoid. If we cheat a bit and assume the earth is spherical, we get away with this formula: + + D = R * sqrt( (deltaLatitude * deltaLatitude) + + (cos(meanLatitidue) * deltaLongitude) * (cos(meanLatitidue) * deltaLongitude)) + +We end up with something like this (all approximate): + + static double const R = 6371009000; // Earth readius in meters + double deltaLatitude = D / R * 180 / M_PI; + double deltaLongitude = D / (R * cos(meanLatitidue)) * 180 / M_PI; + +Our point of interest is: + + CLLocation *pointOfInterest = [[CLLocation alloc] initWithLatitude:52.4992490 + longitude:13.4181670]; + +We want to search within ±263 feet (80 meters): + + static double const D = 80. * 1.1; + double const R = 6371009.; // Earth readius in meters + double meanLatitidue = pointOfInterest.latitude * M_PI / 180.; + double deltaLatitude = D / R * 180. / M_PI; + double deltaLongitude = D / (R * cos(meanLatitidue)) * 180. / M_PI; + double minLatitude = pointOfInterest.latitude - deltaLatitude; + double maxLatitude = pointOfInterest.latitude + deltaLatitude; + double minLongitude = pointOfInterest.longitude - deltaLongitude; + double maxLongitude = pointOfInterest.longitude + deltaLongitude; + +(This math is broken when we're close to the 180° meridian. We'll ignore that since our traffic data is for Berlin which is far, far away.) + + request.result = [NSPredicate predicateWithFormat: + @"(%@ <= longitude) AND (longitude <= %@)" + @"AND (%@ <= latitude) AND (latitude <= %@)", + @(minLongitude), @(maxLongitude), @(minLatitude), @(maxLatitude)]; + +There's no point in specifying a sort descriptor. Since we're going to be doing a second in-memory pass over all objects, we will, however, ask Core Data to fill in all values for all returned objects: + + request.returnsObjectsAsFaults = NO; + +Without this, Core Data will fetch all values into the persistent store coordinator's row cache, but it will not populate the actual objects. Often that makes sense, but since we'll immediately be accessing all of the objects, we don't want that behavior. + +As a safe-guard, it's good to add: + + request.fetchLimit = 200; + +We execute this fetch request: + + NSError *error = nil; + NSArray *stops = [moc executeFetchRequest:request error:&error]; + NSAssert(stops != nil, @"Failed to execute %@: %@", request, error); + +The only (likely) reasons the fetch would fail is if the store went corrupt (file was deleted, etc.) or if there's a syntax error in the fetch request. So it's safe to use `NSAssert()` here. + +We'll now do the second pass over the in-memory data using Core Locations advance distance math: + + NSPredicate *exactPredicate = [self exactLatitudeAndLongitudePredicateForCoordinate:self.location.coordinate]; + stops = [stops filteredArrayUsingPredicate:exactPredicate]; + +and: + + - (NSPredicate *)exactLatitudeAndLongitudePredicateForCoordinate:(CLLocationCoordinate2D)pointOfInterest; + { + return [NSPredicate predicateWithBlock:^BOOL(Stop *evaluatedStop, NSDictionary *bindings) { + CLLocation *evaluatedLocation = [[CLLocation alloc] initWithLatitude:evaluatedStop.latitude longitude:evaluatedStop.longitude]; + CLLocationDistance distance = [self.location distanceFromLocation:evaluatedLocation]; + return (distance < self.distance); + }]; + } + +And we're all set. + +#### Geo-Location Performance + +These fetches take around 360µs on average on a recent MacBook Pro with SSD. That is, you can do approximately 2,800 of these requests per second. On an iPhone 5 we'd be getting around 1.67ms on average, or some 600 requests per second. + +If we add `-com.apple.CoreData.SQLDebug 1` as launch arguments to the app, we get this output: + + sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0 WHERE (? <= t0.ZLONGITUDE AND t0.ZLONGITUDE <= ? AND ? <= t0.ZLATITUDE AND t0.ZLATITUDE <= ?) LIMIT 100 + annotation: sql connection fetch time: 0.0008s + annotation: total fetch execution time: 0.0013s for 15 rows. + +In addition to some statistics (for the store itself), this shows us the generated SQL for these fetches: + + SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0 + WHERE (? <= t0.ZLONGITUDE AND t0.ZLONGITUDE <= ? AND ? <= t0.ZLATITUDE AND t0.ZLATITUDE <= ?) + LIMIT 200 + +which is what we'd expect. If we'd want to investigate the performance, we can use the SQL [`EXPLAIN` command](https://www.sqlite.org/eqp.html). For this, we'd open the database with the command line `sqlite3` command like this: + + % cd TrafficSearch + % sqlite3 transit-data.sqlite + SQLite version 3.7.13 2012-07-17 17:46:21 + Enter ".help" for instructions + Enter SQL statements terminated with a ";" + sqlite> EXPLAIN QUERY PLAN SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0 + ...> WHERE (13.30845219672199 <= t0.ZLONGITUDE AND t0.ZLONGITUDE <= 13.33441458422844 AND 52.42769566863058 <= t0.ZLATITUDE AND t0.ZLATITUDE <= 52.44352370653525) + ...> LIMIT 100; + 0|0|0|SEARCH TABLE ZSTOP AS t0 USING INDEX ZSTOP_ZLONGITUDE_INDEX (ZLONGITUDE>? AND ZLONGITUDE) (~6944 rows) + +This tell us that SQLite was using the `ZSTOP_ZLONGITUDE_INDEX` for the `(ZLONGITUDE>? AND ZLONGITUDE)` condition. We could do better by using a *compound index* as described in the [model article][260]. Since we'd always search for a combination of longitude and latitude that is more efficient, and we can remove the individual indexes on longitude and latitude. + +This would make the output look like this: + + 0|0|0|SEARCH TABLE ZSTOP AS t0 USING INDEX ZSTOP_ZLONGITUDE_ZLATITUDE (ZLONGITUDE>? AND ZLONGITUDE) (~6944 rows) + +In our simple case, adding a compound index hardly affects performance. + +As explained in the [SQLite Documentation](https://www.sqlite.org/eqp.html), the warning sign is a `SCAN TABLE` in the output. That basically means that SQLite needs to go through *all* entries to see which ones are matching. Unless you store just a few objects, you'd probably want an index. + +### Subqueries + +Let's say we only want those stops near us that are serviced within the next twenty minutes. + +We can create a predicate for the *StopTimes* entity like this: + + NSPredicate *timePredicate = [NSPredicate predicateWithFormat:@"(%@ <= departureTime) && (departureTime <= %@)", + startDate, endDate]; + +But what if what we want is a predicate that we can use to filter *Stop* objects based on the relationship to *StopTime* objects, not *StopTime* objects themselves? We can do that with a `SUBQUERY` like this: + + NSPredicate *predicate = [NSPredicate predicateWithFormat: + @"(SUBQUERY(stopTimes, $x, (%@ <= $x.departureTime) && ($x.departureTime <= %@)).@count != 0)", + startDate, endDate]; + +Note that this logic is slightly flawed if we're close to midnight, since we ought to wrap by splitting the predicate up into two. But it'll work for this example. + +Subqueries are very powerful for limiting data across relationship. The Xcode documentation for [`-[NSExpression expressionForSubquery:usingIteratorVariable:predicate:]`](https://developer.apple.com/library/ios/documentation/cocoa/reference/foundation/Classes/NSExpression_Class/Reference/NSExpression.html#//apple_ref/occ/clm/NSExpression/expressionForSubquery:usingIteratorVariable:predicate:) has more info. + +We can combine two predicates simply using `AND` or `&&`, i.e. + + [NSPredicate predicateWithFormat:@"(%@ <= departureTime) && (SUBQUERY(stopTimes .... + +or in code using [`+[NSCompoundPredicate andPredicateWithSubpredicates:]`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSCompoundPredicate_Class/Reference/Reference.html#//apple_ref/occ/clm/NSCompoundPredicate/andPredicateWithSubpredicates:). + +We end up with a predicate that looks like this: + + (lldb) po predicate + (13.39657778010461 <= longitude AND longitude <= 13.42266155792719 + AND 52.63249629924865 <= latitude AND latitude <= 52.64832433715332) + AND SUBQUERY( + stopTimes, $x, CAST(-978250148.000000, "NSDate") <= $x.departureTime + AND $x.departureTime <= CAST(-978306000.000000, "NSDate") + ).@count != 0 + +#### Subquery Performance + +If we look at the generated SQL it looks like this: + + sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0 + WHERE ((? <= t0.ZLONGITUDE AND t0.ZLONGITUDE <= ? AND ? <= t0.ZLATITUDE AND t0.ZLATITUDE <= ?) + AND (SELECT COUNT(t1.Z_PK) FROM ZSTOPTIME t1 WHERE (t0.Z_PK = t1.ZSTOP AND ((? <= t1.ZDEPARTURETIME AND t1.ZDEPARTURETIME <= ?))) ) <> ?) + LIMIT 200 + +This fetch request now takes around 12.3 ms to run on a recent MacBook Pro. On an iPhone 5, it'll take about 110 ms. Note that we have three million stop times and almost 13,000 stops. + +The query plan explanation looks like this: + + sqlite> EXPLAIN QUERY PLAN SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZIDENTIFIER, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME FROM ZSTOP t0 + ...> WHERE ((13.37190946378911 <= t0.ZLONGITUDE AND t0.ZLONGITUDE <= 13.3978625285315 AND 52.41186440524024 <= t0.ZLATITUDE AND t0.ZLATITUDE <= 52.42769244314491) AND + ...> (SELECT COUNT(t1.Z_PK) FROM ZSTOPTIME t1 WHERE (t0.Z_PK = t1.ZSTOP AND ((-978291733.000000 <= t1.ZDEPARTURETIME AND t1.ZDEPARTURETIME <= -978290533.000000))) ) <> ?) + ...> LIMIT 200; + 0|0|0|SEARCH TABLE ZSTOP AS t0 USING INDEX ZSTOP_ZLONGITUDE_ZLATITUDE (ZLONGITUDE>? AND ZLONGITUDE) (~3472 rows) + 0|0|0|EXECUTE CORRELATED SCALAR SUBQUERY 1 + 1|0|0|SEARCH TABLE ZSTOPTIME AS t1 USING INDEX ZSTOPTIME_ZSTOP_INDEX (ZSTOP=?) (~2 rows) + +Note that it is important how we order the predicate. We want to put the longitude and latitude stuff first, since it's cheap, and the subquery last, since it's expensive. + + +### Text Search + +A common scenario is searching for text. In our case, let's look at searching for *Stop* entities by their name. + +Berlin has a stop called "U Görlitzer Bahnhof (Berlin)". A naïve way to search for that would be: + + NSString *searchString = @"U Görli"; + predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@", searchString]; + +Things get even worse if you want to be able to do: + + name BEGINSWITH[cd] 'u gorli' + +i.e. do a case and / or diacritic insensitive lookup. + +Things are not that simple, though. Unicode is very complicated and there are quite a few gotchas. First and foremost ís that many characters can be represented in multiple ways. Both [U+00F6](http://unicode.org/charts/PDF/U0080.pdf) and [U+006F](http://www.unicode.org/charts/PDF/U0000.pdf) [U+0308](http://unicode.org/charts/PDF/U0300.pdf) represent "ö." And concepts such as uppercase / lowercase are very complicated once you're outside the ASCII code points. + +SQLite will do the heavy lifting for you, but it comes at a price. It may seem straightforward, but it's really not. What we want to do for string searches is to have a *normalized* version of the field that you can search on. We'll remove diacritics and make the string lowercase and then put that into a`normalizedName` field. We'll then do the same to our search string. SQLite then won't have to consider diacritics and case, and the search will effectively still be case and diacritics insensitive. But we have to do the heavy lifting up front. + +Searching with `BEGINSWITH[cd]` takes around 7.6 ms on a recent MacBook Pro with the sample strings in our sample code (130 searches / second). On an iPhone 5 those numbers are 47 ms per search and 21 searches per second. + +To make a string lowercase and remove diacritics, we can use `CFStringTransform()`: + + @implementation NSString (SearchNormalization) + + - (NSString *)normalizedSearchString; + { + // C.f.