diff --git a/2013-06-07-containment-view-controller.md b/2013-06-07-containment-view-controller.md new file mode 100644 index 0000000..d1979c0 --- /dev/null +++ b/2013-06-07-containment-view-controller.md @@ -0,0 +1,170 @@ +--- +layout: post +title: "View Controller Containment" +category: "1" +date: "2013-06-07 08:00:00" +author: "Ricki Gregersen" +tags: article +--- + + +Before iOS 5, view controller containers were a privilege for Apple only. +In fact, there was a passage in the view controller programming guide +stating that you should not use them. +The general advice from Apple on view controllers used to be that 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] + +Inconsistent view hierarchy + +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.)* + +Tunnel screenshot + +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: + +PhotoCell.xib screenshot + +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 () + @end + + @implementation PhotoViewController + // ... + - (void)didSelectPhotoAttributeWithKey:(NSString *)key + { + DetailViewController *controller = [[DetailViewController alloc] init]; + controller.key = key; + [self.navigationController pushViewController:controller animated:YES]; + } + @end + +As you can see, this construction comes with the price of some overhead to communicate between the view controllers in return for a clean separation of concerns and better reusability. Depending on the specific use case, this can either end up making things more simple or more complex than necessary. That's for you to consider and decide. + + +## Separating Concerns + +When dealing with table views there are a variety of different tasks involved which cross the borders between models, controllers, and views. In order to prevent view controllers from becoming the place for all these tasks, we will try to isolate as many of these tasks as possible in more appropriate places. This helps readability, maintainability, and testability. + +The techniques described here extend and elaborate upon the concepts +demonstrated in the article [Lighter view controllers](/issue-1/lighter-view-controllers.html). Please refer to this article for how to factor our data source and model logic. In the context of table views, we will specifically look at how to separate concerns between view controllers and views. + +### Bridging the Gap Between Model Objects and Cells + +At some point we have to hand over the data we want to display into the view layer. Since we still want to maintain a clear separation between the model and the view, we often offload this task to the table view's data source: + + - (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + { + PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PhotoCell"]; + Photo *photo = [self itemAtIndexPath:indexPath]; + cell.photoTitleLabel.text = photo.name; + NSString* date = [self.dateFormatter stringFromDate:photo.creationDate]; + cell.photoDateLabel.text = date; + } + +This kind of code clutters the data source with specific knowledge about the design of the cell. We are better off factoring this out into a category of the cell class: + + @implementation PhotoCell (ConfigureForPhoto) + + - (void)configureForPhoto:(Photo *)photo + { + self.photoTitleLabel.text = photo.name; + NSString* date = [self.dateFormatter stringFromDate:photo.creationDate]; + self.photoDateLabel.text = date; + } + + @end + +With this in place, our data source method becomes very simple. + + - (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + { + PhotoCell *cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier]; + [cell configureForPhoto:[self itemAtIndexPath:indexPath]]; + return cell; + } + +In our example code, the data source for this table view is [factored +out into its own controller object](/issue-1/lighter-view-controllers.html#controllers), which gets initialized with a cell configuration block. In this case, the block becomes as simple as this: + + TableViewCellConfigureBlock block = ^(PhotoCell *cell, Photo *photo) { + [cell configureForPhoto:photo]; + }; + +### Making Cells Reusable + +In cases where we have multiple model objects that can be presented using the same cell type, we can even go one step further to gain reusability of the cell. First, we define a protocol on the cell to which an object must conform in order to be displayed by this cell type. Then we simply change the configure method in the cell category to accept any object conforming to this protocol. These simple steps decouple the cell from any specific model object and make it applicable to different data types. + +### Handling Cell State Within the Cell + +If we want to do something beyond the standard highlighting or selection behavior of table views, we could implement two delegate methods, which modify the tapped cell in the way we want. For example: + + - (void)tableView:(UITableView *)tableView + didHighlightRowAtIndexPath:(NSIndexPath *)indexPath + { + PhotoCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + cell.photoTitleLabel.shadowColor = [UIColor darkGrayColor]; + cell.photoTitleLabel.shadowOffset = CGSizeMake(3, 3); + } + + - (void)tableView:(UITableView *)tableView + didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath + { + PhotoCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + cell.photoTitleLabel.shadowColor = nil; + } + +However, the implementation of these two delegate methods relies again on specific knowledge about how the cell is implemented. If we want to swap out the cell or redesign it in a different way, we also have to adapt the delegate code. The implementation details of the view are complected with the implementation of the delegate. Instead, we should move this logic into the cell itself. + + @implementation PhotoCell + // ... + - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated + { + [super setHighlighted:highlighted animated:animated]; + if (highlighted) { + self.photoTitleLabel.shadowColor = [UIColor darkGrayColor]; + self.photoTitleLabel.shadowOffset = CGSizeMake(3, 3); + } else { + self.photoTitleLabel.shadowColor = nil; + } + } + @end + +Generally speaking, we strive to separate the implementation details of the view layer from the implementation details of the controller layer. A delegate has to know about the different states a view can be in, but it shouldn't have to know how to modify the view tree or which attributes to set on some subviews in order to get it into the right state. All this logic should be encapsulated within the view, which then provides a simple API to the outside. + +### Handling Multiple Cell Types + +If you have multiple different cell types within one table view, the data source methods can quickly get out of hand. In our example app we have two different cell types for the photo details table: one cell to display a star rating, and a generic cell to display a key-value pair. In order to separate the code dealing with these different cell types, the data source method simply dispatches the request to specialized methods for each cell type. + + - (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath + { + NSString *key = self.keys[(NSUInteger) indexPath.row]; + id value = [self.photo valueForKey:key]; + UITableViewCell *cell; + if ([key isEqual:PhotoRatingKey]) { + cell = [self cellForRating:value indexPath:indexPath]; + } else { + cell = [self detailCellForKey:key value:value]; + } + return cell; + } + + - (RatingCell *)cellForRating:(NSNumber *)rating + indexPath:(NSIndexPath *)indexPath + { + // ... + } + + - (UITableViewCell *)detailCellForKey:(NSString *)key + value:(id)value + { + // ... + } + +### Table View Editing + +Table views provide easy-to-use editing features, which allow for +reordering and deletion of cells. In case of these events, the table +view's data source gets notified via [delegate methods](http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDataSource/tableView:commitEditingStyle:forRowAtIndexPath:). Therefore, we often see domain logic in these delegate methods that performs the actual modification on the data. + +Modifying data is a task that clearly belongs in the model layer. The model should expose an API for things like deletion and reordering, which we can then call from the data source methods. This way, the controller plays the role of a coordinator between the view and the model, but does not have to know about the implementation details of the model layer. As an added benefit, the model logic also becomes easier to test, because it is not interweaved with other tasks of view controllers anymore. + + +## Conclusion + +Table view controllers (and other controller objects!) should mostly have a [coordinating and mediating role](http://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ControllerObject.html) between model and view objects. They should not be concerned with tasks that clearly belong to the view or the model layer. If you keep this in mind, the delegate and data source methods will become much smaller and mostly contain simple boilerplate code. + +This not only reduces the size and complexity of table view controllers, but it puts the domain logic and view logic in much more appropriate places. Implementation details below and above the controller layer are encapsulated behind a simple API, which ultimately makes it much easier to understand the code and to work on it collaboratively. + +### Further Reading + +* [Blog: Skinnier Controllers using View Categories](http://www.sebastianrehnby.com/blog/2013/01/01/skinnier-controllers-using-view-categories/) +* [Table View Programming Guide](http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/tableview_iphone/AboutTableViewsiPhone/AboutTableViewsiPhone.html) +* [Cocoa Core Competencies: Controller Object](http://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ControllerObject.html) diff --git a/2013-06-07-testing-view-controllers.md b/2013-06-07-testing-view-controllers.md new file mode 100644 index 0000000..3e0f78e --- /dev/null +++ b/2013-06-07-testing-view-controllers.md @@ -0,0 +1,314 @@ +--- +layout: post +title: "Testing View Controllers" +category: "1" +date: "2013-06-07 09:00" +author: "Daniel Eggert" +tags: article +--- + +Let's not be religious about testing. It should help us speed up development and make things more fun. + +## Keeping Things Simple + +Testing simple things is simple, and testing complex things is complex. As we point out in the other articles, keeping things small and simple is good in general. And as a side effect, it also helps testing. It's a win-win. Take a look at [test-driven development](https://en.wikipedia.org/wiki/Test-driven_development) (known as TDD among friends) -- some people love it, some don't. We won't go into detail about it here, but we will say that with TDD, you write the test for your code before you write the code. Check out the Wikipedia article if you’re curious. We would also like to note that refactoring and testing go very well together. + +Testing UI components is often tricky because there are too many moving parts involved. More often than not, the view controller interacts with a lot of classes from both the model and the view layer. In order to be able to test the view controller, we need things to work in isolation. + +There's hope, though: The techniques we describe to make [lighter view controllers](/issue-1/lighter-view-controllers.html) also make testing easier. Generally, if you find something difficult to test, that's a hint that your design may be broken and that you should refactor some of it. Again, refer to the article about [lighter view controllers](/issue-1/lighter-view-controllers.html) for some hints. An overall design goal is to have clear separation of concerns. Each class should do only one thing, and do that one thing well. That will then allow for testing of that one thing. + +Remember: You'll get diminishing returns as you add more tests. First and foremost, add simple tests. Branch into more sophisticated territory as you start to feel comfortable with it. + + + +## Mocking + +When we break things up into small components (i.e. small classes), we can test each class on its own. The class that we're testing interacts with other classes. We get around this by using a so-called *mock* or *stub*. Think of a *mock object* as a placeholder. The class we’re testing will interact with placeholders instead of real objects. That way, we focus our test and ensure that it doesn’t depend on other parts of our app. + +The example app has an array data source that we'll test. The data source will at some point dequeue a cell from a table view. During testing, we don't have a table view, but by passing a *mock* table view, we can test the data source without a *real* table view, as you'll see below. It's a bit confusing at first, but very powerful and straightforward once you've seen it a few times. + +The power tool for mocking in Objective-C is called [OCMock](http://ocmock.org). It's a very mature project that leverages the power and flexibility of the Objective-C runtime. It pulls some cool tricks to make testing with mock objects fun. + +The data source test below shows, in more detail, how all of this plays out together. + + +## SenTestKit + +The other tool we'll use is the test framework that comes as part of the developer tools: SenTestingKit by [Sente](http://www.sente.ch). This dinosaur has been around for Objective-C developers since 1997 -- ten years before the iPhone was released. Today, it's built into Xcode. + +SenTestingKit is what will run your tests. With SenTestingKit, you organize tests into classes. You create one test class for each class you want to test. This class will have a name ending in `Tests`, and the name reflects what the class is about. + +The methods inside each of these *tests* classes will do the actual testing. The method name has to start with `test`, as that's what triggers it to get run as a test. There are special `-setUp` and `-tearDown` methods you can override to set up each test. Remember that your test class is just a class: If it helps you structure your tests, feel free to add properties and helper methods. + +A nice pattern when testing is to create a custom base class for the +tests. We then put convenience logic in there to make our tests easier +and more focused. Check out the [example project](https://github.com/objcio/issue-1-lighter-view-controllers/blob/master/PhotoDataTests/PhotoDataTestCase.h) for some samples of when this might be useful. We’re also not using the Xcode templates for tests -- we’re going for something simpler and more efficient: We add a single `.m` file. By convention the tests class have a name ending in `Tests`. The name should reflect what we're testing. + +## Integration with Xcode + +Tests are built into a bundle of a dynamic library plus resources of your choice. If you need particular resource files for your testing, add them to the test target, and Xcode will put them inside the bundle. You can then locate them with `NSBundle`. The example project implements a `-URLForResource:withExtension:` method to make it easy to use. + +Each *scheme* in Xcode defines what the corresponding test bundle should be. While ⌘-R runs your app, ⌘-U will run your tests. + +The way the tests are run, your app is actually launched, and the test bundle is *injected*. You probably don't want your app to do much, as it may interfere with the testing. Put something like this into your app delegate: + + static BOOL isRunningTests(void) __attribute__((const)); + + - (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions + { + if (isRunningTests()) { + return YES; + } + + // + // Normal logic goes here + // + + return YES; + } + + static BOOL isRunningTests(void) + { + NSDictionary* environment = [[NSProcessInfo processInfo] environment]; + NSString* injectBundle = environment[@"XCInjectBundle"]; + return [[injectBundle pathExtension] isEqualToString:@"octest"]; + } + +Editing your scheme in Xcode gives you a great deal of flexibility. You can run scripts before and after the tests, and you can have multiple test bundles. This can be useful for larger projects. Most importantly, you can turn on and off individual tests. This can be useful for debugging tests -- just remember to turn them all back on. + +Also remember that you can set breakpoints in your code and in test cases and the debugger will stop there as the tests are executed. + + +## Testing a Data Source + +Let's get started. We've made testing easier by splitting up the view controller. Now we'll test the `ArrayDataSource`. First, we create a new and empty basic setup. We put both the interface and implementation into the same file; no one needs to include the `@interface` anywhere else, as it's all nice and tidy inside one file: + + #import "PhotoDataTestCase.h" + + @interface ArrayDataSourceTest : PhotoDataTestCase + @end + + @implementation ArrayDataSourceTest + - (void)testNothing; + { + STAssertTrue(YES, @""); + } + @end + +This will not do much. It shows the basic test setup. When we run the tests, the `-testNothing` method will run. The special `STAssert` macro will do its trivial check. Note that `ST` originates from SenTestingKit. These macros integrate with Xcode and will make failures show up in the *Issues* navigator. + +## Our First Test + +We'll now replace the `testNothing` method with a simple, but real test: + + - (void)testInitializing; + { + STAssertNil([[ArrayDataSource alloc] init], @"Should not be allowed."); + TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){}; + id obj1 = [[ArrayDataSource alloc] initWithItems:@[] + cellIdentifier:@"foo" + configureCellBlock:block]; + STAssertNotNil(obj1, @""); + } + +## Putting Mocking into Practice + +Next, we want to test the + + - (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath; + +method that the ArrayDataSource implements. For that we create a + + - (void)testCellConfiguration; + +test method. + + +First we create a data source: + + __block UITableViewCell *configuredCell = nil; + __block id configuredObject = nil; + TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){ + configuredCell = a; + configuredObject = b; + }; + ArrayDataSource *dataSource = [[ArrayDataSource alloc] initWithItems:@[@"a", @"b"] + cellIdentifier:@"foo" + configureCellBlock:block]; + +Note that the `configureCellBlock` doesn't do anything except store the objects that it was called with. This allows us to easily test it. + +Next, we'll create a *mock object* for a table view: + + id mockTableView = [OCMockObject mockForClass:[UITableView class]]; + +The data source is going to call `-dequeueReusableCellWithIdentifier:forIndexPath:` on the passed-in table view. We'll tell the mock object what to do when it gets this message. We first create a `cell` and then set up the *mock*: + + UITableViewCell *cell = [[UITableViewCell alloc] init]; + NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + [[[mockTableView expect] andReturn:cell] + dequeueReusableCellWithIdentifier:@"foo" + forIndexPath:indexPath]; + + +This will look a bit confusing at first. What's going on here, is that the mock is *recording* this particular call. The mock is not a table view; we're just pretending that it is. The special `-expect` method allows us to set up the mock so that it knows what to do when this method gets called on it. + +In addition, the `-expect` method tells the mock that this call *must* happen. When we later call `-verify` on the mock, the test will fail if the method didn't get called. The corresponding `-stub` method also sets up the mock object, but doesn’t care if the method will get called. + +Now we'll trigger the code to get run. We'll call the method we want to test: + + NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + id result = [dataSource tableView:mockTableView + cellForRowAtIndexPath:indexPath]; + +and then we'll test that things went well: + + STAssertEquals(result, cell, @"Should return the dummy cell."); + STAssertEquals(configuredCell, cell, @"This should have been passed to the block."); + STAssertEqualObjects(configuredObject, @"a", @"This should have been passed to the block."); + [mockTableView verify]; + +The `STAssert` macros test that the values are identical. Note that we use pointer comparison for the first two tests; we don't want to use `-isEqual:`. We actually want to test that `result` and `cell` and `configuredCell` all are the very same object. The third test uses `-isEqual:`, and finally we call `-verify` on our mock. + +Note that in the example, we're setting up the mock with + + id mockTableView = [self autoVerifiedMockForClass:[UITableView class]]; + +This is a convenience wrapper in our base test class which automatically calls `-verify` at the end of the test. + + +## Testing a UITableViewController + +Next, we turn toward the `PhotosViewController`. It's a `UITableViewController` subclass and it uses the data source we've just tested. The code that remains in the view controller is pretty simple. + +We want to test that tapping on a cell takes us to the detail view, i.e. an instance of `PhotoViewController` is pushed onto the navigation controller. We'll again use mocking to make the test depend as little as possible on other parts. + +First we create a `UINavigationController` mock: + + id mockNavController = [OCMockObject mockForClass:[UINavigationController class]]; + +Next up, we'll use *partial mocking*. We want our `PhotosViewController` instance to return the `mockNavController` as its `navigationController`. We can't set the navigation controller directly, so we'll simply stub only that method to return our `mockNavController` and forward everything else to the `PhotosViewController` instance: + + PhotosViewController *photosViewController = [[PhotosViewController alloc] init]; + id photosViewControllerMock = [OCMockObject partialMockForObject:photosViewController]; + [[[photosViewControllerMock stub] andReturn:mockNavController] navigationController]; + +Now, whenever the `-navigationController` method is called on `photosViewController`, it will return the `mockNavController`. This is a very powerful trick that OCMock has up its sleeve. + +We now tell the navigation controller mock what we expect to be called, i.e. a detail view controller with `photo` set to a non-nil value: + + UIViewController* viewController = [OCMArg checkWithBlock:^BOOL(id obj) { + PhotoViewController *vc = obj; + return ([vc isKindOfClass:[PhotoViewController class]] && + (vc.photo != nil)); + }]; + [[mockNavController expect] pushViewController:viewController animated:YES]; + +Now we trigger the view to be loaded and simulate the row to be tapped: + + + UIView *view = photosViewController.view; + STAssertNotNil(view, @""); + NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + [photosViewController tableView:photosViewController.tableView + didSelectRowAtIndexPath:indexPath]; + +Finally we verify that the expected method was called on the mocks: + + [mockNavController verify]; + [photosViewControllerMock verify]; + +We now have a test that tests interaction with the navigation controller and creation of the correct view controller. + +Again, in the example project, we're using our own convenience methods + + - (id)autoVerifiedMockForClass:(Class)aClass; + - (id)autoVerifiedPartialMockForObject:(id)object; + +and hence we don't have to remember to call `-verify`. + + +## Further Possibilities + +As you've seen above, *partial mocking* is extremely powerful. If you take a look at the source code of the `-[PhotosViewController setupTableView]` method, you'll see how it gets the model objects through the app delegate: + + NSArray *photos = [AppDelegate sharedDelegate].store.sortedPhotos; + +The above test depends on this. One way to break this dependency would be to again use *partial mocking* to make the app delegate return predefined data like so: + + id storeMock; // assume we've set this up + id appDelegate = [AppDelegate sharedDelegate] + id appDelegateMock = [OCMockObject partialMockForObject:appDelegate]; + [[[appDelegateMock stub] andReturn:storeMock] store]; + +Now whenever `[AppDelegate sharedDelegate].store` gets called, it will return the `storeMock`. This can be taken to extremes. Make sure to keep your tests as simple as possible and only as complex as needed. + +## Things to Remember + +Partial mocks alter the object they're mocking for as long as they're around. You can stop that behavior early by calling `[aMock stopMocking]`. Most of the time, you want the partial mock to stay active for the entire duration of the test. Make sure that happens by putting a `[aMock verify]` at the end of the test method. Otherwise ARC might dealloc the mock early. And you probably want that `-verify` anyway. + +## Testing NIB Loading + +The `PhotoCell` is setup in a NIB. We can write a simple test that checks that the outlets are set up correctly. Let's review the `PhotoCell` class: + + @interface PhotoCell : UITableViewCell + + + (UINib *)nib; + + @property (weak, nonatomic) IBOutlet UILabel* photoTitleLabel; + @property (weak, nonatomic) IBOutlet UILabel* photoDateLabel; + + @end + +Our simple test implementation looks like this + + @implementation PhotoCellTests + + - (void)testNibLoading; + { + UINib *nib = [PhotoCell nib]; + STAssertNotNil(nib, @""); + + NSArray *a = [nib instantiateWithOwner:nil options:@{}]; + STAssertEquals([a count], (NSUInteger) 1, @""); + PhotoCell *cell = a[0]; + STAssertTrue([cell isMemberOfClass:[PhotoCell class]], @""); + + // Check that outlets are set up correctly: + STAssertNotNil(cell.photoTitleLabel, @""); + STAssertNotNil(cell.photoDateLabel, @""); + } + + @end + +Very basic, but it does its job. + +One may argue that we now need to update both the test and the class / nib when we change things. That's true. We need to weigh this against the likelihood of breaking the outlets. If you've worked with `.xib` files, you've probably noticed that this is a commonly occurring thing. + +## Side Note About Classes and Injection + +As we noted under *Integration with Xcode* the test bundle gets injected into the app. Without getting into too much detail about how injection works (it's a huge topic in its own right): Injection adds the Objective-C classes from the injected bundle (our test bundle) to the running app. That's good, because it allows us to run our tests. + +One thing that can be very confusing, though, is if we add a class to both the app and the test bundle. If we, in the above example, would (by accident) have added the `PhotoCell` class to both the test bundle and the app, then the call to `[PhotoCell class]` would return a different pointer when called from inside our test bundle - that from within the app. And hence our test + + STAssertTrue([cell isMemberOfClass:[PhotoCell class]], @""); + +would fail. Again: Injection is complex. Your take away should be: Don't add `.m` files from your app to your test target. You'll get unexpected behavior. + +## Additional Thoughts + +If you have a Continuous Integration solution, getting your tests up and running there is a great idea. Details are outside the scope of this article. The scripts are triggered by the `RunUnitTests` script, and there's a `TEST_AFTER_BUILD` environment variable. + +Another interesting option is to create an independent test bundle for automated performance tests. You're free to do whatever you want inside your test methods. Timing certain calls and using `STAssert` to check that they're within a certain threshold would be one option. + +### Further Reading + +* [Test-driven development](https://en.wikipedia.org/wiki/Test-driven_development) +* [OCMock](http://ocmock.org) +* [Xcode Unit Testing Guide](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/UnitTesting/) +* [Book: Test Driven Development: By Example](http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530) +* [Blog: Quality Coding](http://qualitycoding.org) +* [Blog: iOS Unit Testing](http://iosunittesting.com) +* [Blog: Secure Mac Programing](http://blog.securemacprogramming.com/?s=testing&searchsubmit=Search) diff --git a/2013-07-07-async-testing.md b/2013-07-07-async-testing.md new file mode 100644 index 0000000..f813d5d --- /dev/null +++ b/2013-07-07-async-testing.md @@ -0,0 +1,223 @@ +--- +layout: post +title: "Testing Concurrent Applications" +category: "2" +tags: article +date: "2013-07-07 06:00:00" +author: "Tobias Kräntzer" +--- + +Testing is an important tool during the development process to create high quality applications. In the past, when concurrency was not such an important part of application architecture, testing was straightforward. Over the past few years it has become more and more important to use concurrent design patterns and we were challenged to develop new best practices to test them. + +The main challenge of testing concurrent code is that the program or information flow is not reflected in the call stack any more. Functions do not return their result to the caller immediately, but deliver it later via callback functions, blocks, notifications, or similar mechanisms, which makes testing more difficult. + +However, testing asynchronous code comes with the benefit of uncovering poor design decisions and facilitating clean implementations. + + +## The Problem with Asynchronous Testing + +Let's first recall an example of a simple synchronous unit test. This method of a simple calculator should sum up two numbers: + + + (int)add:(int)a to:(int)b { + return a + b; + } + +Testing this method is as simple as calling the method and comparing the result to the expected value. If the values don't match, the test fails. + + - (void)testAddition { + int result = [Calculator add:2 to:2]; + STAssertEquals(result, 4, nil); + } + +Now let's change the method to return its result asynchronously via a completion block. We will also add a bug to the implementation, so that we can expect a failing test: + + + (int)add:(int)a to:(int)b block:(void(^)(int))block { + [[NSOperationQueue mainQueue] addOperationWithBlock^{ + block(a - b); // Buggy implementation + }]; + } + +Of course this is a contrived example, but it reflects the general pattern you would use often if the operation would be more computationally intensive. + +A naive approach to testing this method would just move the assertion into the completion block. However, such a test simply never fails, in spite of the bug in our implementation: + + // don't use this code! + - (void)testAdditionAsync { + [Calculator add:2 to:2 block^(int result) { + STAssertEquals(result, 4, nil); // Never fails! + }]; + } + +Why doesn't this assertion fail? + + +## SenTestingKit Under the Hood + +The testing framework used by Xcode 4 is based on [OCUnit][4], which allows us to have a closer look at the internals. To understand the problem with the asynchronous test, we need to have a look at the execution order of the different parts of the test suite. This diagram shows a simplified flow. + +SenTestingKit call stack + +After the testing kit is started on the main run loop, it executes the following main steps: + +1. It sets up a test suite containing all relevant tests (as specified e.g. in the project scheme). +2. It runs the suite, which internally invokes all methods of the test cases starting with _test_. This run returns an object, containing the results of each single test. +3. It exits the process by calling `exit()`. + +The interesting part is how each individual test is invoked. During the asynchronous test, the completion block containing the assertion gets enqueued on the main run loop. Since the testing framework exits the process after all tests run, this block never gets executed and therefore never causes the test to fail. + +There are several approaches to solve this problem. But all of them have to run the main run loop and handle the enqueued operations before the test method returns and the framework checks the result. + +[Kiwi][5] uses a probe poller, which can be invoked within the test method. [GHUnit][6] provides a separate test class, which has to be prepared within the test method and which needs a notification at the end. In both cases we have to write some code, which ensures that the test method will not return until the test finishes. + + +## Async Extension to SenTestingKit + +Our solution to this problem is an [extension][2] to the built-in testing kit, which winds up the synchronous execution on the stack and enqueues each part as a block on the main queue. As you can see in the diagram below, the block that reports the success or failure of the asynchronous test is enqueued before the results of the entire suite are checked. This execution order allows us to fire up a test and wait for its result. + +SenTestingKitAsync call stack + +To give the framework a hint that a test should be treated as asynchronous, the method name has to end with __Async__. Furthermore, in asynchronous tests, we have to report the success of the test case manually and include a timeout, in case the completion block never gets called. We can rewrite our faulty test from above like this: + + - (void)testAdditionAsync { + [Calculator add:2 to:2 block^(int result) { + STAssertEquals(result, 4, nil); + STSuccess(); // Calling this macro reports success + }]; + STFailAfter(2.0, @"Timeout"); + } + + +## Designing Asynchronous Tests + +As with their synchronous counterparts, asynchronous tests should always be a magnitude simpler than the implementation they are testing. Complex tests don't promote better code quality, because the possibility of bugs in the tests increases. In a test-driven development process, simple tests let us think more clearly about the borders of components, their interfaces, and the expected behavior of the architecture. + + +### Example Project + +To put all this into practice, we create an example framework called [PinacotecaCore][3], which requests information of images from a hypothetical server. It has a resource manager, which is the developer-facing interface, providing a method to get an image object with an image id. In the background, the resource manager fetches the information from the server and updates the properties in the database. + +Although this is only an example project for the sake of demonstration, it shares the pattern we use in several of our apps. + +PinacotecaCore architecture + +With this high level overview of the architecture we can dive into the tests of the framework. In general there are three components which should be tested: + +1. the model layer +2. the server API controller, which abstracts the requests to the server +3. the resource manager, which manages the core data stack and ties the model layer and the API controller together + +### Model Layer + +Tests should be synchronous whenever possible, and the model layer is a good example. As long as there are no complicated dependencies between different managed object contexts, the test cases should set up their own core data stack with a context on the main thread in which to execute their operations. + +In this example, the [test case][7] sets up the core data stack in `setUp`, checks if the entity description for `PCImage` is present, creates an object with the constructor, and updates its values. As this has nothing to do with asynchronous testing, we won't go into further details here. + +### Server API Controller + +The second building block of the architecture is the server API controller. It contains the logic to manage the mapping of the server API to the model and handles the requests. In general, we want to evaluate the behavior of the following method: + + - [PCServerAPIController fetchImageWithId:queue:completionHandler:] + +It should be called with the id of an image and call the completion handler on the given queue. + +Because the server doesn't exist yet, and because it's a good habit, we will stub the network request with [OHHTTPStubs][8]. With the newest version, the project can contain a bundle with example responses, which will be delivered to the client. + +To stub a request, OHHTTPStubs has to be configured either in our test setup or in the test itself. First we have to load the bundle containing the responses: + + NSURL *url = [[NSBundle bundleForClass:[self class]] + URLForResource:@"ServerAPIResponses" + withExtension:@"bundle"]; + + NSBundle *bundle = [NSBundle url]; + +Then we can load the response from the bundle and specify for which request it should be returned: + + OHHTTPStubsResponse *response; + response = [OHHTTPStubsResponse responseNamed:@"images/123" + fromBundle:responsesBundle + responseTime:0.1]; + + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return YES /* true, if it's the expected request */; + } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + return response; + }]; + +With this setup, the simplified version of the [API controller test][9] looks like this: + + - (void)testFetchImageAsync + { + [self.server + fetchImageWithId:@"123" + queue:[NSOperationQueue mainQueue] + completionHandler:^(id imageData, NSError *error) { + STAssertEqualObjects([NSOperationQueue currentQueue], queue, nil); + STAssertNil(error, [error localizedDescription]); + STAssertTrue([imageData isKindOfClass:[NSDictionary class]], nil); + + // Check the values of the returned dictionary. + + STSuccess(); + }]; + STFailAfter(2.0, nil); + } + + +### Resource Manager + +The last component is the resource manager, which ties the model layer and the API controller together and manages the core data stack. Here we want to test the method to get an image object: + + -[PCResourceManager imageWithId:usingManagedObjectContext:queue:updateHandler:] + +This method should return an image object for the given id. If this image is not in the database, it will return a new object containing only the id and call the API controller to request the detailed information. + +Since the test of the resource manager should not depend on the API controller, we will stub it with [OCMock][10], which is ideal for partial stubs of methods. This is done in the [resource manager test][11]: + + OCMockObject *mo; + mo = [OCMockObject partialMockForObject:self.resourceManager.server]; + + id exp = [[serverMock expect] + andCall:@selector(fetchImageWithId:queue:completionHandler:) + onObject:self]; + [exp fetchImageWithId:OCMOCK_ANY queue:OCMOCK_ANY completionHandler:OCMOCK_ANY]; + +Instead of calling the real method of the API controller, the test will use the method implemented in the test case itself. + +With this in place, the test of the resource manager is straightforward. It calls the manager to get a resource, which internally will call the stubbed method on the API controller. There we can check if the controller is called with the correct parameters. After invoking the result handler, the resource manager updates the model and will call the result handler of our test. + + - (void)testGetImageAsync + { + NSManagedObjectContext *ctx = self.resourceManager.mainManagedObjectContext; + __block PCImage *img; + img = [self.resourceManager imageWithId:@"123" + usingManagedObjectContext:ctx + queue:[NSOperationQueue mainQueue] + updateHandler:^(NSError *error) { + // Check if the error is nil and + // if the image has been updated. + STSuccess(); + }]; + STAssertNotNil(img, nil); + STFailAfter(2.0, @"Timeout"); + } + + +## Conclusion + +Testing applications using concurrent design patterns can be challenging at first, but once you understand the differences and establish best practices, it is easy and a lot of fun. + +At [nxtbgthng][1] we are using the process described, [SenTestingKitAsync][2], on a daily basis. But the other approaches, like [Kiwi][5] or [GHUnit][6], are also good ways of doing asynchronous testing. You should try them all, find your preferred tool, and start testing asynchronously. + + +[1]: http://nxtbgthng.com "nxtbgthng" +[2]: https://github.com/nxtbgthng/SenTestingKitAsync "SenTestingKitAsync" +[3]: https://github.com/objcio/issue-2-async-testing "Pinacoteca Core: Cocoa Framework for an Imaginary Image Service" +[4]: http://www.sente.ch/software/ocunit/ "OCUnit" +[5]: https://github.com/allending/Kiwi "Kiwi" +[6]: https://github.com/gabriel/gh-unit/ "GHUnit" +[7]: https://github.com/objcio/issue-2-async-testing/blob/master/PinacotecaCore/PinacotecaCoreTests/PCModelLayerTests.m "Pinacoteca Core Model Layer Tests" +[8]: https://github.com/AliSoftware/OHHTTPStubs "OHHTTPStubs" +[9]: https://github.com/objcio/issue-2-async-testing/blob/master/PinacotecaCore/PinacotecaCoreTests/PCServerAPIControllerTests.m "Pinacoteca Core Server API Controller Tests" +[10]: http://ocmock.org "OCMock" +[11]: https://github.com/objcio/issue-2-async-testing/blob/master/PinacotecaCore/PinacotecaCoreTests/PCResourceManagerTests.m "Pinacoteca Core Resource Manager Tests" + diff --git a/2013-07-07-common-background-practices.md b/2013-07-07-common-background-practices.md new file mode 100644 index 0000000..541d505 --- /dev/null +++ b/2013-07-07-common-background-practices.md @@ -0,0 +1,545 @@ +--- +layout: post +title: "Common Background Practices" +category: "2" +date: "2013-07-07 09:00:00" +author: "Chris Eidhof" +tags: article +--- + +{% include links-2.md %} + +In this article we will describe best practices for doing common +tasks in the background. We will look at how to use Core Data concurrently, +how to draw concurrently, and how to do asynchronous networking. Finally, +we'll look at how to process large files asynchronously while keeping a +low memory profile. + +With asynchronous programming it is very easy to make mistakes. Therefore, all +examples in this article will use a very simple approach. Using +simple structures helps us to think through our code and to maintain an overview. +If you end up with complicated nested callbacks, you should probably revise some of your design decisions. + +## Operation Queues vs. Grand Central Dispatch + +Currently, there are two main modern concurrency APIs available on iOS and OS X: +[operation +queues](http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html) +and [Grand Central Dispatch](https://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html) (GCD). +GCD is a low-level C API, whereas operation queues are implemented +on top of GCD and provide an Objective-C API. For a more comprehensive +overview of available concurrency APIs see the [concurrency APIs and challenges][100] +article in this issue. + +Operation queues offer some useful convenience features not easily +reproducible with GCD. In practice, one of the most important ones is +the possibility to cancel operations in the queue, as we will demonstrate below. +Operation queues also make it a bit easier to manage dependencies between operations. +On the flip side, GCD gives you more control and low-level functionality that +is not available with operation queues. Please refer to the +[low level concurrency APIs][300] article for more details. + +Further reading: + +* [StackOverflow: NSOperation vs. Grand Central Dispatch](http://stackoverflow.com/questions/10373331/nsoperation-vs-grand-central-dispatch) +* [Blog: When to use NSOperation vs. GCD](http://eschatologist.net/blog/?p=232) + + +### Core Data in the Background + +Before doing anything concurrent with Core Data, it is important to get +the basics right. We strongly recommend reading through Apple's [Concurrency +with Core Data](https://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html) guide. +This document lays down the ground rules, such as never passing managed objects between +threads. This doesn't just mean that you should never modify a managed object on another +thread, but also that you should never read any properties from it. To pass around +an object, pass its object ID and retrieve the object from the context associated to the +other thread. + +Doing concurrent programming with Core Data is simple when you stick to those rules +and use the method described in this article. + +The standard setup for Core Data in the Xcode templates is one +persistent store coordinator with one managed object context that runs +on the main thread. For many use cases, this is just fine. +Creating some new objects and modifying existing objects is very cheap and can +be done on the main thread without problems. +However, if you want to do big chunks of work, then it makes sense to do this +in a background context. A prime example for this is importing large data sets +into Core Data. + +Our approach is very simple, and well-covered in existing literature: + +1. We create a separate operation for the import work +2. We create a managed object context with the same persistent store + coordinator as the main managed object context +3. Once the import context saves, we notify the main managed object + context and merge the changes + +In the [example application](https://github.com/objcio/issue-2-background-core-data), we will import a big set of transit data for +the city of Berlin. +During the import, we show a progress indicator, and we'd like to be able +to cancel the current +import if it's taking too long. Also, we show a table view with all the +data available so far, which automatically updates when new data comes in. +The example data set is publicly available under the Creative +Commons license, and you can download it [here](http://stg.daten.berlin.de/datensaetze/vbb-fahrplan-2013). It conforms to the [General Transit +Feed](https://developers.google.com/transit/gtfs/reference) format, an +open standard for transit data. + +We create an `ImportOperation` as a subclass of `NSOperation`, which will handle +the import. We override the `main` method, which is +the method that will do all the work. Here we create a separate +managed object context with the private queue concurrency type. This means that +this context will manage its own queue, and all operations on it +need to be performed using `performBlock` or `performBlockAndWait`. +This is crucial to make sure that they will be executed on the right thread. + + NSManagedObjectContext* context = [[NSManagedObjectContext alloc] + initWithConcurrencyType:NSPrivateQueueConcurrencyType]; + context.persistentStoreCoordinator = self.persistentStoreCoordinator; + context.undoManager = nil; + [self.context performBlockAndWait:^ + { + [self import]; + }]; + +Note that we reuse the existing persistent store coordinator. +In modern code, you should initialize managed object contexts with either +the `NSPrivateQueueConcurrencyType` or the `NSMainQueueConcurrencyType`. +The third concurrency type constant, `NSConfinementConcurrencyType`, is for +legacy code, and our advice is to not use it anymore. + +To do the import, we iterate over the lines in our file and create a +managed object for each line that we can parse: + + [lines enumerateObjectsUsingBlock: + ^(NSString* line, NSUInteger idx, BOOL* shouldStop) + { + NSArray* components = [line csvComponents]; + if(components.count < 5) { + NSLog(@"couldn't parse: %@", components); + return; + } + [Stop importCSVComponents:components intoContext:context]; + }]; + +To start this operation, we perform the following code from our view +controller: + + ImportOperation* operation = [[ImportOperation alloc] + initWithStore:self.store fileName:fileName]; + [self.operationQueue addOperation:operation]; + +For importing in the background, that's all you have to do. Now, we will add +support for cancelation, and luckily, it's as simple as adding one check +inside the enumeration block: + + if(self.isCancelled) { + *shouldStop = YES; + return; + } + +Finally, to support progress indication, we create a `progressCallback` +property on our operation. It is vital that we update our progress indicator on the main thread, otherwise UIKit will crash. + + operation.progressCallback = ^(float progress) + { + [[NSOperationQueue mainQueue] addOperationWithBlock:^ + { + self.progressIndicator.progress = progress; + }]; + }; + +To call the progress block, we add the following line in the enumeration block: + + self.progressCallback(idx / (float) count); + +However, if you run this code, you will see that everything slows down +enormously. Also, it looks like the operation doesn't cancel +immediately. The reason for this is that the main operation queue fills up with +blocks that want to update the progress indicator. A simple solution is to decrease +the granularity of updates, i.e. we only call the progress callback for +one percent of the lines imported: + + NSInteger progressGranularity = lines.count / 100; + + if (idx % progressGranularity == 0) { + self.progressCallback(idx / (float) count); + } + + +### Updating the Main Context + +The table view in our app is backed by a fetched results controller +on the main thread. During and after the import, we'd like +to show the results of the import in our table view. + +There is one missing piece to make this work; the data imported into the +background context will not propagate to the main context unless we explicitly +tell it to do so. We add the following line to the `init` method of the `Store` class where we set up the Core Data stack: + + [[NSNotificationCenter defaultCenter] + addObserverForName:NSManagedObjectContextDidSaveNotification + object:nil + queue:nil + usingBlock:^(NSNotification* note) + { + NSManagedObjectContext *moc = self.mainManagedObjectContext; + if (note.object != moc) + [moc performBlock:^(){ + [moc mergeChangesFromContextDidSaveNotification:note]; + }]; + }]; + }]; + +Note that by passing in the main queue as a parameter, the block +will be called on the main thread. +If you now start the app, you will notice that the table view reloads +its data at the end of the import. However, this blocks the user interface +for a couple of seconds. + +To fix this, we need to do something that we should have +done anyway: save in batches. When doing large imports, you want to +ensure that you save regularly, otherwise you might run out of memory, and +performance generally will get worse. Furthermore, saving regularly +spreads out the work on the main thread to update the table view over time. + +How often you save is a matter of trial and +error. Save too often, and you'll spend too much time doing I/O. Save too +little, and the app will become unresponsive. We set the batch +size to 250 after trying out some different numbers. Now the import is +smooth, updates the table view, and doesn't block the main context for +too long. + + +### Other Considerations + +In the import operation, we read the entire file into a string and then +split that into lines. This will work for relatively small files, +but for larger files, it makes sense to lazily read the file line by line. +The last example in this article will do exactly that by using input streams. +There's also an excellent [write-up on +StackOverflow](http://stackoverflow.com/questions/3707427/how-to-read-data-from-nsfilehandle-line-by-line/3711079#3711079) +by Dave DeLong that shows how to do this. + +Instead of importing a large data set into core data when the app first runs, +you could also ship an sqlite file within your app bundle, or download it +from a server, where you could even generate it dynamically. +If your particular use case works +with this solution, it will be a lot faster and save processing time on the device. + +Finally, there is a lot of noise about child contexts these days. Our +advice is not to use them for background operations. If you create a +background context as a child of the main context, saving the background +context [will still block the main thread](http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout) a lot. If you create +the main context as a child of a background context, you actually don't +gain anything compared to a more traditional setup with two independent +contexts, because you still have to merge the changes from the background to +the main context manually. + +The setup with one persistent store coordinator and +two independent contexts is the proven way of doing core data in the background. Stick with it unless you have really good reasons not to. + +Further reading: + +* [Core Data Programming Guide: Efficiently importing data](http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html) +* [Core Data Programming Guide: Concurrency with Core Data](http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/Articles/cdConcurrency.html#//apple_ref/doc/uid/TP40003385-SW1j) +* [StackOverflow: Rules for working with Core Data](http://stackoverflow.com/questions/2138252/core-data-multi-thread-application/2138332#2138332) +* [WWDC 2012 Video: Core Data Best Practices](https://developer.apple.com/videos/wwdc/2012/?id=214) +* [Book: Core Data by Marcus Zarra](http://pragprog.com/book/mzcd/core-data) + + +## UI Code in the Background + +First of all: UIKit only works on the main thread. That said, +there are some parts of UI code which are not directly related to UIKit +and which can take a significant amount of time. These tasks can be moved +to the background to not block the main thread for too long. +But before you start moving parts of your UI code into background queues, +it's important to measure which part of your code really is the problem. +This is vital, otherwise you might be optimizing the wrong thing. + +If you have identified an expensive operation that you can isolate, +put it in an operation queue: + + __weak id weakSelf = self; + [self.operationQueue addOperationWithBlock:^{ + NSNumber* result = findLargestMersennePrime(); + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + MyClass* strongSelf = weakSelf; + strongSelf.textLabel.text = [result stringValue]; + }]; + }]; + +As you can see, this is not completely straightforward; we need to make +a weak reference to self, otherwise we create a retain cycle (the block +retains self, the private operation queue retains the block, and self +retains the operation queue). Within the block we convert it +back to a strong reference to make sure it doesn't get deallocated while +running the block. + +### Drawing in the Background + +If your measurements show that `drawRect`: is your performance bottleneck, +you can move this drawing code to the background. Before +you do that though, check if there are other ways to achieve the same effect, +e.g. by using core animation layers or pre-rendered images instead of plain +Core Graphics drawing. See [this +post](http://floriankugler.com/blog/2013/5/24/layer-trees-vs-flat-drawing-graphics-performance-across-ios-device-generations) +by Florian for graphic performance measurements on current devices, or +[this comment](https://lobste.rs/s/ckm4uw/a_performance-minded_take_on_ios_design/comments/itdkfh) +by Andy Matuschak, a UIKit engineer, to get a good feel for all the +subtleties involved. + +If you do decide that your best option is to execute the drawing code +in the background, the solution is quite simple. Take the code in your +`drawRect:` method and put it in an operation. Then replace the +original view with an image view that gets updated once the operation has +completed. In your drawing method, use +`UIGraphicsBeginImageContextWithOptions` instead of `UIGraphicsGetCurrentContext`: + + UIGraphicsBeginImageContextWithOptions(size, NO, 0); + // drawing code here + UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return i; + +By passing in 0 as the third parameter, the scale of the device's main screen +will be automatically filled in, and the image will look great on both +retina and non-retina devices. + +If you do custom drawing in table view or collection view cells, it makes sense to put +all that into operation subclasses. You can add them to a background operation queue, and +cancel them when the user scrolls cells out of bounds from the `didEndDisplayingCell` +delegate method. All of this is explained in detail in [WWDC 2012 Session 211 -- Building Concurrent User Interfaces on iOS](https://developer.apple.com/videos/wwdc/2012/). + +Instead of scheduling the drawing code in the background yourself, you should +also experiment with the `drawsAsynchronously` property of `CALayer`. However, make sure to measure the effect of this. Sometimes it speeds things up, and sometimes +it's counterproductive. + + +## Asynchronous Networking + +All your networking should be done asynchronously. +However, with Grand Central Dispatch, you sometimes see code like this: + + // Warning: please don't use this code. + dispatch_async(backgroundQueue, ^{ + NSData* contents = [NSData dataWithContentsOfURL:url] + dispatch_async(dispatch_get_main_queue(), ^{ + // do something with the data. + }); + }); + +This might look quite smart, but there is a big problem with this code: there +is no way to cancel this synchronous network call. It will block the +thread until it's done. In case the operation times out, this might take a +very long time (e.g. `dataWithContentsOfURL` has a timeout of 30 seconds). + +If the queue is a serial queue, then it will be blocked for the whole time. If the queue is concurrent, then GCD has to spin up a new thread in order to make up for the thread which you are blocking. Both cases are not good. It's best to avoid blocking altogether. + +To improve upon this situation, we will use the asynchronous methods of +`NSURLConnection` and wrap everything up in an operation. This way we get the +full power and convenience of operation queues; we can easily control +the number of concurrent operations, add dependencies, and cancel +operations. + +However, there is something to watch out for when doing this: URL +connections deliver their events in a run loop. It is easiest to just +use the main run loop for this, as the data delivery doesn't take much +time. Then we can dispatch the processing of the incoming data onto +a background thread. + +Another possibility is the approach that libraries like [AFNetworking](http://afnetworking.com) take: +create a separate thread, set up a run loop on this thread, and schedule +the url connection there. But you probably wouldn't want to do this yourself. + +To kick off the URL connection, we override the `start` method in our custom operation subclass: + + - (void)start + { + NSURLRequest* request = [NSURLRequest requestWithURL:self.url]; + self.isExecuting = YES; + self.isFinished = NO; + [[NSOperationQueue mainQueue] addOperationWithBlock:^ + { + self.connection = [NSURLConnectionconnectionWithRequest:request + delegate:self]; + }]; + } + +Since we overrode the `start` method, we now must manage the +operation's state properties, `isExecuting` and `isFinished`, ourselves. +To cancel an operation, we need to cancel the connection and then set +the right flags so the operation queue knows the operation is done. + + - (void)cancel + { + [super cancel]; + [self.connection cancel]; + self.isFinished = YES; + self.isExecuting = NO; + } + + +When the connection finishes loading, it sends a delegate callback: + + - (void)connectionDidFinishLoading:(NSURLConnection *)connection + { + self.data = self.buffer; + self.buffer = nil; + self.isExecuting = NO; + self.isFinished = YES; + } + +And that's all there is to it. Check the [example project on GitHub](https://github.com/objcio/issue-2-background-networking) +for the full source code. +To conclude, we would like to recommend either taking your time to do this +right, or to use a library like +[AFNetworking](http://afnetworking.com). They +provide handy utilities like a category on `UIImageView` that asynchronously loads an image from a URL. +Using this in your table view code will automatically take care of +canceling image loading operations. + +Further reading: + +* [Concurrency Programming Guide](http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1) +* [NSOperation Class Reference: Concurrent vs. Non-Concurrent Operations](http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html%23http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html%23//apple_ref/doc/uid/TP40004591-RH2-SW15) +* [Blog: synchronous vs. asynchronous NSURLConnection](http://www.cocoaintheshell.com/2011/04/nsurlconnection-synchronous-asynchronous/) +* [GitHub: `SDWebImageDownloaderOperation.m`](https://github.com/rs/SDWebImage/blob/master/SDWebImage/SDWebImageDownloaderOperation.m) +* [Blog: Progressive image download with ImageIO](http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/) +* [WWDC 2012 Session 211: Building Concurrent User Interfaces on iOS](https://developer.apple.com/videos/wwdc/2012/) + + +## Advanced: File I/O in the Background + +In our core data background example, we read the entire file +that is to be imported into memory. +This works for smaller files, but for larger files this is not +feasible, because memory is limited on iOS devices. +To resolve this problem, we will build a class that does two things: +it reads a file line by line without having the entire file in +memory, and process the file on a background queue so the app stays +responsive. + +For this purpose we use `NSInputStream`, which will let us do asynchronous +processing of a file. As [the documentation](http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/TechniquesforReadingandWritingCustomFiles.html) +says: 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). + +GCD queues + +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`. + +Race condition + +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. + +Mutex locking + +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. + +Dead locks + +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. + +Priority Inversion + +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 An Example Page\r\n + 7: \r\n\r\n Hello World, this is a ve + +If you were looking for an HTTP header, it'd be much simpler to concatenate all data chunks into a larger buffer and then scan for `\r\n\r\n`. But in doing so, you'd be copying around data a lot. The other problem with a lot of the *old* C APIs is that there's no ownership of buffers so that functions had to copy data into their own buffers -- another copy. Copying data may seem trivial, but when you're doing a lot of I/O you'll see those copies show up in your profiling tool (Instruments). Even if you only copy each memory region once, you're incurring twice the memory bandwidth and you're burning through twice the amount of memory cache. + +### GCD and Buffers + +The more straightforward piece is about data buffers. GCD has a `dispatch_data_t` type that to some extent is similar to what `NSData` does for Objective-C. It can do other things, though, and is more generic. + +Note that `dispatch_data_t` can be retained and released, and the `dispatch_data_t` object *owns* the buffer it holds onto. + +This may seem trivial, but we have to remember that GCD is a plain C API, and can't use Objective-C. The traditional way was to have a buffer either backed by the stack or a `malloc`'d memory region -- these don't have ownership. + +One relatively unique property of `dispatch_data_t` is that it can be backed by disjoint memory regions. That solves the concatenation problem we just mentioned. When you concatenate two data objects with + + dispatch_data_t a; // Assume this hold some valid data + dispatch_data_t b; // Assume this hold some valid data + dispatch_data_t c = dispatch_data_create_concat(a, b); + +the data object `c` will *not* copy `a` and `b` into a single, larger memory region. Instead it will simply retain both `a` and `b`. You can then traverse the memory regions represented by `c` with `dispatch_data_apply`: + + dispatch_data_apply(c, ^(dispatch_data_t region, size_t offset, const void *buffer, size_t size) { + fprintf(stderr, "region with offset %zu, size %zu\n", offset, size); + return true; + }); + +Similarly you can create a subrange with `dispatch_data_create_subrange` that won't do any copying. + + +### Reading and Writing + +At its core, *Dispatch I/O* is about so-called *channels*. A dispatch I/O channel provides a different way to read and write from a file descriptor. The most basic way to create such a channel is by calling + + dispatch_io_t dispatch_io_create(dispatch_io_type_t type, dispatch_fd_t fd, + dispatch_queue_t queue, void (^cleanup_handler)(int error)); + +This returns the created channel which then *owns* the file descriptor. You must not modify the file descriptor in any way after you've created a channel from it. + +There are two fundamentally different *types* of channels: streams and random access. If you open a file on disk, you can use it to create a random access channel (because such a file descriptor is `seek`able). If you open a socket, you can create a stream channel. + +If you want to create a channel for a file, you're better off using `dispatch_io_create_with_path`, which takes a path, and lets GCD open the file. This is beneficial, since GCD can postpone opening the file -- hence limiting the number of files that are open at the same time. + +Analogous to the normal `read(2)`, `write(2)`, and `close(2)`, GCD offers `dispatch_io_read`, `dispatch_io_write`, and `dispatch_io_close`. Reading and writing is done via a callback block that is called whenever data is read or written. This effectively implements non-blocking, fully async I/O. + +We can't go into all details here, but here's an example for setting up a TCP server: + +First we create a listening socket and set up an event source for incoming connections: + + _isolation = dispatch_queue_create([[self description] UTF8String], 0); + _nativeSocket = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + struct sockaddr_in sin = {}; + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET6; + sin.sin_port = htons(port); + sin.sin_addr.s_addr= INADDR_ANY; + int err = bind(result.nativeSocket, (struct sockaddr *) &sin, sizeof(sin)); + NSCAssert(0 <= err, @""); + + _eventSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, _nativeSocket, 0, _isolation); + dispatch_source_set_event_handler(result.eventSource, ^{ + acceptConnection(_nativeSocket); + }); + + +When accepting a connection, we create an I/O channel: + + + typedef union socketAddress { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } socketAddressUnion; + + socketAddressUnion rsa; // remote socket address + socklen_t len = sizeof(rsa); + int native = accept(nativeSocket, &rsa.sa, &len); + if (native == -1) { + // Error. Ignore. + return nil; + } + + _remoteAddress = rsa; + _isolation = dispatch_queue_create([[self description] UTF8String], 0); + _channel = dispatch_io_create(DISPATCH_IO_STREAM, native, _isolation, ^(int error) { + NSLog(@"An error occured while listening on socket: %d", error); + }); + + //dispatch_io_set_high_water(_channel, 8 * 1024); + dispatch_io_set_low_water(_channel, 1); + dispatch_io_set_interval(_channel, NSEC_PER_MSEC * 10, DISPATCH_IO_STRICT_INTERVAL); + + socketAddressUnion lsa; // remote socket address + socklen_t len = sizeof(rsa); + getsockname(native, &lsa.sa, &len); + _localAddress = lsa; + +If we want to set `SO_KEEPALIVE` (if we're using HTTP-level keep-alive), we need to do so before calling `dispatch_io_create`. + +Having created a `dispatch_io` channel, we can set up the read handler: + + dispatch_io_read(_channel, 0, SIZE_MAX, _isolation, ^(bool done, dispatch_data_t data, int error){ + if (data != NULL) { + if (_data == NULL) { + _data = data; + } else { + _data = dispatch_data_create_concat(_data, data); + } + [self processData]; + } + }); + + +If all you want to do is to read from or write to a file, GCD provides two convenience wrappers: `dispatch_read` and `dispatch_write`. You pass `dispatch_read` a file path and a block to be called for each data block that's read. Similarly, `dispatch_write` takes a file path and a `dispatch_data_t` object to be written. + + +## Benchmarking + +In the obscure corners of GCD, you'll find a neat little tool for optimizing your code: + + uint64_t dispatch_benchmark(size_t count, void (^block)(void)); + +Put this declaration into your code, and you can measure the average number of nanoseconds the given block takes to execute. E.g. + + size_t const objectCount = 1000; + uint64_t n = dispatch_benchmark(10000, ^{ + @autoreleasepool { + id obj = @42; + NSMutableArray *array = [NSMutableArray array]; + for (size_t i = 0; i < objectCount; ++i) { + [array addObject:obj]; + } + } + }); + NSLog(@"-[NSMutableArray addObject:] : %llu ns", n); + +On my machine this outputs + + -[NSMutableArray addObject:] : 31803 ns + +i.e. that adding 1000 objects to an NSMutableArray takes 31803 ns, or about 32 ns per object. + +As the [man page](http://opensource.apple.com/source/libdispatch/libdispatch-84.5/man/dispatch_benchmark.3) for `dispatch_benchmark` points out, measuring performance isn't as trivial as it may seem. Particularly when comparing concurrent code with non-concurrent, you need to pay attention to computational bandwidth and memory bandwidth of the particular piece of hardware you're running on. It will change a lot from machine to machine. And the contention problem we mentioned above will affect code if its performance is bound by access to critical sections. + +Don't put this into shipping code. Aside from the fact that it'd be pretty pointless to do so, it's also private API. It's intended for debugging and performance analysis work only. + +View the man page with + + curl "http://opensource.apple.com/source/libdispatch/libdispatch-84.5/man/dispatch_benchmark.3?txt" + | /usr/bin/groffer --tty -T utf8 + + + + +## Atomic Operations + +The header file `libkern/OSAtomic.h` has lots of powerful functions for lower-level multi-threaded programming. Although it's part of the kernel header files, it's intended to also be used outside kernel and driver programming. + + +These functions are lower-level, and there are a few extra things you need to be aware of. If you do, though, you might find a thing or two here, that you'd otherwise not be able to do -- or not do as easily. It's mostly interesting if you're working on high-performance code and / or are implementing lock-free and wait-free algorithms. + +These functions are all summarized in the `atomic(3)` man page -- run `man 3 atomic` to get the complete documentation. You'll see it talking about memory barriers. Check out the [Wikipedia article about memory barriers](https://en.wikipedia.org/wiki/Memory_barrier). If you're in doubt, you probably need a memory barrier. + +### Counters + +There's a long list of `OSAtomicIncrement` and `OSAtomicDecrement` functions that allow you to increment and decrement an integer value in an atomic way -- thread safe without having to take a lock (or use queues). These can be useful if you need to increment global counters from multiple threads for statistics. If all you do is increment a global counter, the barrier-free `OSAtomicIncrement` versions are fine, and when there's no contention, they're cheap to call. + +Similarly, the `OSAtomicOr`, `OSAtomicAnd`, and `OSAtomicXor` functions can be used to perform logical operations, and `OSAtomicTest` functions to set or clear bits. + +### Compare and Swap + +The `OSAtomicCompareAndSwap` can be useful to do lock-free lazy initializations like this: + + void * sharedBuffer(void) + { + static void * buffer; + if (buffer == NULL) { + void * newBuffer = calloc(1, 1024); + if (!OSAtomicCompareAndSwapPtrBarrier(NULL, newBuffer, &buffer)) { + free(newBuffer); + } + } + return buffer; + } + +If there's no buffer, we create one and then atomically write it to `buffer` if `buffer` is NULL. In the rare case that someone else set `buffer` at the same time as the current thread, we simply free it. Since the compare-and-swap method is atomic, this is a thread-safe way to lazily initialize values. The check that `NULL` and setting `buffer` are done atomically. + +Obviously, you can do something similar with `dispatch_once()`. + +### Atomic Queues + +The `OSAtomicEnqueue()` and `OSAtomicDequeue()` let you implement LIFO queues (also known as stacks) in a thread-safe, lock-free way. For code that has strict latency requirements, this can be a powerful building block. + +There's also a `OSAtomicFifoEnqueue()` and `OSAtomicFifoDequeue()` for FIFO queues, but these only have documentation in the header file -- tread carefully when using. + +### Spin Locks + +Finally, the `OSAtomic.h` header defines the functions to work with spin locks: `OSSpinLock`. Again, Wikipedia has [in-depth information on spin locks](https://en.wikipedia.org/wiki/Spinlock). Check out the `spinlock(3)` man page with `man 3 spinlock`. The short story is that a spin lock is very cheap when there's no lock contention. + +Spin locks can be useful in certain situations as a performance optimization. As always: measure first, then optimize. Don't do optimistic optimizations. + +Here's an example for OSSpinLock: + + + @interface MyTableViewCell : UITableViewCell + + @property (readonly, nonatomic, copy) NSDictionary *amountAttributes; + + @end + + + + @implementation MyTableViewCell + { + NSDictionary *_amountAttributes; + } + + - (NSDictionary *)amountAttributes; + { + if (_amountAttributes == nil) { + static __weak NSDictionary *cachedAttributes = nil; + static OSSpinLock lock = OS_SPINLOCK_INIT; + OSSpinLockLock(&lock); + _amountAttributes = cachedAttributes; + if (_amountAttributes == nil) { + NSMutableDictionary *attributes = [[self subtitleAttributes] mutableCopy]; + attributes[NSFontAttributeName] = [UIFont fontWithName:@"ComicSans" size:36]; + attributes[NSParagraphStyleAttributeName] = [NSParagraphStyle defaultParagraphStyle]; + _amountAttributes = [attributes copy]; + cachedAttributes = _amountAttributes; + } + OSSpinLockUnlock(&lock); + } + return _amountAttributes; + } + +In the above example, it's probably not worth the trouble, but it shows the concept. We're using ARC's `__weak` to make sure that the `amountAttributes` will get `dealloc`'ed once all instances of `MyTableViewCell` go away. Yet we're able to share a single instance of this dictionary among all instances. + +The reason this performs well is that we're unlikely to hit the inner-most part of the method. This is quite esoteric -- don't use this in your App unless you have a real need. diff --git a/2013-07-07-thread-safe-class-design.mdown b/2013-07-07-thread-safe-class-design.mdown new file mode 100644 index 0000000..a70032c --- /dev/null +++ b/2013-07-07-thread-safe-class-design.mdown @@ -0,0 +1,268 @@ +--- +layout: post +title: "Thread-Safe Class Design" +category: "2" +date: "2013-07-07 07:00:00" +author: "Peter Steinberger" +tags: article +--- + +{% include links-2.md %} + + +This article will focus on *practical* tips, design patterns, and anti-patterns with regard to writing thread-safe classes and using Grand Central Dispatch (GCD). + + +## Thread Safety + +### Apple's Frameworks + +First, let's have a look at Apple's frameworks. In general, unless declared otherwise, most classes are not thread-safe by default. For some this is expected; for others it's quite interesting. + +One of the most common mistakes even experienced iOS/Mac developers make is accessing parts of UIKit/AppKit on background threads. It's very easy to make the mistake of setting properties like `image` from a background thread, because their content is being requested from the network in the background anyway. Apple's code is performance-optimized and will not warn you if you change properties from different threads. + +In the case of an image, a common symptom is that your change is picked up with a delay. But if two threads set the image at the same time, it's likely that your app will simply crash, because the currently set image could be released twice. Since this is timing dependent, it usually will crash when used by your customers and not during development. + +There are no *official* tools to find such errors, but there are some tricks that will do the job just fine. The [UIKit Main Thread Guard](https://gist.github.com/steipete/5664345) is a small source file that will patch any calls to UIView's `setNeedsLayout` and `setNeedsDisplay` and check for being executed on the main thread before forwarding the call. Since these two methods are called for a lot of UIKit setters (including image), this will catch many thread-related mistakes. Although this trick does not use private API, we don't recommend using this in production apps -- it's great during development though. + +It's a conscious design decision from Apple's side to not have UIKit be thread-safe. Making it thread-safe wouldn't buy you much in terms of performance; it would in fact make many things slower. And the fact that UIKit is tied to the main thread makes it very easy to write concurrent programs and use UIKit. All you have to do is make sure that calls into UIKit are always made on the main thread. + + +#### Why Isn't UIKit Thread Safe? + +Ensuring thread safety for a big framework like UIKit would be a major undertaking and would come at a great cost. Changing non-atomic to atomic properties would only be a tiny part of the changes required. Usually you want to change several properties at once, and only then see the changed result. For this, Apple would have to expose a method much like CoreData's `performBlock:` and `performBlockAndWait:` to synchronize changes. And if you consider that most calls to UIKit classes are about *configuration*, it's even more pointless to make them thread-safe. + +However, even calls that are not about configuration shared internal state and thus weren't thread-safe. If you already wrote apps back in the dark ages of iOS3.2 and before, you surely experienced random crashes when using NSString's `drawInRect:withFont:` while preparing background images. Thankfully, with iOS4, [Apple made most drawing methods and classes like `UIColor` and `UIFont` usable on background threads](http://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniOS/Articles/iPhoneOS4.html). + +Unfortunately, Apple's documentation is lacking on the subject of thread safety. They recommend access on the main thread only, and even for drawing methods they don't explicitly guarantee thread safety - so it's always a good idea to read the [iOS Release Notes](http://developer.apple.com/library/ios/#releasenotes/General/WhatsNewIniOS/Articles/iPhoneOS4.html) as well. + +For the most part, UIKit classes should be used only from the application’s main thread. This is particularly true either for classes derived from UIResponder or those that involve manipulating your application’s user interface in any way. + +#### The Deallocation Problem + +Another danger when using UIKit objects in the background is called "The Deallocation Problem." Apple outlines the issue in [TN2109](http://developer.apple.com/library/ios/#technotes/tn2109/_index.html) and presents various solutions. The problem is that UI objects should be deallocated on the main thread, because some of them might perform changes to the view hierarchy in `dealloc`. As we know, such calls to UIKit need to happen on the main thread. + +Since it's common that a secondary thread, operation, or block retains the caller, this is very easy to get wrong and quite hard to find/fix. This was also [a long-standing bug in AFNetworking](https://github.com/AFNetworking/AFNetworking/issues/56), simply because not a lot of people know about this issue and -- as usual -- it manifests itself in rare, hard-to-reproduce crashes. Consistent use of \_\_weak and not accessing ivars in async blocks/operations helps. + +#### Collection Classes + +Apple has a good overview document for both [iOS and Mac](https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1) listing thread safety for the most common foundation classes. In general, immutable classes like `NSArray` are thread-safe, while their mutable variants like `NSMutableArray` are not. In fact, it's fine to use them from different threads, as long as access is serialized within a queue. Remember that methods might return a mutable variant of a collection object even if they declare their return type as immutable. It's good practice to write something like `return [array copy]` to ensure the returned object is in fact immutable. + +Unlike in languages like [Java](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html), the Foundation framework doesn't offer thread-safe collection classes out of the box. This is actually very reasonable, because in most cases you want to apply your locks higher up anyway to avoid too many locking operations. A notable exception are caches, where a mutable dictionary might hold immutable data -- here Apple added `NSCache` in iOS4 that not only locks access, but also purges its content in low-memory situations. + +That said, there might be valid cases in your application where a thread-safe, mutable dictionary can be handy. And thanks to the class cluster approach, [it's easy to write one](https://gist.github.com/steipete/5928916). + + +### Atomic Properties + +Ever wondered how Apple is handling atomic setting/getting of properties? By now you have likely heard about spinlocks, semaphores, locks, @synchronized - so what's Apple using? Thankfully, [the Objective-C runtime is public](http://www.opensource.apple.com/source/objc4/), so we can take a look behind the curtain. + +A nonatomic property setter might look like this: + + - (void)setUserName:(NSString *)userName { + if (userName != _userName) { + [userName retain]; + [_userName release]; + _userName = userName; + } + } + +This is the variant with manual retain/release; however, the ARC-generated code looks similar. When we look at this code it's obvious why this means trouble when `setUserName:` is called concurrently. We could end up releasing `_userName` twice, which can corrupt memory and lead to hard-to-find bugs. + +What's happening internally for any property that's not manually implemented is that the compiler generates a call to [`objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)`](https://github.com/opensource-apple/objc4/blob/master/runtime/Accessors.subproj/objc-accessors.mm#L127). In our example, the call parameters would look like this: + + objc_setProperty_non_gc(self, _cmd, + (ptrdiff_t)(&_userName) - (ptrdiff_t)(self), userName, NO, NO);` + +The ptrdiff_t might look weird to you, but in the end it's simple pointer arithmetic, since an Objective-C class is just another C struct. + +`objc_setProperty` calls down to following method: + + static inline void reallySetProperty(id self, SEL _cmd, id newValue, + ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) + { + id oldValue; + id *slot = (id*) ((char*)self + offset); + + if (copy) { + newValue = [newValue copyWithZone:NULL]; + } else if (mutableCopy) { + newValue = [newValue mutableCopyWithZone:NULL]; + } else { + if (*slot == newValue) return; + newValue = objc_retain(newValue); + } + + if (!atomic) { + oldValue = *slot; + *slot = newValue; + } else { + spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)]; + _spin_lock(slotlock); + oldValue = *slot; + *slot = newValue; + _spin_unlock(slotlock); + } + + objc_release(oldValue); + } + +Aside from the rather funny name, this method is actually fairly straightforward and uses one of the 128 available spinlocks in `PropertyLocks`. This is a pragmatic and fast approach -- the worst case scenario is that a setter might have to wait for an unrelated setter to finish because of a hash collision. + +While those methods aren't declared in any public header, it is possible to call them manually. I'm not saying this is a good idea, but it's interesting to know and could be quite useful if you want atomic properties *and* to implement the setter at the same time. + + // Manually declare runtime methods. + extern void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, + id newValue, BOOL atomic, BOOL shouldCopy); + extern id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, + BOOL atomic); + + #define PSTAtomicRetainedSet(dest, src) objc_setProperty(self, _cmd, + (ptrdiff_t)(&dest) - (ptrdiff_t)(self), src, YES, NO) + #define PSTAtomicAutoreleasedGet(src) objc_getProperty(self, _cmd, + (ptrdiff_t)(&src) - (ptrdiff_t)(self), YES) + +[Refer to this gist](https://gist.github.com/steipete/5928690) for the full snippet including code to handle structs. But keep in mind that we don't recommend using this. + +#### What about @synchronized? + +You might be curious why Apple isn't using `@synchronized(self)` for property locking, an already existing runtime feature. Once you [look at the source](https://github.com/opensource-apple/objc4/blob/master/runtime/objc-sync.mm#L291), you'll see that there's a lot more going on. Apple is using [up to three lock/unlock sequences](http://googlemac.blogspot.co.at/2006/10/synchronized-swimming.html), partly because they also add [exception unwinding](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW3). This would be a slowdown compared to the much faster spinlock approach. Since setting the property usually is quite fast, spinlocks are perfect for the job. `@synchonized(self)` is good when you need to ensure that exception can be thrown without the code deadlocking. + + +### Your Own Classes + +Using atomic properties alone won't make your classes thread-safe. It will only protect you against [race conditions][103] in the setter, but won't protect your application logic. Consider the following snippet: + + if (self.contents) { + CFAttributedStringRef stringRef = CFAttributedStringCreate(NULL, + (__bridge CFStringRef)self.contents, NULL); + // draw string + } + +I've made this mistake early on in [PSPDFKit](http://pspdfkit.com). From time to time, the application crashed with a EXC_BAD_ACCESS, when the `contents` property was set to nil after the check. A simple fix for this issue would be to capture the variable: + + NSString *contents = self.contents; + if (contents) { + CFAttributedStringRef stringRef = CFAttributedStringCreate(NULL, + (__bridge CFStringRef)contents, NULL); + // draw string + } + +This would solve the issue here, but in most cases it's not that simple. Imagine that we also have a `textColor` property and we change both properties on one thread. Then our render thread could end up using the new content along with the old color value and we get a weird combination. This is one reason why Core Data binds model objects to one thread or queue. + +There's no one-size-fits-all solution for this problem. Using [immutable models](http://www.cocoawithlove.com/2008/04/value-of-immutable-values.html) is a solution, but it has its own problems. Another way is to limit changes to existing objects to the main thread or a specific queue and to generate copies before using them on worker threads. I recommend Jonathan Sterling's article about [Lightweight Immutability in Objective-C](http://www.jonmsterling.com/posts/2012-12-27-a-pattern-for-immutability.html) for even more ideas on solving this problem. + +The simple solution is to use @synchronize. Anything else is very, very likely to get you into trouble. Way smarter people have failed again and again at doing so. + +#### Practical Thread-Safe Design + +Before trying to make something thread-safe, think hard if it's necessary. Make sure it's not premature optimization. If it's anything like a configuration class, there's no point in thinking about thread safety. A much better approach is to throw some asserts in to ensure it's used correctly: + + void PSPDFAssertIfNotMainThread(void) { + NSAssert(NSThread.isMainThread, + @"Error: Method needs to be called on the main thread. %@", + [NSThread callStackSymbols]); + } + +Now there's code that definitely should be thread-safe; a good example is a caching class. A good approach is to use a concurrent dispatch_queue as read/write lock to maximize performance and try to only lock the areas that are really necessary. Once you start using multiple queues for locking different parts, things get tricky really fast. + +Sometimes you can also rewrite your code so that special locks are not required. Consider this snippet that is a form of a multicast delegate. (In many cases, using NSNotifications would be better, but there are [valid use cases for multicast delegates.](https://code.google.com/r/riky-adsfasfasf/source/browse/Utilities/GCDMulticastDelegate.h)) + + // header + @property (nonatomic, strong) NSMutableSet *delegates; + + // in init + _delegateQueue = dispatch_queue_create("com.PSPDFKit.cacheDelegateQueue", + DISPATCH_QUEUE_CONCURRENT); + + - (void)addDelegate:(id)delegate { + dispatch_barrier_async(_delegateQueue, ^{ + [self.delegates addObject:delegate]; + }); + } + + - (void)removeAllDelegates { + dispatch_barrier_async(_delegateQueue, ^{ + self.delegates removeAllObjects]; + }); + } + + - (void)callDelegateForX { + dispatch_sync(_delegateQueue, ^{ + [self.delegates enumerateObjectsUsingBlock:^(id delegate, NSUInteger idx, BOOL *stop) { + // Call delegate + }]; + }); + } + +Unless `addDelegate:` or `removeDelegate:` is called thousand times per second, a simpler and cleaner approach is the following: + + // header + @property (atomic, copy) NSSet *delegates; + + - (void)addDelegate:(id)delegate { + @synchronized(self) { + self.delegates = [self.delegates setByAddingObject:delegate]; + } + } + + - (void)removeAllDelegates { + self.delegates = nil; + } + + - (void)callDelegateForX { + [self.delegates enumerateObjectsUsingBlock:^(id delegate, NSUInteger idx, BOOL *stop) { + // Call delegate + }]; + } + +Granted, this example is a bit constructed and one could simply confine changes to the main thread. But for many data structures, it might be worth it to create immutable copies in the modifier methods, so that the general application logic doesn't have to deal with excessive locking. Notice how we still have to apply locking in `addDelegate:`, since otherwise delegate objects might get lost if called from different threads concurrently. + + +## Pitfalls of GCD + +For most of your locking needs, GCD is perfect. It's simple, it's fast, and its block-based API makes it much harder to accidentally do imbalanced locks. However, there are quite a few pitfalls, some of which we are going to explore here. + +### Using GCD as a Recursive Lock + +GCD is a queue to serialize access to shared resources. This can be used for locking, but it's quite different than `@synchronized`. GCD queues are not reentrant - this would break the queue characteristics. Many people tried working around this with using `dispatch_get_current_queue()`, which is [a bad idea](https://gist.github.com/steipete/3713233), and Apple had its reasons for deprecating this method in iOS6. + + // This is a bad idea. + inline void pst_dispatch_sync_reentrant(dispatch_queue_t queue, + dispatch_block_t block) + { + dispatch_get_current_queue() == queue ? block() + : dispatch_sync(queue, block); + } + +Testing for the current queue might work for simple solutions, but it fails as soon as your code gets more complex, and you might have multiple queues locked at the same time. Once you are there, you almost certainly will get a [deadlock][104]. Sure, one could use `dispatch_get_specific()`, which will traverse the whole queue hierarchy to test for specific queues. For that you would have to write custom queue constructors that apply this metadata. Don't go that way. There are use cases where a `NSRecursiveLock` is the better solution. + +### Fixing Timing Issues with dispatch_async + +Having some timing-issues in UIKit? Most of the time, this will be the perfect "fix:" + + dispatch_async(dispatch_get_main_queue(), ^{ + // Some UIKit call that had timing issues but works fine + // in the next runloop. + [self updatePopoverSize]; + }); + +Don't do this, trust me. This will haunt you later as your app gets larger. It's super hard to debug and soon things will fall apart when you need to dispatch more and more because of "timing issues." Look through your code and find the proper place for the call (e.g. viewWillAppear instead of viewDidLoad). I still have some of those hacks in my codebase, but most of them are properly documented and an issue is filed. + +Remember that this isn't really GCD-specific, but it's a common anti-pattern and just very easy to do with GCD. You can apply the same wisdom for `performSelector:afterDelay:`, where the delay is 0.f for the next runloop. + +### Mixing dispatch_sync and dispatch_async in Performance Critical Code + +That one took me a while to figure out. In [PSPDFKit](http://pspdfkit.com) there is a caching class that uses a LRU list to track image access. When you scroll through the pages, this is called *a lot*. The initial implementation used dispatch_sync for availability access, and dispatch_async to update the LRU position. This resulted in a frame rate far from the goal of 60 FPS. + +When other code running in your app is blocking GCD's threads, it might take a while until the dispatch manager finds a thread to perform the dispatch_async code -- until then, your sync call will be blocked. Even when, as in this example, the order of execution for the async case isn't important, there's no easy way to tell that to GCD. Read/Write locks won't help you there, since the async process most definitely needs to perform a barrier write and all your readers will be locked during that. Lesson: `dispatch_async` can be expensive if it's misused. Be careful when using it for locking. + +### Using dispatch_async to Dispatch Memory-Intensive Operations + +We already talked a lot about NSOperations, and that it's usually a good idea to use the more high-level API. This is especially true if you deal with blocks of work that do memory-intensive operations. + +In an old version of PSPDFKit, I used a GCD queue to dispatch writing cached JPG images to disk. When the retina iPad came out, this started causing trouble. The resolution doubled, and it took much longer to encode the image data than it took to render it. Consequently, operations piled up in the queue and when the system was busy it could crash of memory exhaustion. + +There's no way to see how many operations are queued (unless you manually add code to track this), and there's also no built-in way to cancel operations in case of a low-memory notification. Switching to NSOperations made the code a lot more debuggable and allowed all this without writing manual management code. + +Of course there are some caveats; for example you can't set a target queue on your `NSOperationQueue` (like `DISPATCH_QUEUE_PRIORITY_BACKGROUND` for throttled I/O). But that's a small price for debuggability, and it also prevents you from running into problem like [priority inversion][102]. I even recommend against the nice `NSBlockOperation` API and suggest real subclasses of NSOperation, including an implementation of description. It's more work, but later on, having a way to print all running/pending operations is insanely useful. diff --git a/2013-08-07-advanced-auto-layout-toolbox.md b/2013-08-07-advanced-auto-layout-toolbox.md new file mode 100644 index 0000000..d0790d4 --- /dev/null +++ b/2013-08-07-advanced-auto-layout-toolbox.md @@ -0,0 +1,496 @@ +--- +layout: post +title: "Advanced Auto Layout Toolbox" +category: "3" +date: "2013-08-07 06:00:00" +author: "Florian Kugler" +tags: article +--- + +{% include links-3.md %} + +Auto Layout was introduced in OS X 10.7, and one year later it made its way into iOS 6. Soon apps on iOS 7 will be expected to honor the systemwide font size setting, thus requiring even more flexibility in the user interface layout next to different screen sizes and orientations. Apple is doubling down on Auto Layout, so now is a good time to get your feet wet if you haven't done so yet. + +Many developers struggle with Auto Layout when first trying it, because of the often-frustrating experience of building constraint-based layouts with Xcode 4's Interface Builder. But don't let yourself be discouraged by that; Auto Layout is much better than Interface Builder's current support for it. Xcode 5 will bring some major relief in this area. + +This article is not an introduction to Auto Layout. If you haven't worked with it yet, we encourage you to watch the Auto Layout sessions from WWDC 2012 ([202 -- Introduction to Auto Layout for iOS and OS X](https://developer.apple.com/videos/wwdc/2012/?id=202), [228 -- Best Practices for Mastering Auto Layout](https://developer.apple.com/videos/wwdc/2012/?id=228), [232 -- Auto Layout by Example](https://developer.apple.com/videos/wwdc/2012/?id=232)). These are excellent introductions to the topic which cover a lot of ground. + +Instead, we are going to focus on several advanced tips and techniques, which enhance productivity with Auto Layout and make your (development) life easier. Most of these are touched upon in the WWDC sessions mentioned above, but they are the kind of things that are easy to oversee or forget while trying to get your daily work done. + + + + +## The Layout Process + +First we will recap the steps it takes to bring views on screen with Auto Layout enabled. When you're struggling to produce the kind of layout you want with Auto Layout, specifically with advanced use cases and animation, it helps to take a step back and to recall how the layout process works. + +Compared to working with springs and struts, Auto Layout introduces two additional steps to the process before views can be displayed: updating constraints and laying out views. Each step is dependent on the one before; display depends on layout, and layout depends on updating constraints. + +The first step -- updating constraints -- can be considered a "measurement pass." It happens bottom-up (from subview to super view) and prepares the information needed for the layout pass to actually set the views' frame. You can trigger this pass by calling `setNeedsUpdateConstraints`. Any changes you make to the system of constraints itself will automatically trigger this. However, it is useful to notify Auto Layout about changes in custom views that could affect the layout. Speaking of custom views, you can override `updateConstraints` to add the local constraints needed for your view in this phase. + +The second step -- layout -- happens top-down (from super view to subview). This layout pass actually applies the solution of the constraint system to the views by setting their frames (on OS X) or their center and bounds (on iOS). You can trigger this pass by calling `setNeedsLayout`, which does not actually go ahead and apply the layout immediately, but takes note of your request for later. This way you don't have to worry about calling it too often, since all the layout requests will be coalesced into one layout pass. + +To force the system to update the layout of a view tree immediately, you can call `layoutIfNeeded`/`layoutSubtreeIfNeeded` (on iOS and OS X respectively). This can be helpful if your next steps rely on the views' frame being up to date. In your custom views you can override `layoutSubviews`/`layout` to gain full control over the layout pass. We will show use cases for this later on. + +Finally, the display pass renders the views to screen and is independent of whether you're using Auto Layout or not. It operates top-down and can be triggered by calling `setNeedsDisplay`, which results in a deferred redraw coalescing all those calls. Overriding the familiar `drawRect:` is how you gain full control over this stage of the display process in your custom views. + +Since each step depends on the one before it, the display pass will trigger a layout pass if any layout changes are pending. Similarly, the layout pass will trigger updating the constraints if the constraint system has pending changes. + +It's important to remember that these three steps are not a one-way street. Constraint-based layout is an iterative process. The layout pass can make changes to the constraints based on the previous layout solution, which again triggers updating the constraints following another layout pass. This can be leveraged to create advanced layouts of custom views, but you can also get stuck in an infinite loop if every call of your custom implementation of `layoutSubviews` results in another layout pass. + + +## Enabling Custom Views for Auto Layout + +When writing a custom view, you need to be aware of the following things with regard to Auto Layout: specifying an appropriate intrinsic content size, distinguishing between the view's frame and alignment rect, enabling baseline-aligned layout, and how to hook into the layout process. We will go through these aspects one by one. + + +### Intrinsic Content Size + +The intrinsic content size is the size a view prefers to have for a specific content it displays. For example, `UILabel` has a preferred height based on the font, and a preferred width based on the font and the text it displays. A `UIProgressView` only has a preferred height based on its artwork, but no preferred width. A plain `UIView` has neither a preferred width nor a preferred height. + +You have to decide, based on the content to be displayed, if your custom view has an intrinsic content size, and if so, for which dimensions. + +To implement an intrinsic content size in a custom view, you have to do two things: override [`intrinsicContentSize`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/intrinsicContentSize) to return the appropriate size for the content, and call [`invalidateIntrinsicContentSize`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/invalidateIntrinsicContentSize) whenever something changes which affects the intrinsic content size. If the view only has an intrinsic size for one dimension, return `UIViewNoIntrinsicMetric`/`NSViewNoIntrinsicMetric` for the other one. + +Note that the intrinsic content size must be independent of the view's frame. For example, it's not possible to return an intrinsic content size with a specific aspect ratio based on the frame's height or width. + + +#### Compression Resistance and Content Hugging + +Each view has content compression resistance priorities and content hugging priorities assigned for both dimensions. These properties only take effect for views which define an intrinsic content size, otherwise there is no content size defined that could resist compression or be hugged. + +Behind the scenes, the intrinsic content size and these priority values get translated into constraints. For a label with an intrinsic content size of `{ 100, 30 }`, horizontal/vertical compression resistance priority of `750`, and horizontal/vertical content hugging priority of `250`, four constraints will be generated: + + H:[label(<=100@250)] + H:[label(>=100@750)] + V:[label(<=30@250)] + V:[label(>=30@750)] + +If you're not familiar with the visual format language for the constraints used above, you can read up about it in [Apple's documentation](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage/VisualFormatLanguage.html). Keeping in mind that these additional constraints are generated implicitly helps to understand Auto Layout's behavior and to make better sense of its error messages. + + +### Frame vs. Alignment Rect + +Auto Layout does not operate on views' frame, but on their alignment rect. It's easy to forget the subtle difference, because in many cases they are the same. But alignment rects are actually a powerful new concept that decouple a view's layout alignment edges from its visual appearance. + +For example, a button in the form of a custom icon that is smaller than the touch target we want to have would normally be difficult to lay out. We would have to know about the dimensions of the artwork displayed within a larger frame and adjust the button's frame accordingly, so that the icon lines up with other interface elements. The same happens if we want to draw custom ornamentation around the content, like badges, shadows, and reflections. + +Using alignment rects we can easily define the rectangle which should be used for layout. In most cases you can just override the [`alignmentRectInsets`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/alignmentRectInsets) method, which lets you return edge insets relative to the frame. If you need more control you can override the methods [`alignmentRectForFrame:`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/alignmentRectForFrame:) and [`frameForAlignmentRect:`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/frameForAlignmentRect:). This can be useful if you want to calculate the alignment rect based on the current frame value instead of just subtracting fixed insets. But you have to make sure that these two methods are inverses of each other. + +In this context it is also good to recall that the aforementioned intrinsic content size of a view refers to its alignment rect, not to its frame. This makes sense, because Auto Layout generates the compression resistance and content hugging constraints straight from the intrinsic content size. + + +### Baseline Alignment + +To enable constraints using the `NSLayoutAttributeBaseline` attribute to work on a custom view, we have to do a little bit of extra work. Of course this only makes sense if the custom view in question has something like a baseline. + +On iOS, baseline alignment can be enabled by implementing [`viewForBaselineLayout`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/viewForBaselineLayout). The bottom edge of the view you return here will be used as baseline. The default implementation simply returns self, while a custom implementation can return any subview. On OS X you don't return a subview but an offset from the view's bottom edge by overriding [`baselineOffsetFromBottom`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/baselineOffsetFromBottom), which has the same default behavior as its iOS counterpart by returning 0 in its default implementation. + + +### Taking Control of Layout + +In a custom view you have full control over the layout of its subviews. You can add local constraints, you can change local constraints if a change in content requires it, you can fine-tune the result of the layout pass for subviews, or you can opt out of Auto Layout altogether. + +Make sure though that you use this power wisely. Most cases can be handled by simply adding local constraints for your subviews. + + +#### Local Constraints + +If we want to compose a custom view out of several subviews, we have to lay out these subviews somehow. In an Auto Layout environment it is most natural to add local constraints for these views. However, note that this makes your custom view dependent on Auto Layout, and it cannot be used anymore in windows without Auto Layout enabled. It's best to make this dependency explicit by implementing [`requiresConstraintBasedLayout`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/clm/NSView/requiresConstraintBasedLayout) to return `YES`. + +The place to add local constraints is [`updateConstraints`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/updateConstraints). Make sure to invoke `[super updateConstraints]` in your implementation and then add whatever constraints you need to lay out the subviews. In this method, you're not allowed to invalidate any constraints, because you are already in the first step of the [layout process][110] described above. Trying to do so will generate a friendly error message informing you that you've made a "programming error." + +If something changes later on that invalidates one of your constraints, you should remove the constraint immediately and call [`setNeedsUpdateConstraints`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/setNeedsUpdateConstraints). In fact, that's the only case where you should have to trigger a constraint update pass. + + +#### Control Layout of Subviews + +If you cannot use layout constraints to achieve the desired layout of your subviews, you can go one step further and override [`layoutSubviews`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/layoutSubviews) on iOS or [`layout`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/layout) on OS X. This way, you're hooking into the second step of the [layout process][110], when the constraint system has already been solved and the results are being applied to the view. + +The most drastic approach is to override `layoutSubviews`/`layout` without calling the super class's implementation. This means that you're opting out of Auto Layout for the view tree within this view. From this point on, you can position subviews manually however you like. + +If you still want to use constraints to lay out subviews, you have to call `[super layoutSubviews]`/`[super layout]` and make fine-tuned adjustments to the layout afterwards. You can use this to create layouts which are not possible to define using constraints, for example layouts involving relationships between the size and the spacing between views. + +Another interesting use case for this is to create a layout-dependent view tree. After Auto Layout has done its first pass and set the frames on your custom view's subviews, you can inspect the positioning and sizing of these subviews and make changes to the view hierarchy and/or to the constraints. WWDC session [228 -- Best Practices for Mastering Auto Layout](https://developer.apple.com/videos/wwdc/2012/?id=228) has a good example of this, where subviews are removed after the first layout pass if they are getting clipped. + +You could also decide to change the constraints after the first layout pass. For example, switch from lining up subviews in one row to two rows, if the views are becoming too narrow. + + - layoutSubviews + { + [super layoutSubviews]; + if (self.subviews[0].frame.size.width <= MINIMUM_WIDTH) { + [self removeSubviewConstraints]; + self.layoutRows += 1; + [super layoutSubviews]; + } + } + + - updateConstraints + { + [super updateConstraints]; + // add constraints depended on self.layoutRows... + } + + +## Intrinsic Content Size of Multi-Line Text + +The intrinsic content size of `UILabel` and `NSTextField` is ambiguous for multi-line text. The height of the text depends on the width of the lines, which is yet to be determined when solving the constraints. In order to solve this problem, both classes have a new property called [`preferredMaxLayoutWidth`](http://developer.apple.com/library/ios/documentation/uikit/reference/UILabel_Class/Reference/UILabel.html#//apple_ref/occ/instp/UILabel/preferredMaxLayoutWidth), which specifies the maximum line width for calculating the intrinsic content size. + +Since we usually don't know this value in advance, we need to take a two-step approach to get this right. First we let Auto Layout do its work, and then we use the resulting frame in the layout pass to update the preferred maximum width and trigger layout again. + + - (void)layoutSubviews + { + [super layoutSubviews]; + myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width; + [super layoutSubviews]; + } + +The first call to `[super layoutSubviews]` is necessary for the label to get its frame set, while the second call is necessary to update the layout after the change. If we omit the second call we get a `NSInternalInconsistencyException` error, because we've made changes in the layout pass which require updating the constraints, but we didn't trigger layout again. + +We can also do this in a label subclass itself: + + @implementation MyLabel + - (void)layoutSubviews + { + self.preferredMaxLayoutWidth = self.frame.size.width; + [super layoutSubviews]; + } + @end + +In this case, we don't need to call `[super layoutSubviews]` first, because when `layoutSubviews` gets called, we already have a frame on the label itself. + +To make this adjustment from the view controller level, we hook into `viewDidLayoutSubviews`. At this point the frames of the first Auto Layout pass are already set and we can use them to set the preferred maximum width. + + - (void)viewDidLayoutSubviews + { + [super viewDidLayoutSubviews]; + myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width; + [self.view layoutIfNeeded]; + } + +Lastly, make sure that you don't have an explicit height constraint on the label that has a higher priority than the label's content compression resistance priority. Otherwise it will trump the calculated height of the content. + + +## Animation + +When it comes to animating views laid out with Auto Layout, there are two fundamentally different strategies: Animating the constraints themselves, and changing the constraints to recalculate the frames and use Core Animation to interpolate between the old and the new position. + +The difference between the two approaches is that animating constraints themselves results in a layout that conforms to the constraint system at all times. Meanwhile, using Core Animation to interpolate between old and new frames violates constraints temporarily. + +Directly animating constraints is really only a feasible strategy on OS X, and it is limited in what you can animate, since only a constraint's constant can be changed after creating it. On iOS you would have to drive the animation manually, whereas on OS X you can use an animator proxy on the constraint's constant. Furthermore, this approach is significantly slower than the Core Animation approach, which also makes it a bad fit for mobile platforms for the time being. + +When using the Core Animation approach, animation conceptually works the same way as without Auto Layout. The difference is that you don't set the views' target frames manually, but instead you modify the constraints and trigger a layout pass to set the frames for you. On iOS, instead of: + + [UIView animateWithDuration:1 animations:^{ + myView.frame = newFrame; + }]; + +you now write: + + // update constraints + [UIView animateWithDuration:1 animations:^{ + [myView layoutIfNeeded]; + }]; + +Note that with this approach, the changes you can make to the constraints are not limited to the constraints' constants. You can remove constraints, add constraints, and even use temporary animation constraints. Since the new constraints only get solved once to determine the new frames, even more complex layout changes are possible. + +The most important thing to remember when animating views using Core Animation in conjunction with Auto Layout is to not touch the views' frame yourself. Once a view is laid out by Auto Layout, you've transferred the responsibility to set its frame to the layout system. Interfering with this will result in weird behavior. + +This means also that view transforms don't always play nice with Auto Layout if they change the view's frame. Consider the following example: + + [UIView animateWithDuration:1 animations:^{ + myView.transform = CGAffineTransformMakeScale(.5, .5); + }]; + +Normally we would expect this to scale the view to half its size while maintaining its center point. But the behavior with Auto Layout depends on the kind of constraints we have set up to position the view. If we have it centered within its super view, the result is as expected, because applying the transform triggers a layout pass which centers the new frame within the super view. However, if we have aligned the left edge of the view to another view, then this alignment will stick and the center point will move. + +Anyway, applying transforms like this to views laid out with constraints is not a good idea, even if the result matches our expectations at first. The view's frame gets out of sync with the constraints, which will lead to strange behavior down the road. + +If you want to use transforms to animate a view or otherwise animate its frame directly, the cleanest technique to do this is to [embed the view into a container view](http://stackoverflow.com/a/14119154). Then you can override `layoutSubviews` on the container, either opting out of Auto Layout completely or only adjusting its result. For example, if we setup a subview in our container which is laid out within the container at its top and left edges using Auto Layout, we can correct its center after the layout happens to enable the scale transform from above: + + - (void)layoutSubviews + { + [super layoutSubviews]; + static CGPoint center = {0,0}; + if (CGPointEqualToPoint(center, CGPointZero)) { + // grab the view's center point after initial layout + center = self.animatedView.center; + } else { + // apply the previous center to the animated view + self.animatedView.center = center; + } + } + +If we expose the `animatedView` property as an IBOutlet, we can even use this container within Interface Builder and position its subview with constraints, while still being able to apply the scale transform with the center staying fixed. + + +## Debugging + +When it comes to debugging Auto Layout, OS X still has a significant advantage over iOS. On OS X you can make use of Instrument's Cocoa Layout template, as well as `NSWindow`'s [`visualizeConstraints:`](http://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWindow_Class/Reference/Reference.html#//apple_ref/occ/instm/NSWindow/visualizeConstraints:) method. Furthermore, `NSView` has an [`identifier`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSUserInterfaceItemIdentification_Protocol/Introduction/Introduction.html#//apple_ref/occ/intfp/NSUserInterfaceItemIdentification/identifier) property, which you can set from Interface Builder or in code, in order to get much more readable Auto Layout error messages. + + +### Unsatisfiable Constraints + +If we run into unsatisfiable constraints on iOS, we only see the views' memory addresses in the printout. Especially in more complex layouts, it's sometimes difficult to identify the views which are part of the problem. However, there are several ways we can help ourselves in this situation. + +First, whenever you see `NSLayoutResizingMaskConstraint`s in the unsatisfiable constraints error message, you almost certainly forgot to set `translatesAutoResizingMaskIntoConstraints` to `NO` for one of your views. While Interface Builder does this automatically, you have to do this manually for all views created in code. + +If it's not obvious which views are causing the trouble, you have to identify the view by its memory address. The most straightforward option is to use the debugger console. You can print out the description of the view itself or its super view, or even the recursive description of the view tree. This mostly gives you lots of cues to identify which view you're dealing with. + + (lldb) po 0x7731880 + $0 = 124983424 > + + (lldb) po [0x7731880 superview] + $2 = 0x07730fe0 > + + (lldb) po [[0x7731880 superview] recursiveDescription] + $3 = 0x07117ac0 > + | > + | > + +A more visual approach is to modify the view in question from the console so that you can spot it on screen. For example, you can do this by changing its background color: + + (lldb) expr ((UIView *)0x7731880).backgroundColor = [UIColor purpleColor] + +Make sure to resume the execution of your app afterward or the changes will not show up on screen. Also note the cast of the memory address to `(UIView *)` and the extra set of round brackets so that we can use dot notation. Alternatively, you can of course also use message sending notation: + + (lldb) expr [(UIView *)0x7731880 setBackgroundColor:[UIColor purpleColor]] + +Another approach is to profile the application with Instrument's allocations template. Once you've got the memory address from the error message (which you have to get out of the Console app when running Instruments), you can switch Instrument's detail view to the Objects List and search for the address with Cmd-F. This will show you the method which allocated the view object, which is often a pretty good hint of what you're dealing with (at least for views created in code). + +You can also make deciphering unsatisfiable constraints errors on iOS easier by improving the error message itself. We can overwrite `NSLayoutConstraint`'s description method in a category to include the views' tags: + + @implementation NSLayoutConstraint (AutoLayoutDebugging) + #ifdef DEBUG + - (NSString *)description + { + NSString *description = super.description; + NSString *asciiArtDescription = self.asciiArtDescription; + return [description stringByAppendingFormat:@" %@ (%@, %@)", + asciiArtDescription, [self.firstItem tag], [self.secondItem tag]]; + } + #endif + @end + +If the integer property `tag` is not enough information, we can also get a bit more adventurous and add our own nametag property to the view class, which we then print out in the error message. We can even assign values to this custom property in Interface Builder using the "User Defined Runtime Attributes" section in the identity inspector. + + @interface UIView (AutoLayoutDebugging) + - (void)setAbc_NameTag:(NSString *)nameTag; + - (NSString *)abc_nameTag; + @end + + @implementation UIView (AutoLayoutDebugging) + - (void)setAbc_NameTag:(NSString *)nameTag + { + objc_setAssociatedObject(self, "abc_nameTag", nameTag, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + + - (NSString *)abc_nameTag + { + return objc_getAssociatedObject(self, "abc_nameTag"); + } + @end + + @implementation NSLayoutConstraint (AutoLayoutDebugging) + #ifdef DEBUG + - (NSString *)description + { + NSString *description = super.description; + NSString *asciiArtDescription = self.asciiArtDescription; + return [description stringByAppendingFormat:@" %@ (%@, %@)", + asciiArtDescription, [self.firstItem abc_nameTag], + [self.secondItem abc_nameTag]]; + } + #endif + @end + +This way the error message becomes much more readable and you don't have to find out which view belongs to which memory address. However, it requires some extra work on your part to consistently assign meaningful names to the views. + +Another neat trick (via [Daniel](https://twitter.com/danielboedewadt)) that gives you better error messages without requiring extra work is to integrate call stack symbols into the error message for each layout constraint. This makes it easy to see where the constraints involved in the problem were created. To do this, you have to swizzle the `addConstraint:` and `addConstraints:` methods of `UIView` or `NSView`, as well as the layout constraint's `description` method. In the methods for adding constraints, you should then add an associated object to each constraint, which describes the first frame of the current call stack backtrace (or whatever information you would like to have from it): + + static void AddTracebackToConstraints(NSArray *constraints) + { + NSArray *a = [NSThread callStackSymbols]; + NSString *symbol = nil; + if (2 < [a count]) { + NSString *line = a[2]; + // Format is + // 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + // 8 MyCoolApp 0x0000000100029809 -[MyViewController loadView] + 99 + // + // Don't add if this wasn't called from "MyCoolApp": + if (59 <= [line length]) { + line = [line substringFromIndex:4]; + if ([line hasPrefix:@"My"]) { + symbol = [line substringFromIndex:59 - 4]; + } + } + } + for (NSLayoutConstraint *c in constraints) { + if (symbol != nil) { + objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingShort, + symbol, OBJC_ASSOCIATION_COPY_NONATOMIC); + } + objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingCallStackSymbols, + a, OBJC_ASSOCIATION_COPY_NONATOMIC); + } + } + + @end + +Once you have this information available on each constraint object, you can simply modify `UILayoutConstraint`'s description method to include it in the output. + + - (NSString *)objcioOverride_description + { + // call through to the original, really + NSString *description = [self objcioOverride_description]; + NSString *objcioTag = objc_getAssociatedObject(self, &ObjcioLayoutConstraintDebuggingShort); + if (objcioTag == nil) { + return description; + } + return [description stringByAppendingFormat:@" %@", objcioTag]; + } + +Check [this GitHub repository](https://github.com/objcio/issue-3-auto-layout-debugging) for a full code example of this technique. + + +### Ambiguous Layout + +Another common problem is ambiguous layout. If we forget to add a constraint, we are often left wondering why the layout doesn't look like what we expected. `UIView` and `NSView` provide three ways to detect ambiguous layouts: [`hasAmbiguousLayout`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/hasAmbiguousLayout), [`exerciseAmbiguityInLayout`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/exerciseAmbiguityInLayout), and the private method `_autolayoutTrace`. + +As the name indicates, `hasAmbiguousLayout` simply returns YES if the view has an ambiguous layout. Instead of traversing through the view hierarchy ourselves and logging this value, we can make use of the private `_autolayoutTrace` method. This returns a string describing the whole view tree -- similar to the printout of [`recursiveDescription`](http://developer.apple.com/library/ios/#technotes/tn2239/_index.html#//apple_ref/doc/uid/DTS40010638-CH1-SUBSECTION34) -- which tells you when a view has an ambiguous layout. + +Since this method is private, make sure to not ship any code which contains this call. One possible way to safeguard yourself against this is to create a method in a view category like this: + + @implementation UIView (AutoLayoutDebugging) + - (void)printAutoLayoutTrace + { + #ifdef DEBUG + NSLog(@"%@", [self performSelector:@selector(_autolayoutTrace)]); + #endif + } + @end + +`_autolayoutTrace` creates a printout like this: + + 2013-07-23 17:36:08.920 FlexibleLayout[4237:907] + * + | * + | | * + | | | * + | | | | * + | | | | | * - AMBIGUOUS LAYOUT + | | + | | | <_UITabBarBackgroundView:0x7272530> + | | | + | | | | + | | | | + +As with the unsatisfiable constraints error message, we still have to figure out which view belongs to the memory address of the printout. + +Another more visual way to spot ambiguous layouts is to use `exerciseAmbiguityInLayout`. This will randomly change the view's frame between valid values. However, calling this method once will also just change the frame once. So chances are that you will not see this change at all when you start your app. It's a good idea to create a helper method which traverses through the whole view hierarchy and makes all views that have an ambiguous layout "jiggle." + + @implementation UIView (AutoLayoutDebugging) + - (void)exerciseAmiguityInLayoutRepeatedly:(BOOL)recursive + { + #ifdef DEBUG + if (self.hasAmbiguousLayout) { + [NSTimer scheduledTimerWithTimeInterval:.5 + target:self + selector:@selector(exerciseAmbiguityInLayout) + userInfo:nil + repeats:YES]; + } + if (recursive) { + for (UIView *subview in self.subviews) { + [subview exerciseAmbiguityInLayoutRepeatedly:YES]; + } + } + #endif + } + @end + + +### NSUserDefault Options + +There are a couple of helpful `NSUserDefault` options that help with debugging and testing Auto Layout. You can either set these [in code](http://stackoverflow.com/a/13044693/862060), or you can specify them as [launch arguments in the scheme editor](http://stackoverflow.com/a/13138933/862060). + +As the names indicate, `UIViewShowAlignmentRects` and `NSViewShowAlignmentRects` make the alignment rects of all views visible. `NSDoubleLocalizedStrings` simply takes every localized string and doubles it in length. This is a great way to test your layout for more verbose languages. Lastly, setting `AppleTextDirection` and `NSForceRightToLeftWritingDirection` to `YES` simulates a right-to-left language. + + +## Constraint Code + +The first thing to remember when setting up views and their constraints in code is to always set [`translatesAutoResizingMaskIntoConstraints`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/translatesAutoresizingMaskIntoConstraints) to NO. Forgetting this will almost inevitably result in unsatisfiable constraint errors. It's something which is easy to miss even after working with Auto Layout for a while, so watch out for this pitfall. + +When you use the [visual format language](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/AutolayoutPG/Articles/formatLanguage.html) to set up constraints, the `constraintsWithVisualFormat:options:metrics:views:` method has a very useful `options` argument. If you're not using it already, check out the [documentation](http://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutConstraint_Class/NSLayoutConstraint/NSLayoutConstraint.html#//apple_ref/occ/clm/NSLayoutConstraint/constraintsWithVisualFormat:options:metrics:views:). It allows you to align the views in a dimension other than the one affected by the format string. For example, if the format specifies the horizontal layout, you can use `NSLayoutFormatAlignAllTop` to align all views included in the format string along their top edges. + +There is also a [neat little trick](https://github.com/evgenyneu/center-vfl) to achieve centering of a view within its superview using the visual format language, which takes advantage of inequality constraints and the options argument. The following code aligns a view horizontally in its super view: + + UIView *superview = theSuperView; + NSDictionary *views = NSDictionaryOfVariableBindings(superview, subview); + NSArray *c = [NSLayoutConstraint + constraintsWithVisualFormat:@"V:[superview]-(<=1)-[subview]"] + options:NSLayoutFormatAlignAllCenterX + metrics:nil + views:views]; + [superview addConstraints:c]; + +This uses the option `NSLayoutFormatAlignAllCenterX` to create the actual centering constraint between the super view and the subview. The format string itself is merely a dummy that results in a constraint specifying that there should be less than one point of space between the super view's bottom and the subview's top edge, which is always the case as long as the subview is visible. You can reverse the dimensions in the example to achieve centering in the vertical direction. + +Another convenient helper when using the visual format language is the `NSDictionaryFromVariableBindings` macro, which we already used in the example above. You pass it a variable number of variables and get back a dictionary with the variable names as keys. + +For layout tasks that you have to do over and over, it's very convenient to create your own helper methods. For example, if you often have to space out a couple of sibling views vertically with a fixed distance between them while aligning all of them horizontally at the leading edge, having a method like this makes your code less verbose: + + @implementation UIView (AutoLayoutHelpers) + + leftAlignAndVerticallySpaceOutViews:(NSArray *)views + distance:(CGFloat)distance + { + for (NSUInteger i = 1; i < views.count; i++) { + UIView *firstView = views[i - 1]; + UIView *secondView = views[i]; + firstView.translatesAutoResizingMaskIntoConstraints = NO; + secondView.translatesAutoResizingMaskIntoConstraints = NO; + + NSLayoutConstraint *c1 = constraintWithItem:firstView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:secondView + attribute:NSLayoutAttributeTop + multiplier:1 + constant:distance]; + + NSLayoutConstraint *c2 = constraintWithItem:firstView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:secondView + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:0]; + + [firstView.superview addConstraints:@[c1, c2]]; + } + } + @end + +In the meantime there are also many different Auto Layout helper libraries out there taking different approaches to simplifying constraint code. + + +## Performance + +Auto Layout is an additional step in the layout process. It takes a set of constraints and translates them into frames. Therefore it naturally comes with a performance hit. In the vast majority of cases, the time it takes to resolve the constraint system is negligible. However, if you're dealing with very performance critical view code, it's good to know about it. + +For example, if you have a collection view which has to bring several new cells on screen when a new row appears, and each cell consists of several subviews laid out by Auto Layout, you may notice the effect. Luckily, we don't need to rely on our gut feeling when scrolling up and down. Instead we can fire up Instruments and actually measure how much time Auto Layout spends. Watch out for methods of the `NSISEngine` class. + +Another scenario where you might run into performance issues with Auto Layout is when you are showing lots of views at once. The [constraint solving algorithm](http://www.cs.washington.edu/research/constraints/cassowary/), which translates the constraints into view frames, is of [super-linear complexity](http://en.wikipedia.org/wiki/P_%28complexity%29). This means that from a certain number of views on, performance will become pretty terrible. The exact number depends on your specific use case and view configuration. But to give you a rough idea, on current iOS devices it's in the order of a magnitude of 100. For more details, you can also read these two [blog](http://floriankugler.com/blog/2013/4/21/auto-layout-performance-on-ios) [posts](http://pilky.me/view/36). + +Keep in mind that these are edge cases. Don't optimize prematurely and avoid Auto Layout for its potential performance impact. It will be fine for most use cases. But if you suspect it might cost you the decisive milliseconds to get the user interface completely smooth, profile your code and only then should you decide if it makes sense to go back to setting frames manually. Furthermore, hardware will become more and more capable, and Apple will continue tweaking the performance of Auto Layout. So the edge cases where it presents a real-world performance problem will decrease over time. + + +## Conclusion + +Auto Layout is a powerful technique to create flexible user interfaces, and it's not going away anytime soon. Getting started with Auto Layout can be a bit rough, but there is light at the end of the tunnel. Once you get the hang of it and have all the little tricks to diagnose and fix problems up your sleeve, it actually becomes very logical to work with. + diff --git a/2013-08-07-collection-view-layouts.md b/2013-08-07-collection-view-layouts.md new file mode 100644 index 0000000..8fb3483 --- /dev/null +++ b/2013-08-07-collection-view-layouts.md @@ -0,0 +1,206 @@ +--- +layout: post +title: "Custom Collection View Layouts" +category: "3" +date: "2013-08-07 08:00:00" +author: "Ole Begemann" +tags: article +--- + +{% include links-3.md %} + +Introduced in iOS 6, `UICollectionView` is [the new star among view classes](http://oleb.net/blog/2012/09/uicollectionview/) in UIKit. It shares its API design with `UITableView` but extends the latter in a few fundamental ways. The most powerful feature of `UICollectionView` and the point where it significantly exceeds `UITableView`'s capabilities is its completely flexible layout architecture. In this article, we will implement a fairly complex custom collection view layout and discuss important aspects of the class's design along the way. + +The [example project](https://github.com/objcio/issue-3-collection-view-layouts) for this article is on GitHub. + +## Layout Objects + +Both `UITableView` and `UICollectionView` are [data-source- and delegate-driven](http://developer.apple.com/library/ios/#documentation/general/conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html). They act as dumb containers for the collection of subviews they are displaying, knowing nothing about their actual contents. + +`UICollectionView` takes the abstraction one step further. It delegates the control over its subviews' positions, sizes, and appearances to a separate layout object. By providing a custom layout object, you can achieve pretty much any layout you can imagine. Layouts inherit from the abstract base class [`UICollectionViewLayout`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewLayout_class/Reference/Reference.html). iOS 6 comes with one concrete layout implementation in the form of the [`UICollectionViewFlowLayout` class](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewFlowLayout_class/Reference/Reference.html#//apple_ref/occ/cl/UICollectionViewFlowLayout). + +A flow layout can be used to implement a standard grid view, which is probably the most common use case for a collection view. Apple was smart enough to not actually name the class `UICollectionView`**`Grid`**`Layout`, even if that is how most of us think about it. The more generic term, _flow layout_, describes the class's capabilities much better: it builds its layout by placing cell after cell, inserting line or column breaks when needed. By customizing the scroll direction, sizing, and spacing of the cells, a flow layout can also layout cells in a single row or column. In fact, `UITableView`'s layout can be thought of as a special case of flow layout. + +Before you consider writing your own `UICollectionViewLayout` subclass, you should always ask yourself if you can achieve the layout you have in mind with `UICollectionViewFlowLayout`. The class is [remarkably customizable](http://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html#//apple_ref/doc/uid/TP40012334-CH3-SW2) and can also be subclassed itself for further customization. See [Knowing When to Subclass the Flow Layout](http://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html#//apple_ref/doc/uid/TP40012334-CH3-SW4) in the Collection View Programming Guide for tips. + +## Cells and Other Views + +To accommodate arbitrary layouts, collection views set up a view hierarchy that is similar to, but more flexible than, that of a table view. As usual, your main content is displayed in **cells**, which can optionally be grouped into sections. Collection view cells must be subclasses of [`UICollectionViewCell`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html). In addition to cells, collection views manage two more kinds of views: supplementary views and decoration views. + +**Supplementary views** in a collection view correspond to a table view's section header and footer views in that they display information about a section. Like cells, their contents are driven by the data source object. Unlike their usage in table views, however, supplementary views are not bound to being header or footer views; their number and placement are entirely controlled by the layout. + +**Decoration views** act as pure ornamentation. They are owned and managed entirely by the layout object and do not get their contents from the data source. When a layout object specifies that it requires a decoration view, the collection view creates it automatically and applies the layout attributes provided by the layout object. Any customization of the view's contents is not intended. + +Supplementary and decoration views must be subclasses of [`UICollectionReusableView`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionReusableView_class/Reference/Reference.html#//apple_ref/occ/cl/UICollectionReusableView). Each view class that your layout uses must be registered with the collection view to enable it to create new instances when its data source asks it to dequeue a view from its reuse pool. If you are using Interface Builder, registering cells with a collection view can be done directly inside the visual editor by dragging a cell onto a collection view. The same method works for supplementary views, but only if you are using a `UICollectionViewFlowLayout`. If not, you have to manually register the view classes with the collection view in code by calling one of its [`registerClass:…`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/Reference/Reference.html#//apple_ref/occ/instm/UICollectionView/registerClass:forCellWithReuseIdentifier:) or [`registerNib:…`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/Reference/Reference.html#//apple_ref/occ/instm/UICollectionView/registerNib:forCellWithReuseIdentifier:) methods. The `viewDidLoad` method in your view controller is the correct place to do this. + +## Custom Layouts + +As an example of a non-trivial custom collection view layout, consider a week view in a typical calendar app. The calendar displays one week at a time, with the days of the week arranged in columns. Each calendar event will be displayed by a cell in our collection view, positioned and sized so as to represent the event start date/time and duration. + +Screenshot of our custom calendar collection view layout + +There are two general types of collection view layouts: + +1. **Layouts whose computations are independent of the content.** This is the “simple” case you know from `UITableView` and `UICollectionViewFlowLayout`. The position and appearance of each cell does not depend on the content it displays but on its order in the list of all cells. Consider the default flow layout as an example. Each cell is positioned next to its predecessor (or at the beginning of the next line if there is no space left). The layout object does not need to access the actual data to compute the layout. + +2. **Layouts that need to do content-dependent computations.** Our calendar view is an example of this type. It requires the layout object to ask the collection view's data source directly for the start and end dates of the events it is supposed to display. In many cases, the layout object not only requires data about the currently visible cells but needs some information from _all_ records in order to determine which cells are currently visible in the first place. + +In our calendar example, the layout object -- if asked for the attributes of the cells inside a certain rectangle -- must iterate over all events provided by the data source to determine which ones lie in the requested time window. Contrast this with a flow layout where some relatively simple and data-source-independent math is sufficient to compute the index paths of the cells that lie in a certain rectangle (assuming that all cells in the grid have the same size). + +Having a content-dependent layout is a strong indication that you will need to write your own custom layout class and won't get by with customizing `UICollectionViewFlowLayout`. So that's exactly what we are going to do. + +The [documentation for `UICollectionViewLayout`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewLayout_class/Reference/Reference.html) lists the methods that subclasses should override. + +### collectionViewContentSize + +Since the collection view does not know anything about its content, the first piece of information the layout must provide is the size of the scroll area so that the collection view can properly manage scrolling. The layout object must compute the total size of its contents here, including all supplementary and decoration views. Note that although most “classic” collection views limit scrolling to one axis (and so does `UICollectionViewFlowLayout`), this is not a requirement. + +In our calendar example, we want the view to scroll vertically. For instance, if we want one hour to take up 100 points of vertical space, the content height to display an entire day should be 2,400 points. Notice that we do not enable horizontal scrolling, which means that our collection view displays only one week. To enable paging between multiple weeks in the calendar, we could embed multiple collection views (one per week) in a separate (paged) scroll view (possibly using [`UIPageViewController`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIPageViewControllerClassReferenceClassRef/UIPageViewControllerClassReference.html) for the implementation), or stick with just one collection view and return a content width that is large enough to let the user scroll freely in both directions. This is beyond the scope of this article, though. + + - (CGSize)collectionViewContentSize + { + // Don't scroll horizontally + CGFloat contentWidth = self.collectionView.bounds.size.width; + + // Scroll vertically to display a full day + CGFloat contentHeight = DayHeaderHeight + (HeightPerHour * HoursPerDay); + + CGSize contentSize = CGSizeMake(contentWidth, contentHeight); + return contentSize; + } + +Note that for clarity reasons, I have chosen to model the layout on a very simple model that assumes a constant number of days per week and hours per day and represents days just as indices from 0 to 6. In a real calendar application, the layout would make heavy use of `NSCalendar`-based date calculations for its computations. + +### layoutAttributesForElementsInRect: + +This is the central method in any layout class and possibly the one that is most confusing. The collection view calls this method and passes a rectangle in its own coordinate system. This rectangle will typically be the visible rectangle of the view (that is, its bounds) but that is not necessarily the case. You should be prepared to handle any rectangle that gets passed to you. + +Your implementation must return an array of [`UICollectionViewLayoutAttributes`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html) objects, containing one such object for each cell, supplementary, or decoration view that is visible in the rectangle. The `UICollectionViewLayoutAttributes` class encapsulates all layout-related properties of an item in the collection view. By default, the class has properties for the `frame`, `center`, `size`, `transform3D`, `alpha`, `zIndex`, and `hidden` attributes. If your layout wants to control other attributes of a view (for example, the background color), you can subclass `UICollectionViewLayoutAttributes` and add your own properties. + +The layout attributes objects are associated with their corresponding cell, supplementary view, or decoration view through an `indexPath` property. After the collection view has asked the layout object for the layout attributes of all items, it will instantiate the views and apply the respective attributes to them. + +Note that this one method is concerned with all types of views, that is, cell, supplementary, and decoration views. A naive implementation might opt to ignore the passed-in rectangle and just return the layout attributes for _all_ views in the collection view. This is a valid approach during prototyping and developing your layout. But note that this can have a bad impact on performance, especially if the number of total cells is much larger than those that are visible at any one time, as the collection view and the layout object will have to perform additional unnecessary work for these invisible views. + +Your implementation should perform these steps: + +1. Create an empty mutable array to contain all the layout attributes. + +2. Identify the index paths of all cells whose frames lie entirely or partly within the rectangle. This computation may require you to ask the collection view's data source for information about the data you want to display. Then call your implementation of [`layoutAttributesForItemAtIndexPath:`][310] in a loop to create and configure a proper layout attributes object for each index path. Add each object to the array. + +3. If your layout includes supplementary views, compute the index paths of the ones that are visible inside the rectangle. Call your implementation of [`layoutAttributesForSupplementaryViewOfKind:atIndexPath:`][310] in a loop and add those objects to the array. By passing different strings of your choice for the `kind` argument, you can distinguish between different types of supplementary views (such as headers and footers). The collection view will pass the `kind` string back to your data source when it needs to create the view. Remember that the number and kind of supplementary and decoration views is entirely controlled by the layout. You are not restricted to headers and footers. + +4. If your layout includes decoration views, compute the index paths of the ones that are visible inside the rectangle. Call your implementation of [`layoutAttributesForDecorationViewOfKind:atIndexPath:`][310] in a loop and add those objects to the array. + +5. Return the array. + +Our custom layout uses no decoration views but two kinds of supplementary views (column headers and row headers): + + - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect + { + NSMutableArray *layoutAttributes = [NSMutableArray array]; + + // Cells + // We call a custom helper method -indexPathsOfItemsInRect: here + // which computes the index paths of the cells that should be included + // in rect. + NSArray *visibleIndexPaths = [self indexPathsOfItemsInRect:rect]; + for (NSIndexPath *indexPath in visibleIndexPaths) { + UICollectionViewLayoutAttributes *attributes = + [self layoutAttributesForItemAtIndexPath:indexPath]; + [layoutAttributes addObject:attributes]; + } + + // Supplementary views + NSArray *dayHeaderViewIndexPaths = + [self indexPathsOfDayHeaderViewsInRect:rect]; + for (NSIndexPath *indexPath in dayHeaderViewIndexPaths) { + UICollectionViewLayoutAttributes *attributes = + [self layoutAttributesForSupplementaryViewOfKind:@"DayHeaderView" + atIndexPath:indexPath]; + [layoutAttributes addObject:attributes]; + } + NSArray *hourHeaderViewIndexPaths = + [self indexPathsOfHourHeaderViewsInRect:rect]; + for (NSIndexPath *indexPath in hourHeaderViewIndexPaths) { + UICollectionViewLayoutAttributes *attributes = + [self layoutAttributesForSupplementaryViewOfKind:@"HourHeaderView" + atIndexPath:indexPath]; + [layoutAttributes addObject:attributes]; + } + + return layoutAttributes; + } + + + +### layoutAttributesFor...IndexPath + +Sometimes, the collection view will ask the layout object for the layout attributes of one specific cell, supplementary, or decoration view rather than the list of all visible ones. This is when three other methods come into play. Your implementation of [`layoutAttributesForItemAtIndexPath:`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewLayout_class/Reference/Reference.html#//apple_ref/occ/instm/UICollectionViewLayout/layoutAttributesForItemAtIndexPath:) should create and return a single layout attributes object that is properly formatted for the cell identified by the index path that is passed to you. + +You do this by calling the [`+[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:]`](http://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html#//apple_ref/occ/clm/UICollectionViewLayoutAttributes/layoutAttributesForCellWithIndexPath:) factory method. Then modify the attributes according to the index path. You may need to ask the collection view's data source for information about the data object that is displayed at this index path to get the data you need. Make sure to at least set the `frame` property here unless all your cells should sit on top of each other. + + - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath: + (NSIndexPath *)indexPath + { + CalendarDataSource *dataSource = self.collectionView.dataSource; + id event = [dataSource eventAtIndexPath:indexPath]; + UICollectionViewLayoutAttributes *attributes = + [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + attributes.frame = [self frameForEvent:event]; + return attributes; + } + +If you are using Auto Layout, you may be surprised that we are modifying the `frame` property of the layout attributes directly rather than working with constraints, but that is how `UICollectionViewLayout` works. Although you would use Auto Layout to define the collection view's frame and the internal layout of each cell, the frames of the cells have to be computed the old-fashioned way. + +Similarly, the methods `layoutAttributesForSupplementaryViewOfKind:atIndexPath:` and `layoutAttributesForDecorationViewOfKind:atIndexPath:` should do the same for supplementary and decoration views, respectively. Implementing these two methods is only required if your layout includes such views. `UICollectionViewLayoutAttributes` contains two more factory methods, [`+layoutAttributesForSupplementaryViewOfKind:withIndexPath:`](http://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html#//apple_ref/occ/clm/UICollectionViewLayoutAttributes/layoutAttributesForSupplementaryViewOfKind:withIndexPath:) and [`+layoutAttributesForDecorationViewOfKind:withIndexPath:`](http://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewLayoutAttributes_class/Reference/Reference.html#//apple_ref/occ/clm/UICollectionViewLayoutAttributes/layoutAttributesForDecorationViewOfKind:withIndexPath:), to create the correct layout attributes object. + +### shouldInvalidateLayoutForBoundsChange: + +Lastly, the layout must tell the collection view if it needs to recompute the layout when the collection view's bounds change. My guess is that most layouts need to be invalidated when the collection view resizes, for example during device rotation. Hence, a naive implementation of this method would simply return `YES`. It is important to realize, however, that a scroll view's bounds also change during scrolling, which means your layout could be invalidated several times per second. Depending on the complexity of the computations, this could have a sizable performance impact. + +Our custom layout must be invalidated when the collection view's width changes but is not affected by scrolling. Fortunately, the collection view passes its new bounds to the `shouldInvalidateLayoutForBoundsChange:` method. This enables us to compare the view's current bounds to the new value and only return `YES` if we have to: + + - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds + { + CGRect oldBounds = self.collectionView.bounds; + if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { + return YES; + } + return NO; + } + +## Animation + +### Insertions and Deletions + +`UITableView` comes with a set of very nice pre-defined animations for cell insertions and deletions. When adding animation functionality for adding and removing cells to `UICollectionView`, UIKit engineers were faced with a problem: if the collection view's layout is entirely flexible, there is no way some pre-defined animations will play well with developers' custom layouts. The solution they came up with is very elegant: when a cell (or supplementary or decoration view) gets inserted into a collection view, the view asks its layout object not only for the cell's “normal” layout attributes but also for its _initial_ layout attributes, i.e., the attributes the cell should have at the beginning of the insertion animation. The collection view then simply creates an animation block in which it changes all cell's attributes from their _initial_ to their “normal” state. + +By supplying different _initial_ layout attributes, you can completely customize the insertion animation. For example, setting the _initial_ `alpha` to `0` will create a fade-in animation. Setting a translation and scale transform at the same time will move and zoom the cell into place. + +The same principle is applied to deletions, this time animating from the “normal” state to a set of _final_ layout attributes you provide. These are the methods you have to implement in your layout class to provide the initial/final layout attributes: + +* `initialLayoutAttributesForAppearingItemAtIndexPath:` +* `initialLayoutAttributesForAppearingSupplementaryElementOfKind:atIndexPath:` +* `initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:` +* `finalLayoutAttributesForDisappearingItemAtIndexPath:` +* `finalLayoutAttributesForDisappearingSupplementaryElementOfKind:atIndexPath:` +* `finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:` + +### Switching Between Layouts + +Changes from one collection view layout to another can be animated in a similar manner. When sent a `setCollectionViewLayout:animated:` method, the collection view will query the new layout for the new layout attributes of the cells and then animate each cell (identified by the same index path in the old and the new layout) from its old to its new attributes. You don't have to do a thing. + +## Conclusion + +Depending on the complexity of a custom collection view layout, writing one is often not easy. In fact, it is essentially just as difficult as writing a totally custom view class that implements the same layout from scratch, since the computations that are involved to determine which subviews are currently visible and where they are positioned are identical. Nevertheless, using `UICollectionView` gives you some nice benefits such as cell reuse and automatic support for animations, not to mention the clean separation of layout, subview management, and data preparation its architecture prescribes. + +A custom collection view layout is also a nice step toward a [lighter view controller](/issue-1/lighter-view-controllers.html) as your view controller does not contain any layout code. Combine this with a separate datasource class as explained in Chris' article and the view controller for a collection view will hardly contain any code at all. + +Whenever I use `UICollectionView`, I feel a certain admiration for its clean design. `NSTableView` and `UITableView` probably needed to come first in order for an experienced Apple engineer to come up with such a flexible class. + +### Further Reading + +* [Collection View Programming Guide](http://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40012334-CH1-SW1). +* [NSHipster on `UICollectionView`](http://nshipster.com/uicollectionview/). +* [`UICollectionView`: The Complete Guide](http://ashfurrow.com/uicollectionview-the-complete-guide/), e-book by Ash Furrow. +* [`MSCollectionViewCalendarLayout`](https://github.com/monospacecollective/MSCollectionViewCalendarLayout) by Eric Horacek is an excellent and more complete implementation of a custom layout for a week calendar view. diff --git a/2013-08-07-custom-controls.markdown b/2013-08-07-custom-controls.markdown new file mode 100644 index 0000000..89478c1 --- /dev/null +++ b/2013-08-07-custom-controls.markdown @@ -0,0 +1,358 @@ +--- +layout: post +title: "Custom Controls" +category: "3" +date: "2013-08-07 07:00:00" +author: "Chris Eidhof" +tags: article +--- + +In this article we will look at tips and tricks to write custom views and +controls. +We will start with an overview of what UIKit provides us already, and +see some tricks for rendering. We will dive into communication +strategies between views and their owners, and very briefly look at +accessibility, localization and testing. + + +## Overview of the View Hierarchy + +If you look at any UIView subclass, you will see three base classes: +responders, views, and controls. We'll quickly go over all three to see what is going on. + +### UIResponder + +The `UIResponder` class is the superclass of `UIView`. A responder can +handle events such as touches, motion, and remote control events. +The reason that this is a separate class, and not merged into `UIView`, +is that there are more subclasses of `UIResponder`, most notably +`UIApplication` and `UIViewController`. By overriding the +methods in `UIResponder`, a class can determine whether it can become a +first responder (i.e. the currently focused element for input). + +When interface events happen, such as touches or motion, they get sent +to the first responder (often, this is a view). When the event +does not get handled by the first responder, it goes up the responder chain to the view +controller, and if it still doesn't get handled, it continues to the application. If +you want to detect a shake gesture, you could do this in all three of these levels, +depending on your needs. + +The `UIResponder` also lets you customize the input methods, from adding +an accessory view to the keyboard with `inputAccessoryView` to providing +a completely custom keyboard by using `inputView`. + +### UIView + +The `UIView` subclass handles everything related to drawing +content and handling touches. Anybody who has built a "Hello, World" app +knows about views, but let's reiterate some of the tricky bits: + +A common misconception is that this area is defined by the view's frame. +In reality, the frame is actually a property that is derived, most notably from the +combination of center and bounds. When not doing Auto Layout, most +people use the frame to position and size the view. Be warned, because [the +documentation](https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/frame) spells out a caveat: + +> If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored. + +One of the other things that lets you add interactivity to views is +gesture recognizers. Note that they don't work on responders, but instead only on +views and their subclasses. + +### UIControl + +Building on views, the `UIControl` class adds more support for +interactivity. Most importantly, it adds the target/action pattern. +Looking at the concrete subclasses, we can see buttons, date pickers, +text fields, and more. When creating interactive controls, you often +want to subclass a descendant of `UIControl`. +Some notable classes that are not controls are bar buttons (although +they do support target/action) and text views (here, getting notified +requires you to use a delegate). + +## Rendering + +Now, let's move on to the visual bit: custom rendering. As Daniel mentioned +in [his article](/issue-3/moving-pixels-onto-the-screen.html), you probably want to avoid doing rendering on the CPU, but +instead offload it to the GPU. There is one rule of thumb to achieve this: +try to avoid `drawRect:`, and instead compose your custom views out of +existing views. + +Often, the quickest way to render something +is just by using image views. For example, let's suppose that you want +to draw round avatars and a border, such as in the picture below: + +Rounded image view + +To achieve this, we created an image view subclass with the following +code: + + // called from initializer + - (void)setupView + { + self.clipsToBounds = YES; + self.layer.cornerRadius = self.bounds.size.width / 2; + self.layer.borderWidth = 3; + self.layer.borderColor = [UIColor darkGrayColor].CGColor; + } + +I would like to encourage you to dive into `CALayer` and its properties, +because most of what you can achieve with that will be faster than +drawing your own things using Core Graphics. Nonetheless, as always, it is +important to profile your code. + +By using stretchable images together with your image views, you can also +greatly improve performance. In a post called [Taming +UIButton](http://robots.thoughtbot.com/post/33427366406/designing-for-ios-taming-uibutton), +Reda Lemeden explores different ways of drawing. At the end of +the article there's a nugget of gold: a link to [a comment by Andy +Matuschak](https://news.ycombinator.com/item?id=4645585) on Hacker News, +explaining which is the fastest of these techniques: a resizable image. +The reason is because a resizable image takes a minimum amount of +data transfer between the CPU and GPU, and the drawing of these images is +highly optimized. + +If you are processing images, you can also often get away with letting the +GPU do that for you, instead of doing it with Core Graphics. Using Core +Image, you can create complicated effects on images without having to +do any rendering on the CPU. You can render directly to an OpenGL +context, and everything will happen on the GPU. + +### Custom Drawing + +If you do decide to do custom drawing, there are several different +options you can choose from. If possible, see if you can generate an image, and +then cache that, either on disk or in memory. If your content +is very dynamic, you can maybe use Core Animation, or if it doesn't +work, go for Core Graphics. If you really want to get close to the +metal, it is not that hard to use GLKit and raw OpenGL, but it does require a lot of +work. + +If you do choose to override `drawRect:`, make sure to take a +look at content modes. The default mode scales the content to fill the +view's bounds, and does not get redrawn when the frame changes. + +## Custom Interaction + +As said, when writing custom controls, you almost always want to +extend a subclass of UIControl. In your subclass, you can fire events +using the target action mechanism, as shown in this example: + + [self sendActionsForControlEvents:UIControlEventValueChanged]; + +To respond to touches, you almost always want to use gesture +recognizers. However, if you want to go low-level, you can still +override the methods `touchesBegan`, `touchesMoved`, and `touchesEnded` +to get access to the raw touches. That said, to separate the gesture +handling from your custom view or view controller, it is almost always +more appropriate to create a gesture recognizer subclass. + +One common design problem you face when creating custom controls is +communicating back the value to the classes that own them. For example, +suppose you want to create a custom control for drawing interactive pie +charts, and you want to know when the user selected a sector. You can +solve this in a lot of different ways, by using target-action, delegates, +blocks or key-value observing, or even notifications. + +### Use Target-Action + +The old-school way, and often the most convenient, is to use +target-action. After the selection, you would do something like this in +your custom view: + + [self sendActionsForControlEvents:UIControlEventValueChanged]; + +If you have a view controller that manages this view, it would do +something like this: + + - (void)setupPieChart + { + [self.pieChart addTarget:self + action:@selector(updateSelection:) + forControlEvents:UIControlEventValueChanged]; + } + + - (void)updateSelection:(id)sender + { + NSLog(@"%@", self.pieChart.selectedSector); + } + +The advantage is that you need to do very little in your custom view +subclass, and you automatically get support for multiple targets. + +### Use Delegates + +If you need more control over the kind of messages being sent from +the view to the controller, it is often handy to +use the delegate pattern. In case of our pie chart, it would look +something like this: + + [self.delegate pieChart:self didSelectSector:self.selectedSector]; + +And in the view controller, you would write code like so: + + + @interface MyViewController + + ... + + - (void)setupPieChart + { + self.pieChart.delegate = self; + } + + - (void)pieChart:(PieChart*)pieChart didSelectSector:(PieChartSector*)sector + { + // Handle the sector + } + + +This is nice when you want to do more complicated things than just +letting the owner know that the value changed. Even though most +programmers can write custom delegates very quickly, there are also some +drawbacks: you might need to check if your delegate implements the +method you want to call (using `respondsToSelector:`), and most +importantly, you can typically only have one delegate (or you need to create an +array of delegates). That said, once the communication between a view's +owner and the view gets a bit more complicated, this is the pattern we +almost always resort to. + +### Use Blocks + +Another option you have is to use blocks. Again, in case of the pie +chart, it would look something like this: + + @interface PieChart : UIControl + + @property (nonatomic,copy) void(^selectionHandler)(PieChartSection* selectedSection); + + @end + +Then, in the selection code, you would just call it. It is important to +check if the block is set, because calling a block that is not set will +crash. + + if (self.selectionHandler != NULL) { + self.selectionHandler(self.selectedSection); + } + +The advantage of setting things up this way is that you can group +related code together in the view controller: + + - (void)setupPieChart + { + self.pieChart.selectionHandler = ^(PieChartSection* section) { + // Do something with the section + } + } + +Just like with delegates, you typically have only one block per +action. Another more important limitation is that you don't want to +create retain cycles. If your view controller holds a strong reference +to the pie chart, and the pie chart to the block, and the block to the +view controller, you've created a retain cycle. To make this mistake, you only +need to reference self in the block. So you often end up with code like +this: + + __weak id weakSelf = self; + self.pieChart.selectionHandler = ^(PieChartSection* section) { + MyViewController* strongSelf = weakSelf; + [strongSelf handleSectionChange:section]; + } + +Once the block bodies get out of hand, you will also probably extract +them to methods of their own, and then you might as well have used +delegates. + +### Use KVO + +If you like KVO, you can also use this for observing. It's a bit more +magical and less direct, but when you are already using it in your +application, it's a nice pattern to decouple things. In your pie chart +class, you would do this: + + self.selectedSegment = theNewSelectedSegment. + +When you use synthesized properties, KVO will pick up this change and +send notifications. In your view controller, you would do something like +this: + + - (void)setupPieChart + { + [self.pieChart addObserver:self forKeyPath:@"selectedSegment" options:0 context:NULL]; + } + + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context + { + if(object == self.pieChart && [keyPath isEqualToString:@"selectedSegment"]) { + // Handle change + } + } + +You also need to remove the observer, for example, in `viewWillDisappear:` or `dealloc`, depending on your use case. Observing multiple children from the same object quickly gets messy. There are some techniques for dealing with this, such as [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) or the more lightweight [`THObserversAndBinders`](https://github.com/th-in-gs/THObserversAndBinders). + +### Use Notifications + +As a final option, if you want a very loose coupling, you can use +notifications for letting other objects know about changes. In case of the pie chart +you almost certainly wouldn't want this, but for completeness, here is +how you would do it. In the pie chart's header file: + + extern NSString* const SelectedSegmentChangedNotification; + +And in the implementation: + + NSString* const SelectedSegmentChangedNotification = @"selectedSegmentChangedNotification"; + + ... + + - (void)notifyAboutChanges + { + [[NSNotificationCenter defaultCenter] postNotificationName:SelectedSegmentChangedNotification object:self]; + } + +Now, to subscribe to notifications, you do the following in your view +controller: + + - (void)setupPieChart + { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(segmentChanged:) + name:SelectedSegmentChangedNotification + object:self.pieChart]; + + } + + ... + + - (void)segmentChanged:(NSNotification*)note + { + } + +When you add the observer, instead of passing in the pie chart as the `object`, you could +also pass in `nil` and receive notifications from all pie chart objects. +Just like with KVO notifications, you also need to unsubscribe from +these notifications in the appropriate place. + +The advantage of this technique is that it's quite decoupled. On the other hand, +you lose type safety, because you get a notification object in your +callback, and unlike with delegation, the compiler can't check if the +types between the notification sender and the notification receiver +match. + +## Accessibility + +The standard iOS controls provided by Apple are all accessible. This is another reason to create your custom controls out of standard controls. + +This is probably the topic of an entire issue, but +if you write a custom view, the [Accessibility Programming Guide](http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/iPhoneAccessibility/Accessibility_on_iPhone/Accessibility_on_iPhone.html#//apple_ref/doc/uid/TP40008785-CH100-SW3) explains how to make the controls accessible. Most notably, if you have a view that has multiple elements inside it that should be accessible, but are not subviews, then you can implement the `UIAccessibilityContainer` protocol for your view. For each element, you then return a `UIAccessibilityElement` object that describes it. + +## Localization + +When creating custom views, it is also important to think about localization. Like accessibility, this could be the topic of an entire issue. The most straightforward thing to localize in your custom views is string contents. If you use `NSString`, you don't have to worry about encoding. If you display dates or numbers in your custom views, use the date and number formatter classes to display them. To localize strings, use `NSLocalizedString`. + +Another really helpful tool in localization is Auto Layout. For example, some words that are short in English might be very long in German. If you hard-code the size of your view based on the word size in English, you will almost certainly run into trouble when translating to German. By using Auto Layout, you can make this quite easy by letting labels adjust to the size of their contents, and adding other constraints on dependent elements to make sure they resize too. Apple provides a good [introduction](http://developer.apple.com/library/ios/#referencelibrary/GettingStarted/RoadMapiOS/chapters/InternationalizeYourApp/InternationalizeYourApp/InternationalizeYourApp.html) for this. Also, with right-to-left languages such as Hebrew, your entire view will display right-to-left automatically if you use leading and trailing attributes, instead of hardcoding left and right. + +## Testing + +Finally, let's consider testing your views. For unit testing, you can use the default tools provided in Xcode or any of the third-party frameworks. In addition, you can use UIAutomation or any of the tools built on top of that. For this, it is essential that your views are fully accessible. An underused feature of UIAutomation is taking screenshots; you can do this to make sure your views are really pixel-perfect by [automatically comparing them](http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/) with the design specification. (And on an unrelated note: you can also use this to [automatically generate the screenshots](http://www.smallte.ch/blog-read_en_29001.html) for the App Store, which is especially useful if you have multiple apps in multiple languages). diff --git a/2013-08-07-editorial.md b/2013-08-07-editorial.md new file mode 100644 index 0000000..b3f662e --- /dev/null +++ b/2013-08-07-editorial.md @@ -0,0 +1,21 @@ +--- +layout: post +title: "Editorial" +category: "3" +date: "2013-08-07 11:00:00" +tags: editorial +--- + +Welcome to objc.io issue #3! + +This issue is all about the view layer. Of course the view layer has so many aspects that we had to narrow it down to a few topics -- which are hopefully of interest to many developers -- while leaving tons of other interesting stuff aside (perhaps to be addressed in a later issue?). + +First, we cover some internals of how pixels actually get drawn onto the screen. Aside from it simply being fascinating to take a sneak peak at the full stack under the covers of the frameworks we use everyday, it will help application developers to make smart choices when it comes to graphic performance. Similarly, [Joe Conway](http://stablekernel.com) -- author of the [Big Nerd Ranch's iOS Programming Guide](http://www.bignerdranch.com/book/ios_programming_the_big_nerd_ranch_guide_rd_edition_) -- takes a closer look at how scroll views take advantage of `UIView`'s architecture to get their work done. + +Balancing these topics of internals, we then dive into a series of very practical view layer details: creating custom collection view layouts (many thanks to [Ole Begemann](http://oleb.net)!), creating custom controls, and advanced Auto Layout tips and tricks. + +As always, we are happy to [hear from you](mailto:mail@objc.io) with feedback, suggestions for future topics, or if you would like to contribute an article to objc.io yourself (you don't need to be a native speaker, thanks to our copy editor [Natalye](https://twitter.com/deutschbitte)). + +We wish you happy reading from a very summery Berlin, + +Chris, Daniel, and Florian. diff --git a/2013-08-07-index.markdown b/2013-08-07-index.markdown new file mode 100644 index 0000000..58ed7ff --- /dev/null +++ b/2013-08-07-index.markdown @@ -0,0 +1,6 @@ +--- +layout: toc +category: "3" +date: "2013-08-07 12:00:00" +tags: toc +--- diff --git a/2013-08-07-moving-pixels-onto-the-screen.md b/2013-08-07-moving-pixels-onto-the-screen.md new file mode 100644 index 0000000..fbfb744 --- /dev/null +++ b/2013-08-07-moving-pixels-onto-the-screen.md @@ -0,0 +1,493 @@ +--- +layout: post +title: "Getting Pixels onto the Screen" +category: "3" +date: "2013-08-07 10:00:00" +author: "Daniel Eggert" +tags: article +--- + +{% include links-3.md %} + + +How does a pixel get onto the screen? There are many ways to get something onto the display and they involve many different frameworks and many different combinations of functions and methods. Here we'll walk through some of the things that happen behind the scenes. We hope this will help you understand which API works best when you need to determine when and how to debug and fix performance problems. We'll focus on iOS, however most of what is discussed will apply to OS X as well. + + +## Graphics Stack + + +There's a lot going on under the hood when pixels have to get onto the screen. But once they're on the screen, each pixel consists of three color components: red, green, and blue. Three individual color cells light up with a particular intensity to give the impression of a single pixel with a specific color. On your iPhone 5, the [liquid-crystal display](https://en.wikipedia.org/wiki/IPS_LCD) has 1,136×640 = 727,040 pixels and hence 2,181,120 color cells. On a 15" MacBook Pro with Retina Display this number is just above 15.5 million. The entire graphics stack works together to make sure each one lights up with the correct intensity. And when you scroll in full screen, all those million intensities have to update 60 times per second. That's a lot of work. + +### The Software Components + +In a simplified view, the software stack looks somewhat like this: + +Software Stack + + +Just above the display sits the GPU, the *graphics processing unit*. The GPU is a highly concurrent processing unit, which is tailored specifically for parallel computation of graphics. That's how it's possible to update all those pixels and push the result onto the display. Its parallel nature also allows it to do compositing of textures onto each other very efficiently. We'll talk about [compositing][210] in more detail in a bit. The key point is that the GPU is extremely specialized and therefore it is efficient at some kinds of work, i.e. it's very fast and uses less power than the CPU for this work. The 'normal' CPU has a very general purpose; it can do many different things, but compositing, for example, would be way slower on the CPU. + +The GPU driver is the piece of code that talks directly to the GPU. Different GPUs are different beasts, and the driver makes them appear more uniform to the next layer, which is typically OpenGL / OpenGL ES. + +OpenGL ([Open Graphics Library](http://en.wikipedia.org/wiki/OpenGL)) is an API for rendering 2D and 3D graphics. Since the GPU is a very specialized piece of hardware, OpenGL works very closely with the GPU to facilitate the GPU's capabilities and to achieve hardware-accelerated rendering. To many, OpenGL may seem very low-level, but when it was first released in 1992 (more than twenty years ago) it was the first major standardized way to talk to graphics hardware (the GPU), and hence a major leap forward since programmers no longer had to rewrite their apps for each GPU. + +Above OpenGL things split out a bit. On iOS nearly everything goes through Core Animation at this point, while on OS X, it's not uncommon for Core Graphics to bypass Core Animation. For some specialized applications, particularly games, the app might talk directly to OpenGL / OpenGL ES. And things get even more confusing, because Core Animation uses Core Graphics for some of its rendering. Frameworks like AVFoundation, Core Image, and others access a mix of everything. + +One thing to keep in mind, though, is this: The GPU is a very powerful piece of graphics hardware and it plays a central role in displaying your pixels. It is connected to the CPU. In hardware there's some sort of [bus](https://en.wikipedia.org/wiki/Bus_%28computing%29) between the two -- and there are frameworks such as OpenGL, Core Animation, and Core Graphics that orchestrate the transfer of data between the GPU and the CPU. In order for your pixels to make it onto the screen, some processing will be done on the CPU. Then data will be transferred to the GPU, which, in turn, will also do processing, and finally your pixels show up on screen. + +Each part of this "journey" has its own challenges, and there are tradeoffs to be made along the way. + +### The Hardware Players + +  + +Graphics Hardware + +A very simplified view of the challenges looks like this: The GPU has textures (bitmaps) that it composites together for each frame (i.e. 60 times a second). Each texture takes up VRAM (video RAM) and therefore there's a limit to how many textures the GPU can hold onto. The GPU is super efficient at compositing, but certain compositing tasks are more complex than others, and there's a limit to how much work the GPU can do in 16.7 ms (1/60 s). + +The next challenge is getting your data to the GPU. In order for the GPU to access data, it needs to be moved from RAM into VRAM. This is referred to as *uploading to the GPU*. This may seem trivial, but for large textures this can be very time-consuming. + +Finally, the CPU runs your program. You may be telling the CPU to load a PNG from your bundle and decompress it. All that happens on the CPU. When you then want to display that decompressed image, it somehow needs to get uploaded to the GPU. Something as mundane as displaying text is a tremendously complex task for the CPU that facilitates a tight integration between the Core Text and Core Graphics frameworks to generate a bitmap from the text. Once ready, it gets uploaded to the GPU as a texture, ready to be displayed. When you scroll or otherwise move that text on screen, however, the very same texture can be reused, and the CPU will simply tell the GPU what the new position is, so the GPU can reuse the existing texture. The CPU doesn't have to re-render the text, and the bitmap doesn't have to be re-uploaded. + +This illustrates some of the complexities involved. With this overview out of the way, we'll dive into some of the technologies involved. + + + + +## Compositing + +Compositing in the graphics world is a term that describes how different bitmaps are put together to create the final image you see on the screen. It is, in many ways, so obvious that it's easy to forget the complexity and computations involved. + +Let's ignore some of the more esoteric cases and assume that everything on the screen is a texture. A texture is a rectangular area of RGBA values, i.e. for each pixel we have red, green, and blue values, and an alpha value. In the Core Animation world this is basically what a `CALayer` is. + +In this slightly simplified setup, each layer is a texture, and all these textures are in some way stacked on top of each other. For each pixel on screen, the GPU needs to to figure out how to blend / mix these textures to get the RGB value of that pixel. That's what compositing is about. + +If all we have is a single texture that is the size of the screen and aligned with the screen pixels, each pixel on the screen corresponds to a single pixel in that texture. The texture's pixels end up being the screen's pixels. + +If we have a second texture that's placed on top of the first texture, the GPU will then have to composite this texture onto the first. There are different blend modes, but if we assume that both textures are pixel-aligned and we're using the normal blend mode, the resulting color is calculated with this formula for each pixel: + + R = S + D * (1 - Sa) + +The resulting color is the source color (top texture) plus the destination color (lower texture) times one minus the source color's alpha. All colors in this formula are assumed to be pre-multiplied with their alpha. + +Obviously there's quite a bit going on here. Let's for a second assume that all textures are fully opaque, i.e. alpha = 1. If the destination (lower) texture is blue (RGB = 0, 0, 1), and the source (top) texture is red (RGB = 1, 0, 0), and because `Sa` is `1`, the result is + + R = S + +and the result is the source's red color. That's what you'd expect. + +If the source (top) layer was 50% transparent, i.e. alpha = 0.5, the RGB values for S would be (0.5, 0, 0) since the alpha component is pre-multiplied into the RGB-values. The formula would then look like this: + + + 0.5 0 0.5 + R = S + D * (1 - Sa) = 0 + 0 * (1 - 0.5) = 0 + 0 1 0.5 + + +We'd end up getting an RGB value of (0.5, 0, 0.5) which is a saturated 'plum' or purple color. That's hopefully what you'd intuitively expect when mixing a transparent red onto a blue background. + +Remember that what we just did is compositing one single pixel of a texture into another pixel from another texture. The GPU needs to do this for all pixels where the two textures overlay. And as you know, most apps have a multitude of layers and hence textures that need to be composited together. This keeps the GPU busy, even though it's a piece of hardware that's highly optimized to do things like this. + +### Opaque vs. Transparent + +When the source texture is fully opaque, the resulting pixels are identical to the source texture. This could save the GPU a lot of work, since it can simply copy the source texture in place of blending all pixel values. But there's no way for the GPU to tell if all pixels in a texture are opaque or not. Only you as a programmer know what you're putting into your `CALayer`. And that's why `CALayer` has a property called `opaque`. If this is `YES`, then the GPU will not do any blending and simply copy from this layer, disregarding anything that's below it. It saves the GPU quite a bit work. This is what the Instruments option **color blended layers** is all about (which is also available in the Simulator's Debug menu). It allows you to see which layers (textures) are marked as non-opaque, i.e. for which layers the GPU is doing blending. Compositing opaque layers is cheaper because there's less math involved. + +So if you know your layer is opaque, be sure to set `opaque` to `YES`. If you're loading an image that doesn't have an alpha channel and displaying it in a `UIImageView` this will happen automatically. But note that there's a big difference between an image without an alpha channel, and an image that has alpha at 100% everywhere. In the latter case, Core Animation has to assume that there might be pixels for which alpha is not 100%. In the Finder, you can use *Get Info* and check the *More Info* section. It'll say if the image has an alpha channel or not. + +### Pixel Alignment and Misalignment + +So far we've looked at layers that have pixels that are perfectly aligned with the display. When everything is pixel-aligned we get the relatively simple math we've looked at so far. Whenever the GPU has to figure out what color a pixel on screen should be, it only needs to look at a single pixel in the layers that are above this screen pixel and composite those together. Or, if the top texture is opaque, the GPU can simply copy the pixel of that top texture. + +A layer is pixel-aligned when all its pixels line up perfectly with the screen's pixels. There are mainly two reasons why this may not be the case. The first one is scaling; when a texture is scaled up or down, the pixels of the texture won't line up with the screen's. Another reason is when the texture's origin is not at a pixel boundary. + +In both of these cases the GPU again has to do extra math. It has to blend together multiple pixels from the source texture to create a value that's used for compositing. When everything is pixel-aligned, the GPU has less work to do. + +Again, the Core Animation Instrument and the Simulator have an option called **color misaligned images** that will show you when this happens for your CALayer instances. + +### Masks + +A layer can have a mask associated with it. The mask is a bitmap of alpha values which are to be applied to the layer's pixels before they're composited onto the content below it. When you're setting a layer's corner radius, you're effectively setting a mask on that layer. But it's also possible to specify an arbitrary mask, e.g. have a mask that's the shape of the letter *A*. Only the part of the layer's content that's part of that mask would be rendered then. + + + + +### Offscreen Rendering + +Offscreen rendering can be triggered automatically by Core Animation or be forced by the application. Offscreen rendering composites / renders a part of the layer tree into a new buffer (which is offscreen, i.e. not on the screen), and then that buffer is rendered onto the screen. + +You may want to force offscreen rendering when compositing is computationally expensive. It's a way to cache composited texture / layers. If your render tree (all the textures and how they fit together) is complex, you can force offscreen rendering to cache those layers and then use that cache for compositing onto the screen. + +If your app combines lots of layers and wants to animate them together, the GPU normally has to re-composite all of these layers onto what's below them for each frame (1/60 s). When using offscreen rendering, the GPU first combines those layers into a bitmap cache based on a new texture and then uses that texture to draw onto the screen. Now when those layers move together, the GPU can re-use this bitmap cache and has to do less work. The caveat is that this only works if those layers don't change. If they do, the GPU has to re-create the bitmap cache. You trigger this behavior by setting `shouldRasterize` to `YES`. + +It's a trade-off, though. For one, it may cause things to get slower. Creating the extra offscreen buffer is an additional step that the GPU has to perform, and particularly if it can never reuse this bitmap, it's a wasted effort. If however, the bitmap can be reused, the GPU may be offloaded. You have to measure the GPU utilization and frame rate to see if it helps. + +Offscreen rendering can also happen as a side effect. If you're directly or indirectly applying a mask to a layer, Core Animation is forced to do offscreen rendering in order to apply that mask. This puts a burden on the GPU. Normally it would just be able to render directly onto the frame buffer (the screen). + +Instruments' Core Animation Tool has an option called **Color Offscreen-Rendered Yellow** that will color regions yellow that have been rendered with an offscreen buffer (this option is also available in the Simulator's Debug menu). Be sure to also check **Color Hits Green and Misses Red**. Green is for whenever an offscreen buffer is reused, while red is for when it had to be re-created. + +Generally, you want to avoid offscreen rendering, because it's expensive. Compositing layers straight onto the frame buffer (onto the display) is way cheaper than first creating an offscreen buffer, rendering into that, and then rendering the result back into the frame buffer. There are two expensive context switches involved (switching the context to the offscreen buffer, then switching the context back to the frame buffer). + +So when you see yellow after turning on **Color Offscreen-Rendered Yellow**, that should be a warning sign. But it isn't necessarily bad. If Core Animation is able to reuse the result of the offscreen rendering, it may improve performance if Core Animation can reuse the buffer. It can reuse when the layers that were used for the offscreen buffer didn't change. + +Note also that there's limited space for rasterized layers. Apple hinted that there's roughly two times the screen size of space for rasterized layers / offscreen buffers. + +And if the way you're using layers causes an offscreen rendering pass, you're probably better off trying to get rid of that offscreen rendering altogether. Using masks or setting a corner radius on a layer causes offscreen rendering, so does applying shadows. + +As for masks, with corner radius (which is just a special mask), and `clipsToBounds` / `masksToBounds`, you may be able to simply create content that has masks already burned in, e.g. by using an image with the right mask already applied. As always, it's a trade-off. If you want to apply a rectangular mask to a layer with its `contents` set, you can probably use `contentsRect` instead of the mask. + +If you end up setting `shouldRasterize` to `YES`, remember to set the `rasterizationScale` to the `contentsScale`. + +### More about Compositing + +As always, Wikipedia has more background on the math of [alpha compositing](https://en.wikipedia.org/wiki/Alpha_compositing). We'll dive a bit more into how red, green, blue, and alpha are represented in memory later on when talking about [pixels][220]. + +### OS X + +If you're working on OS X, you'll find most of those debugging options in a separate app called "Quartz Debug," and not inside Instruments. Quartz Debug is a part of the "Graphics Tools" which is a separate [download at the developer portal](https://developer.apple.com/downloads/). + + +## Core Animation & OpenGL ES + +As the name suggests, Core Animation lets you animate things on the screen. We will mostly skip talking about animations, though, and focus on drawing. The thing to note, however, is that Core Animation allows you to do extremely efficient rendering. And that's why you can do animations at 60 frames per second when you're using Core Animation. + +Core Animation, at its core, is an abstraction on top of OpenGL ES. Simply put, it lets you use the power of OpenGL ES without having to deal with all of its complexities. When we talked about [compositing][210] above we were using the terms layer and texture interchangeably. They're not the same thing, but quite analogous. + +Core Animation layers can have sublayers, so what you end up with is a layer tree. The heavy lifting that Core Animation does is figuring out what layers need to be (re-)drawn, and which OpenGL ES calls need to be made to composite the layers onto the screen. + +For example, Core Animation creates an OpenGL texture when you set a layer's contents to a `CGImageRef`, making sure that the bitmap in that image gets uploaded to the corresponding texture, etc. Or if you override `-drawInContext`, Core Animation will allocate a texture and make sure that the Core Graphics calls you make will turn into that texture's bitmap data. A layer's properties and the `CALayer` subclasses affect how OpenGL rendering is performed, and many lower-level OpenGL ES behaviors are nicely encapsulated in easy-to-understand `CALayer` concepts. + +Core Animation orchestrates CPU-based bitmap drawing through Core Graphics on one end with OpenGL ES on the other end. And because Core Animation sits at this crucial place in the rendering pipeline, how you use Core Animation can dramatically impact performance. + +### CPU bound vs. GPU bound + +There are many components at play when you're displaying something on the screen. The two main hardware players are the CPU and the GPU. The P and U in their names stand for processing unit, and both of these will do processing when things have to be drawn on the screen. Both also have limited resources. + +In order to achieve 60 frames per second, you have to make sure that neither the CPU nor the GPU are overloaded with work. In addition to that, even when you're at 60 fps, you want to put as much of the work as possible onto the GPU. You want to CPU to be free to run your applications code instead of being busy drawing. And quite often, the GPU is more efficient than the CPU at rendering, which turns into a lower overall load and power consumption of the system. + +Since drawing performance depends on both CPU and GPU, you need to figure out which one is limiting your drawing performance. If you're using all GPU resources, i.e. the GPU is what's limiting your performance, your drawing is said to be *GPU bound*. Likewise if you're maxing out the CPU, you're said to be *CPU bound*. + +If you're GPU bound, you need to offload the GPU (and perhaps do more of the work on the CPU). If you're CPU bound, you need to offload the CPU. + +To tell if you're GPU bound, use the *OpenGL ES Driver* instrument. Click on the little *i* button, then *configure*, and make sure *Device Utilization %* is checked. Now, when you run your app, you'll see how loaded the GPU is. If this number is close to 100%, you're trying to do much work on the GPU. + +Being CPU bound is the more *traditional* aspect of your app doing to much work. The *Time Profiler* instrument helps you with that. + + + + +## Core Graphics / Quartz 2D + +Quartz 2D is more commonly known by the name of the framework that contains it: Core Graphics. + +Quartz 2D has more tricks up its sleeve than we'd possibly be able to cover here. We're not going to talk about the huge part that's related to PDF creating, rendering, parsing, or printing. Just note that printing and PDF creation is largely identical to drawing bitmaps on the screen, since it is all based on Quartz 2D. + +Let's just very briefly touch upon the main concepts of Quartz 2D. For details, make sure to check Apple's [Quartz 2D Programming Guide](https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html). + +Rest assured that Quartz 2D is very powerful when it comes to 2D drawing. There's path-based drawing, anti-aliased rendering, transparency layers, and resolution- and device-independency, to name a few features. It can be quite daunting, more so because it's a low-level and C-based API. + +The main concepts are relatively simple, though. Both UIKit and AppKit wrap some Quartz 2D in simply to use API, and even the plain C API is accessible once you've gotten used to it. You end up with a drawing engine that can do most of what you'd be able to do in Photoshop and Illustrator. Apple [mentioned the stocks app on iOS](https://developer.apple.com/videos/wwdc/2011/?id=129) as an example of Quartz 2D usage, as the graph is a simple example of a graph that's dynamically rendered in code using Quartz 2D. + +When your app does bitmap drawing it will -- in one way or another -- be based on Quartz 2D. That is, the CPU part of your drawing will be performed by Quartz 2D. And although Quartz can do other things, we'll be focusing on bitmap drawing here, i.e. drawing that results on a buffer (a piece of memory) that contains RGBA data. + +Let's say we want to draw an [Octagon](https://en.wikipedia.org/wiki/Octagon). We could do that using UIKit + + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint:CGPointMake(16.72, 7.22)]; + [path addLineToPoint:CGPointMake(3.29, 20.83)]; + [path addLineToPoint:CGPointMake(0.4, 18.05)]; + [path addLineToPoint:CGPointMake(18.8, -0.47)]; + [path addLineToPoint:CGPointMake(37.21, 18.05)]; + [path addLineToPoint:CGPointMake(34.31, 20.83)]; + [path addLineToPoint:CGPointMake(20.88, 7.22)]; + [path addLineToPoint:CGPointMake(20.88, 42.18)]; + [path addLineToPoint:CGPointMake(16.72, 42.18)]; + [path addLineToPoint:CGPointMake(16.72, 7.22)]; + [path closePath]; + path.lineWidth = 1; + [[UIColor redColor] setStroke]; + [path stroke]; + +This corresponds more or less to this Core Graphics code: + + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, 16.72, 7.22); + CGContextAddLineToPoint(ctx, 3.29, 20.83); + CGContextAddLineToPoint(ctx, 0.4, 18.05); + CGContextAddLineToPoint(ctx, 18.8, -0.47); + CGContextAddLineToPoint(ctx, 37.21, 18.05); + CGContextAddLineToPoint(ctx, 34.31, 20.83); + CGContextAddLineToPoint(ctx, 20.88, 7.22); + CGContextAddLineToPoint(ctx, 20.88, 42.18); + CGContextAddLineToPoint(ctx, 16.72, 42.18); + CGContextAddLineToPoint(ctx, 16.72, 7.22); + CGContextClosePath(ctx); + CGContextSetLineWidth(ctx, 1); + CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor); + CGContextStrokePath(ctx); + +The question to ask is: Where is this drawing to? This is where the so-called `CGContext` comes into play. The `ctx` argument we were passing is in that context. And the context defines where we're drawing to. If we're implementing `CALayer`'s `-drawInContext:` we're being passed a context. Drawing to that context will draw into the layer's backing store (its buffer). But we can also create our own context, namely a bitmap-based context with e.g. `CGBitmapContextCreate()`. This function returns a context that we can then pass to the `CGContext` functions to draw into that context, etc. + +Note how the `UIKit` version of the code doesn't pass a context into the methods. That's because when using `UIKit` or `AppKit` the context is implicit. `UIKit` maintains a stack of contexts and the UIKit methods always draw into the top context. You can use `UIGraphicsGetCurrentContext()` to get that context. You'd use `UIGraphicsPushContext()` and `UIGraphicsPopContext()` to push and pop context onto UIKit's stack. + +Most notably, UIKit has the convenience methods `UIGraphicsBeginImageContextWithOptions()` and `UIGraphicsEndImageContext()` to create a bitmap context analogous to `CGBitmapContextCreate()`. Mixing UIKit and Core Graphics calls is quite simple: + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(45, 45), YES, 2); + CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGContextBeginPath(ctx); + CGContextMoveToPoint(ctx, 16.72, 7.22); + CGContextAddLineToPoint(ctx, 3.29, 20.83); + ... + CGContextStrokePath(ctx); + UIGraphicsEndImageContext(); + +or the other way around: + + CGContextRef ctx = CGBitmapContextCreate(NULL, 90, 90, 8, 90 * 4, space, bitmapInfo); + CGContextScaleCTM(ctx, 0.5, 0.5); + UIGraphicsPushContext(ctx); + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint:CGPointMake(16.72, 7.22)]; + [path addLineToPoint:CGPointMake(3.29, 20.83)]; + ... + [path stroke]; + UIGraphicsPopContext(ctx); + CGContextRelease(ctx); + + +There's a huge amount of really cool stuff you can do with Core Graphics. For a good reason, the Apple documents call out its *unmatched output fidelity*. We can't get into all the details, but: Core Graphics has a graphics model that (for historic reasons) is very close to how [Adobe Illustrator](https://en.wikipedia.org/wiki/Adobe_Illustrator) and [Adobe Photoshop](https://en.wikipedia.org/wiki/Adobe_Photoshop) work. And most of the tools' concepts translate to Core Graphics. After all, its origins are in [NeXTSTEP](https://en.wikipedia.org/wiki/NextStep), which used [Display PostScript](https://en.wikipedia.org/wiki/Display_PostScript). + +### CGLayer + +We originally indicated that `CGLayer` could be used to speed up repeated drawing of identical elements. As pointed out by [Dave Hayden](https://twitter.com/davehayden), [word in the street](http://iosptl.com/posts/cglayer-no-longer-recommended/) has it that this is no longer true. + + + + +## Pixels + +Pixels on screen are composed of three color components: red, green, blue. Hence bitmap data is also sometimes referred to as RGB data. You may wonder how this data is organized in memory. But the fact is, there are many, many different ways RGB bitmap data can be represented in memory. + +In a bit we'll talk about compressed data, which is entirely different again. For now, let us look at RGB bitmap data, where we have a value for each color component: red, green, and blue. And quite often we'll have a fourth component: alpha. We end up with four individual values for each and every pixel. + +### Default Pixel Layouts + +A very common format on iOS and OS X is what is known amongst friends as *32 bits-per-pixel (bpp), 8 bits-per-component (bpc), alpha premultiplied first*. In memory this looks like + + A R G B A R G B A R G B + | pixel 0 | pixel 1 | pixel 2 + 0 1 2 3 4 5 6 7 8 9 10 11 ... + +This format is often (ambiguously) referred to as ARGB. Each pixel uses four bytes (32 bpp). Each color component is one byte (8 bpc). Each pixel has an alpha value, which comes first (before the RGB values). And finally the red-green-blue values are *pre-multiplied* with the alpha. Pre-multiplied means that the alpha value is baked into the red, green and blue component. If we have an orange color its RGB values at 8 bpc would be something like 240, 99 and 24 respectively. An orange pixel that's fully opaque would have ARGB values of 255, 240, 99, 24 in memory with the above layout. If we had a pixel of the same color, but an alpha value of 33%, the pixel values would be 84, 80, 33, 8. + +Another common format is *32 bpp, 8 bpc, alpha-none-skip-first* which looks like this: + + x R G B x R G B x R G B + | pixel 0 | pixel 1 | pixel 2 + 0 1 2 3 4 5 6 7 8 9 10 11 ... + +This is also referred to as xRGB. The pixels don't have any alpha value (they're assumed to be 100% opaque), but the memory layout is the same. You may wonder why this format is popular, as, if we didn't have that unused byte for each pixel, we would save 25% space. It turns out, though, that this format is much easier to *digest* by modern CPUs and imaging algorithms, because the individual pixels are aligned to 32-bit boundaries. Modern CPUs don't like loading (reading) unaligned data. The algorithms would have to do a lot of shifting and masking, particularly when mixing this format with the above format that does have alpha. + +When dealing with RGB data, Core Graphics also supports putting the alpha value last (and additionally skipping). These are sometimes referred to as RGBA and RGBx respectively, implicitly assuming 8 bpc and pre-multiplied alpha. + +### Esoteric Layouts + +Most of the time, when dealing with bitmap data, we'll be dealing with Core Graphics / Quartz 2D. It has a very specific list of format combinations that it supports. But let's first look at the remaining RGB formats: + +Another option is *16 bpp, 5 bpc without alpha*. This layout takes up only 50% of memory (2 bytes per pixel) compared to the previous ones. That can come in handy if you need to store (uncompressed) RGB data in memory or on disk. But since this format only has 5 bits per pixel, images (particularly smooth gradients) may get [banding artifacts](https://en.wikipedia.org/wiki/Posterization). + +Going the other way, there's *64 bpp, 16 bpc* and finally *128 bpp, 32 bpc, float-components* (both with or without alpha). These use eight bytes and sixteen bytes per pixel respectively and allow for much higher fidelity, at the cost of higher memory usage and being more computationally expensive. + +To round things off, Core Graphics also supports a few grayscale and [CMYK](https://en.wikipedia.org/wiki/CMYK) formats, as well as an alpha-only format (for masks). + + + +### Planar Data + +Most frameworks (including Core Graphics) use pixel data where the components (red, green, blue, alpha) are intermixed. There are situation where we'll run into something called *planar components*, or *component planes*. What this means is that each color component is in its own region of memory, i.e. *plane*. For RGB data, we'd have three independent memory regions, with one large region containing the red values for all pixels, one containing the green values for all pixels, and one containing the blue values for all pixels. + +Some of the video frameworks will use planar data under some circumstances. + +### YCbCr + +[YCbCr](https://en.wikipedia.org/wiki/YCbCr) is a format relatively common when working with video data. It also consists of threecomponents (Y, Cb and Cr) and can represent color data. But (briefly put) it is more similar to the way human vision perceives color. Human vision is less sensitive to fidelity of the two chroma components Cb and Cr, but quite sensitive to fidelity of the luma signal Y. When data is in YCbCr format, the Cb and Cr components can be compressed harder than the Y component with the same perceived quality. + +JPEG images are also sometimes converting pixel data from RGB to YCbCr for the same reason. JPEG compresses each color plane independently. When compressing YCbCr-based planes, the Cb and Cr can be compressed more than the Y plane. + + + + +## Image Formats + +Images on disk are mostly JPEG and PNG when you're dealing with iOS or OS X. Let's take a closer look. + +### JPEG + +Everybody knows [JPEG](https://en.wikipedia.org/wiki/JPEG). It's the stuff that comes out of cameras. It's how photos are stored on computers. Even your mom has heard of JPEG. + +For good reasons, many people think that a JPEG file is just another way to format pixel data, meaning the RGB pixel layouts we [just talked about][220]. That's very far from the truth, though. + +Turning JPEG data into pixel data is a very complex process, certainly nothing you'd be able to pull off as a weekend project, even on a very long weekend. For each [color plane][240], JPEG compression uses an algorithm based on the [discrete cosine transform](https://en.wikipedia.org/wiki/Discrete_cosine_transform) to convert spatial information into the frequency domain. This information is then quantized, sequenced, and packed using a variant of [Huffman encoding](https://en.wikipedia.org/wiki/Huffman_encoding). And quite often, initially, the data is converted from RGB to YCbCr planes. When decoding a JPEG all of this has to run in reverse. + +That's why when you create a `UIImage` from a JPEG file and draw that onto the screen, there'll be a delay, because the CPU is busy decompressing that JPEG. If you have to decompress a JPEG for each table view cell, your scrolling won't be smooth. + +So why would you use JPEG at all? The answer ist that JPEG can compress photos very, very well. An uncompressed photo from your iPhone 5 would take up almost 24MB. With the default compression setting, your photos in your camera roll are usually around 2MB to 3MB. JPEG compression works so well, because it is lossy. It throws away information that is less perceptible to the human eye, and in doing so, it can push the limits far beyond what "normal" compression algorithms such as gzip do. But this only works well on photos, because JPEG relies on the fact that there's a lot in photos that's not very perceptible to the human vision. If you take a screen shot of a web page that's mostly displaying text, JPEG is not going to do very well. Compression will be lower, and you'll most likely see that the JPEG compression has altered the image. + +### PNG + +[PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics) is pronounced "ping". As opposed to JPEG, it's a lossless compression format. When you save an image as PNG, and later open it (and decompress it), all pixel data is exactly what it was originally. Because of this restriction, PNG can't compress photos as well as JPEG, but for app artwork such as buttons, icons etc. it actually works very well. And what's more, decoding PNG data is a lot less complex than decoding JPEG. + +In the real world, things are never quite as simple, and there are actually a slew of different PNG formats. Check Wikipedia for the details. But simply put, PNG supports compressing color (RGB) pixels with or without an alpha channel. That's another reason why it works well for app artwork. + +### Picking a Format + +When you're using photos in your app, you should stick to one of these two: JPEG or PNG. The decompressors and compressors for reading and writing these formats are highly optimized for performance and, to some extent, support parallization. And you'll get the constant additional improvements that Apple is doing to these decompressors for free with future updates of the OS. If you're tempted to use another format, be aware that this is likely to impact performance of your app, and also likely to open security holes, as image decompressors are a favorite target of attackers. + +Quite a bit has been written about [optimizing PNGs](https://duckduckgo.com/?q=%22optimizing%20PNG%22). Search the web yourself, if you feel so inclined. It is very important, though, to note that Xcode's *optimized PNG* option is something quite different that most other optimization engines. + +When Xcode *optimizes* PNG files, it turns these PNG files into something that's, technically speaking, [no longer a valid PNG](https://developer.apple.com/library/ios/#qa/qa1681/_index.html). But iOS can read these files, and in fact can decompress these files way faster that normal PNGs. Xcode changes them in such a way that lets iOS use a more efficient decompression algorithm that doesn't work on regular PNGs. The main point worth noting is that it changes the pixel layout. As we mentioned under [Pixels][220], there are many ways to represent RGB data, and if the format is not what the iOS graphics system needs, it needs to shift the data around for each pixel. Not having to do so speeds things up. + +And, let us stress again: If you can, you should use so-called [resizable images][260] for artwork. Your files will be smaller, and hence there's less data that needs to be loaded from the file system and then decompressed. + + +## UIKit and Pixels + +Each view in UIKit has its own `CALayer`. In turn, this layer (normally) has a backing store, which is pixel bitmap, a bit like an image. This backing store is what actually gets rendered onto the display. + +### With -drawRect: + +If your view class implements `-drawRect:` things work like this: + +When you call `-setNeedsDisplay`, UIKit will call `-setNeedsDisplay` on the views layer. This sets a flag on that layer, marking it as *dirty*, as needing display. It's not actually doing any work, so it's totally acceptable to call `-setNeedsDisplay` multiple times in a row. + +Next, when the rendering system is ready, it calls `-display` on that view's layer. At this point, the layer sets up its backing store. Then it sets up a Core Graphics context (`CGContextRef`) that is backed by the memory region of that backing store. Drawing using that `CGContextRef` will then go into that memory region. + +When you use UIKit's drawing methods such as `UIRectFill()` or `-[UIBezierPath fill]` inside your `-drawRect:` method, they will use this context. The way this works is that UIKit pushes the `CGContextRef` for that backing store onto its *graphics context stack*, i.e. it makes that context the *current* one. Thus `UIGraphicsGetCurrent()` will return that very context. And since the UIKit drawing methods use `UIGraphicsGetCurrent()`, the drawing will go into the layer's backing store. If you want to use Core Graphics methods directly, you can get to that same context by calling `UIGraphicsGetCurrent()` yourself and passing the context into the Core Graphics functions as the context. + +From now on, the layers backing store will be rendered onto the display repeatedly, until something calls the view's `-setNeedsDisplay` again, and that in turn causes the layers backing store to be updated. + +### Without -drawRect: + +When you're using a `UIImageView` things work slightly different. The view still has a `CALayer`, but the layer doesn't allocate a backing store. Instead it uses a `CGImageRef` as its contents and the render server will draw that image's bits into the frame buffer, i.e. onto the display. + +In this case, there's no drawing going on. We're simply passing bitmap data in form of an image to the `UIImageView`, which forwards it to Core Animation, which, in turn, forwards it to the render server. + + +### To -drawRect: or Not to -drawRect: + +It may sound cheesy, but: The fastest drawing is the drawing you don't do. + +Most of the time, you can get away with compositing your custom view from other views or compositing it from layers or a combination of the two. Check Chris' article about [custom controls][400] for more info. This is recommended, because the view classes of `UIKit` are extremely optimized. + +A good example of when you might need custom drawing code is the "finger painting" app that Apple shows in [WWDC 2012's session 506](https://developer.apple.com/videos/wwdc/2012/?id=506): *Optimizing 2D Graphics and Animation Performance*. + +Another place that uses custom drawing is the iOS stocks app. The stock graph is drawn on the device with Core Graphics. Note, however that just because you do custom drawing, you don't necessarily need to have a `-drawRect:` method. At times, it may make more sense to create a bitmap with `UIGraphicsBeginImageContextWithOptions()` or `CGBitmapContextCreate()`, grab the resulting image from that, and set it as a `CALayer`'s `contents`. Test and measure. We'll give an example of this [below][250]. + + +### Solid Colors + +If we look at this example: + + // Don't do this + - (void)drawRect:(CGRect)rect + { + [[UIColor redColor] setFill]; + UIRectFill([self bounds]); + } + +we now know why this is bad: We're causing Core Animation to create a backing store for us, and we're asking Core Graphics to fill the backing store with a solid color. And then that has to get uploaded to the GPU. + +We can save all this work by not implementing `-drawRect:` at all, and simply setting the view's layer's `backgroundColor`. If the view has a `CAGradientLayer` as its layer, the same technique would work for gradients. + + + +### Resizable Images + +Similarly, you can use resizable images to lower pressure on the graphics system. Let's say you want a 300 x 50 points button for which you have artwork. That's 600 x 100 = 60k pixels or 60k x 4 = 240kB of memory that has to get uploaded to the GPU, and that then takes up VRAM. If we were to use a so-called resizable image, we might get away with e.g. a 54 x 12 points image which would be just below 2.6k pixels or 10kB of memory. Things are faster. + +Core Animation can resize images with the [`contentsCenter`](https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CALayer_class/Introduction/Introduction.html#//apple_ref/occ/instp/CALayer/contentsCenter) property on `CALayer` but in most cases you'd want to use [`-[UIImage resizableImageWithCapInsets:resizingMode:]`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/Reference/Reference.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets:resizingMode:). + +Note also, that before this button can be rendered for the first time, instead of having to read a 60k pixel PNG from the file system and decode that 60k pixel PNG, decoding the much smaller PNG is faster. This way, your app has to do way less work in all steps involved and your views will load a lot quicker. + + + + +### Concurrent Drawing + +The [last objc.io issue](/issue-2/index.html) was about concurrency. And as you'll know, UIKit's threading model is very simple: You can only use UIKit classes (views etc.) from the main queue (i.e. main thread). So what's this thing about concurrent drawing? + +If you have to implement `-drawRect:` and you have to draw something non-trivial, this is going to take time. And since you want animations to be smooth, you'll be tempted to do things on a queue other than the main queue. Concurrency is complex, but with a few caveats, drawing concurrently is easily achievable. + +We can't draw into a `CALayer`'s backing store from anything but the main queue. Bad things would happen. But what we can do is to draw into a totally disconnected bitmap context. + +As we mentioned above under [Core Graphics][270], all Core Graphics drawing methods take a *context* argument that specifies which context drawing goes into. And UIKit in turn has a concept of a *current* context that its drawing goes into. This *current* context is per-thread. + +In order to concurrently draw, we'll do the following. We'll create an image on another queue, and once we have that image, we'll switch back to the main queue and set that resulting image as the `UIImageView`'s image. This technique is discussed in [WWDC 2012 session 211](https://developer.apple.com/videos/wwdc/2012/?id=211). + +Add a new method in which you'll do the drawing: + + - (UIImage *)renderInImageOfSize:(CGSize)size; + { + UIGraphicsBeginImageContextWithOptions(size, NO, 0); + + // do drawing here + + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return result; + } + +This method creates a new bitmap `CGContextRef` for the given size through the `UIGraphicsBeginImageContextWithOptions()` function. That function also makes that new context the *current* UIKit context. You can now do your drawing just as you would normally do in `-drawRect:`. Then we get the bitmap data of that context as an `UIImage` with `UIGraphicsGetImageFromCurrentImageContext()`, and finally tear down the context. + +It is important that any calls your drawing code in this method makes are threadsafe, i.e. if you access properties etc., they need to be threadsafe. The reason is that you'll call this method from another queue. If this method is in your view class, that can be a bit tricky. Another option (which may be easier) is to create a separate renderer class that you set all needed properties on and only then trigger to render the image. If so, you might be able to use a plain `UIImageView` or `UITableViewCell`. + +Note that all the UIKit drawing APIs are safe to use on another queue. Just make sure to call them inside the same operation that starts with `UIGraphicsBeginImageContextWithOptions()` and ends with `UIGraphicsEndIamgeContext()`. + +You'd trigger the rendering code with something like this: + + UIImageView *view; // assume we have this + NSOperationQueue *renderQueue; // assume we have this + CGSize size = view.bounds.size; + [renderQueue addOperationWithBlock:^(){ + UIImage *image = [renderer renderInImageOfSize:size]; + [[NSOperationQueue mainQueue] addOperationWithBlock:^(){ + view.image = image; + }]; + }]; + +Note that we're calling `view.image = image` on the main queue. This is a very important detail. You can *not* call this on any other queue. + +As always, with concurrency comes a lot of complexity. You may now have to implement canceling of background rendering. And you'll most likely have to set a reasonable maximum concurrent operation count on the render queue. + +In order to support all this, it's most likely easiest to implement the `-renderInImageOfSize:` inside an `NSOperation` subclass. + +Finally, it's important to point out that setting `UITableViewCell` content asynchronously is tricky. The cell may have been reused at the point in time when the asynchronous rendering is done, and you'll be setting content on it although the cell now is being used for something else. + + +## CALayer Odds and Ends + +By now you should know that a `CALayer` is somehow related and similar to a texture on the GPU. The layer has a backing store, which is the bitmap that gets drawn onto the display. + +Quite often, when you use a `CALayer`, you'll set its `contents` property to an image. What this does, is it tells Core Animation to use that image's bitmap data for the texture. Core Animation will cause the image to be decoded if it's compressed (JPEG or PNG) and then upload the pixel data to the GPU. + +There are other kinds of layers, though. If you use a plain `CALayer`, don't set the `contents`, and set a background color, Core Animation doesn't have to upload any data to the GPU, but will be able to do all the work on the GPU without any pixel data. Similarly for gradient layers, the GPU can create the gradient and the CPU doesn't need to do any work, and nothing needs to be uploaded to the GPU. + +### Layers with Custom Drawing + +If a `CALayer` subclass implements `-drawInContext:` or its delegate, the corresponding `-drawLayer:inContext:`, Core Animation will allocate a backing store for that layer to hold the bitmap that these methods will draw into. The code inside these methods runs on the CPU, and the result is then uploaded to the GPU. + +### Shape and Text Layers + +Things are somewhat different for shape and text layers. First off, Core Animation allocates a backing store for these layers to hold the bitmap data that needs to be generated for the contents. Core Animation will then draw the shape or the text into that backing store. This is conceptually very similar to the situation where you'd implement `-drawInContext:` and would draw the shape or text inside that method. And performance is going to be very similar, too. + +When you change a shape or text layer in a way that it needs to update its backing store, Core Animation will re-render the backing store. E.g. when animating the size of a shape layer, Core Animation has to re-draw the shape for each frame in the animation. + + +### Asynchronous Drawing + +`CALayer` has a property called `drawsAsynchronously`, and this may seems like a silver bullet to solve all problems. Beware, though, that it may improve performance, but it might just as well make things slower. + +What happens, when you set `drawsAsynchronously` to `YES`, is that your `-drawRect:` / `-drawInContext:` method will still get called on the main thread. But all calls to Core Graphics (and hence also UIKit's graphics API, which in turn call Core Graphics) don't do any drawing. Instead, the drawing commands are deferred and processed asynchronously in a background thread. + +One way to look at it is that the drawing commands are recorded first, and then later replayed on a background thread. In order for this to work, more work has to be done, and more memory needs to be allocated. But some work is shifted off the main queue. Test and measure. + +It is most likely to improve performance for expensive drawing methods, and less likely for those that are cheap. diff --git a/2013-08-07-scroll-view.md b/2013-08-07-scroll-view.md new file mode 100644 index 0000000..919bed3 --- /dev/null +++ b/2013-08-07-scroll-view.md @@ -0,0 +1,119 @@ +--- +layout: post +title: "Understanding Scroll Views" +category: "3" +date: "2013-08-07 09:00:00" +author: "Joe Conway" +tags: article +--- + +{% include links-3.md %} + +It may be hard to believe, but a [`UIScrollView`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html) isn't much different than a standard [`UIView`](http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIView_Class/). Sure, the scroll view has a few more methods, but those methods are really just facades of existing `UIView` properties. Thus, most of the understanding of how a UIScrollView works comes from understanding `UIView` - specifically, the details of the two-step view rendering process. + +## Rasterization and Composition + +The first part of the rendering process is known as *rasterization*. Rasterization simply means to take a set of drawing instructions and produce an image. `UIButton`s, for example, draw an image with a rounded rectangle and a title in the center. These images aren't drawn to the screen; instead, they are held onto by their view to be used during the next step. + +Once each view has its rasterized image, these images are drawn on top of each other to produce one screen-sized image in a step called *composition*. The view hierarchy plays a big role in how composition occurs: a view's image is composited on top of its superview's image. Then, that composited image is composited on top of the super-superview's image, and so on. The view at the top of the hierarchy is the window and its composited image (which is a composite of every image in the view hierarchy) is what the user sees. + +Conceptually, this idea of layering independent images on top of each other to produce a final, flat image should make sense, especially if you have used a tool like Photoshop before. We also have another article in this issue explaining in detail [how pixels get onto the screen][200]. + +Now, recall that every view has a [`bounds`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/bounds) and [`frame`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/frame) rectangle. When laying out an interface, we deal with the frame rectangle of a view. This allows us to position and size the view. The frame and bounds of a view will always have the same size, but their origin will differ. Understanding how these two rectangles work is the key to understanding how UIScrollView works. + +During the rasterization step, a view doesn't care about what is going to happen in the upcoming composition step. That is to say, it doesn't care about its frame (which will be used to position the view's image) or its place in the view hierarchy (which will determine the order in which it is composited). The only thing a view cares about at this time is drawing its own content. This drawing occurs in each view's [`drawRect:`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/drawRect:) method. + +Before `drawRect:` is called, a blank image is created for the view to draw its content in. This image's coordinate system is the bounds rectangle of the view. For nearly every view, the bounds rectangle's origin is `{0, 0}`. Thus, to draw something in the top-left corner of the rasterized image, you would draw at the origin of the bounds, the point `{x:0, y:0}`. To draw something in the bottom right corner of an image, you would draw at point `{x:width, y:height}`. If you draw outside of a view's bounds, that drawing is not part of the rasterized image and is discarded. + + + +During the composition step, each view composites its rasterized image on top of its superview's image (and so on). A view's frame rectangle determines where the view's image is drawn on its superview's image - the origin of the frame indicates the offset between the top-left corner of the view's image and its superview's image. So, a frame origin of `{x:20, y:15}` will create a composited image where the view's image is drawn on top of its superview's image, shifted to the right 20 points and down 15 points. Because the frame and bounds rectangle of a view are always the same size, the image is composited pixel for pixel to its superview's image. This ensures there is no stretching or shrinking of the rasterized image. + + + +Remember, we're talking about just one composite operation between a view and its superview. Once those two views are composited together, the resulting composite image is composited with the super-superview's image and so on: a snowball effect. + +Think about the math behind compositing an image onto another. The top-left corner of a view's image is offset by its frame's origin and then drawn onto its superview's image: + + CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x; + CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y; + +Now, as we have said before, the origin of a view's bounds rectangle is typically just `{0, 0}`. Thus, when doing the math, we just drop out one of the values and we get: + + CompositedPosition.x = View.frame.origin.x; + CompositedPosition.y = View.frame.origin.y; + +So, we can look at a few different frames and see how they would look: + + + +And this should make sense. We change the frame's origin of the button, and it changes its position relative to its lovely purple superview. Notice that if we move the button so that parts of it are outside of the bounds of the purple superview, those parts are clipped in the same way drawing during rasterization would be clipped. However, technically, because of how iOS handles compositing under the hood, you can have a subview render outside of its superview's bounds, but drawing during rasterization cannot occur outside of a view's bounds. + +## Scroll View's Content Offset + +Now, what does all of this have to do with UIScrollView? *Everything*. Think about a way we could accomplish scrolling: we could have a view whose frame we change when we drag it. It accomplishes the same thing, right? If I drag my finger to the right, I increase the `origin.x` of the view I'm dragging and voila, scroll view! + +The problem with that, of course, is that there are typically many views in a scroll view. To implement this panning feature, you would have to change the frames of every view every time the user moved his or her finger. But we're missing something. Remember that equation that we came up with to determine where a view composited its image onto its superview? + + CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x; + CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y; + +We dropped the `Superview.bounds.origin` values because they were always 0. But what if they weren't? What if, say, we used the same frames from the previous diagram, but we changed the purple view's `bounds` origin to something like {-30, -30}. We'd get this: + + + +Now, the beauty of this is that every single subview of this purple view is shifted by the change to its bounds. This is, in fact, exactly how a scroll view works when you set its [`contentOffset`](http://developer.apple.com/library/ios/documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html#//apple_ref/occ/instp/UIScrollView/contentOffset) property: it changes the origin of the scroll view's bounds. In fact, `contentOffset` isn't even real! Its code probably looks like this: + + - (void)setContentOffset:(CGPoint)offset + { + CGRect bounds = [self bounds]; + bounds.origin = offset; + [self setBounds:bounds]; + } + +Notice that in the previous diagram, changing the bounds' origin enough moved the button outside of the composited image produced by the purple view and the button. This is just what happens when you scroll a scroll view enough so that a view disappears! + +## A Window into the World: Content Size + +Now that the hard part is out of the way, let's look at another property of `UIScrollView`, [`contentSize`](http://developer.apple.com/library/ios/documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html#//apple_ref/occ/instp/UIScrollView/contentSize). + +The content size of a scroll view doesn't change anything about the bounds of a scroll view and therefore does not impact how a scroll view composites its subviews. Instead, the content size defines the scrollable area. By default, a scroll view's content size is a big, fat `{w:0, h:0}`. Since there is no scrollable area, the user can't scroll, but the scroll view will still display all of the subviews that fit inside the scroll view's bounds. + +When the content size is set to be larger than the bounds of the scroll view, the user is allowed to scroll. You can think of the bounds of a scroll view as a window into the scrollable area defined by the content size: + + + +When the content offset is `{x:0, y:0}`, the viewing window's top-left corner is in the top-left corner of the scrollable area. This is also the minimum value of the content offset; the user can't scroll to the left or above the scrollable area. There's nothing there! + +The maximum value for the content offset is the difference between the content size and the scroll view's bounds' size. This makes sense; scrolling all the way to the bottom right, the user is stopped so that the bottom-right edge of the scrolling area is flush with the bottom-right edge of the scroll view's bounds. You could write the maximum content offset like this: + + contentOffset.x = contentSize.width - bounds.size.width; + contentOffset.y = contentSize.height - bounds.size.height; + +## Tweaking the Window with Content Insets + +The property [`contentInset`](http://developer.apple.com/library/ios/documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html#//apple_ref/occ/instp/UIScrollView/contentInset) can change the maximum and minimum values of the content offset to allow scrolling outside of the scrollable area. Its type is [`UIEdgeInsets`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIKitDataTypesReference/Reference/reference.html#//apple_ref/doc/c_ref/UIEdgeInsets), which consists of 4 numbers: `{top, left, bottom, right}`. When you introduce an inset, you change the range of the content offset. For example, setting the content inset to have a value of 10 for its top value allows the content offset's y value to reach -10. This introduces padding around the scrollable area. + + + +This may not seem very useful at first. In fact, why not just increase the content size? Well, you should avoid changing the content size of a scroll view unless you have to. To understand why, consider a table view (`UITableView` is a subclass of `UIScrollView`, so it has all of the same properties). The table view's scrollable area has been carefully calculated to fit each one of its cells snugly. When you scroll past the boundaries of the table view's first or last cells, the table view snaps the content offset back into place, so that the cells once again fit snugly in the scroll view's bounds. + +Now, what happens when you want to implement pull to refresh using a [`UIRefreshControl`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIRefreshControl_class/Reference/Reference.html)? You can't put the `UIRefreshControl` within the scrollable area of the table view, otherwise, the table view would allow the user to stop scrolling halfway through the refresh control, and the top would snap to the top of the refresh control. Thus, you must put refresh control just above the scrollable area. This allows the content offset to snap back to the first row, not the refresh control. + +But wait, if you initiate the pull-to-refresh mechanism by scrolling far enough, the table view *does* allow the content offset to snap refresh control into the scrollable area, and this is because of the table view's content inset. When the refresh action is initiated, the content inset is adjusted so that the minimum content offset includes the entirety of the refresh control. When the refresh completes, the content inset is returned to normalcy, the content offset follows suit, and none of the math required for determining the content size needs to be re-computed. + +How can you use the content inset in your own code? Well, there is one great use for the it: when the keyboard is on the screen. Typically, you try to design a user interface that fits the screen snugly. When the keyboard appears on the screen, you lose a few hundred pixels of that space. All of the stuff underneath the keyboard is obscured. + +Now, the scroll view's bounds haven't changed, and neither has its content size (nor should it). But the user can't scroll the scroll view. Think about the equation from earlier: the maximum content offset is the difference between the content size and the bounds' size. If they are equal, which they are in your snug interface that now has a keyboard messing up your day, the maximum content offset is `{x:0, y:0}`. + +The trick, then, is to put the interface in a scroll view. The content size of the scroll view remains fixed at the same size as the scroll view's bounds. When the keyboard appears on the screen, you set the bottom of the content inset equal to the height of the keyboard. + + + +This allows the maximum value of the content offset to show the area beyond the scrollable area. The top of the visible area is outside the bounds of the scroll view, and is therefore clipped (although it is also off the screen itself, so that doesn't matter too much). + +Hopefully, this gives you some insight into the inner workings of scroll views. Are you wondering about zooming? Well, we won't talk about it today, but here's a fun tip: check the [`transform`](http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instp/UIView/transform) property of the view you return from [`viewForZoomingInScrollView:`](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html#//apple_ref/doc/uid/TP40006923-CH3-SW7). Once again, you'll find that a scroll view is just cleverly using already-existing properties of `UIView`. + + + + diff --git a/2013-09-09-SQLite-instead-of-core-data.markdown b/2013-09-09-SQLite-instead-of-core-data.markdown new file mode 100644 index 0000000..36964ca --- /dev/null +++ b/2013-09-09-SQLite-instead-of-core-data.markdown @@ -0,0 +1,369 @@ +--- +layout: post +title: On Using SQLite and FMDB Instead of Core Data +category: "4" +date: "2013-09-09 09:00:00" +author: "Brent Simmons" +tags: article +--- + + +I can’t in good conscience tell you not to use Core Data. It’s good and getting better, and it’s understood by many other Cocoa developers, which is important when you add people to your team or when someone else takes over your app. + +More importantly, it’s simply not worth the time and effort to write your own system instead. Use Core Data. Really. + +## Why I Don’t Use Core Data + +[Mike Ash writes](http://www.mikeash.com/pyblog/friday-qa-2013-08-30-model-serialization-with-property-lists.html): + +>Personally, I'm not a big fan. I find the API to be unwieldy and the framework itself to be painfully slow for anything more than a small amount of data. + +### A Real-Life Example: 10,000 Items + +Picture an RSS reader. A user can right-click on a feed and choose Mark All As Read. + +Under the hood, there’s an Article entity with a `read` attribute. To mark all items as read, the app has to load all of the articles for the feed (probably via a to-many relationship) and then set the `read` attribute to YES. + +Most of the time that’s okay. But suppose there are 200 articles in that feed, and you might consider doing this work in a background thread so you don’t block the main thread (especially if the app is an iPhone app). As soon as you start working with multi-threaded Core Data, things start to get tricky. + +That’s probably not so bad, or at least not worth switching away from Core Data. + +But then add syncing. + +I worked with two different RSS sync APIs that returned arrays of uniqueIDs of articles that had been read. One of those returned up to 10,000 IDs. + +You’re not going to load 10,000 articles on the main thread and set `read` to NO. You don’t even want to load 10,000 articles on a background thread, even with careful memory management. It’s just too much work. (Think of the effect on battery life if this is done frequently.) + +What you really want to do, conceptually, is this: tell the database to set `read` to YES for each article in an array of unique IDs. + +With SQLite you can do that. With one call. And, assuming an index on `uniqueID`, it’s fast. And you can do it on a background thread as easily as on the main thread. + +### Another Example: Fast Startup + +With another app of mine I wanted to reduce the start-up time — not just the time for the app to launch, but the amount of time before data is displayed. + +It was kind of like a Twitter app (though it wasn’t): it displayed a timeline of messages. To display that timeline meant fetching the messages and loading the associated users. It was pretty fast, but still, at start-up, the UI would fill in and *then* the data would fill in. + +My theory about iPhone apps (or any app, really) is that start-up time matters more than most developers think. Apps where start-up time is slower are less likely to get launched, because people remember subconsciously and develop a resistance to launching that app. Reducing start-up time reduces friction and makes it more likely people will continue to use your app, as well as recommend it to other people. It’s part of how you make your app successful. + +Since I wasn’t using Core Data I had an easy, old-school solution at hand. I saved the timeline (messages and people objects) to a plist file (via `NSCoding`). At start-up it read the file, created the message and people objects, and displayed the timeline as soon as the UI appeared. + +This noticeably reduced latency. + +Had the messages and people objects been instances of `NSManagedObject`, this wouldn’t have been possible. (I suppose I could have encoded and stored the object IDs, but that would have meant reading the plist and *then* hitting the database. This way I avoided the database entirely.) + +(Later on I ended up removing that code after newer, faster devices came out. In retrospect, I wish I’d left it in.) + +### How I Think About It + +When deciding whether or not to use Core Data, I consider a few things: + +#### Could There Be an Incredible Amount of Data? + +With an RSS reader or Twitter app, the answer is obviously yes. Some people follow hundreds of people. A person might subscribe to thousands of feeds. + +Even if your app doesn’t grab data from the web, it’s still possible that a person might automate adding data. If you do a Mac version with AppleScript support, somebody will write a script that loads crazy amounts of data. This is the same if it has a web API for adding data. + +#### Could There Be a Web API that Includes Database-Like Endpoints (as Opposed to Object-Like Endpoints)? + +An RSS sync API could return a list of the uniqueIDs of read articles. A sync API for a note-taking app might return lists of the uniqueIDs of archived and deleted notes. + +#### Could a User Take Actions that Cut Across Large Numbers of Objects? + +Under the hood, it’s the same issue as the previous consideration. How well does your recipes app perform when someone deletes all 5,000 pasta recipes the app has downloaded? (On an iPhone?) + +If I do decide to use Core Data - (and I have: I’ve shipped Core Data apps) - I pay careful attention to how I’m using it. If, in order to get decent performance, I find that I’m using it as a weird interface to a SQL database, then I know I should drop Core Data and use SQLite more directly. + +## How I Use SQLite + +I use SQLite with the excellent [FMDB wrapper](https://github.com/ccgus/fmdb) from Flying Meat Software, by Gus Mueller. + +### Basic Operation + +I’ve been using SQLite since before iPhones, since before Core Data. Here’s the gist of how it works: + +- All database access — reading and writing — happens in a serial queue, in a background thread. Hitting the database on the main thread is *never* allowed. Using a serial queue ensures that everything happens in order. +- I use blocks extensively to make async programming simpler. +- Model objects exist on the main thread only (with two important exceptions). Changes trigger a background save. +- Model objects list their database-stored attributes. It might be in code or might in a plist file. +- Some model objects are uniqued and some aren’t. It depends on the needs of the app. (They’re usually unique.) +- For relationships I avoid creating lookup tables as much as possible. +- Some object types are read entirely into memory at start-up. For other object types I may create and maintain an NSMutableSet of just their uniqueIDs, so I know what exists and what doesn’t without having to hit the database. +- Web API calls happen in background threads, and they get to use “detached” model objects. + +I'll elaborate, using code from my [current app](http://vesperapp.co/). + +### Database Updating + +I have a single database controller — `VSDatabaseController` in my latest app — that talks to SQLite via FMDB. + +FMDB differentiates between updates and queries. To update the database the app calls: + + -[VSDatabaseController runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock] + +`VSDatabaseUpdateBlock` is simple: + + typedef void (^VSDatabaseUpdateBlock)(FMDatabase *database); + +`runDatabaseBlockInTransaction` is also simple: + + - (void)runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock { + dispatch_async(self.serialDispatchQueue, ^{ + @autoreleasepool { + [self beginTransaction]; + databaseBlock(self.database); + [self endTransaction]; + } + }); + } + +(Note that I’m using my own serial dispatch queue. Gus recommends checking out `FMDatabaseQueue`, which is also a serial dispatch queue. I just haven’t gotten around to checking it out yet, since it’s newer than much of the rest of FMDB.) + +Calls to `beginTransaction` and `endTransaction` are nestable (in my database controller). At the appropriate time they call `-[FMDatabase beginTransaction]` and `-[FMDatabase commit]`. (Using transactions is a big key to making SQLite fast.) Tip: I store the current transaction count in `-[NSThread threadDictionary]`. That’s a handy spot for per-thread data, which I almost never use for anything else. Almost. + +Here’s a simple example of a call to update the database: + + - (void)emptyTagsLookupTableForNote:(VSNote *)note { + NSString *uniqueID = note.uniqueID; + [self runDatabaseBlockInTransaction:^(FMDatabase *database) { + [database executeUpdate: + @"delete from tagsNotesLookup where noteUniqueID = ?;", uniqueID]; + }]; + } + +This illustrates a few things. The first is that SQL isn’t that scary. Even if you’ve never seen it before, you know what’s going on in that line. + +`emptyTagsLookupTableForNote`, like every other public interface for `VSDatabaseController`, should be called from the main thread. Model objects may only be referenced on the main thread, and so the block references `uniqueID` but not the `VSNote` object. + +Note that in this case I’m updating a lookup table. Notes and tags have a many-to-many relationship, and one way to represent that is with a database table that maps note uniqueIDs and tag uniqueIDs. These tables aren’t hard to maintain, but I do try to avoid their use when possible. + +Note the ? in the update string. `-[FMDatabase executeUpdate:]` is a variadic function. SQLite supports using placeholders — ? characters — so you don’t have to put the actual value in the string. This is a security issue: it helps guard against SQL injection. It also saves you the trouble of having to escape values. + +And, finally, note that there is an index on noteUniqueID in the tagsNotesLookup table. (Indexes are another key to SQLite performance.) This line of code runs at each launch: + + [self.database executeUpdate: + @"CREATE INDEX if not exists noteUniqueIDIndex on tagsNotesLookup (noteUniqueID);"]; + +### Database Fetching + +To fetch objects, the app calls: + + -[VSDatabaseController runFetchForClass:(Class)databaseObjectClass + fetchBlock:(VSDatabaseFetchBlock)fetchBlock + fetchResultsBlock:(VSDatabaseFetchResultsBlock)fetchResultsBlock]; + +These two lines do much of the work: + + FMResultSet *resultSet = fetchBlock(self.database); + NSArray *fetchedObjects = [self databaseObjectsWithResultSet:resultSet + class:databaseObjectClass]; + +A database fetch using FMDB returns an `FMResultSet`. With that resultSet you can step through and create model objects. + +I recommend writing general code for turning database rows into objects. One way I’ve used is to include a plist with the app that maps column names to model object properties. It also includes types, so you know whether or not to call `-[FMResultSet dateForColumn:]` versus `-[FMResultSet stringForColumn:]` versus something else. + +In my latest app I did something simpler. The database rows map exactly to model object property names. All the properties are strings, except for those properties whose names end in "Date." Simple, but you can see how an explicit map might be needed. + +#### Uniquing Objects + +Creation of model objects happens in the same background thread that fetches from the database. Once fetched, the app turns these over to the main thread. + +Usually I have the objects *uniqued*. The same database row will always result in the same object. + +To do the uniquing, I create an object cache, an NSMapTable, in the init method: `_objectCache = [NSMapTable weakToWeakObjectsMapTable]`. I’ll explain: + +When, for instance, you do a database fetch and turn the objects over to a view controller, you want those objects to disappear after the view controller is finished with them, or once a different view controller is displayed. + +If your object cache is an `NSMutableDictionary`, you’ll have to do some extra work to empty objects from the object cache. It becomes a pain to be sure that it references only objects that have a reference somewhere else. NSMapTable with weak references handles this automatically. + +So: we unique the objects on the main thread. If an object already exists in the object cache, we use that existing object. (Main thread wins, since it might have newer changes.) If it doesn’t exist in the object cache, it’s added. + +#### Keeping Objects in Memory + +There are times when it makes sense to keep an entire object type in memory. My latest app has a `VSTag` object. While there may be many hundreds or thousands of notes, the number of tags is small, often less than 10. And a tag has just six properties: three BOOLs, two very small NSStrings, and one NSDate. + +At start-up, the app fetches all the tags and stores them in two dictionaries: one keyed by tag uniqueID, and another keyed by the lowercase tag name. + +This simplifies a bunch of things, not least is the tag auto-completion system, which can operate entirely in memory and doesn’t require a database fetch. + +However, there are times when keeping all objects in memory is impractical. We don’t keep all notes in memory, for instance. + +There are times, though, when for an object type that you can’t keep in memory, you will want to keep all the uniqueIDs in memory. You’d do a fetch like this: + + FMResultSet *resultSet = [self.database executeQuery:@"select uniqueID from some_table"]; + +The resultSet would contain just uniqueIDs, which you’d then store in an NSMutableSet. + +I’ve found this useful sometimes with web APIs. Picture an API call that returns a list of the uniqueIDs of notes created since a certain date and time. If I had an NSMutableSet containing all the uniqueIDs of notes known locally, I could check quickly (via `-[NSMutableSet minusSet]`) to see if there are any missing notes, and then make another API call to download any missing notes. All without hitting the database at all. + +But, again, things like this should be done carefully. Can the app afford the memory? Does it really simplify programming *and* help performance? + +Using SQLite and FMDB instead of Core Data allows for a ton of flexibility and makes room for clever solutions. The thing to remember is that sometimes clever is good — and sometimes clever is a big mistake. + +### Web APIs + +My API calls all happen in a background thread (usually with an `NSOperationQueue`, so I can cancel operations). Model objects are main-thread only — and yet I pass model objects to my API calls. + +Here’s how: a database object has a `detachedCopy` method which copies the database object. That copy is *not* referenced in the object cache I use for uniquing. The only thing that references that object is the API call. When the API call is finished, that object, the detached copy, goes away. + +This is a nice system, because it means I can still use model objects with the API calls. A method might look like this: + + - (void)uploadNote:(VSNote *)note { + VSNoteAPICall *apiCall = [[VSNoteAPICall alloc] initWithNote:[note detachedCopy]]; + [self enqueueAPICall:apiCall]; + } + +And VSNoteAPICall would pull values from the detached `VSNote` and create the HTTP request, rather than having a dictionary or some other representation of the note. + +#### Handling Web API Return Values + +I do something similar with values returned from the web. I’ll create a model object with the returned JSON or XML or whatever, and that model object is also detached. That is, it’s not stored in the object cache used for uniquing. + +Here’s where things get dicey. It is sometimes necessary to use that model object to make local changes in two places: the in-memory cache *and* in the database. + +The database is generally the easy part. For instance: there’s already a method in my app which saves a note object. It uses a SQL `insert or replace into` string. I just call that with a note object generated from a web API return value and the database is updated. + +But there might also be an in-memory version of that same object. Luckily this is easy to find: + + VSNote *cachedNote = [self.mapTable objectForKey:downloadedNote.uniqueID]; + +If the cachedNote exists, rather than replace it (which would violate uniquing), I have it pull values from the `downloadedNote`. (This can share code with the `detachedCopy` method.) + +Once that cachedNote is updated, observers will note the change via KVO, or I’ll have it post an `NSNotification` of some kind. Or both. + +There are other return values from web API calls; I mentioned the big list of read items that an RSS reader might get. In this case, I’d create an `NSSet` out of that list, update the `read` property for each article cached in memory, then call `-[FMDatabase executeUpdate:]`. + +The key to making this work is that an NSMapTable lookup is fast. If you find yourself looking for objects inside an NSArray, it’s time to re-think. + +## Database Migration + +Core Data’s database migration is pretty cool, [when it works](http://openradar.appspot.com/search?query=migration). + +But it is, inescapably, a layer between the code and the database. If you’re using SQLite more directly, you update the database directly. + +You can do this safely and easily. + +To add a table, for instance: + + [self.database executeUpdate:@"CREATE TABLE if not exists tags " + "(uniqueID TEXT UNIQUE, name TEXT, deleted INTEGER, deletedModificationDate DATE);"]; + +Or add an index: + + [self.database executeUpdate:@"CREATE INDEX if not exists " + "archivedSortDateIndex on notes (archived, sortDate);"]; + +Or add a column: + + [self.database executeUpdate:@"ALTER TABLE tags ADD deletedDate DATE"]; + +The app should set up the database in code in the first place using lines like the above. Any changes added later are just added executeUpdate calls — I leave them all in and have them run in order. Since it’s my database that I designed, this isn’t a problem. (And I’ve never seen a performance issue here. It’s fast.) + +Bigger changes take more code, of course. But if your data is available via the web, sometimes you can start with a fresh database model and re-download what you need. Sometimes. + +## Performance Tips + +SQLite can be very, very fast. It can be very slow, too. It’s all in how you use it. + +### Transactions + +Wrap updates in transactions. Use `-[FMDatabase beginTransaction]` before your updates and `-[FMDatabase commit]` after the updates. + +### Denormalize If You Have To + +[Denormalization](http://en.wikipedia.org/wiki/Denormalization) is a bummer. The idea is that you add redundant data in order to speed up queries, but, of course, it also means maintaining redundant data. + +I avoid it like crazy, right up until it makes a serious performance difference. And then I do it as minimally as possible. + +### Use Indexes + +The create table statement for my app’s tags table looks like this: + + CREATE TABLE if not exists tags + (uniqueID TEXT UNIQUE, name TEXT, deleted INTEGER, deletedModificationDate DATE); + +The uniqueID column is automatically indexed, since it’s defined as unique. But if I wanted to query that table by name, I might make an index on the name, like this: + + CREATE INDEX if not exists tagNameIndex on tags (name); + +You can do indexes on multiple columns at once, like this: + + CREATE INDEX if not exists archivedSortDateIndex on notes (archived, sortDate); + +But note that too many indexes can slow down your inserts. You need just enough amount and just the right ones. + +### Use the Command Line App + +I have an `NSLog` that runs when my app launches in the simulator. It prints the path to the database, so I can open it using the command-line sqlite3 app. (Do a man sqlite3 for info about the app.) + +To open the database: `sqlite3 path/to/database`. + +Once open, you can look at the schema: type `.schema`. + +You can do updates and run queries; it’s a great way to get your SQL correct before using it in your app. + +One of the coolest parts is the [SQLite Explain Query Plan command](http://www.sqlite.org/eqp.html). You want to make sure your queries run as quickly as possible. + +### Real-Life Example + +My app displays a table listing all the tags of non-archived notes. This query is re-run whenever a note or tag changes, and it needs to be super fast. + +I was able to do the query with a [SQL join](http://en.wikipedia.org/wiki/Join_%28SQL%29), but it was slow. (Joins are slow.) + +So I fired up sqlite3 and started experimenting. I looked again at my schema and realized I could denormalize. While the archived status of a note is stored in the notes table, it could also be stored in the tagsNotesLookup table. + +Then I could do a query like this: + + select distinct tagUniqueID from tagsNotesLookup where archived=0; + +I already had an index on tagUniqueID. So I used explain query plan to tell me what would happen when I ran that query. + + sqlite> explain query plan select distinct tagUniqueID from tagsNotesLookup where archived=0; + 0|0|0|SCAN TABLE tagsNotesLookup USING INDEX tagUniqueIDIndex (~100000 rows) + +It’s nice that it’s using an index, but SCAN TABLE sounds ominous. Better yet would be a SEARCH TABLE and a [covering index](http://www.sqlite.org/queryplanner.html#covidx). + +I added an index on tagUniqueID and archive: + + CREATE INDEX archivedTagUniqueID on tagsNotesLookup(archived, tagUniqueID); + +I ran explain query plan again: + + sqlite> explain query plan select distinct tagUniqueID from tagsNotesLookup where archived=0; + 0|0|0|SEARCH TABLE tagsNotesLookup USING COVERING INDEX archivedTagUniqueID (archived=?) (~10 rows) + +*Way* better. + +### More Performance Tips + +Somewhere along the line FMDB, added the ability to cache statements, so I always call `[self.database setShouldCacheStatements:YES]` when creating/opening a database. This means you don’t have to re-compile each statement for every call. + +I’ve never found good guidance on using `vacuum`. If the database isn’t compacted periodically, it gets slower and slower. I have my app run a vacuum about once a week. (It stores the last vacuum date in NSUserDefaults, and checks at start if it’s been a week.) + +It’s possible that auto_vacuum would be better — see the list of [pragma statements supported by SQLite](http://www.sqlite.org/pragma.html#pragma_auto_vacuum). + +## Bonus Cool Thing + +Gus Mueller asked me to cover custom SQLite functions. This isn’t something I’ve actually used, but now that he’s pointed it out, it’s a safe bet I’ll find a use for it. Because it’s cool. + +[Gus posted a Gist](https://gist.github.com/ccgus/6324222) where a query looks like this: + +
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? AND ZLONGITUDE? AND ZLONGITUDE ?) + 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 + NSString *mutableName = [self mutableCopy]; + CFStringTransform((__bridge CFMutableStringRef) mutableName, NULL, + (__bridge CFStringRef)@"NFD; [:Nonspacing Mark:] Remove; Lower(); NFC", NO); + return mutableName; + } + + @end + +We'll update the `Stop` class to automatically update the `normalizedName`: + + @interface Stop (CoreDataForward) + + @property (nonatomic, strong) NSString *primitiveName; + @property (nonatomic, strong) NSString *primitiveNormalizedName; + + @end + + + @implementation Stop + + @dynamic name; + - (void)setName:(NSString *)name; + { + [self willAccessValueForKey:@"name"]; + [self willAccessValueForKey:@"normalizedName"]; + self.primitiveName = name; + self.primitiveNormalizedName = [name normalizedSearchString]; + [self didAccessValueForKey:@"normalizedName"]; + [self didAccessValueForKey:@"name"]; + } + + // ... + + @end + + +With this, we can search with `BEGINSWITH` instead of `BEGINSWITH[cd]`: + + predicate = [NSPredicate predicateWithFormat:@"normalizedName BEGINSWITH %@", [searchString normalizedSearchString]]; + +Searching with `BEGINSWITH` takes around 6.2 ms on a recent MacBook Pro with the sample strings in our sample code (160 searches / second). On an iPhone 5 it takes 40ms corresponding to 25 searches / second. + + +#### Free Text Search + +Our search still only works if the beginning of the string matches the search string. The way to fix that is to create another *Entity* that we search on. Let's call this Entity `SearchTerm`, and give it a single attribute `normalizedWord` and a relationship to a `Stop`. For each `Stop` we would then normalize the name and split it into words, e.g.: + + "Gedenkstätte Dt. Widerstand (Berlin)" + -> "gedenkstatte dt. widerstand (berlin)" + -> "gedenkstatte", "dt", "widerstand", "berlin" + +For each word, we create a `SearchTerm` and a relationship from the `Stop` to all its `SearchTerm` objects. When the user enters a string, we search on the `SearchTerm` objects' `normalizedWord` with: + + predicate = [NSPredicate predicateWithFormat:@"normalizedWord BEGINSWITH %@", [searchString normalizedSearchString]] + +This can also be done in a subquery directly on the `Stop` objects. + +## Fetching All Objects + +If we don't set a predicate on our fetch request, we'll retrieve all objects for the given *Entity*. If we did that for the `StopTimes` entity, we'll be pulling in three million objects. That would be slow, and use up a lot of memory. Sometimes, however, we need to get all objects. The common example is that we want to show all objects inside a table view. + +What we would do in this case, is to set a batch size: + + request.fetchBatchSize = 50; + +When we run `-[NSManagedObjectContext executeFetchRequest:error:]` with a batch size set, we still get an array back. We can ask it for its count (which will be close to three million for the `StopTime` entity), but Core Data will only populate it with objects as we iterate through the array. And Core Data will get rid of objects again, as they're no longer accessed. Simply put, the array has batches of size 50 (in this case). Core Data will pull in 50 objects at a time. Once more than a certain number of these batches are around, Core Data will release the oldest batch. That way you can loop through all objects in such an array, without having to have all three million objects in memory at the same time. + +On iOS, when you use an `NSFetchedResultsController` and you have a lot of objects, make sure that the `fetchBatchSize` is set on your fetch request. You'll have to experiment with what size works well for you. Twice the amount of objects that you'll be displaying at any given point in time is a good starting point. diff --git a/2013-09-09-core-data-migration.md b/2013-09-09-core-data-migration.md new file mode 100644 index 0000000..e27098d --- /dev/null +++ b/2013-09-09-core-data-migration.md @@ -0,0 +1,335 @@ +--- +layout: post +title: Custom Core Data Migrations +category: "4" +date: "2013-09-06 05:00:00" +author: "Martin Hwasser" +tags: article +--- + +{% include links-4.md %} + +Custom Core Data migrations are somewhat of an obscure topic. Apple provides little documentation on the subject, and it can be a frightening experience when first ventured into. Given the nature of client side programs, there’s no way to test all the possible permutations of datasets that your users will have. Moreover, it’s very hard to fix any issues that might occur during migration and rollback isn’t an option since you most likely have code that depends on the latest data model. + +In this article we’ll go through the process of setting up custom Core Data migrations, with a focus on refactoring the data model. We’ll look into extracting data from the previous model and using that data to populate the destination model with new entities and relationships. In addition, there’s an [example project](https://github.com/objcio/issue-4-core-data-migration) including unit tests that demonstrate two custom migrations. + +Note that for simple changes to the data model, like adding an entity or optional attribute, lightweight migrations are great. They're extremely easy to set up which is why the topic won’t be brought up in this article. To find out what kind of changes are supported by lightweight migrations and which are not, take a look at the [official documentation](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html). + +That said, if you work fast and need to make relatively complex changes to your data model, custom migrations are for you. + +## Mapping Models + +When you add a new version of your data model, you are asked to select the model on which it should be based. For lightweight migrations, the persistent store will infer a *mapping model* automatically for you. However, if you want to make a change to the new model that’s not supported by lightweight migrations, you need to create a mapping model. A mapping model needs a source and a destination data model. `NSMigrationManager` can infer the mapping model between two models. This makes it tempting to create a mapping model between each previous model all the way up to your latest one, but this quickly gets messy. For each new model version, the amount of mapping models you need to create increases linearly. This may not seem like a big deal, but along with it comes the added complexity of testing each mapping model. + +Imagine that you just shipped an update containing version 3 of your data model. One of your users hasn’t updated your app in some time. This user is currently on version 1 of your data model. Now you’d need a mapping model from version 1 to version 3. You also need a mapping model from version 2 to version 3. As you add version 4, you need to create three new mapping models. Clearly this doesn’t scale very well. Enter progressive migrations. + +## Progressive Migrations + +Rather than creating one mapping model between each previous data model to your new one, you create one mapping model for each consecutive data model. Given the previous example, you would need one mapping model between version 1 and version 2, and one mapping model between version 2 and version 3. This way you migrate from versions 1 > 2 > 3. Granted, this kind of migration will be slower for users on an older data model, but it will save development time and ensure robustness, since you only need to make sure that the migration from your previous model to your new model works, as the previous mapping models have already been tested. + +The general idea is to manually figure out the mapping model between the current version `v` and `v+1`, migrate between those, and continue recursively until the persistent store is compatible with the current data model. + +This looks something like this (the full version can be found in the [example project](https://github.com/objcio/issue-4-core-data-migration)): + + - (BOOL)progressivelyMigrateURL:(NSURL *)sourceStoreURL + ofType:(NSString *)type + toModel:(NSManagedObjectModel *)finalModel + error:(NSError **)error + { + NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type + URL:sourceStoreURL + error:error]; + if (!sourceMetadata) { + return NO; + } + if ([finalModel isConfiguration:nil + compatibleWithStoreMetadata:sourceMetadata]) { + if (NULL != error) { + *error = nil; + } + return YES; + } + NSManagedObjectModel *sourceModel = [self sourceModelForSourceMetadata:sourceMetadata]; + NSManagedObjectModel *destinationModel = nil; + NSMappingModel *mappingModel = nil; + NSString *modelName = nil; + if (![self getDestinationModel:&destinationModel + mappingModel:&mappingModel + modelName:&modelName + forSourceModel:sourceModel + error:error]) { + return NO; + } + // We have a mapping model, time to migrate + NSURL *destinationStoreURL = [self destinationStoreURLWithSourceStoreURL:sourceStoreURL + modelName:modelName]; + NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel + destinationModel:destinationModel]; + if (![manager migrateStoreFromURL:sourceStoreURL + type:type + options:nil + withMappingModel:mappingModel + toDestinationURL:destinationStoreURL + destinationType:type + destinationOptions:nil + error:error]) { + return NO; + } + // Migration was successful, move the files around to preserve the source in case things go bad + if (![self backupSourceStoreAtURL:sourceStoreURL + movingDestinationStoreAtURL:destinationStoreURL + error:error]) { + return NO; + } + // We may not be at the "current" model yet, so recurse + return [self progressivelyMigrateURL:sourceStoreURL + ofType:type + toModel:finalModel + error:error]; + } + +Credit for the major chunk of this code goes to [Marcus Zarra](https://twitter.com/mzarra), who wrote a great book on Core Data. [Check it out here](http://pragprog.com/book/mzcd2/core-data). + +## Migration Policies + +`NSEntityMigrationPolicy` is the essence of the custom migration process. [From the documentation](https://developer.apple.com/library/ios/documentation/cocoa/Reference/NSEntityMigrationPolicy_class/NSEntityMigrationPolicy.html): +> Instances of `NSEntityMigrationPolicy` customize the migration process for an entity mapping. +Simply put, this class allows us not only to modify the attributes and relationships of an entity, but do any other processing we might need as each instance of that entity is migrated. + +### A Migration Example + +Let’s say we have [a book app with a simple data model](https://github.com/objcio/issue-4-core-data-migration). There are two entities: `User` and `Book`. The `Book` entity has an attribute called `authorName`. We want to improve this model and add a new entity: `Author`. We also want to create a many-to-many relationship between `Book` and `Author`, as a book can have multiple authors, and an author can write multiple books. We will extract the `authorName` from the `Book` object, and use that to populate a new entity and establish the relationship. + +The very first thing we need to do is to add a new model version based on the first data model. For this example, we added an `Author` entity with a many-to-many relationship with `Book`. + + + +Now the data model suits our purposes, but we’ll need to migrate any existing data. This is where `NSEntityMigrationPolicy` comes in. We create a subclass of `NSEntityMigrationPolicy` called [`MHWBookToBookPolicy`](https://github.com/objcio/issue-4-core-data-migration/blob/master/BookMigration/MHWBookToBookPolicy.m). In the mapping model, we select the `Book` entity and set it as the custom policy in the Utilities section. + + + +We also use the user info dictionary to set a `modelVersion` which will come in handy in future migrations. + +In [`MHWBookToBookPolicy`](https://github.com/hwaxxer/BookMigration/blob/master/BookMigration/MHWBookToBookPolicy.m) we’ll override `-createDestinationInstancesForSourceInstance:entityMapping:manager:error:` which lets us customize how to migrate each `Book` instance. If the value of `modelVersion` isn’t 2, we’ll just call the super implementation, otherwise we need to do a custom migration. We’ll start off by inserting a new `NSManagedObject` based on the mapping’s destination entity into the destination context. Then we iterate through the attribute keys of the destination instance and populate them with the values from the source instance. This ensures that we preserve the existing data and avoid setting any values that have been removed in the destination instance: + + NSNumber *modelVersion = [mapping.userInfo valueForKey:@"modelVersion"]; + if (modelVersion.integerValue == 2) { + NSMutableArray *sourceKeys = [sourceInstance.entity.attributesByName.allKeys mutableCopy]; + NSDictionary *sourceValues = [sourceInstance dictionaryWithValuesForKeys:sourceKeys]; + NSManagedObject *destinationInstance = [NSEntityDescription insertNewObjectForEntityForName:mapping.destinationEntityName + inManagedObjectContext:manager.destinationContext]; + NSArray *destinationKeys = destinationInstance.entity.attributesByName.allKeys; + for (NSString *key in destinationKeys) { + id value = [sourceValues valueForKey:key]; + // Avoid NULL values + if (value && ![value isEqual:[NSNull null]]) { + [destinationInstance setValue:value forKey:key]; + } + } + } + +Then we’ll create the `Author` entity, based on the values from the source instance. But what happens now if there are multiple books with the same author? We’ll make use of a category method on `NSMigrationManager` to create a lookup dictionary, making sure we only create one `Author` entity for each unique `Author` name: + + NSMutableDictionary *authorLookup = [manager lookupWithKey:@"authors"]; + // Check if we’ve already created this author + NSString *authorName = [sourceInstance valueForKey:@"author"]; + NSManagedObject *author = [authorLookup valueForKey:authorName]; + if (!author) { + // Create the author + // Populate lookup for reuse + [authorLookup setValue:author forKey:authorName]; + } + [destinationInstance performSelector:@selector(addAuthorsObject:) withObject:author]; + +Finally, we need to tell the migration manager to associate data between the source and destination stores: + + [manager associateSourceInstance:sourceInstance + withDestinationInstance:destinationInstance + forEntityMapping:mapping]; + return YES; + +In a category on `NSMigrationManager`: + + @implementation NSMigrationManager (Lookup) + + - (NSMutableDictionary *)lookupWithKey:(NSString *)lookupKey + { + NSMutableDictionary *userInfo = (NSMutableDictionary *)self.userInfo; + // Check if we’ve already created a userInfo dictionary + if (!userInfo) { + userInfo = [@{} mutableCopy]; + self.userInfo = userInfo; + } + NSMutableDictionary *lookup = [userInfo valueForKey:lookupKey]; + if (!lookup) { + lookup = [@{} mutableCopy]; + [userInfo setValue:lookup forKey:lookupKey]; + } + return lookup; + } + + @end + +### A More Complex Migration + +Later on, we want to move the `fileURL` from the `Book` entity into a new entity called `File`. +We want to rearrange the relationships so that a `User` has a one-to-many relationship with `File`, which in turn has a many-to-one relationship with `Book`. + + + +In the previous migration, we were only migrating one entity. When we add `File`, things become a bit more tricky. We can’t simply insert a `File` entity when migrating a `Book` and set its relationship with `User`, because the `User` entity hasn’t yet been migrated and has no files-relationship. *We have to think about the order in which the migration is executed*. In the mapping model, it’s possible to rearrange the order of the entity mappings. For this case, we want to put the `UserToUser` mapping above the `BookToBook` mapping. This guarantees that the `User` entity will be migrated before the `Book` entity. + + + +The approach for adding a `File` entity is similar to when we created the `Author` entity. We’ll create `File` objects when we migrate the `Book` entity in `MHWBookToBookPolicy`. We’ll look at the source instance’s users, create a new `File` object for each user, and establish the relationship: + + NSArray *users = [sourceInstance valueForKey:@"users"]; + for (NSManagedObject *user in users) { + + NSManagedObject *file = [NSEntityDescription insertNewObjectForEntityForName:@"File" + inManagedObjectContext:manager.destinationContext]; + [file setValue:[sourceInstance valueForKey:@"fileURL"] forKey:@"fileURL"]; + [file setValue:destinationInstance forKey:@"book"]; + + NSInteger userId = [[user valueForKey:@"userId"] integerValue]; + NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"User"]; + request.predicate = [NSPredicate predicateWithFormat:@"userId = %d", userId]; + NSManagedObject *user = [[manager.destinationContext executeFetchRequest:request error:nil] lastObject]; + [file setValue:user forKey:@"user"]; + } + +### Large Datasets + +If your store contains a lot of data, to a point where the migration will consume too much memory, Core Data provides a way to migrate in chunks. Apple briefly mentions it [here](https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/CoreDataVersioning/Articles/vmCustomizing.html#//apple_ref/doc/uid/TP40004399-CH8-SW9). The approach is to separate your migration using multiple mapping models and migrating once with each of the mapping models. This requires that you have an object graph in which migration can be divided into two or more parts. The code we need to add to support this is actually very little. + +First, let’s update our migration method to support migrating using multiple mapping models. Since the order of mapping models is important, we’ll ask for them in a delegate method: + + NSArray *mappingModels = @[mappingModel]; // The one we found previously + if ([self.delegate respondsToSelector:@selector(migrationManager:mappingModelsForSourceModel:)]) { + NSArray *explicitMappingModels = [self.delegate migrationManager:self + mappingModelsForSourceModel:sourceModel]; + if (0 < explicitMappingModels.count) { + mappingModels = explicitMappingModels; + } + } + for (NSMappingModel *mappingModel in mappingModels) { + didMigrate = [manager migrateStoreFromURL:sourceStoreURL + type:type + options:nil + withMappingModel:mappingModel + toDestinationURL:destinationStoreURL + destinationType:type + destinationOptions:nil + error:error]; + } + +Now, how do we know which mapping models to use for a particular source model? The API here makes this a bit clumsy but the following solution does the job. In the delegate method, we figure out the name of the source model and return the relevant mapping models: + + - (NSArray *)migrationManager:(MHWMigrationManager *)migrationManager + mappingModelsForSourceModel:(NSManagedObjectModel *)sourceModel + { + NSMutableArray *mappingModels = [@[] mutableCopy]; + NSString *modelName = [sourceModel mhw_modelName]; + if ([modelName isEqual:@"Model2"]) { + // Add mapping models to mappingModels + } + return mappingModels; + } + +We’ll add a category on `NSManagedObjectModel` that helps us figure out its filename: + + - (NSString *)mhw_modelName + { + NSString *modelName = nil; + NSArray *modelPaths = // get paths to all the mom files in the bundle + for (NSString *modelPath in modelPaths) { + NSURL *modelURL = [NSURL fileURLWithPath:modelPath]; + NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; + if ([model isEqual:self]) { + modelName = modelURL.lastPathComponent.stringByDeletingPathExtension; + break; + } + } + return modelName; + } + +Since the `User` was isolated from the rest of the object graph in the previous example (no source relationship mapping), we could take advantage of this approach and migrate `User` by itself. We’ll remove the `UserToUser` mapping from our first mapping model, and create another mapping model with only a `UserToUser` mapping. Remember to return the new `User` mapping model in the list of mapping models, since we’re setting the new relationship in the other mapping. + +## Unit Tests + +Setting up unit tests for this is surprisingly simple: + +1. Populate your old store with relevant data\*. +2. Copy the resulting persistent store file to your *test target*. +3. Write tests that assert compliance with your latest data model. +4. Run tests that migrate data to your new data model. + +*\*This can easily be done by running the latest production version of your app in the simulator* + +Steps 1 and 2 are simple. Step 3 is left to the reader as an exercise, and I will guide you through step 4. + +When the persistent store file has been added to the unit testing target, we need to tell the migration manager to migrate from that store to our destination store. This is demonstrated in the example project like this: + + - (void)setUpCoreDataStackMigratingFromStoreWithName:(NSString *)name + { + NSURL *storeURL = [self temporaryRandomURL]; + [self copyStoreWithName:name toURL:storeURL]; + + NSURL *momURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"]; + self.managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL]; + + NSString *storeType = NSSQLiteStoreType; + + MHWMigrationManager *migrationManager = [MHWMigrationManager new]; + [migrationManager progressivelyMigrateURL:storeURL + ofType:storeType + toModel:self.managedObjectModel + error:nil]; + + self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; + [self.persistentStoreCoordinator addPersistentStoreWithType:storeType + configuration:nil + URL:storeURL + options:nil + error:nil]; + + self.managedObjectContext = [[NSManagedObjectContext alloc] init]; + self.managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator; + } + + - (NSURL *)temporaryRandomURL + { + NSString *uniqueName = [NSProcessInfo processInfo].globallyUniqueString; + return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingString:uniqueName]]; + } + + - (void)copyStoreWithName:(NSString *)name toURL:(NSURL *)url + { + // Create a unique url every test so migration always runs + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSFileManager *fileManager = [NSFileManager new]; + NSString *path = [bundle pathForResource:[name stringByDeletingPathExtension] ofType:name.pathExtension]; + [fileManager copyItemAtPath:path + toPath:url.path error:nil]; + } + +Put this code in a superclass, and reuse it in test classes that test migration: + + - (void)setUp + { + [super setUp]; + [self setUpCoreDataStackMigratingFromStoreWithName:@"Model1.sqlite"]; + } + +## Conclusion + +When doing lightweight migrations, the migration occurs directly inside the SQLite store. This is very fast and efficient compared to custom migrations, where the source objects need to be fetched into memory, their values copied onto the destination objects, their relationships re-established, and finally inserted into the new store. Not only is this much slower, it can also impose problems when migrating larger datasets due to memory constraints. + +### Add More Data Than You Think You Might Need + +One of the most important things when working with any type of data persisting is to think through your model carefully. You’ll want it to be sustainable. Put more things into your data model than you think you might need. Empty attributes or entities is better than having to migrate and create them later on. Migrations are prone to mistakes. Unused data is not. + +### Debugging Migrations + +A useful launch argument when testing migrations is `-com.apple.CoreData.MigrationDebug`. When set to 1, you will receive information in the console about exceptional cases as it migrates data. If you’re used to SQL but new to Core Data, set `-com.apple.CoreData.SQLDebug` to 1 to see actual SQL commands. + diff --git a/2013-09-09-core-data-models-and-model-objects.md b/2013-09-09-core-data-models-and-model-objects.md new file mode 100644 index 0000000..16d6f74 --- /dev/null +++ b/2013-09-09-core-data-models-and-model-objects.md @@ -0,0 +1,268 @@ +--- +layout: post +title: Data Models and Model Objects +category: "4" +date: "2013-09-09 08:00:00" +author: "Florian Kugler" +tags: article +--- + +{% include links-4.md %} + + +In this article we are going to have a closer look at Core Data models and managed object classes. It's not meant to be an introduction to the topic, but rather a collection of some lesser-known or often-forgotten aspects which can come in handy when working with Core Data. If you're looking for a more extensive and step-by-step overview, we recommend you read through [Apple's Core Data Programming Guide](https://developer.apple.com/library/mac/documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html#//apple_ref/doc/uid/TP30001200-SW1). + + + +## Data Model + +The Core Data data model (stored in the `*.xcdatamodel` file) is where the data types ("Entities" in Core Data) are defined. Mostly we will define a data model using Xcode's graphical interface, but it's equally possible to create the whole thing in code. You would start by creating an [`NSManagedObjectModel`](https://developer.apple.com/library/ios/documentation/cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectModel_Class/Reference/Reference.html) object, then create entities represented by [`NSEntitiyDescription`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSEntityDescription_Class/NSEntityDescription.html) objects, which in turn contain relationships and attributes represented by [`NSAttributeDescription`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSAttributeDescription_Class/reference.html) and [`NSRelationshipDescription`](https://developer.apple.com/library/ios/documentation/cocoa/Reference/CoreDataFramework/Classes/NSRelationshipDescription_Class/NSRelationshipDescription.html) objects. You'll hardly ever need to do this, but it's good to know about these classes. + + +### Attributes + +Once we have created an entity, we now have to define some attributes on it. Attribute definition is very straightforward, but there are some properties which we are going to look at in detail. + + +#### Default/Optional + +Each attribute can be defined as optional or non-optional. A non-optional attribute will cause saving to fail if it's not set on one of the modified objects. At the same time, we can define a default value for each attribute. Nobody is stopping us from declaring an attribute as optional and defining a default value on it. But when you think about it, this doesn't make much sense and can be confusing. So we recommend to always uncheck *optional* for attributes with default values. + + +#### Transient + +Another often-overlooked property of attributes is their *transient* option. Attributes declared as transient are never saved to the persistent store, but they otherwise behave just like normal attributes. This means they will participate in [validation][220], undo management, faulting, etc. Transient attributes can be very useful once you start moving more model logic into managed object subclasses. We'll talk more about this below and how it's better to [use transient attributes instead of ivars][230]. + + + +#### Indexes + +If you've worked with relational databases before, you will be familiar with indexes. If you haven't, you can think of an [index](http://en.wikipedia.org/wiki/Database_index) on an attribute as a way to speed up lookups on it dramatically. It's a trade-off though; indexes speed up reads and slow down writes, because the index has to be updated when the data changes. + +Setting the `indexed` property on an attribute will translate into an index on the underlying table column in SQLite. We can create indexes on any attribute we like, but be aware of write performance implications. Core Data also offers the possibility to create compound indexes (look for the Indexes section in the entity inspector), i.e. indexes which span more than one attribute. This can help performance when you typically use a predicate with conditions on multiple attributes to fetch data. Daniel has an example for this in his article about [fetching data][600]. + + +#### Scalar Types + +Core Data has support for many common data types like integers, floats, booleans, and so on. However, by default, the data model editor generates these attributes as `NSNumber` properties in the managed object subclasses. This often results in endless `floatValue`, `boolValue`, `integerValue`, or similar calls on these `NSNumber` objects in the application code. + +But we can also just specify those properties with their correct scalar type, e.g. as `int64_t`, `float_t`, or `BOOL`, and it will work with Core Data. Xcode even has a little checkbox in the save dialogue of the `NSManagedObject` generator ("Use scalar properties for primitive data types") which does this for you. Anyway, instead of: + + @property (nonatomic, strong) NSNumber *myInteger; + +the property would be declared as: + + @property (nonatomic) int64_t myInteger; + +That's all we have to do to retrieve and store scalar types in Core Data. The documentation still states that Core Data cannot automatically generate accessor methods for scalar values, but that seems to be outdated. + + +#### Storing Other Objects + +Core Data doesn't limit us to only storing values of the predefined types. In fact we can easily store any object which conforms to [`NSCoding`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html) and pretty much anything else including structs with some more work. + +In order to store `NSCoding` compliant objects we use [transformable attributes](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdNSAttributes.html#//apple_ref/doc/uid/TP40001919-SW7). All we have to do is select the "Transformable" type in the drop-down menu and we are ready to go. If you generate the managed object subclasses you will see a property declared like this: + + @property (nonatomic, retain) id anObject; + +We can manually change the type from `id` to whatever we want to store in this attribute to get type checking from the compiler. However, there is one pitfall when using transformable attributes: we must not specify the name of the transformer if we want to use the default transformer (which we mostly want). Even specifying the default transformer's name, `NSKeyedUnarchiveFromDataTransformerName`, will result in [bad things](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdNSAttributes.html#//apple_ref/doc/uid/TP40001919-SW7). + +But it doesn't stop there. We can also create our custom value transformers and use them to store arbitrary object types. We can store anything as long as we can transform it into one of the supported basic types. In order to store not supported non-object types like structs, the basic approach is to create a transient attribute of undefined type and a persistent "shadow attribute" of one of the supported types. Then the accessor methods of the transient attribute are overridden to transform the value from and to the persisted type. This is not trivial, because these accessor methods have to be KVC and KVO compliant and make correct use of Core Data's primitive accessor methods. Please read the [custom code](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdNSAttributes.html#//apple_ref/doc/uid/TP40001919-SW8) section in Apple's guide to [non-standard persistent attributes](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdNSAttributes.html#//apple_ref/doc/uid/TP40001919-SW1). + + +### Fetched Properties + +Fetched properties are mostly used to create relationships across multiple persistent stores. Since having multiple persistent stores already is a very uncommon and advanced use case, fetched properties are rarely used either. + +Behind the scenes, Core Data executes a fetch request and caches the results when we access a fetched property. This fetch request can be configured directly in Xcode's data model editor by specifying a target entity type and a predicate. This predicate is not static though, but can be configured at runtime via the `$FETCH_SOURCE` and `$FETCHED_PROPERTY` variables. Please refer to Apple's [documentation](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW7) for more details. + + +### Relationships + +Relationships between entities should always be defined in both directions. This gives Core Data enough information to fully manage the object graph for us. However, defining relationships bidirectionally is not a strict requirement, although it is strongly recommended. + +If you know what you're doing, you can define unidirectional relationships and Core Data will not complain. However, you just have taken on a lot of responsibilities normally managed by Core Data, including ensuring the consistency of the object graph, change tracking, and undo management. To take a simple example, if we have `Book` and `Author` entities and define a to-one relationship from book to author, deleting an author will not propagate the deletion to the affected books. We still can access the book's author relationship, but we'll get a fault pointing to nowhere. + +It should become clear that unidirectional relationships are basically never what you want. Always define relationships in both ways to stay out of trouble. + + +### Data Model Design + +When designing a data model for Core Data, we have to remember that Core Data is not a relational database. Therefore, we shouldn't design the model like we would design a database schema, but think much more from the perspective of how the data should be used and presented. + +Often it makes sense to [denormalize](http://en.wikipedia.org/wiki/Denormalization) the data model, in order to avoid extensive fetching of related objects when displaying this data. For example, if we have an `Authors` entity with a to-many relationship to a `Books` entity, it might make sense to store the number of books associated with one author in the Author entity, if that's something we want to show later on. + +Let's say we want to show a table view listing all authors and the number of books for each author. If the number of books per author can only be retrieved by counting the number of associated book objects, one fetch request per author cell is necessary to get this data. This is not going to perform very well. We could pre-fetch the book objects using [`relationshipKeyPathsForPrefetching`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSFetchRequest_Class/NSFetchRequest.html#//apple_ref/occ/instm/NSFetchRequest/relationshipKeyPathsForPrefetching), but this might not be ideal either if we have large amounts of books stored. However, if we manage a book count attribute for each author, the author's fetch request gets all the data we need. + +Naturally denormalization comes with the cost of being responsible to ensure that the duplicated data stays in sync. We have to weigh the benefits and drawbacks on a case-by-case basis, because sometimes it is trivial to do, and other times it can create major headaches. This very much depends on the specifics of the data model, if the app has to interact with a backend, or if the data even has to be synched between multiple clients either via a central authority or peer-to-peer. + +Often the data model will already be defined by some kind of backend service and we might just duplicate this model for client applications. However, even in this case we have the freedom to make some modifications to the model on the client side, as long as we can define an unambiguous mapping to the data model of the backend. For the simple example of books and authors, it would be trivial to add a book count property to the `Author` Core Data entity, which lives only on the client side to help with performance, but never gets sent to the server. If we make a change locally or receive new data from the server, we update this attribute and keep it in sync with the rest of the data. + +It's not always as simple as that, but often small changes like this can alleviate serious performance bottlenecks that arise from dealing with a too normalized, relational, database-ish data model. + + + + +### Entity Hierarchy vs. Class Hierarchy + +Managed object models offer the possibility to create entity hierarchies, i.e. we can specify an entity as the parent of another entity. This might sound good, for example, if our entities share some common attributes. In practice, however, it's rarely what you would want to do. + +What happens behind the scenes is that Core Data stores all entities with the same parent entity in the same table. This quickly creates tables with a large number of attributes, slowing down performance. Often the purpose of creating an entity hierarchy is solely to create a class hierarchy, so that we can put code shared between multiple entities into the base class. There is a much better way to achieve this though. + +The entity hierarchy is *independent* of the `NSManagedObject` subclass hierarchy. Or put differently, we don't need to have a hierarchy within our entities to create a class hierarchy. + +Let's have a look at the `Author` and `Book` example again. Both of these entities have common fields, like an identifier, a `createdAt` date field, and a `changedAt` date field. For this case we could create the following structure: + + Entity hierarchy Class hierarchy + ---------------- --------------- + + BaseEntity BaseEntity + | | | | + Authors Books Authors Books + +However, we can equally maintain this class hierarchy while flattening the entity hierarchy: + + Entity hierarchy Class hierarchy + ---------------- --------------- + + Authors Books BaseEntity + | | + Authors Books + +The classes would be declared like this: + + @interface BaseEntity : NSManagedObject + @property (nonatomic) int64_t identifier; + @property (nonatomic, strong) NSDate *createdAt; + @property (nonatomic, strong) NSDate *changedAt; + @end + + @interface Author : BaseEntity + // Author specific code... + @end + + @interface Book : BaseEntity + // Book specific code... + @end + +This gives us the benefit of being able to move common code into the base class without the performance overhead of storing all entities in a single table. We cannot create class hierarchies deviating from the entity hierarchy with Xcode's managed object generator though. But that's a small price to pay, since there are more benefits to not auto-generating managed object classes, as we will discuss [below][210]. + + +### Configurations and Fetch Request Templates + +Everybody who has used Core Data has worked with the entity-modeling aspect of data models. But data models also have two lesser-known or lesser-used aspects: configurations and fetch request templates. + +Configurations are used to define which entity should be stored in which persistent store. Persistent stores are added to the persistent store coordinator using [`addPersistentStoreWithType:configuration:URL:options:error:`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSPersistentStoreCoordinator_Class/NSPersistentStoreCoordinator.html#//apple_ref/occ/instm/NSPersistentStoreCoordinator/addPersistentStoreWithType:configuration:URL:options:error:), where the configuration argument defines this mapping. Now, in almost all cases, we will only use one persistent store, and therefore never deal with multiple configurations. The one default configuration we need for a single store setup is created for us. There are some rare use cases for multiple stores though, and one of those is outlined in the article, [Importing Large Data Sets][120], in this issue. + +Fetch request templates are just what the name suggests: predefined fetch requests stored with the managed object model, which can be used later on using [`fetchRequestFromTemplateWithName:substitutionVariables`](https://developer.apple.com/library/ios/documentation/cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectModel_Class/Reference/Reference.html#//apple_ref/occ/instm/NSManagedObjectModel/fetchRequestFromTemplateWithName:substitutionVariables:). We can define those templates in Xcode's data model editor or in code. Xcode's editor doesn't support all the features of `NSFetchRequest` though. + +To be honest, I have a hard time coming up with convincing use cases of fetch request templates. One advantage is that the predicate of the fetch request will be pre-parsed, so this step doesn't have to happen each time you're performing a new fetch request. This will hardly ever be relevant though, and we are in trouble anyway if we have to fetch that frequently. But if you're looking for a place to define your fetch requests (and you should not define them in view controllers...), check if storing them with the managed object model might be a viable alternative. + + + + +## Managed Objects + +Managed objects are at the heart of any Core Data application. Managed objects live in a managed object context and represent our data. Managed objects are supposed to be passed around in the application, crossing at least the model-controller barrier, and potentially even the controller-view barrier. The latter is somewhat more controversial though, and can be [abstracted in a better way](/issue-1/table-views.html) by e.g. defining a protocol to which an object must conform in order to be consumed by a certain view, or by implementing configuration methods in a view category that bridge the gap from the model object to the specifics of the view. + +Anyway, we shouldn't limit managed objects to the model layer and pull out their data into different structures as soon as we want to pass them around. Managed objects are first-class citizens in a Core Data app and we should use them accordingly. For example, managed objects should be passed between view controllers to provide them with the data they need. + +In order to access the managed object context we often see code like this in view controllers: + + NSManagedObjectContext *context = + [(MyApplicationDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; + +If you already pass a model object to the view controller, it's much better to access the context directly via this object: + + NSManagedObjectContext *context = self.myObject.managedObjectContext; + +This removes the hidden dependency on the application delegate, and makes it much more readable and also easier to test. + + +### Working with Managed Object Subclasses + +Similarly, managed object subclasses are meant to be used. We can and should implement custom model logic, validation logic, and helper methods in these classes and create class hierarchies in order to pull out common code into super classes. The latter is easy to do because of the decoupling of the class and the entity hierarchy as [mentioned above][240]. + +You may wonder how to implement custom code in managed object subclasses if Xcode keeps overwriting these files when regenerating them. Well, the answer is pretty simple: don't generate them with Xcode. If you think about it, the generated code in these classes is trivial and very easy to write yourself, or generate once and then keep up to date manually. It's really just a bunch of property declarations. + +There are other solutions like putting the custom code in a category, or using tools like [mogenerator](https://github.com/rentzsch/mogenerator), which creates a base class for each entity and a subclass of it where the user-written code is supposed to go. But none of these solutions allow for a flexible class hierarchy independent of the entity hierarchy. So at the cost of writing a few lines of trivial code, our advice is to just write those classes manually. + + + + +#### Instance Variables in Managed Object Subclasses + +Once we start using our managed object subclasses to implement model logic, we might want to create some instance variables to cache computed values or something similar. It's much more convenient though to use transient properties for this purpose. The reason is that the lifecycle of managed objects is somewhat different from normal objects. Core Data often [faults](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdFaultingUniquing.html#//apple_ref/doc/uid/TP30001202-CJBDBHCB) objects that are no longer needed. If we would use instance variables, we would have to manually participate in this process and release our ivars. If we use transient properties instead, all this is done for us. + + + +#### Creating New Objects + +One good example of implementing useful helper methods on our model classes is a class method to insert a new object into a managed object context. Core Data's API for creating new objects is not very intuitive: + + Book *newBook = [NSEntityDescription insertNewObjectForEntityForName:@"Book" + inManagedObjectContext:context]; + +Luckily, we can easily solve this task in a much more elegant manner in our own subclass: + + @implementation Book + // ... + + + (NSString *)entityName + { + return @"Book" + } + + + (instancetype)insertNewObjectIntoContext:(NSManagedObjectContext *)context + { + return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] + inManagedObjectContext:context]; + } + @end + +Now, creating a new book object is much easier: + + Book *book = [Book insertNewObjectIntoContext:context]; + +Of course, if we subclass our actual model classes from a common base class, we should move the `insertNewObjectIntoContext:` and `entityName` class methods into the common super class. Then each subclass only needs to overwrite `entityName`. + + +#### To-Many Relationship Mutators + +If you generate a managed object subclass with Xcode which has a to-many relationship, it will create methods like this to add and remove objects to and from this relationship: + + - (void)addBooksObject:(Book *)value; + - (void)removeBooksObject:(Book *)value; + - (void)addBooks:(NSSet *)values; + - (void)removeBooks:(NSSet *)values; + +Instead of using those four mutator methods, there is a much more elegant way of doing this, especially if we don't generate the managed object subclasses. We can simply use the [`mutableSetValueForKey:`](https://developer.apple.com/library/mac/documentation/cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html#//apple_ref/occ/instm/NSObject/mutableSetValueForKey:) method to retrieve a mutable set of related objects (or [`mutableOrderedSetValueForKey:`](https://developer.apple.com/library/mac/documentation/cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html#//apple_ref/occ/instm/NSObject/mutableOrderedSetValueForKey:) for ordered relationships). This can be encapsulated into a simple accessor method: + + - (NSMutableSet *)mutableBooks + { + return [self mutableSetValueForKey:@"books"]; + } + +Then we can use this mutable set like any other set. Core Data will pick up the changes and do the rest for us: + + Book *newBook = [Book insertNewObjectIntoContext:context]; + [author.mutableBooks addObject:newBook]; + +  + + + + +#### Validation + +Core Data supports various ways of data validation. Xcode's data model editor lets us specify some basic requirements on our attribute, like a string's minimum and maximum length or a minimum and maximum number of objects in a to-many relationship. But beyond that, there's much we can do in code. + +The section ["Managed Object Validation"](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdValidation.html#//apple_ref/doc/uid/TP40004807-SW1) is the go-to place for in-depth information on this topic. Core Data supports property-level validation by implementing `validate:error:` methods, as well as inter-property validation via [`validateForInsert:`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/Reference/NSManagedObject.html#//apple_ref/occ/instm/NSManagedObject/validateForInsert:), [`validateForUpdate:`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/Reference/NSManagedObject.html#//apple_ref/occ/instm/NSManagedObject/validateForUpdate:), and [`validateForDelete:`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/Reference/NSManagedObject.html#//apple_ref/occ/instm/NSManagedObject/validateForDelete:). Validation happens automatically before saving, but we can also trigger it manually on the property level with [`validateValue:forKey:error:`](https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/Reference/NSManagedObject.html#//apple_ref/occ/instm/NSManagedObject/validateValue:forKey:error:). + + +## Conclusion + +Data models and model objects are the bread and butter of any Core Data application. We would like to encourage you not to jump to convenience wrappers right away, but to embrace working with managed object subclasses and objects. With something like Core Data, it is very important to understand what's happening, otherwise it can backfire very easily once your application grows and becomes more complex. + +We hope to have demonstrated some simple techniques to make working with managed objects easier without introducing any magic. Additionally, we peeked into some very advanced stuff we can do with data models in order to give you an idea of what's possible. Use these techniques sparingly though, as at the end of the day, simplicity mostly wins. + diff --git a/2013-09-09-core-data-overview.md b/2013-09-09-core-data-overview.md new file mode 100644 index 0000000..ecc2c32 --- /dev/null +++ b/2013-09-09-core-data-overview.md @@ -0,0 +1,171 @@ +--- +layout: post +title: Core Data Overview +category: "4" +date: "2013-09-09 11:00:00" +author: "Daniel Eggert" +tags: article +--- + +{% include links-4.md %} + + +Core Data is probably one of the most misunderstood Frameworks on OS X and iOS. To help with that, we'll quickly go through Core Data to give you an overview of what it is all about, as understanding Core Data concepts is essential to using Core Data the right way. Just about all frustrations with Core Data originate in misunderstanding what it does and how it works. Let's dive in... + +## What is Core Data? + +More than eight years ago, in April 2005, Apple released OS X version 10.4, which was the first to sport the Core Data framework. That was back when YouTube launched. + +Core Data is a model layer technology. Core Data helps you build the model layer that represents the state of your app. Core Data is also a persistent technology, in that it can persist the state of the model objects to disk. But the important takeaway is that Core Data is much more than just a framework to load and save data. It's also about working with the data while it's in memory. + +If you've worked with [Object-relational mapping (O/RM)](https://en.wikipedia.org/wiki/Object-relational_mapping) before: Core Data is **not** an O/RM. It's much more. If you've been working with [SQL](https://en.wikipedia.org/wiki/Sql) wrappers before: Core Data is **not** an SQL wrapper. It does by default use SQL, but again, it's a way higher level of abstraction. If you want an O/RM or SQL wrapper, Core Data is not for you. + +One of the very powerful things that Core Data provides is its object graph management. This is one of the pieces of Core Data you need to understand and learn in order to bring the powers of Core Data into play. + +On a side note: Core Data is entirely independent from any UI-level frameworks. It's, by design, purely a model layer framework. And on OS X it may make a lot of sense to use it even in background daemons and the like. + +## The Stack + +There are quite a few components to Core Data. It's a very flexible technology. For most uses cases, the setup will be relatively simple. + +When all components are tied together, we refer to them as the *Core Data Stack*. There are two main parts to this stack. One part is about object graph management, and this should be the part that you know well, and know how to work with. The second part is about persistence, i.e. saving the state of your model objects and retrieving the state again. + +In between the two parts, in the middle of the stack, sits the Persistent Store Coordinator (PSC), also known to friends as the *central scrutinizer*. It ties together the object graph management part with the persistence part. When one of the two needs to talk to the other, this is coordinated by the PSC. + + + +The *object graph management* is where your application's model layer logic will live. Model layer objects live inside a context. In most setups, there's one context and all objects live in that context. Core Data supports multiple contexts, though, for more advanced use cases. Note that contexts are distinct from one another, as we'll see in a bit. The important thing to remember is that objects are tied to their context. Each *managed* object knows which context it's in, and each context known which objects it is managing. + +The other part of the stack is where persistency happens, i.e. where Core Data reads and writes from / to the file system. In just about all cases, the persistent store coordinator (PSC) has one so-called *persistent store* attached to it, and this store interacts with a [SQLite](https://www.sqlite.org) database in the file system. For more advanced setups, Core Data supports using multiple stores that are attached to the same persistent store coordinator, and there are a few store types than just SQL to choose from. + +The most common scenario, however, looks like this: + + + +## How the Components Play Together + +Let's quickly walk through an example to illustrate how these components play together. In our article about a [full application using Core Data][400], we have exactly one *entity*, i.e. one kind of object: We have an *Item* entity that holds on to a title. Each item can have sub-items, hence we have a *parent* and a *child* relationship. + +This is our data model. As we mention in the article about [Data Models and Model Objects][200], a particular *kind* of object is called an *Entity* in Core Data. In this case we have just one entity: an *Item* entity. And likewise, we have a subclass of `NSManagedObject` which is called `Item`. The *Item* entity maps to the `Item` class. The [data models article][200] goes into more detail about this. + +Our app has a single *root* item. There's nothing magical to it. It's simply an item we use to show the bottom of the item hierarchy. It's an item that we'll never set a parent on. + +When the app launches, we set up our stack as depicted above, with one store, one managed object context, and a persistent store coordinator to tie the two together. + +On first launch, we don't have any items. The first thing we need to do is to create the *root* item. You add managed objects by inserting them into the context. + +### Creating Objects + +It may seem cumbersome. The way to insert objects is with the + + + (id)insertNewObjectForEntityForName:(NSString *)entityName + inManagedObjectContext:(NSManagedObjectContext *)context + +method on `NSEntityDescription`. We suggest that you add two convenience methods to your model class: + + + (NSString *)entityName + { + return @“Item”; + } + + + (instancetype)insertNewObjectInManagedObjectContext:(NSManagedObjectContext *)moc; + { + return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] + inManagedObjectContext:moc]; + } + +Now, we can insert our root object like so: + + Item *rootItem = [Item insertNewObjectInManagedObjectContext:managedObjectContext]; + +Now there's a single item in our managed object context (MOC). The context knows about this newly inserted *managed object* and the managed object `rootItem` knows about the context (it has a `-managedObjectContext` method). + +### Saving Changes + +At this point, though, we have not touched the persistent store coordinator or the persistent store, yet. The new model object, `rootItem`, is just in memory. If we want to save the state of our model objects (in this case just that one object), we need to *save* the context: + + NSError *error = nil; + if (! [managedObjectContext save:&error]) { + // Uh, oh. An error happened. :( + } + +At this point, a lot is going to happen. First, the managed object context figures out what has changed. It is the context's responsibility to track any and all changes you make to any managed objects inside that context. In our case, the only change we've made thus far is inserting one object, our `rootItem`. + +The managed object context then passes these changes on to the persistent store coordinator and asks it to propagate the changes through to the store. The persistent store coordinator coordinates with the store (in our case, an SQL store) to write our inserted object into the SQL database on disk. The `NSPersistentStore` class manages the actual interaction with SQLite and generates the SQL code that needs to be executed. The persistent store coordinator's role is to simply coordinate the interaction between the store and the context. In our case, that role is relatively simple, but complex setups can have multiple stores and multiple contexts. + +### Updating Relationships + +The power of Core Data is managing relationships. Let's look at the simple case of adding our second item and making it a child item of the `rootItem`: + + Item *item = [Item insertNewObjectInManagedObjectContext:managedObjectContext]; + item.parent = rootItem; + item.title = @"foo"; + +That's it. Again, these changes are only inside the managed object context. Once we save the context, however, the managed object context will tell the persistent store coordinator to add that newly created object to the database file just like for our first object. But it will also update the relationship from our second item to the first and the other way around, from the first object to the second. Remember how the *Item* entity has a *parent* and a *children* relationship. These are reverse relationships of one another. Because we set the first item to be the parent of the second, the second will be a child of the first. The managed object context tracks these relationships and the persistent store coordinator and the store persist (i.e. save) these relationships to disk. + + + +### Getting to Objects + +Let's say we've been using our app for a while and have added a few sub-items to the root item, and even sub-items to the sub-items. Then we launch our app again. Core Data has saved the relationships of the items in the database file. The object graph is persisted. We now need to get to our *root* item, so we can show the bottom-level list of items. There are two ways for us to do that. We'll look at the simpler one first. + +When we created our `rootItem` object, and once we've saved it, we can ask it for its `NSManagedObjectID`. This is an opaque object that uniquely represents that object. We can store this into e.g. `NSUSerDefaults`, like this: + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setURL:rootItem.managedObjectID.URIRepresentation forKey:@"rootItem"]; + +Now when the app is relaunched, we can get back to the object like so: + + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSURL *uri = [defaults URLForKey:@"rootItem"]; + NSManagedObjectID *moid = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri]; + NSError *error = nil; + Item *rootItem = (id) [managedObjectContext existingObjectWithID:moid error:&error]; + +Obviously, in a real app, we'd have to check if `NSUserDefaults` actually returns a valid value. + +What just happened is that the managed object context asked the persistent store coordinator to get that particular object from the database. The root object is now back inside the context. However, all the other items are not in memory, yet. + +The `rootItem` has a relationship called `children`. But there's nothing there, yet. We want to display the sub-items of our `rootItem`, and hence we'll call: + + NSOrderedSet *children = rootItem.children; + +What happens now, is that the context notes that the relationship `children` from that `rootItem` is a so-called *fault*. Core Data has marked the relationship as something it still has to resolve. And since we're accessing it at this point, the context will now automatically coordinate with the persistent store coordinator to bring those child items into the context. + +This may sound very trivial, but there's actually a lot going on at this point. If any of the child objects happen to already be in memory, Core Data guarantees that it will reuse those objects. That's what is called *uniquing*. Inside the context, there's never going to be more than a single object representing a given item. + +Secondly, the persistent store coordinator has its own internal cache of object values. If the context needs a particular object (e.g. a child item), and the persistent store coordinator already has the needed values in its cache, the object (i.e. the item) can be added to the context without talking to the store. That's important, because accessing the store means running SQL code, which is much slower than using values already in memory. + +As we continue to traverse from item to sub-item to sub-item, we're slowly bringing the entire object graph into the managed object context. Once it's all in memory, operating on objects and traversing the relationships is super fast, since we're just working inside the managed object context. We don't need to talk to the persistent store coordinator at all. Accessing the `title`, `parent`, and `children` properties on our `Item` objects is super fast and efficient at this point. + +It's important to understand how data is fetched in these cases, since it affects performance. In our particular case, it doesn't matter too much, since we're not touching a lot of data. But as soon as you do, you'll need to understand what goes on under the hood. + +When you traverse a relationship (such as `parent` or `children` in our case) one of three things can happen: (1) the object is already in the context and traversing is basically for free. (2) The object is not in the context, but the persistent store coordinator has its values cached, because you've recently retrieved the object from the store. This is reasonably cheap (some locking has to occur, though). The expensive case is (3) when the object is accessed for the first time by both the context and the persistent store coordinator, such that is has to be retrieved by store from the SQLite database. This last case is much more expensive than (1) and (2). + +If you know you have to fetch objects from the store (because you don't have them), it makes a huge difference when you can limit the number of fetches by getting multiple objects at once. In our example, we might want to fetch all child items in one go instead of one-by-one. This can be done by crafting a special `NSFetchRequest`. But we must take care to only to run a fetch request when we need to, because a fetch request will also cause option (3) to happen; it will always access the SQLite database. Hence, when performance matters, it makes sense to check if objects are already around. You can use [`-[NSManagedObjectContext objectRegisteredForID:]`](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html#//apple_ref/occ/instm/NSManagedObjectContext/objectRegisteredForID:) for that. + + +### Changing Object Values + +Now, let's say we are changing the `title` of one of our `Item` objects: + + item.title = @"New title"; + +When we do this, the items title changes. But additionally, the managed object context marks the specific managed object (`item`) as changed, such that it will be saved through the persistent store coordinator and attached store when we call `-save:` on the context. One of the key responsibilities of the context is *change tracking*. + +The context knows which objects have been inserted, changed, and deleted since the last save. You can get to those with the `-insertedObjects`, `-updatedObjects`, and `-deletedObjects` methods. Likewise, you can ask a managed object which of its values have changed by using the `-changedValues` method. You will probably never have to. But this is what Core Data uses to be able to push changes you make to the backing database. + +When we inserted new `Item` objects above, this is how Core Data knew it had to push those to the store. And now, when we changed the `title`, the same thing happened. + +Saving values needs to coordinate with both the persistent store coordinator and the persistent store, which, in turn, accesses the SQLite database. As when retrieving objects and values, accessing the store and database is relatively expensive when compared to simply operating on objects in memory. There's a fixed cost for a save, regardless of how many changes you're saving. And there's a per-change cost. This is simply how SQLite works. When you're changing a lot of things, you should therefore try to batch changes into reasonably sized batches. If you save for each change, you'd pay a high price, because you have to save very often. If you save to rarely, you'd have a huge batch of changes that SQLite would have to process. + +It is also important to note that saves are atomic. They're transactional. Either all changes will be committed to the store / SQLite database or none of the changes will be saved. This is important to keep in mind when implementing custom [`NSIncrementalStore`](https://developer.apple.com/library/ios/DOCUMENTATION/CoreData/Reference/NSIncrementalStore_Class/Reference/NSIncrementalStore.html) subclasses. You have to either guarantee that a save will never fail (e.g. due to conflicts), or your store subclass has to revert all changes when the save fails. Otherwise, the object graph in memory will end up being inconsistent with the one in the store. + +Saves will normally never fail if you use a simple setup. But Core Data allows multiple contexts per persistent store coordinator, so you can run into conflicts at the persistent store coordinator level. Changes are per-context, and another context may have introduced conflicting changes. And Core Data even allows for completely separate stacks both accessing the same SQLite database file on disk. That can obviously also lead to conflicts (i.e. one context trying to update a value on an object that was deleted by another context). Another reason why a save can fail is validation. Core Data supports complex validation policies for objects. It's an advanced topic. A simple validation rule could be that the `title` of an `Item` must not be longer than 300 characters. But Core Data also supports complex validation policies across properties. + +## Final Words + +If Core Data seems daunting, that's most likely because its flexibility allows you to use it in very complex ways. As always: try to keep things as simple as possible. It will make development easier and save you and your user from trouble. Only use the more complex things such as background contexts if you're certain they will actually help. + +When you're using a simple Core Data stack, and you use managed objects the way we've tried to outline in this issue, you'll quickly learn to appreciate what Core Data can do for you, and how it speeds up your development cycle. + diff --git a/2013-09-09-editorial.md b/2013-09-09-editorial.md new file mode 100644 index 0000000..e5f0ad2 --- /dev/null +++ b/2013-09-09-editorial.md @@ -0,0 +1,32 @@ +--- +layout: post +title: "Editorial" +category: "4" +date: "2013-09-09 12:00:00" +tags: editorial +--- + +[100]:/issue-4/importing-large-data-sets-into-core-data.html +[200]:/issue-4/core-data-models-and-model-objects.html +[300]:/issue-4/core-data-overview.html +[400]:/issue-4/full-core-data-application.html +[500]:/issue-4/SQLite-instead-of-core-data.html +[600]:/issue-4/core-data-fetch-requests.html +[700]:/issue-4/core-data-migration.html + + +Welcome to objc.io issue #4! + +In this issue we are taking an in-depth look into Apple's model layer framework, Core Data. + +Core Data is a powerful object graph management and persistence framework, which has been available on both iOS and OS X for many years. Nevertheless, even experienced developers often don't use it. Additionally, there is a lot of misleading information about Core Data on the Internet, which can confuse newcomers. + +The articles in this issue provide a deeper overview of what Core Data is, how it works, and how you should use it. If you haven't worked with Core Data before, you will find the [overview][300] and the example of building a [full Core Data application][400] useful. If you're familiar with Core Data, we have several articles which cover the topics of [managed objects][200], [fetching data][600], [importing large data sets][100], and [migrations][700] (the latter is thanks to our guest writer Martin Hwasser). + +Last but not least, special guest writer [Brent Simmons](http://inessential.com) gives you an overview of [how he uses SQLite directly][500] in his applications, including the recently released [Vesper](http://vesperapp.co), but why you probably shouldn't. It's an interesting peek behind the scenes that shows what it takes to manually recreate what Core Data does for us. + +We wish you a successful iOS 7 launch month! + +All the best from Berlin, + +Chris, Daniel, and Florian. diff --git a/2013-09-09-full-core-data-application.md b/2013-09-09-full-core-data-application.md new file mode 100644 index 0000000..e04ae5d --- /dev/null +++ b/2013-09-09-full-core-data-application.md @@ -0,0 +1,584 @@ +--- +layout: post +title: A Complete Core Data Application +category: "4" +date: "2013-09-09 10:00:00" +author: "Chris Eidhof" +tags: article +--- + +{% include links-4.md %} + +In this article, we will build a small but complete Core Data backed application. +The application allows you to create nested lists; each list item can have a sub-list, allowing you to create very deep hierarchies of items. +Instead of using the Xcode template for Core Data, we will build our stack by hand, in order to fully understand what's going on. +The example code for this application is on [GitHub](https://github.com/objcio/issue-4-full-core-data-application). + +### How Will We Build It? + +First, we will create a `PersistentStack` object that, given a Core Data Model and a filename, returns a managed object context. Then we will build our Core Data Model. Next, we will create a simple table view controller that shows the root list of items using a fetched results controller, and add interaction step-by-step, by adding items, navigating to sub-items, deleting items, and adding undo support. + +## Set Up the Stack + +We will create a managed object context for the main queue. In older +code, you might see `[[NSManagedObjectContext alloc] init]`. These days, +you should use the `initWithConcurrencyType:` initializer to make it +explicit that you're using the queue-based concurrency model: + + - (void)setupManagedObjectContext + { + self.managedObjectContext = + [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; + self.managedObjectContext.persistentStoreCoordinator = + [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; + NSError* error; + [self.managedObjectContext.persistentStoreCoordinator + addPersistentStoreWithType:NSSQLiteStoreType + configuration:nil + URL:self.storeURL + options:nil + error:&error]; + if (error) { + NSLog(@"error: %@", error); + } + self.managedObjectContext.undoManager = [[NSUndoManager alloc] init]; + } + +It's important to check the error, because this will probably fail a +lot during development. When you change your data model, Core Data detects this and will +not continue. You can also pass in options to instruct Core Data about +what to do in this case, which Martin explains thoroughly in his article +about [migrations][700]. Note that the +last line adds an undo manager; we will need this later. On iOS, you +need to explicitly add an undo manager, whereas on Mac it is there by +default. + +This code creates a really simple Core Data Stack: one managed object +context, which has a persistent store coordinator, which has one +persistent store. [More complicated setups][310] are possible; the most +common is to have multiple managed object contexts (each on a +separate queue). + +## Creating a Model + +Creating a model is simple, as we just add a new file to our project, choosing the +Data Model template (under Core Data). This model file will get compiled to a +file with extension `.momd`, which we will load at runtime to +create a `NSManagedObjectModel`, which is needed for the persistent store. The +source of the model is simple XML, and in our experience, you typically won't +have any merge problems when checking it into source control. +It is also possible to create a managed object model in code, if you +prefer that. + +Once you create the model, you can add an `Item` entity with two +attributes: `title`, which is a string, and `order`, which is an integer. +Then, you add two relationships: `parent`, which +relates an item to its parent, and `children`, which is a to-many +relationship. Set the relationships as the inverse of one another, which means +that if you set `a`'s parent to be `b`, then `b` will have `a` in its children +automatically. + +Normally, you could even use ordered relationships, and leave out the +`order` property entirely. However, they don't play together nicely with +fetched results controllers (which we will use later on). We would +either need to reimplement part of fetched results controllers, or +reimplement the ordering, and we chose the latter. + +Now, choose *Editor > Create NSManagedObject subclass...* from the menu, and +create a subclass of `NSManagedObject` that is tied to this entity. This +creates two files: `Item.h` and `Item.m`. There is an extra category in the header +file, which we will delete immediately (it is there for legacy reasons). + + +### Create a `Store` Class + +For our model, we will create a root node that is the start of our item +tree. We need a place to create this root node and to find it later. +Therefore, we create a simple `Store` class, that does exactly this. It +has a managed object context, and one method `rootItem`. +In our app delegate, we will find this root item at launch +and pass it to our root view controller. As an optimization, you can +store the object id of the item in the user defaults, in order to look it up even +faster: + + - (Item*)rootItem + { + NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:@"Item"]; + request.predicate = [NSPredicate predicateWithFormat:@"parent = %@", nil]; + NSArray* objects = [self.managedObjectContext executeFetchRequest:request error:NULL]; + Item* rootItem = [objects lastObject]; + if (rootItem == nil) { + rootItem = [Item insertItemWithTitle:nil + parent:nil + inManagedObjectContext:self.managedObjectContext]; + } + return rootItem; + } + + +Adding an item is mostly +straightforward. +However, we have to set the `order` property to be +larger than any of the existing items with that parent. The invariant we +will use is +that the first child has an `order` of 0, and every subsequent child has an +`order` value that is 1 higher. +We create a custom method on the `Item` class where we +put the logic: + + + (instancetype)insertItemWithTitle:(NSString*)title + parent:(Item*)parent + inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext + { + NSUInteger order = parent.numberOfChildren; + Item* item = [NSEntityDescription insertNewObjectForEntityForName:self.entityName + inManagedObjectContext:managedObjectContext]; + item.title = title; + item.parent = parent; + item.order = @(order); + return item; + } + +The number of children is a very simple method: + + - (NSUInteger)numberOfChildren + { + return self.children.count; + } + +To support automatic updates to our table view, we will use a fetched +results controller. A fetched results controller is an object that can +manage a fetch request with a big number of items and is the perfect +Core Data companion to a table view, as we will see in the next section: + + - (NSFetchedResultsController*)childrenFetchedResultsController + { + NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:[self.class entityName]]; + request.predicate = [NSPredicate predicateWithFormat:@"parent = %@", self]; + request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"order" ascending:YES]]; + return [[NSFetchedResultsController alloc] initWithFetchRequest:request + managedObjectContext:self.managedObjectContext + sectionNameKeyPath:nil + cacheName:nil]; + } + +## Add a Table-View Backed by Fetched Results Controller + +Our next step is to create the root view controller: a table view, which +gets its data from an `NSFetchedResultsController`. The fetched results +controller manages your fetch request, and if you assign a delegate, it +also notifies you of any changes in the managed object context. In +practice, this means that if you implement the delegate methods, you can +automatically update your table view when relevant changes happen in the +data model. For example, if you synchronize in a background thread, +and then you store the changes in the database, your table view will +update automatically. + + +### Creating the Table View's Data Source + +In our article on [lighter view controllers](/issue-1/lighter-view-controllers.html), we demonstrated how to separate out the data source of a table view. We will do exactly the same for a fetched results controller; we create a separate class `FetchedResultsControllerDataSource` that acts as a table view's data source, and by listening to the fetched results controller, updates the table view automatically. + +We initialize the object with a table view, and the initializer looks like this: + + - (id)initWithTableView:(UITableView*)tableView + { + self = [super init]; + if (self) { + self.tableView = tableView; + self.tableView.dataSource = self; + } + return self; + } + +When we set the fetch results controller, we have to make ourselves the +delegate, and perform the initial fetch. It is easy to forget the +`performFetch:` call, and you will get no results (and no errors): + + - (void)setFetchedResultsController:(NSFetchedResultsController*)fetchedResultsController + { + _fetchedResultsController = fetchedResultsController; + fetchedResultsController.delegate = self; + [fetchedResultsController performFetch:NULL]; + } + +Because our class implements the `UITableViewDataSource` protocol, we need to implement some methods for that. In these two methods we just ask the fetched results controller for the required information: + + - (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView + { + return self.fetchedResultsController.sections.count; + } + + - (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)sectionIndex + { + id section = self.fetchedResultsController.sections[sectionIndex]; + return section.numberOfObjects; + } + +However, when we need to create cells, it requires some simple steps: we +ask the fetched results controller for the right object, we dequeue a +cell from the table view, and then we tell our delegate (which will be a +view controller) to configure that cell with the object. Now, we have a +nice separation of concerns, as the view controller only has to care about +updating the cell with the model object: + + - (UITableViewCell*)tableView:(UITableView*)tableView + cellForRowAtIndexPath:(NSIndexPath*)indexPath + { + id object = [self.fetchedResultsController objectAtIndexPath:indexPath]; + id cell = [tableView dequeueReusableCellWithIdentifier:self.reuseIdentifier + forIndexPath:indexPath]; + [self.delegate configureCell:cell withObject:object]; + return cell; + } + +### Creating the Table View Controller + +Now, we can create a view controller that displays a list of items using +the class we just created. In the example app, we created a Storyboard, +and added a navigation controller with a table view controller. This +automatically sets the view controller as the data source, which is not +what we want. Therefore, in our `viewDidLoad`, we do the following: + + fetchedResultsControllerDataSource = + [[FetchedResultsControllerDataSource alloc] initWithTableView:self.tableView]; + self.fetchedResultsControllerDataSource.fetchedResultsController = + self.parent.childrenFetchedResultsController; + fetchedResultsControllerDataSource.delegate = self; + fetchedResultsControllerDataSource.reuseIdentifier = @"Cell"; + + +In the initializer of the fetched results controller data source, the +table view's data source gets set. The reuse identifier matches the one +in the Storyboard. Now, we have to implement the delegate method: + + - (void)configureCell:(id)theCell withObject:(id)object + { + UITableViewCell* cell = theCell; + Item* item = object; + cell.textLabel.text = item.title; + } + +Of course, you could do a lot more than just setting the text label, but +you get the point. Now we have pretty much everything in place for +showing data, but as there is no way to add anything yet, it looks +pretty empty. + +## Adding Interactivity + +We will add a couple of ways of interacting with the data. First, we +will make it possible to add items. Then we will implement the fetched +results controller's delegate methods to update the table view, and add +support for deletion and undo. + +### Adding Items + +To add items, we steal the interaction design from +[Clear](http://www.realmacsoftware.com/clear/), which is high on my list +of most beautiful apps. We add a text field as the table view's header, +and modify the content inset of the table view to make sure it stays +hidden by default, as explained in Joe's [scroll view +article](/issue-3/scroll-view.html). As always, the full code is on github, but +here's the relevant call to inserting the item, in `textFieldShouldReturn`: + + [Item insertItemWithTitle:title + parent:self.parent + inManagedObjectContext:self.parent.managedObjectContext]; + textField.text = @""; + [textField resignFirstResponder]; + +### Listening to Changes + +The next step is making sure that your table view inserts a row for the +newly created item. There are several ways to go about this, but we'll use +the fetched results controller's delegate method: + + - (void)controller:(NSFetchedResultsController*)controller + didChangeObject:(id)anObject + atIndexPath:(NSIndexPath*)indexPath + forChangeType:(NSFetchedResultsChangeType)type + newIndexPath:(NSIndexPath*)newIndexPath + { + if (type == NSFetchedResultsChangeInsert) { + [self.tableView insertRowsAtIndexPaths:@[newIndexPath] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + } + +The fetched results controller also calls these methods for deletions, +changes, and moves (we'll implement that later). If you have multiple +changes happening at the same time, you can implement two more methods +so that the table view will animate everything at the same time. For +simple single-item insertions and deletions, it doesn't make a difference, +but if you choose to implement syncing at some time, it makes everything +a lot prettier: + + - (void)controllerWillChangeContent:(NSFetchedResultsController*)controller + { + [self.tableView beginUpdates]; + } + + - (void)controllerDidChangeContent:(NSFetchedResultsController*)controller + { + [self.tableView endUpdates]; + } + +#### Using a Collection View + +It's worth noting that fetched results controllers are not at all +limited to table views; you can use them with any kind of view. Because +they are index-path-based, they also work really well with +collection views, although you unfortunately have to jump through some +minor hoops in order to make it work perfectly, as a collection view +doesn't have a `beginUpdates` and `endUpdates` method, but rather a single +method `performBatchUpdates`. To deal with this, you can collect all the +updates you get, and then in the `controllerDidChangeContent`, perform +them all inside the block. Ash Furrow wrote an example of [how you could +do this](https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController). + +#### Implementing Your Own Fetched Results Controller + +You don't have to use `NSFetchedResultsController`. In fact, in a lot of +cases it might make sense to create a similar class that works +specifically for your application. What you can do is subscribe to the +`NSManagedObjectContextObjectsDidChangeNotification`. You then get a +notification, and the `userInfo` dictionary will contain a list of the +changed objects, inserted objects, and deleted objects. Then you can +process them in any way you want. + +### Passing Model Objects Around + +Now that we can add and list items, it's time to make sure we can make +sub-lists. In the Storyboard, you can create a segue by dragging from a +cell to the view controller. It's wise to give the segue a name, so that +it can be identified if we ever have multiple segues originating from +the same view controller. + +My pattern for dealing with segues looks like this: first, you try to +identify which segue it is, and for each segue you pull out a separate +method that prepares the destination view controller: + + - (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender + { + [super prepareForSegue:segue sender:sender]; + if ([segue.identifier isEqualToString:selectItemSegue]) { + [self presentSubItemViewController:segue.destinationViewController]; + } + } + + - (void)presentSubItemViewController:(ItemViewController*)subItemViewController + { + Item* item = [self.fetchedResultsControllerDataSource selectedItem]; + subItemViewController.parent = item; + } + +The only thing the child view controller needs is the item. From the +item, it can also get to the managed object context. +We get the selected item from our data source (which looks up +the table view's selected item index and fetches the correct item from the +fetched results controller). It's as simple as that. + +One pattern that's unfortunately very common is having the managed +object context as a property on the app delegate, and then always accessing +it from everywhere. This is a bad idea. If you ever want to use a +different managed object context for a certain part of your view +controller hierarchy, this will be very hard to refactor, +and additionally, your code will be a lot more difficult to test. + +Now, try adding an item in the sub-list, and you will probably get a +nice crash. This is because we now have two fetched results controllers, +one for the topmost view controller, but also one for the root view +controller. The latter one tries to update its table view, which is +offscreen, and everything crashes. The solution is to tell our data +source to stop listening to the fetched results controller delegate methods: + + - (void)viewWillAppear:(BOOL)animated + { + [super viewWillAppear:animated]; + self.fetchedResultsControllerDataSource.paused = NO; + } + + - (void)viewWillDisappear:(BOOL)animated + { + [super viewWillDisappear:animated]; + self.fetchedResultsControllerDataSource.paused = YES; + } + +One way to implement this inside the data source is setting the fetched +results controller's delegate to nil, so that no updates are received +any longer. We then need to add it after we come out of `paused` state: + + - (void)setPaused:(BOOL)paused + { + _paused = paused; + if (paused) { + self.fetchedResultsController.delegate = nil; + } else { + self.fetchedResultsController.delegate = self; + [self.fetchedResultsController performFetch:NULL]; + [self.tableView reloadData]; + } + } + +The `performFetch` will then make sure your data source is up to date. +Of course, a nicer implementation would be to not set the delegate to nil, but +instead keep a list of the changes that happened while in paused state, +and update the table view accordingly after you get out of paused state. + +### Deletion + +To support deletion, we need to take a few steps. First, we need to +convince the table view that we support deletion, and second, we need to +delete the object from core data and make sure our order invariant stays +correct. + +To allow for swipe to delete, we need to implement two methods in the +data source: + + - (BOOL)tableView:(UITableView*)tableView + canEditRowAtIndexPath:(NSIndexPath*)indexPath + { + return YES; + } + + - (void)tableView:(UITableView *)tableView + commitEditingStyle:(UITableViewCellEditingStyle)editingStyle + forRowAtIndexPath:(NSIndexPath *)indexPath { + if (editingStyle == UITableViewCellEditingStyleDelete) { + id object = [self.fetchedResultsController objectAtIndexPath:indexPath] + [self.delegate deleteObject:object]; + } + } + +Rather than deleting immediately, we tell our delegate (the view +controller) to delete the object. That way, we don't have to share the +store object with our data source (the data source should be reusable +across projects), and we keep the flexibility to do any custom actions. +The view controller simply calls `deleteObject:` on the managed object +context. + +However, there are two important problems to solve: what do we do with +the children of the item that we delete, and how do we enforce our order +variant? Luckily, propagating deletion is easy: in our data model, we +can choose *Cascade* as the delete rule for the children relationship. + +For enforcing our order variant, we can override the +`prepareForDeletion` method, and update all the siblings with a higher +`order`: + + - (void)prepareForDeletion + { + NSSet* siblings = self.parent.children; + NSPredicate* predicate = [NSPredicate predicateWithFormat:@"order > %@", self.order]; + NSSet* siblingsAfterSelf = [siblings filteredSetUsingPredicate:predicate]; + [siblingsAfterSelf enumerateObjectsUsingBlock:^(Item* sibling, BOOL* stop) + { + sibling.order = @(sibling.order.integerValue - 1); + }]; + } + +Now we're almost there. We can interact with table view cells and delete +the model object. The final step is to implement the necessary code to +delete the table view cells once the model objects get deleted. In our +data source's `controller:didChangeObject:...` method we add another if +clause: + + ... + else if (type == NSFetchedResultsChangeDelete) { + [self.tableView deleteRowsAtIndexPaths:@[indexPath] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + + +### Add Undo Support + +One of the nice things about Core Data is that it comes with integrated +undo support. We will add *the shake to undo* feature, and a first step +is telling the application that we can do this: + + application.applicationSupportsShakeToEdit = YES; + +Now, whenever a shake is triggered, the application will ask the first +responder for its undo manager, and perform an undo. In [last month's +article](http://www.objc.io/issue-3/custom-controls.html), we saw that a +view controller is also in the responder chain, and this is exactly +what we'll use. In our view controller, we override the following two +methods from the `UIResponder` class: + + - (BOOL)canBecomeFirstResponder { + return YES; + } + + - (NSUndoManager*)undoManager + { + return self.managedObjectContext.undoManager; + } + +Now, when a shake gesture happens, the managed object context's undo manager will get an +undo message, and undo the last change. Remember, on iOS, a managed object context doesn't have an undo manager by default, (whereas on Mac, a newly created managed object context does have an undo manager), so we created that in the setup of the persistent +stack: + + self.managedObjectContext.undoManager = [[NSUndoManager alloc] init]; + +And that's almost all there is to it. Now, when you shake, you get the +default iOS alert view with two buttons: one for undoing, and one for +canceling. One nice feature of Core Data is that it automatically groups +changes. For example, the `addItem:parent` will record as one undo +action. For the deletion, it's the same. + +To make managing the undos a bit easier for the user, we can also name +the actions, and change the first lines of `textFieldShouldReturn:` to +this: + + NSString* title = textField.text; + NSString* actionName = [NSString stringWithFormat: + NSLocalizedString(@"add item \"%@\"", @"Undo action name of add item"), title]; + [self.undoManager setActionName:actionName]; + [self.store addItem:title parent:nil]; + +Now, when the user shakes, he or she gets a bit more context than just the +generic label "Undo". + +### Editing + +Editing is currently not supported in the example application, but is a +matter of just changing properties on the objects. For example, to +change the title of an item, just set the `title` property and you're +done. To change the parent of an item `foo`, just set the `parent` property +to a new value `bar`, and everything gets updated: `bar` now has `foo` +in its `children`, and because we use fetched results controllers the +user interface also updates automatically. + +### Reordering + +Reordering cells is also not possible in the sample application, but +is mostly straightforward to implement. Yet, there is one caveat: if you +allow user-driven reordering, you will update the `order` property in +the model, and then get a delegate call from the fetched results +controller (which you should ignore, because the cells have already +moved). This is explained in the +[`NSFetchedResultsControllerDelegate` documentation](https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html#//apple_ref/doc/uid/TP40008228-CH1-SW14) + +## Saving + +Saving is as easy as calling `save` on the managed object context. +Because we don't access that directly, we do it in the store. The only +hard part is when to save. Apple's sample code does it in +`applicationWillTerminate:`, but depending on your use case it could +also be in `applicationDidEnterBackground:` or even while your app is +running. + +## Discussion + +In writing this article and the example application, I made an initial +mistake: I chose to not have an empty root item, but instead let all +the user-created items at root level have a `nil` parent. This caused a +lot of trouble: because the `parent` item in the view controller could +be `nil`, we needed to pass the store (or the managed object context) +around to each child view controller. Also, enforcing the order +invariant was harder, as we needed a fetch request to find an item's +siblings, thus forcing Core Data to go back to disk. Unfortunately, +these problems were not immediately clear when writing the code, and +some only became clear when writing the tests. When rewriting the code, +I was able to move almost all code from the `Store` class into the +`Item` class, and everything became a lot cleaner. diff --git a/2013-09-09-importing-large-data-sets-into-core-data.md b/2013-09-09-importing-large-data-sets-into-core-data.md new file mode 100644 index 0000000..81e6e1d --- /dev/null +++ b/2013-09-09-importing-large-data-sets-into-core-data.md @@ -0,0 +1,162 @@ +--- +layout: post +title: Importing Large Data Sets +category: "4" +date: "2013-09-09 07:00:00" +author: "Florian Kugler" +tags: article +--- + +{% include links-4.md %} + +Importing large data sets into a Core Data application is a common problem. There are several approaches you can take dependent on the nature of the data: + +1. Downloading the data from a web server (for example as JSON) and inserting it into Core Data. +2. Downloading a pre-populated Core Data SQLite file from a web server. +3. Shipping a pre-populated Core Data SQLite file in the app bundle. + +The latter two options especially are often overlooked as viable options for some use cases. Therefore, we are going to have a closer look at them in this article, but we will also outline how to efficiently import data from a web service into a live application. + + +## Shipping Pre-Populated SQLite files + +Shipping or downloading pre-populated SQLite files is a viable option to seed Core Data with big amounts of data and is much more efficient then creating the database on the client side. If the seed data set consists of static data and can live relatively independently next to potential user-generated data, it might be a use case for this technique. + +The Core Data framework is shared between iOS and OS X, therefore we can create an OS X command line tool to generate the SQLite file, even if it should be used in an iOS application. + +In our example, (which you can [find on GitHub](https://github.com/objcio/issue-4-importing-and-fetching)), we created a command line tool which takes two files of a [transit data set](http://stg.daten.berlin.de/datensaetze/vbb-fahrplan-2013) for the city of Berlin as input and inserts them into a Core Data SQLite database. The data set consists of roughly 13,000 stop records and 3 million stop-time records. + +The most important thing with this technique is to use exactly the same data model for the command line tool as for the client application. If the data model changes over time and you're shipping new seed data sets with application updates, you have to be very careful about managing data model versions. It's usually a good idea to not duplicate the .xcdatamodel file, but to link it into the client applications project from the command line tool project. + +Another useful step is to perform a `VACUUM` command on the resulting SQLite file. This will bring down the file size and therefore the size of the app bundle or the database download, dependent on how you ship the file. + +Other than that, there is really no magic to the process; as you can see in our [example project](), it's all just simple standard Core Data code. And since generating the SQLite file is not a performance-critical task, you don't even need to go to great lengths of optimizing its performance. If you want to make it fast anyway, the same rules apply as outlined below for [efficiently importing large data sets in a live application][110]. + + + + +### User-Generated Data + +Often we will have the case where we want to have a large seed data set available, but we also want to store and modify some user-generated data next to it. Again, there are several approaches to this problem. + +The first thing to consider is if the user-generated data is really a candidate to be stored with Core Data. If we can store this data just as well e.g. in a plist file, then it's not going to interfere with the seeded Core Data database anyway. + +If we want to store it with Core Data, the next question is whether the use case might require updating the seed data set in the future by shipping an updated pre-populated SQLite file. If this will never happen, we can safely include the user-generated data in the same data model and configuration. However, if we might ever want to ship a new seed database, we have to separate the seed data from the user-generated data. + +This can be done by either setting up a second, completely independent Core Data stack with its own data model, or by distributing the data of the same data model between two persistent stores. For this, we would have to create a second [configuration](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdMOM.html#//apple_ref/doc/uid/TP40002328-SW3) within the same data model that holds the entities of the user-generated data. When setting up the Core Data stack, we would then instantiate two persistent stores, one with the URL and configuration of the seed database, and the other one with the URL and configuration of the database for the user-generated data. + +Using two independent Core Data stacks is the more simple and straightforward solution. If you can get away with it, we strongly recommend this approach. However, if you want to establish relationships between the user-generated data and the seed data, Core Data cannot help you with that. If you include everything in one data model spread out across two persistent stores, you still cannot define relationships between those entities as you would normally do, but you can use Core Data's [fetched properties](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdRelationships.html#//apple_ref/doc/uid/TP40001857-SW7) in order to automatically fetch objects from a different store when accessing a certain property. + + +### SQLite Files in the App Bundle + +If we want to ship a pre-populated SQLite file in the application bundle, we have to detect the first launch of a newly updated application and copy the database file out of the bundle into its target directory: + + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError *error; + + if([fileManager fileExistsAtPath:self.storeURL.path]) { + NSURL *storeDirectory = [self.storeURL URLByDeletingLastPathComponent]; + NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:storeDirectory + includingPropertiesForKeys:nil + options:0 + errorHandler:NULL]; + NSString *storeName = [self.storeURL.lastPathComponent stringByDeletingPathExtension]; + for (NSURL *url in enumerator) { + if (![url.lastPathComponent hasPrefix:storeName]) continue; + [fileManager removeItemAtURL:url error:&error]; + } + // handle error + } + + NSString* bundleDbPath = [[NSBundle mainBundle] pathForResource:@"seed" ofType:@"sqlite"]; + [fileManager copyItemAtPath:bundleDbPath toPath:self.storeURL.path error:&error]; + + +Notice that we're first deleting the previous database files. This is not as straightforward as one may think though, because there can be different auxiliary files (e.g. journaling or write-ahead logging files) next to the main `.sqlite` file. Therefore we have to enumerate over the items in the directory and delete all files that match the store file name without its extension. + +However, we also need a way to make sure that we only do this once. An obvious solution would be to delete the seed database from the bundle. However, while this works on the simulator, it will fail as soon as you try this on a real device because of restricted permissions. There are many options to solve this problem though, like setting a key in the user defaults which contains information about the latest seed data version imported: + + NSString* bundleVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey]; + NSString *seedVersion = [[NSUserDefaults standardUserDefaults] objectForKey@"SeedVersion"]; + if (![seedVersion isEqualToString:bundleVersion]) { + // Copy the seed database + } + + // ... after the import succeeded + NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary; + [[NSUserDefaults standardUserDefaults] setObject:bundleVersion forKey:@"SeedVersion"]; + +Alternatively for example, we could also copy the existing database file to a path including the seed version and detect its presence to avoid doing the same import twice. There are many practicable solutions which you can choose from, dependent on what makes the most sense for your case. + + +## Downloading Pre-Populated SQLite Files + +If for some reason we don't want to include a seed database in the app bundle (e.g. because it would push the bundle size beyond the cellular download threshold), we can also download it from a web server. The process is exactly the same as soon as we have the database file locally on the device. We need to make sure though, that the server sends a version of the database which is compatible with the data model of the client, if the data model might change across different app versions. + +Beyond replacing a file in the app bundle with a download though, this option also opens up possibilities to deal with seeding more dynamic datasets without incurring the performance and energy cost of importing the data dynamically on the client side. + +We can run a similar command line importer as we used before on a (OS X) server in order to generate the SQLite file on the fly. Admittedly, the computing resources required for this operation might not permit doing this fully on demand for each request, depending on the size of the data set and the number of request we would have to serve. A viable alternative though is to generate the SQLite file in regular intervals and to serve these readily available files to the clients. + +This of course requires some additional logic on the server and the client, in order to provide an API next to the SQLite download which can provide the data to the clients which has changed since the last seed file generation. The whole setup becomes a bit more complex, but it enables easily seeding Core Data with dynamic data sets of arbitrary size without performance problems (other than bandwidth limitations). + + +## Importing Data from Web Services + +Finally, let's have a look at what we have to do in order to live import large amounts of data from a web server that provides the data, for example, in JSON format. + +If we are importing different object types with relationships between them, then we will need to import all objects independently first before attempting to resolve the relationships between them. If we could guarantee on the server side that the client receives the objects in the correct order in order to resolve all relationships immediately, we wouldn't have to worry about this. But mostly this will not be the case. + +In order to perform the import without affecting user-interface responsiveness, we have to perform the import on a background thread. In a previous issue, Chris wrote about a simple way of [using Core Data in the background](/issue-2/common-background-practices.html). If we do this right, devices with multiple cores can perform the import in the background without affecting the responsiveness of the user interface. Be aware though that using Core Data concurrently also creates the possibility of conflicts between different managed object contexts. You need to come up with a [policy](http://thermal-core.com/2013/09/07/in-defense-of-core-data-part-I.html) of how to prevent or handle these situations. + +In this context, it is important to understand how concurrency works with Core Data. Just because we have set up two managed object contexts on two different threads does not mean that they both get to access the database at the same time. Each request issued from a managed object context will cause a lock on the objects from the context all the way down to the SQLite file. For example, if you trigger a fetch request in a child context of the main context, the main context, the persistent store coordinator, the persistent store, and ultimately the SQLite file will be locked in order to execute this request (although the [lock on the SQLite file will come and go faster](https://developer.apple.com/wwdc/videos/?id=211) than on the stack above). During this time, everybody else in the Core Data stack is stuck waiting for this request to finish. + +In the example of mass-importing data on a background context, this means that the save requests of the import will repeatedly lock the persistent store coordinator. During this time, the main context cannot execute, for example, a fetch request to update the user interface, but has to wait for the save request to finish. Since Core Data's API is synchronous, the main thread will be stuck waiting and the responsiveness of the user interface might be affected. + +If this is an issue in your use case, you should consider using a separate Core Data stack with its own persistent store coordinator for the background context. Since, in this case, the only shared resource between the background context and the main context is the SQLite file, lock contention will likely be lower than before. Especially when SQLite is operating in [write-ahead logging](http://www.sqlite.org/draft/wal.html) mode, (which it is by default on iOS 7 and OS X 10.9), you get true concurrency even on the SQLite file level. Multiple readers and a single writer are able to access the database at the same time (See [WWDC 2013 session "What's New in Core Data and iCloud"](https://developer.apple.com/wwdc/videos/?id=207)). + +Lastly, it's mostly not a good idea to merge every change notification into the main context immediately while mass-importing data. If the user interface reacts to these changes automatically, (e.g. by using a `NSFetchedResultsController`), it will come to a grinding halt quickly. Instead, we can send a custom notification once the whole import is done to give the user interface a chance to reload its data. + +If the use case warrants putting additional effort into this aspect of live-updating the UI during the import, then we can consider filtering the save notifications for certain entity types, grouping them together in batches, or other means of reducing the frequency of interface updates, in order to keep it responsive. However, in most cases, it's not worth the effort, because very frequent updates to the user interface are more confusing then helpful to the user. + +After laying out the general setup and modus operandi of a live import, we'll now have a look at some specific aspects to make it as efficient as possible. + + + + +### Importing Efficiently + +Our first recommendation for importing data efficiently is to read [Apple's guide on this subject](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdImporting.html). We would also like to highlight a few aspects described in this document, which are often forgotten. + +First of all, you should always set the `undoManager` to `nil` on the context that you use for importing. This applies only to OS X though, because on iOS, contexts come without an undo manager by default. Nilling out the `undoManager` property will give a significant performance boost. + +Next, accessing relationships between objects in *both directions* creates retain cycles. If you see growing memory usage during the import despite well-placed auto-release pools, watch out for this pitfall in the importer code. [Here, Apple describes](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdMemory.html#//apple_ref/doc/uid/TP40001860-SW3) how to break these cycles using [`refreshObject:mergeChanges:`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html#//apple_ref/occ/instm/NSManagedObjectContext/refreshObject:mergeChanges:). + +When you're importing data which might already exist in the database, you have to implement some kind of find-or-create algorithm to prevent creating duplicates. Executing a fetch request for every single object is vastly inefficient, because every fetch request will force Core Data to go to disk and fetch the data from the store file. However, it's easy to avoid this by importing the data in batches and using the efficient find-or-create algorithm Apple outlines in the above-mentioned guide. + +A similar problem often arises when establishing relationships between the newly imported objects. Using a fetch request to get each related object independently is vastly inefficient. There are two possible ways out of this: either we resolve relationships in batches similar to how we imported the objects in the first place, or we cache the objectIDs of the already-imported objects. + +Resolving relationships in batches allows us to greatly reduce the number of fetch requests required by fetching many related objects at once. Don't worry about potentially long predicates like: + + [NSPredicate predicateWithFormat:@"identifier IN %@", identifiersOfRelatedObjects]; + +Resolving a predicate with many identifiers in the `IN (...)` clause is always way more efficient than going to disk for each object independently. + +However, there is also a way to avoid fetch requests altogether (at least if you only need to establish relationships between newly imported objects). If you cache the objectIDs of all imported objects (which is not a lot of data in most cases really), you can use them later to retrieve faults for related objects using `objectWithID:`. + + // after a batch of objects has been imported and saved + for (MyManagedObject *object in importedObjects) { + objectIDCache[object.identifier] = object.objectID; + } + + // ... later during resolving relationships + NSManagedObjectID objectID = objectIDCache[object.foreignKey]; + MyManagedObject *relatedObject = [context objectWithID:objectId]; + object.toOneRelation = relatedObject; + +Note that this example assumes that the `identifier` property is unique across all entity types, otherwise we would have to account for duplicate identifiers for different types in the way we cache the object IDs. + + +## Conclusion + +When you face the challenge of having to import large data sets into Core Data, try to think out of the box first, before doing a live-import of massive amounts of JSON data. Especially if you're in control of the client and the server side, there are often much more efficient ways of solving this problem. But if you have to bite the bullet and do large background imports, make sure to operate as efficiently and as independently from the main thread as possible. diff --git a/2013-09-09-index.markdown b/2013-09-09-index.markdown new file mode 100644 index 0000000..c437624 --- /dev/null +++ b/2013-09-09-index.markdown @@ -0,0 +1,6 @@ +--- +layout: toc +category: "4" +date: "2013-09-06 12:00:00" +tags: toc +--- diff --git a/2013-10-08-collection-views-and-uidynamics.md b/2013-10-08-collection-views-and-uidynamics.md new file mode 100644 index 0000000..9bab21c --- /dev/null +++ b/2013-10-08-collection-views-and-uidynamics.md @@ -0,0 +1,355 @@ +--- +layout: post +title: "UICollectionView + UIKit Dynamics" +category: "5" +date: "2013-10-07 10:00:00" +tags: article +author: "Ash Furrow" +--- + +UIKit Dynamics is the new physics-based animation engine in iOS 7 – it has been specifically designed to work well with collection views, which were first introduced in iOS 6. We're going to take a tour of how you put these two together. + +This article is going to discuss two examples of using collection views with UIKit Dynamics. The first example demonstrates how to reproduce the springy effect in the iOS 7 Messages app and is then amended to incorporate a tiling mechanism that makes the layout scalable. The second example shows how to use UIKit Dynamics to simulate a Newton's Cradle where items can be added to the collection view one at a time, interacting with one another. + +Before we get started, I'm going to assume that you have a baseline understanding of how `UICollectionView` works – see [this objc.io post](http://www.objc.io/issue-3/collection-view-layouts.html) for all the details you'll need. I'll also assume that you understand how UIKit Dynamics works – see [this post](http://www.teehanlax.com/blog/introduction-to-uikit-dynamics/) for more. + +The two example projects for this article are on GitHub: + +- [`ASHSpringyCollectionView`](https://github.com/objcio/issue-5-springy-collection-view) (based on [`UICollectionView` Spring Demo](https://github.com/TeehanLax/UICollectionView-Spring-Demo) +- [Newtownian `UICollectionView`](https://github.com/objcio/issue-5-newtonian-collection-view) + +## The Dynamic Animator + +The key component backing a `UICollectionView` that employes UIKit Dynamics is the `UIDynamicAnimator`. This class belongs inside a `UICollectionViewFlowLayout` object and should be strongly referenced by it (someone needs to retain the animator, after all). + +When we create our dynamic animator, we're not going to be giving it a reference view like we normally would. Instead, we'll use a different initializer that requires a collection view layout as a parameter. This is critical, as the dynamic animator needs to be able to invalidate the collection view layout when the attributes of its behaviors' items should be updated. In other words, the dynamic animator is going to be invalidating the layout a lot. + +We'll see how things are hooked up shortly, but it's important to understand at a conceptual level how a collection view interacts with a dynamic animator. The collection view layout is going to add behaviors for each `UICollectionViewLayoutAttributes` object in the collection view (later, we'll talk about tiling these). After adding these behaviors to the dynamic animator, the collection view layout is going to be queried by UIKit about the state of its collection view layout attributes. Instead of doing any calculations ourselves, we're going to return the items provided by our dynamic animator. The animator is going to invalidate the layout whenever its simulation state changes. This will prompt UIKit to requery the layout, and the cycle continues until the simulation comes to a rest. + +So to recap, the layout creates the dynamic animator and adds behaviors corresponding to the layout attributes for each of its items. When asked about layout information, it provides the information supplied by the dynamic animator. + +## Subclassing UICollectionViewFlowLayout + +We're going to build a simple example of how to use UIKit Dynamics with a collection view layout. The first thing we need is, of course, a data source to drive our collection view. I know that you're smart enough to provide your own data source, but for the sake of completeness, I've provided one for you: + + @implementation ASHCollectionViewController + + static NSString * CellIdentifier = @"CellIdentifier"; + + -(void)viewDidLoad + { + [super viewDidLoad]; + [self.collectionView registerClass:[UICollectionViewCell class] + forCellWithReuseIdentifier:CellIdentifier]; + } + + -(UIStatusBarStyle)preferredStatusBarStyle + { + return UIStatusBarStyleLightContent; + } + + -(void)viewDidAppear:(BOOL)animated + { + [super viewDidAppear:animated]; + [self.collectionViewLayout invalidateLayout]; + } + + #pragma mark - UICollectionView Methods + + -(NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section + { + return 120; + } + + -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath + { + UICollectionViewCell *cell = [collectionView + dequeueReusableCellWithReuseIdentifier:CellIdentifier + forIndexPath:indexPath]; + + cell.backgroundColor = [UIColor orangeColor]; + return cell; + } + + @end + +Notice that it's invalidating the layout when the view first appears. That's a consequence of not using Storyboards (the timing of the first invocation of the prepareLayout method is different when using Storyboards – or not – something they didn't tell you in the WWDC video). As a result, we need to manually invalidate the collection view layout once the view appears. When we use tiling, this isn't necessary. + +Let's create our collection view layout. We need to have a strong reference to a dynamic animator that will drive the attributes of our collection view layout. We'll have a private property declared in the implementation file: + + @interface ASHSpringyCollectionViewFlowLayout () + + @property (nonatomic, strong) UIDynamicAnimator *dynamicAnimator; + + @end + +We'll initialize our dynamic animator in the init method of the layout. We'll also set up some of our properties belonging to `UICollectionViewFlowLayout`, our superclass: + + - (id)init + { + if (!(self = [super init])) return nil; + + self.minimumInteritemSpacing = 10; + self.minimumLineSpacing = 10; + self.itemSize = CGSizeMake(44, 44); + self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); + + self.dynamicAnimator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self]; + + return self; + } + +The next method we'll implement is prepareLayout. We'll need to call our superclass's implementation first. Since we're subclassing `UICollectionViewFlowLayout`, calling super's prepareLayout method will position the collection view layout attributes for us. We can now rely on them being laid out and can ask for all of the attributes in a given rect. Let's load *all* of them. + + [super prepareLayout]; + + CGSize contentSize = self.collectionView.contentSize; + NSArray *items = [super layoutAttributesForElementsInRect: + CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height)]; + +This is *really* inefficient code. Since our collection view could have tens of thousands of cells, loading all of them at once is potentially an incredibly memory-intensive operation. We're going to iterate over those elements in a moment, making this a time-intensive operation as well. An efficiency double-whammy! Don't worry – we're responsible developers so we'll solve this problem shortly. For now, we'll just continue on with a simple, naïve implementation. + +After loading all of our collection view layout attributes, we need to check and see if they've already been added to our animator. If a behavior for an item already exists in the animator, then we can't re-add it or we'll get a very obscure runtime exception: + + (0.004987s) in + \{\{0, 0}, \{0, 0\}\}: + body type: representedObject: + [ + index path: ( {length = 2, path = 0 - 0}); + frame = (10 10; 300 44); ] 0xa2877c0 + PO:(159.999985,32.000000) AN:(0.000000) VE:(0.000000,0.000000) AV:(0.000000) + dy:(1) cc:(0) ar:(1) rs:(0) fr:(0.200000) re:(0.200000) de:(1.054650) gr:(0) + without representedObject for item + index path: ( {length = 2, path = 0 - 0}); + frame = (10 10; 300 44); + +If you see this error, then it basically means that you're adding two behaviors for identical `UICollectionViewLayoutAttributes`, which the system doesn't know how to handle. + +At any rate, once we've checked that we haven't already added behaviors to our dynamic animator, we'll need to iterate over each of our collection view layout attributes to create and add a new dynamic behavior: + + if (self.dynamicAnimator.behaviors.count == 0) { + [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + UIAttachmentBehavior *behaviour = [[UIAttachmentBehavior alloc] initWithItem:obj + attachedToAnchor:[obj center]]; + + behaviour.length = 0.0f; + behaviour.damping = 0.8f; + behaviour.frequency = 1.0f; + + [self.dynamicAnimator addBehavior:behaviour]; + }]; + } + +The code is very straightforward. For each of our items, we create a new `UIAttachmentBehavior` with the center of the item as the attachment point. We then set the length of our attachment behavior to zero so that it requires the cell to be centered under the behavior's attachment point at all times. We then set the damping and frequency to some values that I determined experimentally to be visually pleasing and not over-the-top. + +That's it for prepareLayout. We now need to respond to two methods that UIKit will call to query us about the layout of collection view layout attributes, `layoutAttributesForElementsInRect:` and `layoutAttributesForItemAtIndexPath:`. Our implementations will forward these queries onto the dynamic animator, which has methods specifically designed to respond to these queries: + + -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect + { + return [self.dynamicAnimator itemsInRect:rect]; + } + + -(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath + { + return [self.dynamicAnimator layoutAttributesForCellAtIndexPath:indexPath]; + } + +## Responding to Scroll Events + +What we've implemented so far will provide a static-feeling `UICollectionView` that scrolls normally; there is nothing special about the way that it works. That's fine, but it's not really *dynamic*, is it? + +In order to behave dynamically, we need our layout and dynamic animator to react to changes in the scroll position of the collection view. Luckily there is a method perfectly suited for our task called `shouldInvalidateLayoutForBoundsChange:`. This method is called when the bounds of the collection view change and it provides us with an opportunity to adjust the behaviors' items in our dynamic animator to the new [content offset](http://www.objc.io/issue-3/scroll-view.html#scroll_views_content_offset). After adjusting the behaviors' items, we're going to return NO from this method; since the dynamic animator will take care of invalidating our layout, there's no need to invalidate it in this case: + + -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds + { + UIScrollView *scrollView = self.collectionView; + CGFloat delta = newBounds.origin.y - scrollView.bounds.origin.y; + + CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView]; + + [self.dynamicAnimator.behaviors enumerateObjectsUsingBlock:^(UIAttachmentBehavior *springBehaviour, NSUInteger idx, BOOL *stop) { + CGFloat yDistanceFromTouch = fabsf(touchLocation.y - springBehaviour.anchorPoint.y); + CGFloat xDistanceFromTouch = fabsf(touchLocation.x - springBehaviour.anchorPoint.x); + CGFloat scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500.0f; + + UICollectionViewLayoutAttributes *item = springBehaviour.items.firstObject; + CGPoint center = item.center; + if (delta < 0) { + center.y += MAX(delta, delta*scrollResistance); + } + else { + center.y += MIN(delta, delta*scrollResistance); + } + item.center = center; + + [self.dynamicAnimator updateItemUsingCurrentState:item]; + }]; + + return NO; + } + +Let's go through this implementation in detail. First, we grab the scroll view (that's our collection view) and calculate the change in the content offset's y component (our collection view scrolls vertically in this example). Once we have the delta, we need to grab the location of the user's touch. This is important because we want items closer to the touch to move more immediately while items further from the touch should lag behind. + +For each behavior in our dynamic animator, we divide the sum of the x and y distances from the touch to the behavior's item by a denominator of 1500, a value determined experimentally. Use a smaller denominator to make the collection view react with more spring. Once we have this "scroll resistance," we move the behavior's item's center.y component by that delta, multiplied by the scrollResistance variable. Finally, note that we clamp the product of the delta and scroll resistance by the delta in case the scroll resistance exceeds the delta (meaning the item might begin to move in the wrong direction). This is unlikely since we're using such a high denominator, but it's something to watch out for in more bouncy collection view layouts. + +That's really all there is to it. In my experience, this naïve approach is effective for collection views with up to a few hundred items. Beyond that, the burden of loading all the items into memory at once becomes too great and you'll begin to drop frames when scrolling. + +Springy Collection View + +## Tiling your Dynamic Behaviors for Performance + +A few hundred cells is all well and good, but what happens when your collection view data source exceeds that size? Or what if you can't predict exactly how large your data source will grow at runtime? Our naïve approach breaks down. + +Instead of loading *all* of our items in prepareLayout, it would be nice if we could be *smarter* about which items we load. Say, just the items that are visible or are about to become visible. That's exactly the approach that we're going to take. + +The first thing we need to do is keep track of all of the index paths that are currently represented by behaviors' items in the dynamic animator. We'll add a property to our collection view layout to do this: + + @property (nonatomic, strong) NSMutableSet *visibleIndexPathsSet; + +We're using a set because it features constant-time lookup for testing inclusion, and we'll be testing for inclusion *a lot*. + +Before we dive into a whole new prepareLayout method – one that tiles behaviors – it's important to understand what tiling means. When we tile behaviors, we're removing behaviors as their items leave the visible bounds of the collection view and adding behaviors as their items enter the visible bounds. There's a big challenge though: when we create new behaviors, we need to create them *in flight*. That means creating them as though they were already in the dynamic animator and being modified by the `shouldInvalidateLayoutForBoundsChange:` method. + +Since we're creating these new behaviors in flight, we need to maintain some state of our current collection view. In particular, we need to keep track of the latest delta in our bounds change. This state will be used to create our behaviors in flight: + + @property (nonatomic, assign) CGFloat latestDelta; + +After adding this property, we'll add the following line to our `shouldInvalidateLayoutForBoundsChange:` method: + + self.latestDelta = delta; + +That's all we need to modify to our method that responds to scrolling events. Our two methods for relaying queries about the layout of items in the collection view to the dynamic animator remain completely unchanged. Actually, most of the time, when backing your collection view with a dynamic animator, you'll have `layoutAttributesForElementsInRect:` and `layoutAttributesForItemAtIndexPath:` implemented the way we have them above. + +The most complicated bit is now the tiling mechanism. We're going to completely rewrite our prepareLayout. + +The first step of this method is going to be to remove certain behaviors from the dynamic animator where those behaviors represent items whose index paths are no longer on screen. The second step is to add new behaviors for items that are *becoming* visible. Let's take a look at the first step. + +Like before, we're going to call `[super prepareLayout]` so that we can rely on the layout information provided by `UICollectionViewFlowLayout`, our superclass. Also like before, we're going to be querying our superclass for the layout attributes for the elements in a rect. The difference is that instead of asking about attributes for elements in the *entire collection view*, we're going to only query about elements in the *visible rect*. + +So we need to calculate the visible rect. But not so fast! There's one thing to keep in mind. Our user might scroll the collection view too fast for the dynamic animator to keep up, so we need to expand the visible rect slightly so that we're including items that are *about* to become visible. Otherwise, flickering could appear when scrolling quickly. Let's calculate our visible rect: + + CGRect originalRect = (CGRect){.origin = self.collectionView.bounds.origin, .size = self.collectionView.frame.size}; + CGRect visibleRect = CGRectInset(originalRect, -100, -100); + +I determined that insetting the actual visible rect by -100 points in both directions works for my demo. Double-check these values for your collection view, especially if your cells are really small. + +Next we need to collect the collection view layout attributes which lie within the visible rect. Let's also collect their index paths: + + NSArray *itemsInVisibleRectArray = [super layoutAttributesForElementsInRect:visibleRect]; + + NSSet *itemsIndexPathsInVisibleRectSet = [NSSet setWithArray:[itemsInVisibleRectArray valueForKey:@"indexPath"]]; + +Notice that we're using an NSSet. That's because we're going to be testing for inclusion within that set and we want constant-time lookup: + +What we're going to do is iterate over our dynamic animator's behaviors and filter out the ones that represent items that are in our `itemsIndexPathsInVisibleRectSet`. Once we've filtered our behaviors, we'll iterate over the ones that are no longer visible and remove those behaviors from the animator (along with the index paths from the `visibleIndexPathsSet` property): + + NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UIAttachmentBehavior *behaviour, NSDictionary *bindings) { + BOOL currentlyVisible = [itemsIndexPathsInVisibleRectSet member:[[[behaviour items] firstObject] indexPath]] != nil; + return !currentlyVisible; + }] + NSArray *noLongerVisibleBehaviours = [self.dynamicAnimator.behaviors filteredArrayUsingPredicate:predicate]; + + [noLongerVisibleBehaviours enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) { + [self.dynamicAnimator removeBehavior:obj]; + [self.visibleIndexPathsSet removeObject:[[[obj items] firstObject] indexPath]]; + }]; + +The next step is to calculate a list of `UICollectionViewLayoutAttributes` that are *newly* visible – that is, ones whose index paths are in `itemsIndexPathsInVisibleRectSet` but not in our property `visibleIndexPathsSet`: + + NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *item, NSDictionary *bindings) { + BOOL currentlyVisible = [self.visibleIndexPathsSet member:item.indexPath] != nil; + return !currentlyVisible; + }]; + NSArray *newlyVisibleItems = [itemsInVisibleRectArray filteredArrayUsingPredicate:predicate]; + +Once we have our newly visible layout attributes, we can iterate over them to create our new behaviors and add their index paths to our `visibleIndexPathsSet` property. First, however, we'll need to grab the touch location of our user's finger. If it's CGPointZero, then we know that the user isn't scrolling the collection view and we can *assume* that we don't have to create new behaviors in flight: + + CGPoint touchLocation = [self.collectionView.panGestureRecognizer locationInView:self.collectionView]; + +This is a potentially dangerous assumption. What if the user has scrolled the collection view quickly and released his or her finger? The collection view would still be scrolling but our method wouldn't create the new behaviors in flight. Luckily, that also means that the scroll view is scrolling too fast to notice! Huzzah! This might become a problem, however, for collection views using large cells. In this case, increase the bounds of your visible rect so you're loading more items. + +Now we need to enumerate our newly visible items and create new behaviors for them and add their index paths to our `visibleIndexPathsSet` property. We'll also need to do some [math](http://www.youtube.com/watch?v=gENVB6tjq_M) to create the behavior in flight: + + [newlyVisibleItems enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *item, NSUInteger idx, BOOL *stop) { + CGPoint center = item.center; + UIAttachmentBehavior *springBehaviour = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:center]; + + springBehaviour.length = 0.0f; + springBehaviour.damping = 0.8f; + springBehaviour.frequency = 1.0f; + + if (!CGPointEqualToPoint(CGPointZero, touchLocation)) { + CGFloat yDistanceFromTouch = fabsf(touchLocation.y - springBehaviour.anchorPoint.y); + CGFloat xDistanceFromTouch = fabsf(touchLocation.x - springBehaviour.anchorPoint.x); + CGFloat scrollResistance = (yDistanceFromTouch + xDistanceFromTouch) / 1500.0f; + + if (self.latestDelta < 0) { + center.y += MAX(self.latestDelta, self.latestDelta*scrollResistance); + } + else { + center.y += MIN(self.latestDelta, self.latestDelta*scrollResistance); + } + item.center = center; + } + + [self.dynamicAnimator addBehavior:springBehaviour]; + [self.visibleIndexPathsSet addObject:item.indexPath]; + }]; + +A lot of this code should look familiar. About half of it is from our naïve implementation of prepareLayout without tiling. The other half is from our `shouldInvalidateLayoutForBoundsChange:` method. We use our latestDelta property in lieu of a calculated delta from a bounds change and adjust the center point of our `UICollectionViewLayoutAttributes` appropriately so that the cell it represents will be "pulled" by the attachment behavior. + +And that's it. Really! I've tested this on a device displaying ten thousand cells and it works perfectly. Go [give it a shot](https://github.com/objcio/issue-5-springy-collection-view). + +## Beyond Flow Layouts + +As usual, when working with `UICollectionView`, it's easier to subclass `UICollectionViewFlowLayout` rather than `UICollectionViewLayout` itself. This is because *flow* layouts will do a lot of the work for us. However, flow layouts are restricted to line-based, breaking layouts. What if you have a layout that doesn't fit that criteria? Well, if you've already tried fitting it into a `UICollectionViewFlowLayout` and you're sure that won't work, then it's time to break out the heavy-duty `UICollectionViewLayout` subclass. + +This is true when dealing with UIKit Dynamics as well. + +Let's subclass `UICollectionViewLayout`. It's very important to implement `collectionViewContentSize` when subclassing `UICollectionViewLayout`. Otherwise the collection view won't have any idea how to display itself and nothing will be displayed at all. Since we want our collection view not to scroll at all, we'll return our collection view's frame's size, minus its contentInset.top component: + + -(CGSize)collectionViewContentSize + { + return CGSizeMake(self.collectionView.frame.size.width, + self.collectionView.frame.size.height - self.collectionView.contentInset.top); + } + +In this (somewhat pedagogical) example, our collection view *always begins* with zero cells and items are added via `performBatchUpdates:`. That means that we have to use the `-[UICollectionViewLayout prepareForCollectionViewUpdates:]` method to add our behaviors (i.e. the collection view data source always starts at zero). + +Instead of just adding an attachment behavior for each individual item, we'll also maintain two other behaviors: gravity and collision. For each item we add to the collection view, we'll have to add these items to our collision and attachment behaviors. The final step is to set the item's initial position to somewhere offscreen so that it's pulled onscreen by the attachment behavior: + + -(void)prepareForCollectionViewUpdates:(NSArray *)updateItems + { + [super prepareForCollectionViewUpdates:updateItems]; + + [updateItems enumerateObjectsUsingBlock:^(UICollectionViewUpdateItem *updateItem, NSUInteger idx, BOOL *stop) { + if (updateItem.updateAction == UICollectionUpdateActionInsert) { + UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes + layoutAttributesForCellWithIndexPath:updateItem.indexPathAfterUpdate]; + + attributes.frame = CGRectMake(CGRectGetMaxX(self.collectionView.frame) + kItemSize, 300, kItemSize, kItemSize); + + UIAttachmentBehavior *attachmentBehaviour = [[UIAttachmentBehavior alloc] initWithItem:attributes + attachedToAnchor:attachmentPoint]; + attachmentBehaviour.length = 300.0f; + attachmentBehaviour.damping = 0.4f; + attachmentBehaviour.frequency = 1.0f; + [self.dynamicAnimator addBehavior:attachmentBehaviour]; + + [self.gravityBehaviour addItem:attributes]; + [self.collisionBehaviour addItem:attributes]; + } + }]; + } + +Demo + +Deletion is far more complicated. We want the item to "fall off" instead of simply disappearing. This involves more than just removing the cell from the collection view, as we want it to remain in the collection view until it moves offscreen. I've implemented something to that effect in the code, but it is a bit of a cheat. + +What we basically do is provide a method in the layout that removes the attachment behavior, then, after two seconds, removes the cell from the collection view. We're hoping that in that time, the cell can fall offscreen, but that's not necessarily going to happen. If it doesn't, that's OK. It'll just fade away. However, we also have to prevent new cells from being added and old cells from being deleted during this two-second interval. (I said it was a cheat.) + +Pull requests welcome. + +This approach is somewhat limited. I've capped the number of cells at ten, but even then the animation is slow on older hardware like the second-generation iPad. However, this example is supposed to be demonstrative of the approach you can take for interesting dynamics simulations – it's not meant to be a turn-key solution for any data set. The individual aspects of your simulation, including its performance, are up to you. diff --git a/2013-10-08-editorial.md b/2013-10-08-editorial.md new file mode 100644 index 0000000..c1c6c38 --- /dev/null +++ b/2013-10-08-editorial.md @@ -0,0 +1,24 @@ +--- +layout: post +title: "Editorial" +category: "5" +date: "2013-10-07 12:00:00" +tags: editorial +--- + +Welcome to objc.io issue #5! + +We hope you're as excited by the launch of iOS 7 as we are. Choosing +this as our theme was a no-brainer; there's a lot of stuff we wanted to +talk about. As always, the list of potential topics was much greater +than what we could possibly write, so this is by no means a definitive +guide to iOS 7. Instead, we picked some highlights and wrote about those. + +This time, we have more guest writers than ever. Holger Riegel and Tobias Kreß explain the process they went through as they upgraded their app to iOS 7. One of the things that's worth considering when redesigning is the addition of UIDynamics, and Ash Furrow shows us how to use this in collection views. Chris talks about the new view controller transitions API, Mattt Thompson writes about moving from NSURLConnection to NSURLSession, and David shows us the new possibilities in multitasking. Max Seelemann covers one of the most game-changing features on the new iOS: TextKit. Finally, Peter Steinberger will guide us through his favorite new things in iOS 7. + +We are still looking for more guest writers. We have a great number of +ideas for upcoming topics, and would be very happy to have you on board. + +All the best from Berlin, + +Chris, Daniel, and Florian. diff --git a/2013-10-08-from-nsurlconnection-to-nsurlsession.md b/2013-10-08-from-nsurlconnection-to-nsurlsession.md new file mode 100644 index 0000000..52c7375 --- /dev/null +++ b/2013-10-08-from-nsurlconnection-to-nsurlsession.md @@ -0,0 +1,210 @@ +--- +layout: post +title: "From NSURLConnection to NSURLSession" +category: "5" +date: "2013-10-07 08:00:00" +tags: article +author: "Mattt Thompson" +--- + +One of the more significant changes in iOS 7 and Mac OS X 10.9 Mavericks was the overhaul of the Foundation URL Loading System. + +As someone [steeped in Apple's networking infrastructure](http://afnetworking.com), I thought it would be useful to share my thoughts and impressions of these new APIs, how they will change the way we build apps, and what they signify in terms of the evolution of API design philosophies. + +`NSURLConnection` got its start a decade ago, with the original release of Safari in 2003, as an abstraction on top of the Core Foundation / CFNetwork APIs. The name `NSURLConnection` actually refers to a group of the interrelated components that form the Foundation URL Loading System: `NSURLRequest`, `NSURLResponse`, `NSURLProtocol`, `NSURLCache`, `NSHTTPCookieStorage`, `NSURLCredentialStorage`, and its namesake, `NSURLConnection`. + +`NSURLRequest` objects are passed to an `NSURLConnection` object. The delegate (conforming to the erstwhile informal `` and `` protocols) responds asynchronously as an `NSURLResponse`, and any associated `NSData` are sent from the server. + +Before a request is sent to the server, the shared cache is consulted, and depending on the policy and availability, a cached response may be returned immediately and transparently. If no cached response is available, the request is made with the option to cache its response for any subsequent requests. + +In the process of negotiating a request to a server, that server may issue an authentication challenge, which is either handled automatically by the shared cookie or credential storage, or by the connection delegate. Outgoing requests could also be intercepted by a registered `NSURLProtocol` object to seamlessly change loading behavior as necessary. + +For better or worse, `NSURLConnection` has served as the networking +infrastructure for hundreds of thousands of Cocoa and Cocoa Touch +applications, and has held up rather well, considering. But over the +years, emerging use cases--on the iPhone and iPad, especially--have challenged several core assumptions, and created cause for refactoring. + +At WWDC 2013, Apple unveiled the successor to `NSURLConnection`: `NSURLSession`. + +--- + +Like `NSURLConnection`, `NSURLSession` refers to a group of interdependent classes, in addition to the eponymous class `NSURLSession`. `NSURLSession` is comprised of the same pieces as before, with `NSURLRequest`, `NSURLCache`, and the like, but replaces `NSURLConnection` with `NSURLSession`, `NSURLSessionConfiguration`, and three subclasses of `NSURLSessionTask`: `NSURLSessionDataTask`, `NSURLSessionUploadTask`, and `NSURLSessionDownloadTask`. + +The most immediate improvement `NSURLSession` provides over `NSURLConnection` is the ability to configure per-session cache, protocol, cookie, and credential policies, rather than sharing them across the app. This allows the networking infrastructure of frameworks and parts of the app to operate independently, without interfering with one another. Each `NSURLSession` object is initialized with an `NSURLSessionConfiguration`, which specifies these policies, as well a number of new options specifically added to improve performance on mobile devices. + +The other big part of `NSURLSession` is session tasks, which handle the loading of data, as well as uploading and downloading files and data between the client and server. `NSURLSessionTask` is most analogous to `NSURLConnection` in that it is responsible for loading data, with the main difference being that tasks share the common delegate of their parent `NSURLSession`. + +We'll dive into tasks first, and then talk more about session configuration later on. + +## NSURLSessionTask + +`NSURLSessionTask` is an abstract subclass, with three concrete subclasses that are used directly: `NSURLSessionDataTask`, `NSURLSessionUploadTask`, and `NSURLSessionDownloadTask`. These three classes encapsulate the three essential networking tasks of modern applications: fetching data, such as JSON or XML, and uploading and downloading files. + +NSURLSessionTask class diagram + +When an `NSURLSessionDataTask` finishes, it has associated data, whereas an `NSURLSessionDownloadTask` finishes with a temporary file path for the downloaded file. `NSURLSessionUploadTask` inherits from `NSURLSessionDataTask`, since the server response of an upload often has associated data. + +All tasks are cancelable, and can be paused and resumed. When a download task is canceled, it has the option to create _resume data_, which can then be passed when creating a new download task to pick up where it left off. + +Rather than being `alloc-init`'d directly, tasks are created by an `NSURLSession`. Each task constructor method has a version with and without a `completionHandler` property, for example `–dataTaskWithRequest:` +and `–dataTaskWithRequest:completionHandler:`. Similar to `NSURLConnection -sendAsynchronousRequest:queue:completionHandler:`, specifying a `completionHandler` creates an implicit delegate to be used instead of the task's session. For any cases where a session task delegate's default behavior needs to be overridden, the less convenient non-`completionHandler` variant would need to be used. + +### Constructors + +In iOS 5, `NSURLConnection` added the method `sendAsynchronousRequest:queue:completionHandler:`, which greatly simplified its use for one-off requests, and offered an asynchronous alternative to `-sendSynchronousRequest:returningResponse:error:`: + + NSURL *URL = [NSURL URLWithString:@"http://example.com"]; + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + + [NSURLConnection sendAsynchronousRequest:request + queue:[NSOperationQueue mainQueue] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + // ... + }]; + +`NSURLSession` iterates on this pattern with its task constructor methods. Rather than running immediately, the task object is returned to allow for further configuration before being kicked off with `-resume`. + +Data tasks can be created with either an `NSURL` or `NSURLRequest` (the former being a shortcut for a standard `GET` request to that URL): + + NSURL *URL = [NSURL URLWithString:@"http://example.com"]; + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionDataTask *task = [session dataTaskWithRequest:request + completionHandler: + ^(NSData *data, NSURLResponse *response, NSError *error) { + // ... + }]; + + [task resume]; + +Upload tasks can also be created with a request and either an `NSData` object for a URL to a local file to upload: + + NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"]; + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + NSData *data = ...; + + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:data + completionHandler: + ^(NSData *data, NSURLResponse *response, NSError *error) { + // ... + }]; + + [uploadTask resume]; + +Download requests take a request as well, but differ in their `completionHandler`. Rather than being returned all at once upon completion, as data and upload tasks, download tasks have their data written to a local temp file. It's the responsibility of the completion handler to move the file from its temporary location to a permanent location, which is then the return value of the block: + + NSURL *URL = [NSURL URLWithString:@"http://example.com/file.zip"]; + NSURLRequest *request = [NSURLRequest requestWithURL:URL]; + + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request + completionHandler: + ^(NSURL *location, NSURLResponse *response, NSError *error) { + NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + NSURL *documentsDirectoryURL = [NSURL fileURLWithPath:documentsPath]; + return [documentsDirectoryURL URLByAppendingPathComponent:[[response URL] lastPathComponent]]; + }]; + + [downloadTask resume]; + +### NSURLSession & NSURLConnection Delegate Methods + +Overall, the delegate methods of `NSURLSession` are a marked improvement over the rather ad-hoc pattern that emerged over the decade of `NSURLConnection`'s evolution. For a complete overview, check out this [mapping table](https://gist.github.com/dkduck/6870499). + +A few specific observations: + +NSURLSession has both session and task delegate methods for handling authentication challenge. The session delegate method handles connection level concerns, such as Server Trust and Client Certificate evaluation, NTLM, and Kerberos, while the task delegate handles request-based challenges, such as Basic, Digest, or Proxy authentication. + +Whereas `NSURLConnection` has two methods that signal that a request has finished (`NSURLConnectionDataDelegate -connectionDidFinishLoading:` and `NSURLConnectionDelegate -connection:didFailWithError:`), there is a single delegate method for `NSURLSession` (`NSURLSessionTaskDelegate -URLSession:task:didCompleteWithError:`) + +Delegate methods signaling the transfer of a certain number of bytes use parameters with the `int64_t` type in `NSURLSession`, as compared to `long long` used by `NSURLConnection`. + +NSURLSession introduces a new pattern to Foundation delegate methods with its use of `completionHandler:` parameters. This allows delegate methods to safely be run on the main thread without blocking; a delegate can simply `dispatch_async` to the background, and call the `completionHandler` when finished. It also effectively allows for multiple return values, without awkward argument pointers. In the case of `NSURLSessionTaskDelegate -URLSession:task:didReceiveChallenge:completionHandler:`, for example, `completionHandler` takes two arguments: the authentication challenge disposition, and the credential to be used, if applicable. + +> For more information about session tasks, check out [WWDC Session 705: "What’s New in Foundation Networking"](http://asciiwwdc.com/2013/sessions/705) + +## NSURLSessionConfiguration + +`NSURLSessionConfiguration` objects are used to initialize `NSURLSession` objects. Expanding on the options available on the request level with `NSMutableURLRequest`, `NSURLSessionConfiguration` provides a considerable amount of control and flexibility on how a session makes requests. From network access properties, to cookie, security, and caching policies, as well as custom protocols, launch event settings, and several new properties for mobile optimization, you'll find what you're looking for with `NSURLSessionConfiguration`. + +Sessions copy their configuration on initialization, and though `NSURLSession` has a `readonly` `configuration` property, changes made on that object have no effect on the policies of the session. Configuration is read once on initialization, and set in stone after that. + +### Constructors + +There are three class constructors for `NSURLSessionConfiguration`, which do well to illustrate the different use cases for which `NSURLSession` is designed. + +`+defaultSessionConfiguration` returns the standard configuration, which is effectively the same as the `NSURLConnection` networking stack, with the same shared `NSHTTPCookieStorage`, shared `NSURLCache`, and shared `NSURLCredentialStorage`. + +`+ephemeralSessionConfiguration` returns a configuration preset with no persistent storage for caches, cookies, or credentials. This would be ideal for a feature like private browsing. + +`+backgroundSessionConfiguration:` is unique in that it creates a _background session_. Background sessions differ from regular, run-of-the-mill sessions in that they can run upload and download tasks even when the app is suspended, exits, or crashes. The identifier specified during initialization is used to provide context to any daemons that may resume background transfers out of process. + +> For more information about background sessions, check out [WWDC Session 204: "What's New with Multitasking"](http://asciiwwdc.com/2013/sessions/204) + +### Properties + +There are 20 properties on `NSURLSessionConfiguration`. Having a working knowledge of what they are will allow apps to make the most of its networking environments. + +#### General + +`HTTPAdditionalHeaders` specifies a set of default headers to be set on outbound requests. This is useful for information that is shared across a session, such as content type, language, user agent, and authentication: + + NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password]; + NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding]; + NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0]; + NSString *authString = [NSString stringWithFormat:@"Basic %@", base64EncodedCredential]; + NSString *userAgentString = @"AppName/com.example.app (iPhone 5s; iOS 7.0.2; Scale/2.0)"; + + configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json", + @"Accept-Language": @"en", + @"Authorization": authString, + @"User-Agent": userAgentString}; + +`networkServiceType` distinguishes between standard network traffic, VOIP, voice, video, and traffic used by a background process. Most applications won't need to set this. + + `allowsCellularAccess` and `discretionary` are used to save bandwidth over cellular connections. It is recommended that the `discretionary` property is used instead of `allowsCellularAccess` for background transfers, as it takes WiFi and power availability into account. + +`timeoutIntervalForRequest` and `timeoutIntervalForResource` specify the timeout interval for the request as well as the resource. Many developers have used the `timeoutInterval` in an attempt to limit the total amount of time spent making the request, rather than what it actually represents: the amount of time between packets. `timeoutIntervalForResource` actually provides that overall timeout, which should only really be used for background transfers, rather than anything a user might actually want to wait for. + +`HTTPMaximumConnectionsPerHost` is a new configuration option for the Foundation URL Loading System. It used to be that `NSURLConnection` would manage a private connection pool. Now with `NSURLSession`, developers can limit that number of concurrent connections to a particular host, should the need arise. + +`HTTPShouldUsePipelining` can be found on `NSMutableURLRequest` as well, and can be used to turn on [HTTP pipelining](http://en.wikipedia.org/wiki/HTTP_pipelining), which can dramatically reduce loading times of requests, but is not widely supported by servers, and is disabled by default. + +`sessionSendsLaunchEvents` is another new property that specifies whether the session should be launched from the background. + +`connectionProxyDictionary` specifies the proxies used by connections in the sessions. Again, most consumer-facing applications don't deal with proxies, so it's unlikely that this property would need to be configured. + +> Additional information about connection proxies can be found in the [`CFProxySupport` Reference](https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFProxySupport/Reference/reference.html). + +#### Cookie Policies + +`HTTPCookieStorage` is the cookie storage used by the session. By default, `NSHTTPCookieShorage +sharedHTTPCookieStorage` is used, which is the same as `NSURLConnection`. + +`HTTPCookieAcceptPolicy` determines the conditions in which the session should accept cookies sent from the server. + +`HTTPShouldSetCookies` specifies whether requests should use cookies from the session `HTTPCookieStorage`. + +#### Security Policies + +`URLCredentialStorage` is the credential storage used by the session. By default, `NSURLCredentialStorage +sharedCredentialStorage` is used, which is the same as `NSURLConnection`. + +`TLSMaximumSupportedProtocol` and `TLSMinimumSupportedProtocol` determine the supported `SSLProtocol` versions for the session. + +#### Caching Policies + +`URLCache` is the cache used by the session. By default, `NSURLCache +sharedURLCache` is used, which is the same as `NSURLConnection`. + +`requestCachePolicy` specifies when a cached response should be returned for a request. This is equivalent to `NSURLRequest -cachePolicy`. + +#### Custom Protocols + +`protocolClasses` is a session-specific array of registered `NSURLProtocol` classes. + +## Conclusion + +The changes to the URL Loading System in iOS 7 and Mac OS X 10.9 Mavericks are a thoughtful and natural evolution of `NSURLConnection`. Overall, the Foundation Team did an amazing job of identifying and anticipating the existing and emerging use cases of mobile developers, by creating genuinely useful APIs that lend themselves well to everyday tasks. + +While certain decisions in the architecture of session tasks are a step backward in terms of composability and extensibility, `NSURLSession` nonetheless serves as a great foundation for higher-level networking functionality. diff --git a/2013-10-08-getting-to-know-textkit.md b/2013-10-08-getting-to-know-textkit.md new file mode 100644 index 0000000..df82c05 --- /dev/null +++ b/2013-10-08-getting-to-know-textkit.md @@ -0,0 +1,403 @@ +--- +layout: post +title: "Getting to Know TextKit" +category: "5" +date: "2013-10-07 11:00:00" +tags: article +author: "Max Seelemann" +--- + + +The release of iOS 7 brings a lot of new tools to the table for developers. One of these is *TextKit*. TextKit consists of a bunch of new classes in UIKit that, as the name suggests, somehow deal with text. Here, we will cover how TextKit came to be, what it’s all about, and — by means of a couple of examples — how developers can put it to great use. + +But let’s have some perspective first: TextKit is probably *the* most significant recent addition to UIKit. iOS 7’s new interface replaces a lot of icons and bezels with plain-text buttons. Overall, text and text layout play a much more significant role in all visual aspects of the OS. It is perhaps no overstatement to say that iOS 7’s redesign is driven by text — text that is all handled by TextKit. + +To give an idea of how big this change really is: in every version of iOS prior to 7, (almost) all text was handled by WebKit. That’s right: WebKit, the web browser engine. All `UILabel`s, `UITextField`s, and `UITextView`s were using web views in the background in some way to lay out and render text. For the new interface style, they have all been reengineered to take advantage of TextKit. + + +## A Short History of Text on iOS + +These new classes are no replacement for something that was previously available to developers. What TextKit does is absolutely new to the SDK. Before iOS 7, all the things TextKit does now would have to be done manually. It is the missing link between already existing functionalities. + +For a long time, there was a framework for bare bones text layout and rendering: *CoreText*. There also was a way to directly grab a user’s input from the keyboard: the `UITextInput` protocol. In iOS 6, there even was a way of getting the system’s text selection for almost free: by subclassing `UITextView`. + +(This is probably the point where I should disclose my 10 years of experience in shipping text editors.) There is a huge (read: HUGE) gap between rendering text and grabbing keyboard input. This gap is probably also the reason why there always were so few rich-text or syntax-highlighting editors — getting a text editor right was, without doubt, a couple of months' worth of work. + +So here it goes - a short rundown of the (not so) short history of text on iOS: + +**iOS 2**: The first public SDK includes a simple text display component (`UILabel`), a simple text input component (`UITextField`), and a simple, scrollable, editable component for larger amounts of text: `UITextView`. These are all plain text only, have no selection support (just insertion points), and allow almost no customization beyond setting a font and a text color. + +**iOS 3**: New features are copy and paste and — as a requirement for these — also text selection. Data detectors introduce a way to highlight phone numbers and URLs in text views. However, there is still essentially nothing a developer could influence beyond turning these features off or on. + +**iOS 3.2**: The introduction of the iPad brings CoreText, the +aforementioned low-level text layout and rendering engine (which was ported from Mac OS X 10.5), and `UITextInput`, the also-mentioned keyboard access. Apple demos Pages as the new light tower of text editing on mobile devices[^1]. However, due to the framework gap I talked about earlier, only very few apps follow suit. + +**iOS 4**: Announced only months after iOS 3.2, there is nothing new to text. +_(Anecdote: Me at WWDC, I walk up to engineers, tell them I want the fully fledged text layout system on iOS. The answer: “Yeah… File a radar.” Not unexpected…)_ + +**iOS 5**: No news regarding text. +_(Anecdote: Me at WWDC, I tell engineers about a text system on iOS. The answer: “We don’t see a lot of requests for that…” Doh!)_ + +**iOS 6**: Some movement: Attributed text editing comes to `UITextView`. It is, unfortunately, hardly customizable. The default UI does bold, italic, and underline. Users can set font sizes and colors. While this is great at first sight, there is still no control over layout or a convenient way to customize text attributes. For (text-editing) developers however, there is a big new feature: it’s now possible to subclass `UITextView` to get, in addition to the keyboard input that was previously available, text selection “for free.” Having to implement a completely custom text selection has probably put most previous attempts of non-plain-text tools to a halt. +_(Anecdote: Me, WWDC, engineers. I want a text system on iOS. Answer: “Uhhhm. Ehm. Yes. Maybe? See, it just doesn’t perform…” So there is hope after all, isn’t there?)_ + +**iOS 7**: Finally. TextKit. + + +## Features + +So here we are. iOS 7-land with TextKit. Let’s see what it can do! Before we dive into it I still want to mention that, strictly speaking, most of these things were *possible* before. If you had plenty of resources and time to build a text engine on top of CoreText, these were all doable. But if you wanted to build a fully fledged rich text editor before, this could mean *months* of work. Now it’s as easy as opening an interface file in Xcode and dropping a `UITextView` into your view controller to get all these features: + +**Kerning**: Drop the idea that all characters have simple quadratic shapes and that these shapes must be placed exactly adjacent to each other. Modern text layout takes into account that, for example, a capital letter “T” does have a lot of free space under its “wings” and moves the following lowercase letters closer. This results in significantly improved legibility of text, especially in longer pieces of writing. + +Kerning: the bounding box of the letter “a” (blue rect) clearly overlapp the capital “T” when kerning is enabled. + +**Ligatures**: I consider this mostly an artistic feature, but some texts do look nicer (more artistic) when certain character combinations (like an “f” followed by an “l”) are drawn using combined symbols (so-called glyphs). + +Ligatures: the “Futura” font family contains special symbols for character combinations like “fl”. + +**Image Attachments**: It is now possible to have images inside a text view. + +**Hyphenation**: Not so important for editing text, but for presenting text in a nice and readable way. Hyphenation means splitting longer words at line boundaries, creating a more homogeneous flow and look of the overall text. +_Anecdote:_ Before iOS 7, developers had to employ CoreText directly. Like so: Start by detecting the text language on a per-sentence basis, then get the possible hyphenation point for each word in the sentence, then insert a custom hyphenation placeholder character at each suggested point. After preparation is done, run CoreText’s layout and manually insert a hyphen into wrapped lines. If you want great results, check afterward if the text with the hyphen still fits into the line’s boundaries and if it doesn’t, re-run the lines’s layout without the previously used hyphenation point. +With TextKit, enabling hyphenation is as easy as setting the `hyphenationFactor` property. + +The text in this view would have looked much more compartmentalized without hyphenation. + +**Customizability**: For me, even more than the improved typesetting, this is *the* new feature. Before, developers had the choice between using what was there or rewriting it all from scratch on their own. Now there is a set of classes that have delegate protocols or can be overwritten to change *part* of their behavior. For example, you can now influence the line break behavior of certain words without having to re-write the complete text component. I consider this a win. + +**More Rich Text Attributes**: It is now possible to set different underline styles (double, thick, dashed, dotted, or a combination thereof). It is very easy to shift the text baseline, for example, for doing superscript numbers. Also, developers no longer have to draw background colors for custom rendered text on their own (CoreText does not have support for these). + +**Serialization**: Previously, there was no built-in way to read strings with text attributes from disk. Or to write it out again. Now there is. + +**Text Styles**: iOS 7’s interface introduces a new concept of globally predefined types of text. These types of text are assigned a globally predefined look. Ideally this will result in headings and continuous text looking the same all over the system. Users can define their reading habits (like text size) from the Preferences app, and apps that use text styles will automatically have the right text size and look. + +**Text Effects**: Last and least. In iOS 7 there is exactly one text effect: Letterpress. Text with this effect will look like it was physically stamped into a sheet of paper. Inner shadows, etcetera. +_Opinion: Really? What the…? In an OS that completely, radically, and unforgivably kills useless [skeuomorphism][1], who needs the look of text-stamped into paper?_ + +## Structure + +The best way to get an overview of a system is probably to draw an image. Here is a schematic of UIKit’s text system, TextKit: + +The structure of all essential TextKit classes. Highlighted with a “New” badge are classes introduced in iOS 7 + +As can be seen from the picture, putting a text engine to work requires a couple of actors. We will cover them starting from outside: + +**String**: Where there is text to be drawn, there must somewhere be a string to hold it. In the default configuration, the string is contained within and managed by the `NSTextStorage`, and in these cases, it may be left out from the drawing. +But that need not necessarily be the case. With TextKit, the text can originate from whatever source suits the use case. For a code editor, for example, the string could actually be an annotated syntax tree (AST) that contains all information about the structure of the displayed code. With a custom-built text storage, this text is then only later and dynamically enriched with text attributes like font or color highlights. For the first time, developers are able to directly use their own model for a text component. All that is needed is a specially engineered text storage. Which leads us to: + +`NSTextStorage`: If you see the text system as model-view-controller (MVC) architecture, this class represents the model. A text storage is the central object that knows everything about the text and its attributes. It provides access to them through a mere two accessor methods and allows changing text and attributes through just two more methods. We will give them a closer look below. +For now, it’s important to understand that `NSTextStorage` inherits these methods from its superclass, `NSAttributedString`. This makes it clear that a text storage — as seen by the text system – is just a string with attributes, albeit with a few extensions. The only significant difference between the two is that a text storage contains a way to post notifications about all changes made to its contents. We will also cover that in a moment. + +`UITextView`: On the opposite end of the stack is the actual view. In TextKit, the text view serves two purposes: For one, it is the view that is drawn into by the text system. The text view itself does *not* do any drawing on its own; it just provides a region that others draw to. +As the only component that is contained in the view hierarchy, the second purpose is to deal with all user interaction. Specifically, the text view implements the `UITextInput` protocol to handle keyboard events, and it provides a way for the user to set an insertion point or select text. It does not do any actual change to the text but merely forwards these changes to the just-discussed text storage. + +`NSTextContainer`: Every text view defines a region that text can be drawn to. For this, each text view has a *text container* that precisely describes the area available. In simple cases, this is a vertical infinitely sizable rectangular area. Text is then filled into the region and the text view enables the user to scroll through it. +In more advanced cases however, this region may be an infinitely large rectangle. For example, when rendering a book, each page has a maximum height and width. The text container would then define this size and not accept any text beyond. In the same case, an image might cover parts of the page and text should re-flow around its edges. This is also handled by the text container, as we will see in a example a little later. + +`NSLayoutManager`: The layout manager is the central component that brings it all together: + +1. The manager listens to text or attribute change notifications in the text storage and, upon reception, triggers the layout process. +2. Starting with the text provided by the text storage, it translates all characters into glyphs[^2]. +3. Once the glyphs have been generated, the manager consults its text container(s) for the available regions for text. +4. These regions are then filled stepwise with lines, which again are filled stepwise with glyphs. Once a line has been filled, the next one is started. +5. For each line, the layout manager must consider line-breaking behavior (the word not fitting must be moved to the next line), hyphenation, inline image attachments, and so forth. +6. When layout is finished, the text view’s current display state is invalidated and the layout manager draws the previously set text *into* the text view. + +**CoreText**: Not directly contained within TextKit, CoreText is the library that does the actual typesetting. For each of the layout manager’s steps, CoreText is consulted in one way or another. It provides the translation from characters to glyphs, fills line segments with them, and suggest hyphenation points. + + +### Cocoa Text System + +Building a system as big and as complex as TextKit is certainly nothing done easily or quickly and definitely requires *a lot* of experience and expertise to be successful. The fact that a “real” text component has been missing from iOS for six subsequent major releases is also quite telling. Apple is definitely correct in selling it as a big new feature. But is it really new? + +Here’s a number: out of the [131 public classes in UIKit][3], all but nine have the `UI` prefix in their name. These nine classes carry the legacy, old-world (read: Mac OS) prefix `NS`. And out of these nine classes, seven deal with text. Coincidence? Well… + +Here’s a schematic of the Cocoa Text System. Feel free to compare it to the one for TextKit above. + +The structure of all essential classes of the Cocoa Text System as present on Mac OS today. + +The similarity is staggering. It’s clear that, at least in huge parts, the two are the same. Obviously – with the exception of the right side with `NSTextView` and `UITextView` — the primary classes are all the same. TextKit is (at least a partial) port of Cocoa’s text system to iOS. _(The one I asked for in my anecdotes, yay!)_ + +When giving it a closer look, there are some differences. Most notable of these are: + +- There are no `NSTypesetter` and no `NSGlyphGenerator` classes on iOS. While on Mac OS there is every imaginable way of customizing typesetting, these possibilities have been dramatically simplified. This allowed for the ridding of some abstractions and allowed the whole process to be merged into `NSLayoutManager`. What remains is a handful of delegate methods to alter text layout and line break behavior. +- There are a couple of new, nice conveniences in the iOS variants of the classes. Excluding certain regions from a text container (see above) must be done by hand in Cocoa. The UIKit class, however, provides a simple `exclusionPaths` property. +- Some examples of left-out functionality are support for inline tables and different attachments than images. + +All-in-all the system is still the same, though. `NSTextStorage` is exactly the same class on both platforms and `NSLayoutManager`and `NSTextContainer` do not differ so significantly. The changes that were made seem to (in some cases dramatically) ease the use of the text system while not cutting too many special cases. I consider this a good thing. + +_Reviewing the answers I got from Apple engineers regarding porting the Cocoa Text System to iOS in hindsight reveals quite a bit of background information. The reason for the delay and the reduction in functionality is simple: performance, performance, performance. Text layout can be an extremely expensive task — memory-wise, power-wise, and time-wise — especially on a mobile device. Apple had to opt for a simpler solution and to wait for more processing power to be able to at least partially support a fully fledged text layout engine._ + + +## Examples + +To illustrate what is possible with TextKit, I created a little demo project that [can be found on GitHub][4]. In this demo I did only things that were not easily possible before. I must admit that coding it up took only a Sunday morning; something similar would probably have taken me days or weeks before. + +TextKit consists of well over 100 methods, and is just to big to be covered exhaustively in one article. Besides the fact that, most of the time, all you need is just the one right method, TextKit’s usage and customizability are also still to be explored. So instead of doing one big demonstration that covers it all, I decided to do four smaller ones. In each one, I tried to show a different aspect and a different class for customization. + + +### Demo 1: Configurations + +Let’s get started with an easy one: configuring the text system. As you may have seen from the TextKit figure above, the arrows between `NSTextStorage`, `NSLayoutManager`, and `NSTextContainer` are double-headed. With that, I tried to indicate these relationships are 1-to-N. That’s right: a text storage can hold multiple layout managers, and a layout manager can hold multiple text containers. These multiplicities enable great features: + +- Adding multiple layout managers on a single text storage results in two (or more) *visual representations of the same text* that can be shown side by side. Each of these representations can be placed and sized independently. If the corresponding text views are editable, all changes performed in either view are then immediately mapped to the other. +- Adding multiple text containers to a single layout manager results in one representation of the text being *spread across multiple views*. One example where this is useful is a page-based layout: each page would contain a separate text view. The text containers of those views would, however, be referenced with a single layout manager which then spreads text across them. + +When instantiating a `UITextView` from a storyboard or an interface file, it will come with a text system preconfigured: one text storage, referencing one layout manager, referencing one text container. In the same way, a text system stack can be built directly from code: + + NSTextStorage *textStorage = [NSTextStorage new]; + + NSLayoutManager *layoutManager = [NSLayoutManager new]; + [textStorage addLayoutManager: layoutManager]; + + NSTextContainer *textContainer = [NSTextContainer new]; + [layoutManager addTextContainer: textContainer]; + + UITextView *textView = [[UITextView alloc] initWithFrame:someFrame + textContainer:textContainer]; + +This is as straightforward as it can be. The only thing to remember when building a text system by hand is that your view controller must retain the text storage. The text view being at the end of the stack only weakly references the text storage and the layout manager. When the text storage is released, the layout manager is as well, leaving the text view with a disconnected container. + +There is one exception to this rule. Only when instantiating a text view from an interface or a storyboard, the text view *does* retain the text storage. The framework applies some black magic to ensure all objects are retained without forming a retain cycle. + +With that in mind, creating a more advanced setup is also pretty straightforward. Assume in a view that there is already a text view as instantiated from the nib, called `originalTextView`. Adding a second text view for the same text essentially means just copying the above code and reusing the text storage from the original view: + + NSTextStorage *sharedTextStorage = originalTextView.textStorage; + + NSLayoutManager *otherLayoutManager = [NSLayoutManager new]; + [sharedTextStorage addLayoutManager: otherLayoutManager]; + + NSTextContainer *otherTextContainer = [NSTextContainer new]; + [otherLayoutManager addTextContainer: otherTextContainer]; + + UITextView *otherTextView = [[UITextView alloc] initWithFrame:someFrame + textContainer:otherTextContainer]; + +Adding a second text container to a layout manager works almost the same. Let’s say we wanted the text in the above example to fill *two* text views instead of just one. Easy: + + NSTextContainer *thirdTextContainer = [NSTextContainer new]; + [otherLayoutManager addTextContainer: thirdTextContainer]; + + UITextView *thirdTextView = [[UITextView alloc] initWithFrame:someFrame + textContainer:thirdTextContainer]; + +But there is one caveat: Since the text container in the other text view resizes infinitely, the third one will never get any text. We must thus indicate that instead of resizing and scrolling, the text should reflow from one view to the other: + + otherTextView.scrollEnabled = NO; + +Unfortunately, as it seems, adding multiple text containers to a single layout manager disables editing. If text should remain editable, only a single text container may be used per layout manager. + +For a working example of this configuration see the “Configuration” tab in the aforementioned [TextKitDemo][4]. + + +### Demo 2: Syntax Highlighting + +While configuring text views is not *that* exciting, here comes something much more interesting: syntax highlighting! + +Looking at the distribution of responsiveness across the TextKit components, it’s clear syntax highlighting should be implemented in the text storage. Since `NSTextStorage` is a class cluster[^3], subclassing requires a little bit of work. The idea here is to build a composite object: Implement all methods by just forwarding them to a concrete instance, modifying `inout` parameters or results as wished. + +`NSTextStorage` inherits from `NSMutableAttributedString` and must implement the following four methods — two getters and two setters: + + - (NSString *)string; + - (NSDictionary *)attributesAtIndex:(NSUInteger)location + effectiveRange:(NSRangePointer)range; + - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str; + - (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range; + +The implementation of a composite object of a class cluster subclass is also pretty straightforward. First, find the *simplest* class that fulfills all requirements. In our case this is `NSMutableAttributedString`, which we use as implementation of the custom storage: + + @implementation TKDHighlightingTextStorage + { + NSMutableAttributedString *_imp; + } + + - (id)init + { + self = [super init]; + if (self) { + _imp = [NSMutableAttributedString new]; + } + return self; + } + +With the object in place, responding to the getters requires just a one-liner: + + - (NSString *)string + { + return _imp.string; + } + + - (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range + { + return [_imp attributesAtIndex:location effectiveRange:range]; + } + +Responding to the setters is also almost as simple. There is one catch, though: The text storage needs to notify its layout managers that a change happened. The setters must thus also invoke `-edited:range:changeInLegth:` and pass along a change description. Sounds worse that it turns out to be: + + - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str + { + [_imp replaceCharactersInRange:range withString:str]; + [self edited:NSTextStorageEditedCharacters range:range + changeInLength:(NSInteger)str.length - (NSInteger)range.length]; + } + + - (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range + { + [_imp setAttributes:attrs range:range]; + [self edited:NSTextStorageEditedAttributes range:range changeInLength:0]; + } + +With that, we have a fully functional replacement for the text storage in the text system stack. Plugging it into a text view as loaded from a interface file goes like this — but remember to reference the text storage from an instance variable: + + _textStorage = [TKDHighlightingTextStorage new]; + [_textStorage addLayoutManager: self.textView.layoutManager]; + +So far so good. We managed to plug in a custom text storage, and next we need to actually highlight some parts of the text. For now, a simple highlighting should suffice: We want to color all iWords red — words that start with a lowercase “i” followed by an uppercase letter. + +A convenient place to implement highlighting is to overwrite `-processEditing`. This method is automatically called after every change to the text storage. `NSTextStorage` uses this method to sanitize the string after an edit. When, for example, some characters would be invisible with the chosen font, the storage replaces them with a font that is capable of showing them. + +As with everything else, adding the simple highlighting of iWords is straightforward. We override `-processEditing`, call `super`’s implementation, and set up a regular expression for finding words: + + - (void)processEditing + { + [super processEditing]; + + static NSRegularExpression *iExpression; + NSString *pattern = @"i[\\p{Alphabetic}&&\\p{Uppercase}][\\p{Alphabetic}]+"; + iExpression = iExpression ?: [NSRegularExpression regularExpressionWithPattern:pattern + options:0 + error:NULL]; + +Then first, clean all previously assigned highlights: + + NSRange paragaphRange = [self.string paragraphRangeForRange: self.editedRange]; + [self removeAttribute:NSForegroundColorAttributeName range:paragaphRange]; + +And second, iterate all pattern matches and assign new highlights: + + [iExpression enumerateMatchesInString:self.string + options:0 range:paragaphRange + usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) + { + [self addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:result.range]; + }]; + } + +That’s it. We built a dynamic text view with syntax highlighting. The highlighting will be applied *live* as the user types. And it only took a couple lines of code. How cool is that? + +A screenshot from the TextKitDemo project showing the text view with iWords highlighted. + +Please note that just using the *edited range* will not be sufficient. For example, when manually typing in iWords, the regexp would only match *after* the third character of a word has been entered. But then the `editedRange` covers just the third character and thus all processing would inspect this character only. By re-processing the complete paragraph, we’re on the safe side without giving up too much performance. + +For a working example, see the “Highlighting” tab in the aforementioned [TextKitDemo][4]. + + +### Demo 3: Layout Modifications + +As previously mentioned, the layout manager is the central layout workhorse. What is highly customizable in `NSTypesetter` on Mac OS has been merged into `NSLayoutManager` on iOS. While the complete customizability of the Cocoa Text System is not available in TextKit, a bunch of delegate methods allow some adjustments. As mentioned, TextKit is more tightly integrated with CoreText mostly due to performance reasons. But the philosophy is to some extent different between the two text systems: + +**Cocoa Text System**: On Mac OS, where performance is not a problem, the design is all about flexibility. Probably so: “This is the thing that does it. You can override if you want. You can adjust about everything. Performance does not matter. You may also supply your *completely* own character to glyph translation, just go ahead…” + +**TextKit**: Here is where performance seems to be a real issue. The philosophy (at least for now) is more in along the lines of: “We did it in a simple but performant way. Here is the result, but we give you the chance to alter some of it. You’re asked only at points that don’t hurt performance too hard, though.” + +Enough philosophy, let’s customize something. For example, how about adjusting line heights? Sounds crazy, but adjusting line heights has been [at least hacky or required using private API][8] on previous releases of iOS. Fortunately, this is — again — now a no-brainer. Set the layout manager’s delegate and implement a single method: + + - (CGFloat) layoutManager:(NSLayoutManager *)layoutManager + lineSpacingAfterGlyphAtIndex:(NSUInteger)glyphIndex + withProposedLineFragmentRect:(CGRect)rect + { + return floorf(glyphIndex / 100); + } + +In the above code, I changed the line spacing to increase with the text length. This results in lines on top being closer together than those at the bottom. Not particularly useful I admit, but it’s *possible* (and there surely are more practical use cases). + +OK, let’s have a more realistic scenario. Say you have links in a text and do not want these to be wrapped around lines. If possible, a URL should always appear as a whole in a single piece of text. Nothing could be simpler that that. + +We start by using a custom text storage just like the one previously discussed. But instead of detecting iWords, it finds links and marks them as such: + + static NSDataDetector *linkDetector; + linkDetector = linkDetector ?: [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink error:NULL]; + + NSRange paragaphRange = [self.string paragraphRangeForRange: NSMakeRange(range.location, str.length)]; + [self removeAttribute:NSLinkAttributeName range:paragaphRange]; + + [linkDetector enumerateMatchesInString:self.string + options:0 + range:paragaphRange + usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) + { + [self addAttribute:NSLinkAttributeName value:result.URL range:result.range]; + }]; + +With this at hand, changing the line break behavior is as easy as implementing a single layout manager delegate method: + + - (BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex + { + NSRange range; + NSURL *linkURL = [layoutManager.textStorage attribute:NSLinkAttributeName + atIndex:charIndex + effectiveRange:&range]; + + return !(linkURL && charIndex > range.location && charIndex <= NSMaxRange(range)); + } + +For a working example, see the “Layout” tab in the aforementioned [TextKitDemo][4]. Here’s a screenshot: + +A screenshot from the TextKitDemo project showing altered line break behavior for link URLs. + +By the way, the green outline in the shot above is something usually not possible with TextKit. In the same demo, I’ve added a little trick for drawing an outline around text right from within a layout manager subclass. Extending TextKit’s drawing in special ways is also done quite easily. Be sure to check it out! + + +### Demo 4: Text Interaction + +Having covered `NSTextStorage` and `NSLayoutManager`, the last demo will play a little with `NSTextContainer`. This class is not very complex and it doesn’t do anything other than specifying where text may or may not be placed. + +Not placing text in some regions of a view is a common requirement, for example, in magazine apps. For this case, `NSTextContainer` on iOS provides a property Mac developers have long sought after: `exclusionPaths` allows developers to set an array of `NSBezierPath`s that should not be filled with text. To get an idea of what this is, take a look at the following screenshot: + +A screenshot from the TextKitDemo project showing text revolving around an excluded oval view. + +As you can see, all text is placed outside the blue shape. Getting this behavior into a text view is simple, but it has a small catch: The coordinates of the bezier path must be specified in container coordinates. Here is the conversion: + + - (void)updateExclusionPaths + { + CGRect ovalFrame = [self.textView convertRect:self.circleView.bounds + fromView:self.circleView]; + + ovalFrame.origin.x -= self.textView.textContainerInset.left; + ovalFrame.origin.y -= self.textView.textContainerInset.top; + + UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:ovalFrame]; + self.textView.textContainer.exclusionPaths = @[ovalPath]; + } + +In this example, I am using a user-positionable view that can then be moved around freely while the text reflows live around the shape. We start by converting its bounds (`self.circleView.bounds`) to the coordinate system of the text view. + +Because without an inset, text would glitch too close to the view borders, `UITextView` insets its text container by a few points. Thus, to get the exclusion path in container coordinates, the text container inset must be subtracted from the origin. + +After that, getting the exclusion to be applied is as easy as setting a bezier path on the text container. Everything else is transparently and automatically handled by TextKit for you. + +For a working example, see the “Interaction” tab in the [TextKitDemo][4]. As a little gimmick, it also includes a view that follows the current text selection. Because, you know, what would a good text editor demo be without a little ugly annoying paper clip getting in your way? + +[^1]: Pages did — according to Apple — use absolutely no private API. \*cough\*
+My theory: it either used an extremely early version of TextKit or copied half of UIKit’s private sources. Or a mix thereof. + +[^2]: _Glyphs_: While characters are the “semantical” representation of a letter, glyphs are the visual representation thereof. Depending on the font used, glyphs are either bezier paths or bitmap images defining the shape that should be drawn. See also the excellent [Wikipedia article][2] about glyphs. + +[^3]: In a class cluster, only an abstract superclass is public. Allocating an instance actually creates an object of a private subclass. As such, subclassing always happens on an abstract class and requires all methods to be implemented. See also the [class cluster documentation][6]. + +[1]: http://en.wikipedia.org/wiki/Skeuomorph "Skeuomorphism on Wikipedia" +[2]: http://en.wikipedia.org/wiki/Glyph "Glyphs on Wikipedia" +[3]: https://developer.apple.com/library/ios/documentation/uikit/reference/UIKit_Framework/_index.html +[4]: https://github.com/objcio/issue-5-textkit "TextKitDemo by macguru on GitHub" +[6]: https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html +[8]: http://stackoverflow.com/questions/3760924/set-line-height-in-uitextview/3914228 + +[image-1]: /images/issue-5/kerning.png +[image-2]: /images/issue-5/ligature.png +[image-3]: /images/issue-5/Screen%20Shot%202013-09-29%20at%2022.19.58.png +[image-4]: /images/issue-5/TextKit.png +[image-5]: /images/issue-5/CocoaTextSystem.png +[image-6]: /images/issue-5/SyntaxHighlighting.png +[image-7]: /images/issue-5/LineBreaking.png +[image-8]: /images/issue-5/ReflowingTextAndClippy.png diff --git a/2013-10-08-iOS7-hidden-gems-and-workarounds.md b/2013-10-08-iOS7-hidden-gems-and-workarounds.md new file mode 100644 index 0000000..6509f8e --- /dev/null +++ b/2013-10-08-iOS7-hidden-gems-and-workarounds.md @@ -0,0 +1,256 @@ +--- +layout: post +title: "iOS 7: Hidden Gems and Workarounds" +category: "5" +date: "2013-10-07 06:00:00" +tags: article +author: "Peter Steinberger" +--- + +When iOS 7 was first announced, Apple developers all over the world tried to compile their apps, and spent the next few months fixing whatever was broken, or even rebuilding the app from scratch. As a result, there wasn't much time to take a closer look at what's new in iOS 7. Apart from the obvious great small tweaks like NSArray's `firstObject`, which has been retroactively made public all the way back to iOS 4, there are a lot more hidden gems waiting to be discovered. + +## Smooth Fade Animations + +I'm not talking about the new spring animation APIs or UIDynamics, but something more subtle. CALayer gained two new methods: `allowsGroupOpacity` and `allowsEdgeAntialiasing`. Now, group opacity isn't something particularly new. iOS was evaluating the `UIViewGroupOpacity` key in the Info.plist for quite some time and enabled/disabled this application-wide. For most apps, this was unwanted, as it decreases global performance. In iOS 7, this now is enabled by default for applications that link against SDK 7 - and since certain animations will be slower when this is enabled, it can also be controlled on the layer. + +An interesting detail is that `_UIBackdropView` (which is used as the background view inside `UIToolbar` or `UIPopoverView`) can't animate its blur if `allowsGroupOpacity` is enabled, so you might want to temporarily disable this when you do an alpha transform. Since this degrades the animation experience, you can fall back to the old way and temporarily enable `shouldRasterize` during the animation. Don't forget setting the appropriate `rasterizationScale` or the view will look pixelerated on retina devices. + +The edge antialiasing property can be useful if you want to replicate the animation that Safari does when showing all tabs. + +## Blocking Animations + +A small but very useful addition is `[UIView performWithoutAnimation:]`. It's a simple wrapper that checks if animations are currently enabled, disables them, executes the block, and re-enables animations. One caveat is that this will *not* block CoreAnimation-based animations. So don't be too eager in replacing all your calls from: + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + view.frame = CGRectMake(...); + [CATransaction commit]; + +to: + + [UIView performWithoutAnimation:^{ + view.frame = CGRectMake(...); + }]; + +However, most of the time this will do the job just fine, as long as you don't deal with CALayers directly. + +In iOS 7, I had quite a few code paths (mostly `UITableViewCells`) that needed additional protection against accidental animations, for example, if a popover is resized and at the same time the displayed table view loads up new cells because of the height change. My usual workaround is wrapping the entire `layoutSubviews` into the animation-block-method: + + - (void)layoutSubviews + { + // Otherwise the popover animation could leak into our cells on iOS 7 legacy mode. + [UIView performWithoutAnimation:^{ + [super layoutSubviews]; + _renderView.frame = self.bounds; + }]; + } + + +## Dealing with Long Table Views + +`UITableView` is very efficient and fast, unless you begin using `tableView:heightForRowAtIndexPath:`, where it starts calling this for *every* element in your table, even the non-visible ones - just so that the underlying `UIScrollView` can get the correct `contentSize`. There were some workarounds before, but nothing great. In iOS 7, Apple finally acknowledged the problem and added `tableView:estimatedHeightForRowAtIndexPath:`, which defers most of the cost down to actual scrolling time. If you don't know the size of a cell at all, simply return `UITableViewAutomaticDimension`. + +There's now a similar API for section headers/footers as well. + +## UISearchDisplayController + +Apple's search controller learned a new trick to simplify moving the search bar into the navigation bar. Enable `displaysSearchBarInNavigationBar` for that (unless you also use a scope bar - then you're out of luck.) Now I would love to write that that's it, but sadly, `UISearchDisplayController` seems to be terribly broken on iOS 7, especially on iPad. Apple seems to have run out of time, so showing the search results will not hide the actual table view. Before 7, this wasn't an issue, but since the `searchResultsTableView` has a transparent background color, it looks pretty bad. As a workaround, you can either set an opaque background color, or move to some [more sophisticated ways of hacking](http://petersteinberger.com/blog/2013/fixing-uisearchdisplaycontroller-on-ios-7/) to get what you expect. I've had very mixed results with this control, including it not showing the search table view *at all* when using `displaysSearchBarInNavigationBar`. + +Your results may vary, but I've required some severe hacks to get `displaysSearchBarInNavigationBar` working: + + - (void)restoreOriginalTableView + { + if (PSPDFIsUIKitFlatMode() && self.originalTableView) { + self.view = self.originalTableView; + } + } + + - (UITableView *)tableView + { + return self.originalTableView ?: [super tableView]; + } + + - (void)searchDisplayController:(UISearchDisplayController *)controller + didShowSearchResultsTableView:(UITableView *)tableView + { + // HACK: iOS 7 requires a cruel workaround to show the search table view. + if (PSPDFIsUIKitFlatMode()) { + if (!self.originalTableView) self.originalTableView = self.tableView; + self.view = controller.searchResultsTableView; + controller.searchResultsTableView.contentInset = UIEdgeInsetsZero; // Remove 64 pixel gap + } + } + + - (void)searchDisplayController:(UISearchDisplayController *)controller + didHideSearchResultsTableView:(UITableView *)tableView + { + [self restoreOriginalTableView]; + } + +Also, don't forget calling `restoreOriginalTableView` in `viewWillDisappear`, or things will crash badly. Remember that this is only one solution; there might be less radical ways that don't replace the view itself, but this really should be fixed by Apple. (TODO: RADAR!) + +## Pagination + +`UIWebView` learned a new trick to automatically paginate websites with `paginationMode`. There are a whole bunch of new properties related to this feature: + + @property (nonatomic) UIWebPaginationMode paginationMode NS_AVAILABLE_IOS(7_0); + @property (nonatomic) UIWebPaginationBreakingMode paginationBreakingMode NS_AVAILABLE_IOS(7_0); + @property (nonatomic) CGFloat pageLength NS_AVAILABLE_IOS(7_0); + @property (nonatomic) CGFloat gapBetweenPages NS_AVAILABLE_IOS(7_0); + @property (nonatomic, readonly) NSUInteger pageCount NS_AVAILABLE_IOS(7_0); + +Now while this might not be useful for most websites, it certainly is to build simple ebook readers or display text in a nicer way. For added fun, try setting it to `UIWebPaginationModeBottomToTop`. + +## Flying Popovers + +Wonder why your popovers are flying around like crazy? There's a new delegate in the `UIPopoverControllerDelegate` protocol which allows you to control the madness: + + - (void)popoverController:(UIPopoverController *)popoverController + willRepositionPopoverToRect:(inout CGRect *)rect + inView:(inout UIView **)view + +`UIPopoverController` will behave if anchored to a `UIBarButtonItem`, but if you're showing it with a view and rect, you might have to implement this method and return something sane. This took me quite a long time to figure out - it's especially required if you dynamically resize your popovers via changing `preferredContentSize`. Apple now takes those sizing requests more serious and will move the popover around if there's not enough space left. + +## Keyboard Support + +Apple didn't only give us [a whole new framework for game controllers](https://developer.apple.com/library/ios/documentation/ServicesDiscovery/Conceptual/GameControllerPG/Introduction/Introduction.html), it also gave us keyboard-lovers some attention! You'll find new defines for common keys like `UIKeyInputEscape` or `UIKeyInputUpArrow` that can be intercepted using the all-new [`UIKeyCommand`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIKeyCommand_class/Reference/Reference.html#//apple_ref/occ/instp/UIKeyCommand/input) class. Before iOS 7, [unspeakable hacks were necessary to react to keyboard commands](http://petersteinberger.com/blog/2013/adding-keyboard-shortcuts-to-uialertview/). Now, let's grab a bluetooth keyboard and see what we can do with this new hotness! + +Before starting, you need some basic understanding for the responder chain. Your `UIApplication` inherits from `UIResponder`, and so does `UIView` and `UIViewController`. If you've ever had to deal with `UIMenuItem` and weren't using [my block-based wrapper](https://github.com/steipete/PSMenuItem), you know this already. So events will be sent to the topmost responder and then trickle down level by level until they end at UIApplication. To capture key commands, you need to tell the system what key commands you're interested in (there's no catch-all). To do so, override the new `keyCommands` property: + + - (NSArray *)keyCommands + { + return @[[UIKeyCommand keyCommandWithInput:@"f" + modifierFlags:UIKeyModifierCommand + action:@selector(searchKeyPressed:)]]; + } + + - (void)searchKeyPressed:(UIKeyCommand *)keyCommand + { + // Respond to the event + } + + + +Now don't get too excited; there are some caveats. This only works when the keyboard is visible (if there's some first responder like `UITextView`.) For truly global hotkeys, you still need to revert to the above-linked hackery. But apart from that, the routing is very elegant. Don't try overriding system shortcuts like cmd-V, as those will be mapped to `paste:` automatically. + +There are also some new predefined responder actions like: + + - (void)increaseSize:(id)sender NS_AVAILABLE_IOS(7_0); + - (void)decreaseSize:(id)sender NS_AVAILABLE_IOS(7_0); + +which are called for cmd+ and cmd- respectively, to increase/decrease content size. + +## Match the Keyboard Background + +Apple finally made [`UIInputView`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIInputView_class/Reference/Reference.html) public, which provides a way to match the keyboard style with using `UIInputViewStyleKeyboard`. This allows you to write custom keyboards or (toolbar) extensions for the default keyboards that match the default style. This class has existed since the [dawn of time](https://github.com/nst/iOS-Runtime-Headers/commits/master/Frameworks/UIKit.framework/UIInputView.h), but now we can finally use it without resorting to hacks. + +`UIInputView` will only show a background if it's the the *root view* of your `inputView` or `inputAccessoryView` - otherwise it will be transparent. Sadly, this doesn't enable you to implement a split keyboard without fill, but it's still better than using a simple UIToolbar. I haven't yet seen a spot where Apple uses this new API - it still seems to use a `UIToolbar` in Safari. + +## Know your Radio + +While most of the carrier information has been exposed in CTTelephony as early as iOS 4, it was usually special-case and not useful. With iOS 7, Apple added one method here, the most useful of them all: `currentRadioAccessTechnology`. This allows you to know if the phone is on dog-slow GPRS, on lighting-fast LTE, or on anything in between. Now there's no method that would give you the connection speed (since the phone can't really know that), but it's good enough to fine-tune a download manager to not try downloading six images *simultaneously* when the user's on EDGE. + +Now there's absolutely no documentation around `currentRadioAccessTechnology`, so it took some trial and error to make this work. Once you have the current value, you should register for the `CTRadioAccessTechnologyDidChangeNotification` instead of polling the property. To actually get iOS to emit those notifications, you need to carry an instance of `CTTelephonyNetworkInfo` around. Don't try to create a new instance of `CTTelephonyNetworkInfo` inside the notification, or it'll crash. + +In this simple example, I am abusing the fact that capturing `telephonyInfo` in a block will retain it: + + CTTelephonyNetworkInfo *telephonyInfo = [CTTelephonyNetworkInfo new]; + NSLog(@"Current Radio Access Technology: %@", telephonyInfo.currentRadioAccessTechnology); + [NSNotificationCenter.defaultCenter addObserverForName:CTRadioAccessTechnologyDidChangeNotification + object:nil + queue:nil + usingBlock:^(NSNotification *note) + { + NSLog(@"New Radio Access Technology: %@", telephonyInfo.currentRadioAccessTechnology); + }]; + +The log output can look something like this, when the phone's moving from Edge to 3G: + + iOS7Tests[612:60b] Current Radio Access Technology: CTRadioAccessTechnologyEdge + iOS7Tests[612:1803] New Radio Access Technology: (null) + iOS7Tests[612:1803] New Radio Access Technology: CTRadioAccessTechnologyHSDPA + +Apple exported all string symbols so it's easy to compare and detect the current technology. + + +## Core Foundation, Autorelease and You. + +There's a new helper in Core Foundation that people have been missing and hacking around for years: + + CFTypeRef CFAutorelease(CFTypeRef CF_RELEASES_ARGUMENT arg) + +It does exactly what you expect it to do, and it's quite puzzling how long it took Apple to make it public. With ARC, most people solved returning Core Foundation objects via casting them to their NS-equivalent - like returning an `NSDictionary`, even though it's a `CFDictionaryRef` and simply using `CFBridgingRelease()`. This works well, until you need to return methods where no NS-equivalent is available, like `CFBagRef`. Then you either use id and lose any type safety, or you rename your method to `createMethod` and have to think about all the memory semantics and using CFRelease afterward. There were also hacks like [this one](http://favstar.fm/users/AndrePang/status/18099774996), using a non-ARC-file so you can compile it, but using CFAutorelease() is really the way to go. Also: Don't write code using Apple's namespace. All those custom CF-Macros are programmed to break sooner or later. + +## Image Decompression + +When showing an image via `UIImage`, it might need to be decompressed before it can be displayed (unless the source is a pixel buffer already). This can take significant time for JPG/PNG files and result in stuttering. Before iOS 6, this was usually solved by creating a new bitmap context and drawing the image into it. [(See how AFNetworking solves this)](https://github.com/AFNetworking/AFNetworking/blob/09658b352a496875c91cc33dd52c3f47b9369945/AFNetworking/AFURLResponseSerialization.m#L442-518). + +Starting with iOS 7, you can now force decompression directly at image creation time with the new `kCGImageSourceShouldCacheImmediately`: + + + (UIImage *)decompressedImageWithData:(NSData *)data + { + CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); + CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: @YES}); + + UIImage *image = [UIImage imageWithCGImage:cgImage]; + CGImageRelease(cgImage); + CFRelease(source); + return image; + } + +I was very excited when I first found out about this, but you really shouldn't be. In my tests, performance actually *decreased* when I enabled immediate caching. Either this method calls up to the main thread (unlikely) or perceived performance is simply worse because it locks in `copyImageBlockSetJPEG`, which is also used when showing a non-decrypted image on the main thread. In my app, I load small preview thumbnails from the main thread, and load the large page images from a background thread. Using `kCGImageSourceShouldCacheImmediately` now blocks the main thread where only a tiny decompression would take place, with a much more expensive operation on the background thread. + + + +There's a lot more to image decompression which isn't new to iOS 7, like `kCGImageSourceShouldCache`, which controls the ability where the system can automatically unload decompressed image data. Make sure you're setting this to YES, otherwise all the extra work could be pointless. Interesting detail: Apple changed the *default* of `kCGImageSourceShouldCache` from NO to YES with the 64-bit runtime. + +## Piracy Check + +Apple added a way to evaluate the App Store receipt in Lion with the new [`appStoreReceiptURL`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/Reference/Reference.html#//apple_ref/occ/instm/NSBundle/appStoreReceiptURL) method on `NSBundle`, and finally also ported this to iOS. This allows you to check if your app was legitimately purchased or cracked. There's another important reason for checking the receipt. It contains the *initial purchase date*, which can be very useful when moving your app from a paid model to free and in-app purchases. You can use this initial purchase date to determine if your users get the extra content free (because they already paid for it), or if they have to purchase it. + +The receipt also lets you check if the app was purchased via the volume purchase program and if that license wasn't revoked, there's a property named `SKReceiptPropertyIsVolumePurchase` indicating that. + +You need to take special care when calling `appStoreReceiptURL`, since it exists as private API on iOS 6, but will call `doesNotRecognizeSelector:` when called from user code. Check the running (foundation) version before calling. During development, there won't be a file at the URL returned from this method. You will need to use StoreKit's [`SKReceiptRefreshRequest`](https://developer.apple.com/library/ios/documentation/StoreKit/Reference/SKReceiptRefreshRequest_ClassRef/SKReceiptRefreshRequest.html), also new in iOS 7, to download the certificate. Use a test user who made at least one purchase, or else it won't work: + + // Refresh the Receipt + SKReceiptRefreshRequest *request = [[SKReceiptRefreshRequest alloc] init]; + [request setDelegate:self]; + [request start]; + +Verifying the receipt requires a lot of code. You need to use OpenSSL and embed the [Apple Root Certificate](http://www.apple.com/certificateauthority/), and you should understand some basics about certificates, [PCKS containers](http://en.wikipedia.org/wiki/PKCS), and [ASN.1](http://de.wikipedia.org/wiki/Abstract_Syntax_Notation_One). There's some [sample code](https://github.com/rmaddy/VerifyStoreReceiptiOS) out there, but you shouldn't make it too easy for someone with less honorable intents - don't just copy the existing validation methods, at least modify them or write your own. You don't want a generic patcher app to undo your hard work in seconds. + +You should definitely read Apple's guide on [Validating Mac App Store Receipts](https://developer.apple.com/library/mac/releasenotes/General/ValidateAppStoreReceipt/index.html#//apple_ref/doc/uid/TP40010573-CH1-SW6) - a lot of this applies to iOS as well. Apple also details the changes with its new "Grand Unified Receipt" in [Session 308 "Using Receipts to Protect Your Digital Sales" @ WWDC 2013](https://developer.apple.com/wwdc/videos/). + +## Comic Sans MS + +Admit it. You've missed Comic Sans MS. With iOS 7, you can finally get it back. Now, downloadable fonts have been added to iOS 6, but back then the list of fonts was pretty small and not really interesting. Apple added some more fonts in iOS 7, including "famous" ones like [PT Sans](http://www.fontsquirrel.com/fonts/PT-Sans) or [Comic Sans MS](http://sixrevisions.com/graphics-design/comic-sans-the-font-everyone-loves-to-hate/). The `kCTFontDownloadableAttribute` was not declared in iOS 6, so this wasn't really usable until iOS 7, but Apple retroactively declared this property to be available on iOS 6 upward. + + + +The list of fonts is [dynamic](http://mesu.apple.com/assets/com_apple_MobileAsset_Font/com_apple_MobileAsset_Font.xml) and might change in the future. Apple lists some of the available fonts in [Tech Note HT5484](http://support.apple.com/kb/HT5484), but the document is outdated and doesn't yet reflect the changes on iOS 7. + +Here's how you get an array of fonts that can be downloaded with `CTFontDescriptorRef`: + + CFDictionary *descriptorOptions = @{(id)kCTFontDownloadableAttribute : @YES}; + CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)descriptorOptions); + CFArrayRef fontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, NULL); + +The system won't check if the font is already on disk and will return the same list. Additionally, this method might do a network call and thus block. You don't want to call this from the main thread. + +To download the font, use this block-based API: + + bool CTFontDescriptorMatchFontDescriptorsWithProgressHandler( + CFArrayRef descriptors, + CFSetRef mandatoryAttributes, + CTFontDescriptorProgressHandler progressBlock) + +This method handles the network call and calls your `progressBlock` with progress information until the download either succeeds or fails. Refer to Apple's [DownloadFont Example](https://developer.apple.com/library/ios/samplecode/DownloadFont/Listings/DownloadFont_ViewController_m.html) to see how this can be used. + +There are a few gotchas here. This font will only be available during the current app run, and has to be loaded again into memory on the next run. Since fonts are saved in a shared place, you can't rely on them being available. They most likely will be, but it's not guaranteed and the system might clean this folder, or your app is being copied to a new device where the font doesn't yet exist, and you might run without a working network. On the Mac or in the Simulator you can obtain the `kCTFontURLAttribute` to get the absolute path of the font and speed up loading time, but this won't work on iOS, since the folder is outside of your app - you need to call `CTFontDescriptorMatchFontDescriptorsWithProgressHandler` again. + +You can also subscribe to the new `kCTFontManagerRegisteredFontsChangedNotification` to be notified whenever new fonts are loaded into the font registry. You can find out more in [Session 223 "Using Fonts with TextKit" @ WWDC 2013](https://developer.apple.com/wwdc/videos/). + +## Can't Get Enough? + +Don't worry - there's a lot more new in iOS 7! [Over at NSHipster](http://nshipster.com/ios7/) you'll learn about speech synthesis, base64, the all-new `NSURLComponents`, `NSProgress`, bar codes, reading lists, and, of course, `CIDetectorEyeBlink`. And there's a lot more waiting we couldn't cover - study Apple's [iOS 7 API Diffs](https://developer.apple.com/library/ios/releasenotes/General/iOS70APIDiffs/index.html#//apple_ref/doc/uid/TP40013203), the [What's new in iOS](https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS7.html) guide, and the [Foundation Release Notes](https://developer.apple.com/library/prerelease/mac/releasenotes/Foundation/RN-Foundation/index.html#//apple_ref/doc/uid/TP30000742) (those are for OS X - but since this is shared code, a lot applies to iOS as well). +Many of the new methods don't even have documentation yet, so it's up to you to play around and blog about it! diff --git a/2013-10-08-index.markdown b/2013-10-08-index.markdown new file mode 100644 index 0000000..ddc0562 --- /dev/null +++ b/2013-10-08-index.markdown @@ -0,0 +1,6 @@ +--- +layout: toc +category: "5" +date: "2013-10-07 12:00:00" +tags: toc +--- diff --git a/2013-10-08-multitasking.md b/2013-10-08-multitasking.md new file mode 100644 index 0000000..3810115 --- /dev/null +++ b/2013-10-08-multitasking.md @@ -0,0 +1,308 @@ +--- +layout: post +title: "Multitasking in iOS 7" +category: "5" +date: "2013-10-07 07:00:00" +tags: article +author: "David Caunt" +--- + + +Prior to iOS 7, developers were pretty limited in what they could do when their apps left the foreground. Aside from VOIP and location-based features, the only way to execute code in the background was to use background tasks, restricted to running for a few minutes. If you wanted to download a large video for offline viewing, or backup a user’s photos to your server, you could only complete part of the work. + +iOS 7 adds two new APIs for updating your app’s UI and content in the background. The first, Background Fetch, allows you to fetch new content from the network at regular intervals. The second, Remote Notifications, is a new feature leveraging Push Notifications to notify an app when an event has occurred. Both of these new mechanisms help you to keep your app's interface up to date, and can schedule work on the new Background Transfer Service, which allows you to perform out-of-process network transfers (downloads and uploads). + +Background Fetch and Remote Notifications are simple application delegate hooks with 30 seconds of wall-clock time to perform work before your app is suspended. They're not intended for CPU intensive work or long running tasks, rather, they are for queuing up long-running networking requests, like a large movie download, or performing quick content updates. + +From a user’s perspective, the only obvious change to multitasking is the new app switcher, which displays a snapshot of each app’s UI as it was when it left the foreground. But there’s a reason for displaying the snapshots – you can now update your app’s snapshot after you complete background work, showing a preview of new content. Social networking, news, or weather apps can now display the latest content without the user having to open the app. We'll see how to update the snapshot later. + +## Background Fetch + +Background Fetch is a kind of smart polling mechanism which works best for apps that have frequent content updates, like social networking, news, or weather apps. The system wakes up the app based on a user’s behavior, and aims to trigger background fetches in advance of the user launching the app. For example, if the user always uses an app at 1 p.m., the system learns and adapts, performing fetches ahead of usage periods. Background fetches are coalesced across apps by the device’s radio in order to reduce battery usage, and if you report that new data was not available during a fetch, iOS can adapt, using this information to avoid fetches at quiet times. + +The first step in enabling Background Fetch is to specify that you’ll use the feature in the [`UIBackgroundModes`](https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/iPhoneOSKeys.html#//apple_ref/doc/uid/TP40009252-SW22) key in your info plist. The easiest way to do this is to use the new Capabilities tab in Xcode 5’s project editor, which includes a Background Modes section for easy configuration of multitasking options. + +A screenshot showing Xcode 5’s new Capabilities tab + +Alternatively, you can edit the key manually: + + UIBackgroundModes + + fetch + + +Next, tell iOS how often you'd like to fetch: + + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions + { + [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; + + return YES; + } + +The default fetch interval is never, so you'll need to set a time interval or the app won't ever be called in the background. The value of `UIApplicationBackgroundFetchIntervalMinimum` asks the system to manage when your app is woken, as often as possible, but you should specify your own time interval if this is unnecessary. For example, a weather app might only update conditions hourly. iOS will wait at least the specified time interval between background fetches. + +If your application allows a user to logout, and you know that there won’t be any new data, you may want to set the `minimumBackgroundFetchInterval` back to `UIApplicationBackgroundFetchIntervalNever` to be a good citizen and to conserve resources. + +The final step is to implement the following method in your application delegate: + + - (void) application:(UIApplication *)application + performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler + { + NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; + + NSURL *url = [[NSURL alloc] initWithString:@"http://yourserver.com/data.json"]; + NSURLSessionDataTask *task = [session dataTaskWithURL:url + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + + if (error) { + completionHandler(UIBackgroundFetchResultFailed); + return; + } + + // Parse response/data and determine whether new content was available + BOOL hasNewData = ... + if (hasNewData) { + completionHandler(UIBackgroundFetchResultNewData); + } else { + completionHandler(UIBackgroundFetchResultNoData); + } + }]; + + // Start the task + [task resume]; + } + +This is where you can perform work when you are woken by the system. Remember, you only have 30 seconds to determine whether new content is available, to process the new content, and to update your UI. This should be enough time to fetch data from the network and to fetch a few thumbnails for your UI, but not much more. When your network requests are complete and your UI has been updated, you should call the completion handler. + +The completion handler serves two purposes. First, the system measures the power used by your process and records whether new data was available based on the `UIBackgroundFetchResult` argument you passed. Second, when you call the completion handler, a snapshot of your UI is taken and the app switcher is updated. The user will see the new content when he or she is switching apps. This completion handler snapshotting behavior is common to all of the completion handlers in the new multitasking APIs. + +In a real-world application, you should pass the `completionHandler` to sub-components of your application and call it when you've processed data and updated your UI. + +At this point, you might be wondering how iOS can snapshot your app's UI when it is running in the background, and how the application lifecycle works with Background Fetch. If your app is currently suspended, the system will wake it before calling `application: performFetchWithCompletionHandler:`. If your app is not running, the system will launch it, calling the usual delegate methods, including `application: didFinishLaunchingWithOptions:`. You can think of it as the app running exactly the same way as if the user had launched it from Springboard, except the UI is invisible, rendered offscreen. + +In most cases, you'll perform the same work when the application launches in the background as you would in the foreground, but you can detect background launches by looking at the [`applicationState`](https://developer.apple.com/library/ios/documentation/uikit/reference/UIApplication_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006728-CH3-SW77) property of UIApplication: + + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions + { + NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState); + + return YES; + } + +### Testing Background Fetch + +There are two ways you can simulate a background fetch. The easiest method is to run your application from Xcode and click _Simulate Background Fetch_ under Xcode's Debug menu while your app is running. + +Alternatively, you can use a scheme to change how Xcode runs your app. Under the Xcode menu item Product, choose Scheme and then Manage Schemes. From here, edit or add a new scheme and check the _Launch due to a background fetch event_ checkbox as shown below. + +A screenshot showing Xcode 5’s scheme editor + +## Remote Notifications + +Remote notifications allow you to notify your app when important events occur. You might have new instant messages to deliver, breaking news alerts to send, or the latest episode of your user's favorite TV show ready for him or her to download for offline viewing. Remote notifications are great for sporadic but immediately important content, where the delay between background fetches might not be acceptable. Remote Notifications can also be much more efficient than Background Fetch, as your application only launches when necessary. + +A Remote Notification is really just a normal Push Notification with the `content-available` flag set. You might send a push with an alert message informing the user that something has happened, while you update the UI in the background. But Remote Notifications can also be silent, containing no alert message or sound, used only to update your app’s interface or trigger background work. You might then post a local notification when you've finished downloading or processing the new content. + +Silent push notifications are rate-limited, so don't be afraid of sending as many as your application needs. iOS and the APNS servers will control how often they are delivered, and you won’t get into trouble for sending too many. If your push notifications are throttled, they might be delayed until the next time the device sends a keep-alive packet or receives another notification. + +### Sending Remote Notifications + +To send a remote notification, set the content-available flag in a push notification payload. The content-available flag is the same key used to notify Newsstand apps, so most push scripts and libraries already support remote notifications. When you're sending a Remote Notification, you might also want to include some data in the notification payload, so your application can reference the event. This could save you a few networking requests and increase the responsiveness of your app. + +I recommend using [Nomad CLI’s Houston](http://nomad-cli.com/#houston) utility to send push messages while developing, but you can use your favorite library or script. + +You can install Houston as part of the nomad-cli ruby gem: + + gem install nomad-cli + +And then send a notification with the apn utility included in Nomad + + # Send a Push Notification to your Device + apn push -c /path/to/key-cert.pem -n -d content-id=42 + +Here the `-n` flag specifies that the content-available key should be included, and `-d` allows us to add our own data keys to the payload. + +The resulting notification payload looks like this: + + { + "aps" : { + "content-available" : 1 + }, + "content-id" : 42 + } + +iOS 7 adds a new application delegate method, which is called when a push notification with the content-available key is received: + + - (void) application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler + { + NSLog(@"Remote Notification userInfo is %@", userInfo); + + NSNumber *contentID = userInfo[@"content-id"]; + // Do something with the content ID + completionHandler(UIBackgroundFetchResultNewData); + } + +Again, the app is launched into the background and given 30 seconds to fetch new content and update its UI, before calling the completion handler. We could perform a quick network request as we did in the Background Fetch example, but let's use the powerful new Background Transfer Service to enqueue a large download task and see how we can update our UI when it completes. + +## NSURLSession and Background Transfer Service + +While `NSURLSession `is a new class in iOS 7, it also refers to the new technology in Foundation networking. Intended to replace `NSURLConnection`, familiar concepts and classes such as `NSURL`, `NSURLRequest`, and `NSURLResponse` are preserved. You’ll work with `NSURLConnection`’s replacement, `NSURLSessionTask`, to make network requests and handle their responses. There are three types of session tasks – data, download, and upload – each of which add syntactic sugar to `NSURLSessionTask`, so you should use the appropriate one for your use case. + +An `NSURLSession` coordinates one or more of these `NSURLSessionTask`s and behaves according to the `NSURLSessionConfiguration` with which it was created. You may create multiple `NSURLSession`s to group related tasks with the same configuration. To interact with the Background Transfer Service, you'll create a session configuration using `[NSURLSessionConfiguration backgroundSessionConfiguration]`. Tasks added to a background session are run in an external process and continue even if your app is suspended, crashes, or is killed. + +[`NSURLSessionConfiguration`](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html) allows you to set default HTTP headers, configure cache policies, restrict cellular network usage, and more. One option is the `discretionary` flag, which allows the system to schedule tasks for optimal performance. What this means is that your transfers will only go over Wifi when the device has sufficient power. If the battery is low, or only a cellular connection is available, your task won't run. The `discretionary` flag only has an effect if the session configuration object has been constructed by calling the [`backgroundSessionConfiguration:`](https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/clm/NSURLSessionConfiguration/backgroundSessionConfiguration:) method and if the background transfer is initiated while your app is in the foreground. If the transfer is initiated from the background the transfer will _always_ run in discretionary mode. + +Now we know a little about `NSURLSession`, and how a background session functions, let's return to our Remote Notification example and add some code to enqueue a download on the background transfer service. When the download completes, we'll notify the user that the file is available for use. + +### NSURLSessionDownloadTask + +First of all, let's handle a Remote Notification and enqueue an `NSURLSessionDownloadTask` on the background transfer service. In `backgroundURLSession`, we create an `NURLSession` with a background session configuration and add our application delegate as the session delegate. The documentation advises against instantiating multiple sessions with the same identifier, so we use `dispatch_once` to avoid potential issues: + + - (NSURLSession *)backgroundURLSession + { + static NSURLSession *session = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *identifier = @"io.objc.backgroundTransferExample"; + NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier]; + session = [NSURLSession sessionWithConfiguration:sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + }); + + return session; + } + + - (void) application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler + { + NSLog(@"Received remote notification with userInfo %@", userInfo); + + NSNumber *contentID = userInfo[@"content-id"]; + NSString *downloadURLString = [NSString stringWithFormat:@"http://yourserver.com/downloads/%d.mp3", [contentID intValue]]; + NSURL* downloadURL = [NSURL URLWithString:downloadURLString]; + + NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; + NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request]; + task.taskDescription = [NSString stringWithFormat:@"Podcast Episode %d", [contentID intValue]]; + [task resume]; + + completionHandler(UIBackgroundFetchResultNewData); + } + +We create a download task using the `NSURLSession` class method and configure its request, and provide a description for use later. You must remember to call `[task resume]` to actually start the task, as all session tasks begin in the suspended state. + +Now we need to implement the `NSURLSessionDownloadDelegate` methods to receive callbacks when the download completes. You may also need to implement `NSURLSessionDelegate` or `NSURLSessionTaskDelegate` methods if you need to handle authentication or other events in the session lifecycle. You should consult Apple's document [Life Cycle of a URL Session with Custom Delegates](https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html#//apple_ref/doc/uid/10000165i-CH2-SW42), which explains the full life cycle across all types of session tasks. + +None of the `NSURLSessionDownloadDelegate` delegate methods are optional, though the only one where we need to take action in this example is `[NSURLSession downloadTask:didFinishDownloadingToURL:]`. When the task finishes downloading, you're provided with a temporary URL to the file on disk. You must move or copy the file to your app's storage, as it will be removed from temporary storage when you return from this delegate method. + + #Pragma Mark - NSURLSessionDownloadDelegate + + - (void) URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didFinishDownloadingToURL:(NSURL *)location + { + NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location); + + // Copy file to your app's storage with NSFileManager + // ... + + // Notify your UI + } + + - (void) URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset + expectedTotalBytes:(int64_t)expectedTotalBytes + { + } + + - (void) URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten + totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite + { + } + +If your app is still running in the foreground when the background session task completes, the above code will be sufficient. In most cases, however, your app won't be running, or it will be suspended in the background. In these cases, you must implement two application delegates methods so the system can wake your application. Unlike previous delegate callbacks, the application delegate is called twice, as your session and task delegates may receive several messages. The app delegate method `application: handleEventsForBackgroundURLSession:` is called before these `NSURLSession` delegate messages are sent, and `URLSessionDidFinishEventsForBackgroundURLSession` is called afterward. In the former method, you store a background `completionHandler`, and in the latter you call it to update your UI: + + - (void) application:(UIApplication *)application + handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler + { + // You must re-establish a reference to the background session, + // or NSURLSessionDownloadDelegate and NSURLSessionDelegate methods will not be called + // as no delegate is attached to the session. See backgroundURLSession above. + NSURLSession *backgroundSession = [self backgroundURLSession]; + + NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession); + + // Store the completion handler to update your UI after processing session events + [self addCompletionHandler:completionHandler forSession:identifier]; + } + + - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session + { + NSLog(@"Background URL session %@ finished events.\n", session); + + if (session.configuration.identifier) { + // Call the handler we stored in -application:handleEventsForBackgroundURLSession: + [self callCompletionHandlerForSession:session.configuration.identifier]; + } + } + + - (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier + { + if ([self.completionHandlerDictionary objectForKey:identifier]) { + NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n"); + } + + [self.completionHandlerDictionary setObject:handler forKey:identifier]; + } + + - (void)callCompletionHandlerForSession: (NSString *)identifier + { + CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier]; + + if (handler) { + [self.completionHandlerDictionary removeObjectForKey: identifier]; + NSLog(@"Calling completion handler for session %@", identifier); + + handler(); + } + } + + +This two-stage process is necessary to update your app UI if you aren't already in the foreground when the background transfer completes. Additionally, if the app is not running at all when the background transfer finishes, iOS will launch it into the background, and the preceding application and session delegate methods are called after `application:didFinishLaunchingWithOptions:`. + +### Configuration and Limitation + +We've briefly touched on the power of background transfers, but you should explore the documentation and look at the `NSURLSessionConfiguration` options that best support your use case. For example, `NSURLSessionTasks` support resource timeouts through the `NSURLSessionConfiguration`'s `timeoutIntervalForResource` property. You can use this to specify how long you want to allow for a transfer to complete before giving up entirely. You might use this if your content is only available for a limited time, or if failure to download or upload the resource within the given timeInterval indicates that the user doesn't have sufficient Wifi bandwidth. + +In addition to download tasks, `NSURLSession` fully supports upload tasks, so you might upload a video to your server in the background and assure your user that he or she no longer needs to leave the app running, as might have been done in iOS 6. A nice touch would be to set the `sessionSendsLaunchEvents` property of your `NSURLSessionConfiguration` to `NO`, if your app doesn't need launching in the background when the transfer completes. Efficient use of system resources keeps both iOS and the user happy. + +Finally, there are a couple of limitations in using background sessions. As a delegate is required, you can't use the simple block-based callback methods on `NSURLSession`. Launching your app into the background is relatively expensive, so HTTP redirects are always taken. The background transfer service only supports HTTP and HTTPS and you cannot use custom protocols. The system optimizes transfers based on available resources and you cannot force your transfer to progress in the background at all times. + +Also note that `NSURLSessionDataTasks` are not supported in background sessions at all, and you should only use these tasks for short-lived, small requests, not for downloads or uploads. + +## Summary + +The powerful new multitasking and networking APIs in iOS 7 open up a whole range of possibilities for both new and existing apps. Consider the use cases in your app which can benefit from out-of-process network transfers and fresh data, and make the most of these fantastic new APIs. In general, implement background transfers as if your application is running in the foreground, making appropriate UI updates, and most of the work is already done for you. + +- Use the appropriate new API for your app’s content. +- Be efficient, and call completion handlers as early as possible. +- Completion handlers update your app’s UI snapshot. + +## Further Reading + +- [WWDC 2013 session “What’s New with Multitasking”](https://developer.apple.com/wwdc/videos/?id=204) +- [WWDC 2013 session “What’s New in Foundation Networking”](https://developer.apple.com/wwdc/videos/?id=705) +- [URL Loading System Programming Guide](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html#//apple_ref/doc/uid/10000165i) + + + + diff --git a/2013-10-08-redesigning-for-ios-7.md b/2013-10-08-redesigning-for-ios-7.md new file mode 100644 index 0000000..48dcad9 --- /dev/null +++ b/2013-10-08-redesigning-for-ios-7.md @@ -0,0 +1,98 @@ +--- +layout: post +title: "Re-Designing an App for iOS 7" +category: "5" +date: "2013-10-07 05:00:00" +tags: article +author: "Holger Riegel & Tobias Kreß" +--- + + +Watching the WWDC presentation of the new version of iOS, we were looking at our app [Grocery List](http://appstore.com/grocerylistpx) and realized: This iOS version is a complete restart, like the first introduction of the iPhone seven years ago. It's not enough to simply change the design. We had to rethink and rebuild the application to fit within the new environment. So we did. + +While the basic behavior of an app should not be changed, we decided to +do so based on user feedback and our own usage that made us realize we +had to improve some workflows. As an example, in the old application, +adding an amount and unit to a product is a multi-step process that +requires navigating through multiple controllers. In *Grocery List 2*, +you will be able to set the values in place without leaving the current +screen. + +On our way to achieving this, we encountered some topics that we thought would be useful to share. We'll start with animations and gestures. After that, we'll take a look at the interface, the colors, and the fonts. To attract the user to open our app, we'll have a look on how to design an iOS 7 app icon. Finally, we will share with you our opinion on what the new update means. + +## Animations + +Current mobile devices are becoming more powerful with each iteration. Meanwhile, the animations of objects are increasingly more realistic due to the ability to calculate the physical constraints in real time. There is no longer a need to apply shadows and gradients to your interface. Instead, you could focus on the feel, the motion, and the impact of your interactions. You can create a new world with the same rules but without simulating the old one. + +The new SDK allows you to easily create and use custom animations. Before iOS 7 changing the transition from one view controller to another required lots of extra work. The possibility to easily add your own animation helps the user keep his or her path through different screens without losing focus. + +In *Grocery List*, we use a slightly modified transition to display a modal controller. But most of the animations and transitions are default. With *Grocery List 2* and the new APIs, we could have added more custom animations as before. But Apple's solution to handle transitions is a good fit to most of our problems. That's why navigating through controllers in our app behaves the same as default apps do. As mentioned before, some of our app's workflows have significantly changed. As a result, we will also use custom animations to support the user better keeping focus. + +Comparison of the Grocery List and the default view controller push + +The default animations on iOS 7 will feel new and natural to most of the users, and you don't have to do much to use them and to make your users happy. But adding some custom animations in places where they fit in will improve the overall experience of your app. Just be careful not to overdo it. + +## Gestures + +After several years of experience with touch devices, Apple discovered that the wider use of gestures is becoming more natural to users. In iOS 7, there are now a lot more possibilities to do so than before. Newly integrated ones, like swiping on a table view cell to reveal a hidden menu or swiping from the left edge to go back to the previous controller, have become so familiar in no time at all that you would miss them if an app didn't support them. The benefit of the direct manipulation in place helps the user to finish his task more efficiently without losing focus. + +In *Grocery List*, we didn't have any custom gestures. But for our goal to improve some workflows in the next version, we support swiping on a cell from both directions to present different options for a product. And instead of having to go back your navigation stack to get to your lists or templates, you can easily swipe from the right device edge to quickly access the menu. + +Grocery List 2 gestures + +Buttons and links are visible and recognizable for the user but gestures are not. If you're planning to support your features with gestures, great! But if some features in your app rely on gestures and don't have an equivalent and visible control, always provide a good way to discover them. An interface always should be self-explanatory. If you need introduction screens or videos to describe the basic features of your app, you might be doing something wrong. + +## Interface + +Probably the most-discussed topic before the final iOS 7 presentation +has been the difference between flat and skeuomorphic design. iOS 7 has +completely removed all real-world dependency from its design but has +mostly maintained its well-known interaction model. The new, thin +toolbars icons help the content to stand out. But keep in mind, they also easily fail to be identified and to be self-explanatory - especially when there is no label to describe the action the icon triggers. + +We discovered it isn't all about recreating or removing all appearances of real objects, but rather it's important to support the content in the best way. If adding a subtle shadow to your navigation bar helps your app's content to stand out, there is no need to avoid it. The most important thing is to increase contrast where it is necessary and to present your content in a usable way. + +*Grocery List* depends heavily on its closeness to the real world. Chalkboard as background, cells like paper, all framed by glossy wood. As nice as it looks, it is a challenging task to place controls you can interact with and to add new features which have to fit within its narrow context. With iOS 7 and its cleaner design, we don't have to be super realistic but can instead focus on improving the interaction to let the user achieve his or her goal. *Grocery List 2* will definitely be using the new design language but trying to keeping its own style. + +Comparison of the Grocery List and Grocery List 2 interface + +After using iOS 7 on a device for a few weeks, interacting with it seems more convenient then it was with previous versions. The new animations and gestures, and the abstinence of skeuomorphic elements, allow the user to better focus on the content. + +## Colors + +The main difference between iOS 6 and iOS 7 is the overall feeling of color. Apple has made the transition from a dark appearance to a bright one. The colors are getting more vibrant and saturated to support the more frequently used transparency and background blur. + +Due to the skeuomorphic approach of *Grocery List*, colors could not be adjusted heavily. They were primary defined by the materials that we wanted to imitate. While we like the friendlier look of iOS 7, most build-in apps are mainly white. In *Grocery List 2*, the app will primarily be defined by its color scheme that also fits in the grocery scope. We don't want our app to be seen as just another iOS 7 app; instead we are trying to create a unique appearance. + +Comparison of colors in a build-in iOS 7 app and Grocery List 2 + +Colors affect the perception of your app. Don't let your app be all white like the build-in ones. Instead, create your own unique identity. With the clean and new design of iOS 7 and the lack of skeuomorphic elements, you can now use colors that pop to represent what you want to achieve with your app. + +## Fonts + +Apple acknowledges the importance of type, as we see in the rewrite of the text system that has taken place in the new version. Labels and text fields now directly rely on the power of core text providing all typographic features a font has included. Ligatures, swooshes, and the like can be easily enabled by using the new Framework. And by getting your font object with text styles, your app will be able to display content at font sizes the user has chosen. For detailed information, have a look at [this great article](http://typographica.org/on-typography/beyond-helvetica-the-real-story-behind-fonts-in-ios-7/) about fonts in iOS 7. + +Due to the lack of "real" buttons and less chrome around text, text is getting more attention. Criticized from typographic experts for the overall use of the thin weight of *Helvetica Neue* in the early beta builds, Apple finally switched back to a more readable font weight. + +In *Grocery List*, we use a slab-serif that matches the skeuomorphic style. Running the app on an iOS 7 device has shown that this font is not the best choice for the next version. We decided to use a custom, sans-serif font that could better support the overall appearance of our app than the default one could. + +Comparison of the Grocery List and Grocery List 2 fonts + +As content is the fundament of apps, it is important to enhance its legibility and the key to good legibility is good typography. Although Apple's default font, *Helvetica Neue*, is a good choice for most use cases, it is worth considering using a custom font - especially when your app presents lots of text. + +## App Icon + +Not only has Apple changed the dimensions and outlines of the app icons in iOS 7, it has also changed the visual mood. App icons don't have an applied glow anymore, and most of the build-in app icons fit in the grid Apple has shown. Also, the icons are much simpler, removing the realistic effects and mostly only displaying simple symbolics on a colorful background. This is a transition away from little photorealistic illustrations toward the basic meaning of what an icon should be: Iconographic. + +From a skeuomorphic app icon with chalkboard and wood to a simple, colorful icon with a coherent symbol, the *Grocery List* app icon does not really fit into the new homescreen. For the next version, we simplified the icon to a shopping basket symbol and chose a background color that we can also use as a main color in the app. This combination helps make the icon pop. + +Comparison of the Grocery List and Grocery List 2 app icons + +With iOS 7, icons will automatically be scaled to fit the new dimensions and result in blurry images. Shadows and lighting might look weird due to the new super-ellipse outline that iOS 7 uses to mask your icon. If you don't plan to update your app to adopt the new iOS 7 style, you should at least update it to add the new icon sizes. + +## Conclusion + +While the whole look and feel of iOS 7 seems to be new and polished, the concept of navigating through an app is still the same as in the first iteration of iOS. Viewing data in lists and tables, pushing view controllers to navigate further, and receiving push notifications have become so familiar to the user that the change of colors and fonts and the removal of skeuomorphic elements does not interrupt the well-known flow. + +Though Apple doesn't force you to change your application in this sense, we recommend to always try to improve it and keep the user in mind. + diff --git a/2013-10-08-view-controller-transitions.md b/2013-10-08-view-controller-transitions.md new file mode 100644 index 0000000..415507e --- /dev/null +++ b/2013-10-08-view-controller-transitions.md @@ -0,0 +1,259 @@ +--- +layout: post +title: "View Controller Transitions" +category: "5" +date: "2013-10-07 09:00:00" +tags: article +author: "Chris Eidhof" +--- + +## Custom Animations + +One of the most exciting iOS 7 features for me is the new View +Controller Transitioning API. Before iOS 7, I would also create custom +transitions between view controllers, but doing this was not really +supported, and a bit painful. Making those transitions interactive, +however, was harder. + +Before we continue with the article, I'd like to issue a warning: this +is a very new API, and while we normally try to write about best +practices, there are no clear best practices yet. It'll probably take a +few months, at least, to figure them out. This article is not so much a +recommendation of best practices, but rather an exploration of a new +feature. Please contact us if you find out better ways of using +this API, so we can update this article. + +Before we look at the API, note how the default behavior of navigation +controllers in iOS 7 +changed: the animation between two view controllers in a navigation +controller looks slightly different, and it is interactive. For example, to pop a +view controller, you can now pan from the left edge of the screen and +interactively drag the current view controller to the right. + +That said, let's have a look at the API. What I found interesting is the +heavy use of protocols and not concrete objects. While it felt a bit +weird at first, I prefer this kind of API. It gives us as programmers +much more flexibility. First, let's try to do a very simple +thing: having a custom animation when pushing a view controller in a +navigation controller (the [sample project](https://github.com/objcio/issue5-view-controller-transitions) for this article is on github). To do this, we have to implement one of the new +`UINavigationControllerDelegate` methods: + + - (id) + navigationController:(UINavigationController *)navigationController + animationControllerForOperation:(UINavigationControllerOperation)operation + fromViewController:(UIViewController*)fromVC + toViewController:(UIViewController*)toVC + { + if (operation == UINavigationControllerOperationPush) { + return self.animator; + } + return nil; + } + +We can look at the kind of operation (either push or pop) and return a +different animator based on that. Or, if we want to share code, it might +be the same object, and we might store the operation in a property. We +might also create a new object for each operation. There's a lot of flexibility +here. + +To perform the animation, we create a custom object that implements the +`UIViewControllerContextTransitioning` protocol: + + @interface Animator : NSObject + + @end + +The protocol requires us to implement two methods, one for the animation +duration: + + - (NSTimeInterval)transitionDuration:(id )transitionContext + { + return 0.25; + } + +and one that performs the animation: + + - (void)animateTransition:(id)transitionContext + { + UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; + UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; + [[transitionContext containerView] addSubview:toViewController.view]; + toViewController.view.alpha = 0; + + [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ + fromViewController.view.transform = CGAffineTransformMakeScale(0.1, 0.1); + toViewController.view.alpha = 1; + } completion:^(BOOL finished) { + fromViewController.view.transform = CGAffineTransformIdentity; + [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; + + }]; + + } + +Here, you can see how the protocols are used: instead of giving a +concrete object with properties, this methods gets a transition context +that is of type `id`. The only +thing that's extremely important, is that we call `completeTransition:` after we're done with the animation. This tells the +animation context that we're done and updates the view controller's +state accordingly. The other code is standard; we ask the transition +context for the two view controllers, and just use plain `UIView` +animations. That's really all there is to it, and now we have a custom +zooming animation. + +Note that we only have specified a custom transition for the push +animation. For the pop animation, iOS falls back to the default sliding +animation. Also, by implementing this method, the transition is not +interactive anymore. Let's fix that. + +## Interactive Animations + +Making this animation interactive is really simple. We need to override another new +navigation controller delegate method: + + - (id )navigationController:(UINavigationController*)navigationController + interactionControllerForAnimationController:(id )animationController + { + return self.interactionController; + } + +Note that, in a non-interactive animation, this will return nil. + +The interaction controller is an instance of +`UIPercentDrivenInteractionTransition`. No further configuration or setup is +necessary. We create a pan recognizer, and here's the code that handles +the panning: + + if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) { + if (location.x > CGRectGetMidX(view.bounds)) { + navigationControllerDelegate.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init]; + [self performSegueWithIdentifier:PushSegueIdentifier sender:self]; + } + } + +Only when the user is on the right-hand side of the screen, do we set the +next animation to be interactive (by setting the `interactionController` +property). Then we just perform the segue (or if +you're not using storyboards, push the view controller). To drive the +transition, we call a method on the interaction controller: + + else if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) { + CGFloat d = (translation.x / CGRectGetWidth(view.bounds)) * -1; + [interactionController updateInteractiveTransition:d]; + } + +This will set the percentage based on how much we have panned. The +really cool thing here is that the interaction controller cooperates +with the animation controller, and because we used a normal `UIView` +animation, it controls the progression of the animation. We don't need to +connect the interaction controller to the animation controller, as all of +this happens automatically in a decoupled way. + +Finally, when the gesture recognizer ends or is canceled, we need to +call the appropriate methods on the interaction controller: + + else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) { + if ([panGestureRecognizer velocityInView:view].x < 0) { + [interactionController finishInteractiveTransition]; + } else { + [interactionController cancelInteractiveTransition]; + } + navigationControllerDelegate.interactionController = nil; + } + +It's important that we set the interaction controller to nil after the +transition was completed or cancelled. If the next transition is +non-interactive, we don't want to return our old interaction controller. + +Now we have a fully interactive custom transition. Using just plain +gesture recognizers and a concrete object provided by UIKit, we achieve +this in only a few lines of code. For most custom transitions, +you can probably stop reading here and do everything with the methods described +above. However, if you want to have completely +custom animations or interactions, this is also possible. We'll look at +that in the next section. + +## Custom Animation Using GPUImage + +One of the cool things we can do now is completely custom animations, +bypassing UIView and even Core Animation. Just do all the animation +yourself, [Letterpress-style](http://www.macstories.net/featured/a-conversation-with-loren-brichter/). In a first attempt, I did this with Core +Image, however, on my old iPhone 4 I only managed to get around 9 FPS, +which is definitely too far off the 60 FPS I wanted. + +However, after bringing in [GPUImage](https://github.com/BradLarson/GPUImage), it was simple to have a custom animation with really nice effects. The animation we want is pixelating and dissolving the two view controls into each other. +The approach is to take a snapshot of the two view +controllers, and apply GPUIImage's image filters on the two snapshots. + +First, we create a custom class that implements both the animation and +interactive transition protocols: + + @interface GPUImageAnimator : NSObject + + + @property (nonatomic) BOOL interactive; + @property (nonatomic) CGFloat progress; + + - (void)finishInteractiveTransition; + - (void)cancelInteractiveTransition; + + @end + +To make the animations perform really fast, we want to upload the images +to the GPU once, and then do all the processing and drawing on the GPU, +without going back to the CPU (the data transfer will be very slow). By +using a GPUImageView, we can do the drawing in OpenGL (without having to +do manual OpenGL code; we can keep writing high-level code). + +Creating the filter chain is very straightforward. Have a look at +`setup` in the sample code to see how to do it. A bit more challenging +is animating the filters. With GPUImage, we don't get automatic +animation, so we want to update our filters at each frame that's +rendered. We can use the `CADisplayLink` class to do this: + + self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(frame:)]; + [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + +In the `frame:` method, we can update our progress based on how much +time has elapsed, and update the filters accordingly: + + - (void)frame:(CADisplayLink*)link + { + self.progress = MAX(0, MIN((link.timestamp - self.startTime) / duration, 1)); + self.blend.mix = self.progress; + self.sourcePixellateFilter.fractionalWidthOfAPixel = self.progress *0.1; + self.targetPixellateFilter.fractionalWidthOfAPixel = (1- self.progress)*0.1; + [self triggerRenderOfNextFrame]; + } + +And that's pretty much all we need to do. In case of interactive +transitions, we need to make sure that we set the progress based on our +gesture recognizer, not based on time, but the rest of the code is +pretty much the same. + +This is really powerful, and you can use any of the existing filters in +GPUImage, or write your own OpenGL shaders to achieve this. + +## Conclusion + +We only looked at animating between two view controllers in a navigation +controller, but you can do the same for tab bar controllers or your own +custom container view controllers. Also, the `UICollectionViewController` +is now extended in such a way that you can automatically and +interactively animate between layouts, using the same mechanism. This is +really powerful. + +When talking to [Orta](https://twitter.com/orta) about this API, he +mentioned that he already uses it a lot to create lighter view controllers. +Instead of managing state within your view controller, you can just +create a new view controller and have a custom animation between the +two, moving views between the two view controllers during the +transition. + +## More Reading + +* [WWDC: Custom Transitions using View Controllers](http://asciiwwdc.com/2013/sessions/218) +* [Custom UIViewController transitions](http://www.teehanlax.com/blog/custom-uiviewcontroller-transitions/) +* [iOS 7: Custom Transitions](http://www.doubleencore.com/2013/09/ios-7-custom-transitions/) +* [Custom View Controller Transitions with Orientation](http://whoisryannystrom.com/2013/10/01/View-Controller-Transition-Orientation/) diff --git a/2013-11-08-build-process.md b/2013-11-08-build-process.md new file mode 100644 index 0000000..a10a63f --- /dev/null +++ b/2013-11-08-build-process.md @@ -0,0 +1,247 @@ +--- +layout: post +title: "The Build Process" +category: "6" +date: "2013-11-08 11:00:00" +tags: article +author: "Florian Kugler" +--- + +We are kind of spoiled these days -- we just hit a single button in Xcode which looks like it's supposed to play some music, and a few seconds later, our app is running. It's magical. Until something goes wrong. + +In this article, we're going to take a high-level tour through the build process and discover how all this ties in with the project settings Xcode exposes in its interface. For a deeper look at how each step along the way actually works, I will refer you to the other articles in this issue. + + +## Deciphering the Build Log + +Our first point of attack to learn about the inner workings of the Xcode build process is to have a look at the complete log file. Open the Log Navigator, select a build from the list, and Xcode will show you the log file in a prettified format. + +![Xcode build log navigator]({{ site.images_path }}/issue-6/build-log.png) + +By default, this view hides a lot of information, but you can reveal the details of each task by selecting it and clicking on the expand button at the right side. Another option is to select one or more tasks from the list and hit Cmd-C. This will copy the full plain text to the clipboard. Last but not least, you also can dump the complete log into the clipboard by selecting "Copy transcript for shown results" from the Editor menu. + +In our example, the log is just shy of 10,000 lines long (admittedly, the biggest chunk originates from compiling OpenSSL, not from our own code). So let's get started! + +The first thing you'll notice is that the log output is split into several big chunks corresponding to the targets in your project: + + Build target Pods-SSZipArchive + ... + Build target Makefile-openssl + ... + Build target Pods-AFNetworking + ... + Build target crypto + ... + Build target Pods + ... + Build target ssl + ... + Build target objcio + +Our project has several dependencies: AFNetworking and SSZipArchive, which are included as Pods, as well as OpenSSL, which is included as subproject. + +For each of these targets, Xcode goes through a series of steps to actually translate the source code into machine-readable binaries for the selected platform(s). Let's take a closer look at the first target, SSZipArchive. + +Within the log output for this target we see the details for each task performed along the way. For example, the first one is for processing a precompiled header file (in order to make it more readable, I have stripped out a lot of details): + + (1) ProcessPCH /.../Pods-SSZipArchive-prefix.pch.pch Pods-SSZipArchive-prefix.pch normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler + (2) cd /.../Dev/objcio/Pods + setenv LANG en_US.US-ASCII + setenv PATH "..." + (3) /.../Xcode.app/.../clang + (4) -x objective-c-header + (5) -arch armv7 + ... configuration and warning flags ... + (6) -DDEBUG=1 -DCOCOAPODS=1 + ... include paths and more ... + (7) -c + (8) /.../Pods-SSZipArchive-prefix.pch + (9) -o /.../Pods-SSZipArchive-prefix.pch.pch + +These blocks appear for each task in the build process, so let's go through this one in some more detail. + +1. Each of these blocks starts with a line which describes the task. +2. The following indented lines list the statements which are executed for this task. In this case, the working directory is changed, and the `LANG` and `PATH` environment variables are set. +3. This is where all the fun happens. In order to process a `.pch` file, clang gets called with a ton of options. This line shows the complete call with all arguments. Let's look at a few of them... +4. The `-x` flag specifies the language, which is, in this case, `objective-c-header`. +5. The destination architecture is specified as `armv7`. +6. Implicit `#define`s are added. +7. The `-c` flag tells clang what it actually should do. `-c` means run the preprocessor, parser, type-checking, LLVM generation and optimization, and target specific assembly code generation stages. Finally, it means to run the assembler itself to produce a `.o` object file. +8. The input file. +9. The output file. + +There is quite a lot going on, and we will not go through each of the possible tasks in great detail. The point is that you have complete insight into what tools get called and with which arguments behind the scenes during the build process. + +For this target, there are actually two tasks to process `objective-c-header` files, although only one `.pch` file exists. A closer look at these tasks tells us what's going on: + + ProcessPCH /.../Pods-SSZipArchive-prefix.pch.pch Pods-SSZipArchive-prefix.pch normal armv7 objective-c ... + ProcessPCH /.../Pods-SSZipArchive-prefix.pch.pch Pods-SSZipArchive-prefix.pch normal armv7s objective-c ... + +The target builds for two architectures -- armv7 and armv7s -- and therefore clang has to process files twice, once for each architecture. + +Following the tasks of processing the precompiled header files, we find a couple of other task types for the SSZipArchive target: + + CompileC ... + Libtool ... + CreateUniversalBinary ... + +These names are almost self-explanatory: `CompileC` compiles `.m` and `.c` files, `Libtool` creates a library from object files, and the `CreateUniversalBinary` task finally combines the two `.a` files from the previous stage (one for each architecture) into a universal binary file that runs on both armv7 and armv7s. + +Subsequently, similar steps happen for all the other dependencies in our project. AFNetworking gets compiled and linked together with SSZipArchive as pod library. OpenSSL gets built, processing the crypto and ssl targets. + +After all these dependencies have been prepared, we finally arrive at the target for our app. The log output for this target includes some other interesting tasks next to the ones we already saw during the compilation of libraries above: + + PhaseScriptExecution ... + DataModelVersionCompile ... + Ld ... + GenerateDSYMFile ... + CopyStringsFile ... + CpResource ... + CopyPNGFile ... + CompileAssetCatalog ... + ProcessInfoPlistFile ... + ProcessProductPackaging /.../some-hash.mobileprovision ... + ProcessProductPackaging objcio/objcio.entitlements ... + CodeSign ... + +The only task that doesn't have a self-explanatory name in this list is probably `Ld`, which is the name of the linker tool. It is very similar to `libtool`. In fact, `libtool` simply calls into `ld` and `lipo`. `ld` is used to create executables, `libtool` for libraries. Check out [Daniel's](/issue-6/mach-o-executables.html) and [Chris's](/issue-6/compiler.html) articles for more details about how compilation and linking works. + +Each of these steps will, in turn, call command line tools to do the actual work, just like we saw for the `ProcessPCH` step above. But instead of torturing you any longer with going through log files, we will explore these tasks from a different angle: How does Xcode know which tasks have to be performed? + + +## Controlling the Build Process + +When you select a project in Xcode 5, the project editor is presented to you with six tabs at the top: General, Capabilities, Info, Build Settings, Build Phases, and Build Rules. + +![Xcode project editor tabs]({{ site.images_path }}/issue-6/project-editor-tabs.png) + +For our purpose of understanding the build process, the last three are the most relevant. + + +### Build Phases + +Build phases represent the high-level plan of how to get from your code to an executable binary. They describe the different kind of tasks that have to be performed along the way. + +![Xcode build phases]({{ site.images_path }}/issue-6/build-phases.png) + +First, the target dependencies are established. These tell the build system which targets have to be built before the build of the current target can commence. This is not a "real" build phase. Xcode just presents the GUI together with the build phases. + +After a CocoaPods specific *script execution* build phase -- see [Michele's article](/issue-6/cocoapods-under-the-hood.html) for more information about CocoaPods and the build process -- the "Compile Sources" section specifies all the files that have to be compiled. Note that this doesn't say anything about *how* these files have to be compiled. We will learn more about this aspect when looking at build rules and build settings. Files that are in this section will be processed according to those rules and settings. + +When compilation is complete, the next step is to link everything together. And, lo and behold, that's what we find as the next build phase listed in Xcode: "Link Binary with Libraries." This section lists all static and dynamic libraries that are to be linked with the object files generated by compilation in the previous step. There are important differences between how static and dynamic libraries get handled, but I'll refer you to Daniel's article about [Mach-O executables](/issue-6/mach-o-executables.html) for more details. + +When linking is done, the last build phase is copying static resources, like images and fonts, into the app bundle. PNG images are actually not only copied to their destination, but also optimized along the way (if you have PNG optimization turned on in build settings). + +Although copying static resources is the last build phase, the build process is not complete yet. For example, code signing still has to happen, but that's not considered to be a build phase; it belongs to the final build step, "Packaging." + + +#### Custom Build Phases + +You have full control over these build phases if the default settings don't do what you need. For example, you can add build phases that run custom scripts, which [CocoaPods uses](/issue-6/cocoapods-under-the-hood.html) to do extra work. You can also add additional build phases to copy resources. This can be useful if you want to copy certain resources into specific target directories. + +Another nice use of a custom build phase is to watermark your app icon with the version number and commit hash. To do this, you add a "Run Script" build phase where you retrieve the version number and commit hash with the following commands: + + version=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_FILE}"` + commit=`git rev-parse --short HEAD` + +After that, you can modify the app icon using ImageMagick. For a complete example of how to do this, check out [this GitHub project](https://github.com/krzysztofzablocki/IconOverlaying). + +If you'd like to encourage yourself or your code workers to keep your source files concise, you can add a "Run Script" build phase that spits out a warning if a source file exceeds a certain size, in this example 200 lines. + + find "${SRCROOT}" \( -name "*.h" -or -name "*.m" \) -print0 | xargs -0 wc -l | awk '$1 > 200 && $2 != "total" { print $2 ":1: warning: file more than 200 lines" }' + + +### Build Rules + +Build rules specify how different file types should be compiled. Normally you don't have to change anything here, but if you want to add custom processing for a certain file type, you can simply add a new build rule. + +A build rule specifies which file type it applies to, how the file should be processed, and where the output should go. Let's say we have created a preprocessor that takes basic Objective-C implementation files as input, parses comments within this file for a language we've created to generate layout constraints, and outputs a `.m` file which includes the generated code. Since we cannot have a build rule which takes a `.m` file as input and output, we're going to use the extension `.mal` and add a custom build rule for that: + +![Custom build rule]({{ site.images_path }}/issue-6/custom-build-rule.png) + +This rule specifies that it applies to all files matching `*.mal` and that those files should be processed using a custom script (which calls our preprocessor with the input and output paths as arguments). Finally, the rule tells the build system where it can find the output of this build rule. + +Since the output is just a plain `.m ` file in this case, it will get picked up by the build rule for compiling `.m` files and everything will proceed as if we had written the result of the preprocessing step manually as `.m` file to start with. + +In the script we use a few variables to specify the correct paths and file names. You can find a list of all the variables available in Apple's [Build Setting Reference](https://developer.apple.com/library/mac/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW105). To see the values of all the existing environment variables during a build, you can add a "Run Script" build phase and check the "Show environment variables in build log" option. + + +### Build Settings + +So far, we have seen how build phases are used to define the steps in the build process and how build rules specify how each file type should be processed during compilation. In build settings, you can configure the details of how each of the tasks we saw before in the build log output are performed. + +You will find a ton of options for each stage of the build process, from compilation over linking to code signing and packaging. Note how the settings are divided into sections, which roughly correlate to the build phases and, sometimes, specific file types for compilation. + +Many of these options have reasonably good documentation, which you can see in the quick help inspector on the right-hand side or in the [Build Setting Reference](https://developer.apple.com/library/mac/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW105). + + +## The Project File + +All the settings we have discussed above are saved to the project file (`.pbxproj`), in addition to other project-related information (e.g. file groups). You will rarely get in touch with the internals of this file until you have a merge conflict on it. + +I would encourage you to open a project file in your favorite text editor and to go through it from top to bottom. It is surprisingly readable, and you will recognize the meaning of most sections without many problems. Reading and understanding a complete project file like this will make merge conflicts on it much less scary. + +First, we look for an entry called `rootObject`. In our project file, this reveals the following line: + + rootObject = 1793817C17A9421F0078255E /* Project object */; + +From there, we just follow the ID of this object (`1793817C17A9421F0078255E`) and find our main project definition: + + /* Begin PBXProject section */ + 1793817C17A9421F0078255E /* Project object */ = { + isa = PBXProject; + ... + +This section contains several keys which we can follow further to understand how this file is constructed. For example, `mainGroup` points to the root file group. If you follow this reference you will quickly see how the project structure is represented in the `.pbxproj` file. But let's have a look at something which is related to the build process. The `target` key points to the build target definitions: + + targets = ( + 1793818317A9421F0078255E /* objcio */, + 170E83CE17ABF256006E716E /* objcio Tests */, + ); + +Following the first reference we find the target definition: + + 1793818317A9421F0078255E /* objcio */ = { + isa = PBXNativeTarget; + buildConfigurationList = 179381B617A9421F0078255E /* Build configuration list for PBXNativeTarget "objcio" */; + buildPhases = ( + F3EB8576A1C24900A8F9CBB6 /* Check Pods Manifest.lock */, + 1793818017A9421F0078255E /* Sources */, + 1793818117A9421F0078255E /* Frameworks */, + 1793818217A9421F0078255E /* Resources */, + FF25BB7F4B7D4F87AC7A4265 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1769BED917CA8239008B6F5D /* PBXTargetDependency */, + 1769BED717CA8236008B6F5D /* PBXTargetDependency */, + ); + name = objcio; + productName = objcio; + productReference = 1793818417A9421F0078255E /* objcio.app */; + productType = "com.apple.product-type.application"; + }; + +The `buildConfigurationList` points to the available configurations, usually "Debug" and "Release." Following the debug reference, we finally end up where all the options from the build settings tab are stored: + + 179381B717A9421F0078255E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 05D234D6F5E146E9937E8997 /* Pods.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = YES; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_ENTITLEMENTS = objcio/objcio.entitlements; + ... + +The `buildPhases` attribute simply lists all the build phases we have defined in Xcode. It's easy to identify them because, luckily, Xcode augments the IDs of the objects with their real names in a C-style comment. The `buildRules` attribute is empty because we have not defined any custom build rules in this project. `dependencies` lists the target dependencies defined in Xcode's build phase tab. + +Not so scary, is it? I'll leave it as an exercise for you to go through the remaining parts of the project file. Just follow the object IDs. Once you get the hang of it and understand how all the different sections relate to the project settings in Xcode, figuring out what went wrong in case of more complicated merge conflicts becomes pretty easy. You can even start to read project files on GitHub without cloning the project and opening it in Xcode. + + +## Conclusion + +Modern software is built on a complex stack of other software, like libraries and build tools. Those, in turn, are themselves built on top of a lower-level stack. It's like peeling an onion layer for layer. While the whole stack all the way down to the silicon is probably too complex for any one person to comprehend, the realization that you actually can peel off the next layer and understand what's going on there is very empowering. There is no magic; it's just large pile of layers stacked on top of each other, and each layer has fundamentally the same building blocks. + +Looking under the hood of the build system is peeling off one of these layers. We don't need to understand the whole stack below to gain some insight into what's going on when we hit the run button. We just take a look one level deeper and find a well-organized and controllable sequence of calls to other tools, which we can investigate further if we'd like to do so. I encourage you to read the other articles in this issue to take on the next layer of the onion! + diff --git a/2013-11-08-cocoapods-under-the-hood.md b/2013-11-08-cocoapods-under-the-hood.md new file mode 100644 index 0000000..5e17f98 --- /dev/null +++ b/2013-11-08-cocoapods-under-the-hood.md @@ -0,0 +1,187 @@ +--- +layout: post +title: "CocoaPods Under The Hood" +category: "6" +date: "2013-11-08 08:00:00" +tags: article +author: "Michele Titolo" +--- + + +CocoaPods is a library dependency management tool for OS X and iOS applications. With CocoaPods, you can define your dependencies, called `pods`, and manage their versions easily over time and across development environments. + +The philosophy behind CocoaPods is twofold. Firstly, including third-party code in your projects involves many hoops. For the beginning Objective-C developer, the project file is daunting. Going through the steps of configuring build phases and linker flags leaves a lot of room for human error. CocoaPods simplifies all of that, and automatically configures your compiler settings. + +Secondly, CocoaPods makes it easy to discover new third-party libraries. Now, this doesn't mean you should go and build a FrankenApp, where every part is written by somebody else and simply stitched together. It does mean that you can find really good libraries that shorten your development cycle and improve the quality of your software. + +In this article, we will walk through the `pod install` process, and take a deeper look at what CocoaPods is doing behind the scenes. + +##Core Components +CocoaPods is written in Ruby and actually is made of several Ruby Gems. The most important gems when explaining the integration process are [CocoaPods/CocoaPods](https://github.com/CocoaPods/CocoaPods/), [CocoaPods/Core](https://github.com/CocoaPods/Core), and [CocoaPods/Xcodeproj](https://github.com/CocoaPods/Xcodeproj) (yes, CocoaPods is a dependency manager that is built using a dependency manager!). + +###CocoaPods/CocoaPod + +This is the user-facing component and is activated whenever you call a `pod` command. It includes all the functionality you need to actually use CocoaPods, and makes use of all of the other gems to perform tasks. + +###CocoaPods/Core + +The Core gem provides support for working with the files that are involved with CocoaPods, mainly the Podfile and podspecs. + +#####Podfile + +The Podfile is the file that defines the pods you want to use. It is highly customizable, and you can be as specific as you'd like. For more information, check out [the Podfile guide](http://guides.cocoapods.org/syntax/podfile.html). + +####Podspec + +The `.podspec` is a file that determines how a particular pod is added to a project. It supports features such as listing source files, frameworks, compiler flags, and any other dependencies that a library requires, to name a few. + +###CocoaPods/Xcodeproj + +This gem handles all of the project file interactions. It has the ability to both create and modify `.xcodeproj` and `.xcworkspace` files. It is also useable as a standalone gem, so if you ever wanted to write scripts and easily modify the project file, this gem is for you. + +##Running `pod install` + +There is a lot that happens when `pod install` runs. The easiest insight into this is running the command with `--verbose`. Run that command, `pod install --verbose` now, and then come back. It will look something like this: + + $ pod install --verbose + + Analyzing dependencies + + Updating spec repositories + Updating spec repo `master` + $ /usr/bin/git pull + Already up-to-date. + + + Finding Podfile changes + - AFNetworking + - HockeySDK + + Resolving dependencies of `Podfile` + Resolving dependencies for target `Pods' (iOS 6.0) + - AFNetworking (= 1.2.1) + - SDWebImage (= 3.2) + - SDWebImage/Core + + Comparing resolved specification to the sandbox manifest + - AFNetworking + - HockeySDK + + Downloading dependencies + + -> Using AFNetworking (1.2.1) + + -> Using HockeySDK (3.0.0) + - Running pre install hooks + - HockeySDK + + Generating Pods project + - Creating Pods project + - Adding source files to Pods project + - Adding frameworks to Pods project + - Adding libraries to Pods project + - Adding resources to Pods project + - Linking headers + - Installing libraries + - Installing target `Pods-AFNetworking` iOS 6.0 + - Adding Build files + - Adding resource bundles to Pods project + - Generating public xcconfig file at `Pods/Pods-AFNetworking.xcconfig` + - Generating private xcconfig file at `Pods/Pods-AFNetworking-Private.xcconfig` + - Generating prefix header at `Pods/Pods-AFNetworking-prefix.pch` + - Generating dummy source file at `Pods/Pods-AFNetworking-dummy.m` + - Installing target `Pods-HockeySDK` iOS 6.0 + - Adding Build files + - Adding resource bundles to Pods project + - Generating public xcconfig file at `Pods/Pods-HockeySDK.xcconfig` + - Generating private xcconfig file at `Pods/Pods-HockeySDK-Private.xcconfig` + - Generating prefix header at `Pods/Pods-HockeySDK-prefix.pch` + - Generating dummy source file at `Pods/Pods-HockeySDK-dummy.m` + - Installing target `Pods` iOS 6.0 + - Generating xcconfig file at `Pods/Pods.xcconfig` + - Generating target environment header at `Pods/Pods-environment.h` + - Generating copy resources script at `Pods/Pods-resources.sh` + - Generating acknowledgements at `Pods/Pods-acknowledgements.plist` + - Generating acknowledgements at `Pods/Pods-acknowledgements.markdown` + - Generating dummy source file at `Pods/Pods-dummy.m` + - Running post install hooks + - Writing Xcode project file to `Pods/Pods.xcodeproj` + - Writing Lockfile in `Podfile.lock` + - Writing Manifest in `Pods/Manifest.lock` + + Integrating client project + +There's a lot going on here, but when broken down, it's all very simple. Let's walk through it. + +###Reading the Podfile + +If you've ever wondered why the Podfile syntax looks kind of weird, that's because you are actually writing Ruby. It's just a simpler DSL to use than the other formats available right now. + +So, the first step during installation is figuring out what pods are both explicitly or implicitly defined. CocoaPods goes through and makes a list of all of these, and their versions, by loading podspecs. Podspecs are stored locally in `~/.cocoapods`. + +####Versioning and Conflicts + +CocoaPods uses the conventions established by [Semantic Versioning](http://semver.org/) to resolve dependency versions. This makes resolving dependencies much easier, since the conflict resolution system can rely on non-breaking changes between patch versions. Say, for example, that two different pods rely on two versions of CocoaLumberjack. If one relies on `2.3.1` and another `2.3.3`, the resolver can use the newer version, `2.3.3`, since it should be backward compatible with `2.3.1`. + +But this doesn't always work. There are many libraries that don't use this convention, which makes resolution difficult. + +And of course, there will always be some manual resolution of conflicts. If one library depends on CocoaLumberjack `1.2.5` and another `2.3.1`, only the end user can resolve that by explicitly setting a version. + +###Loading Sources + +The next step in the process is actually loading the sources. Each `.podspec` contains a reference to files, normally including a git remote and tag. These are resolved to commit SHAs, which are then stored in `~/Library/Caches/CocoaPods`. The files created in these directories are the responsibility of the Core gem. + +The source files are then downloaded to the `Pods` directory using the information from the `Podfile`, `.podspec`, and caches. + +###Generating the Pods.xcodeproj + +Every time `pod install` is run and changes are detected, the `Pods.xcodeproj` is updated using the Xcodeproj gem. If the file doesn't exist, it's created with some default settings. Otherwise, the existing settings are loaded into memory. + +###Installing Libraries + +When CocoaPods adds a library to the project, it adds a lot more than just the source. Since the change to each library getting its own target, for each library, several files are added. Each source needs: + +- An `.xcconfig` that contains the build settings +- A private `.xcconfig` that merges these build settings with the default CocoaPods configuration +- A `prefix.pch` which is required for building +- A `dummy.m` which is also required for building + +Once this is done for each pod target, the overall `Pods` target is created. This adds the same files, with the addition of a few more. If any source contains a resource bundle, instructions on adding that bundle to your app's target will be added to `Pods-Resources.sh`. There's also a `Pods-environment.h`, which has some macros for you to check whether or not a component comes from a pod. And lastly, two acknowledgement files are generated, one `plist`, one `markdown`, to help end users conform with licensing. + +###Writing to Disk + +Up until now, a lot of this work has been done using objects in memory. In order for this work to be reproducible, we need a file record of all of this. So the `Pods.xcodeproj` is written to disk, along with two other very important files, `Podfile.lock` and `Manifest.lock`. + +####Podfile.lock + +This is one of the most important files that CocoaPods creates. It keeps track of all of the resolved versions of pods that need to be installed. If you are ever curious as to what version of a pod was installed, check this file. This also helps with consistency across teams if this file is checked in to source control, which is recommended. + +####Manifest.lock + +This is a copy of the `Podfile.lock` that gets created every time you run `pod install`. If you've ever seen the error `The sandbox is not in sync with the Podfile.lock`, it's because this file is no longer the same as the `Podfile.lock`. Since the `Pods` directory is not always under version control, this is a way of making sure that developers update their pods before running, as otherwise the app would crash, or the build would fail in another, less visible, way. + +###xcproj + +If you have [xcproj](https://github.com/0xced/xcproj) installed on your system, which we recommend, it will `touch` the `Pods.xcodeproj` to turn it into the old ASCII plist format. Why? The writing of those files is no longer supported, and hasn't been for a while, yet Xcode still relies on it. Without xcproj, your `Pods.xcodeproj` is written as an XML plist, and when you open it in Xcode, it will be rewritten, causing large file diffs. + +##The Result + +The finished product of running `pod install` is that a lot of files have been added to your project and created on the system. This process usually only takes a few seconds. And, of course, everything that CocoaPods does can be done without it. But it'll take a lot longer than a few seconds. + +##Sidenote: Continuous Integration + +CocoaPods plays really well with Continuous Integration. And, depending on how you have your project set up, it's still fairly easy to get these projects building. + +###With a Version-Controlled Pods Folder + +If you version control the `Pods` folder and everything inside it, you don't need to do anything special for continuous integration. Just make sure to build your `.xcworkspace` with the correct scheme selected, as you need to specify a scheme when building with a workspace. + +###Without a Pods Folder + +When you do not version control your `Pods` folder, you need to do a few more things to get continuous integration working properly. At the very least, the `Podfile` needs to be checked in. It's recommended that the generated `.xcworkspace` and `Podfile.lock` are also under version control for ease of use, as well as making sure that the correct pod versions are used. + +Once you have that setup, the key with running CocoaPods on CI is making sure `pod install` is run before every build. On most systems, like Jenkins or Travis, you can just define this as a build step (in fact, Travis will do it automatically). With the release of [Xcode Bots, we haven't quite figured out a smooth process as of writing](https://groups.google.com/d/msg/cocoapods/eYL8QB3XjyQ/10nmCRN8YxoJ), but are working toward a solution, and we'll make sure to share it once we do. + +##Wrapping Up + +CocoaPods streamlines development with Objective-C, and our goal is to improve the discoverability of, and engagement in, third-party open-source libraries. Understanding what's happening behind the scenes can only help you make better apps. We've walked through the entire process, from loading specs and sources and creating the `.xcodeproj` and all its components, to writing everything to disk. So next time, run `pod install --verbose` and watch the magic happen. diff --git a/2013-11-08-compiler.markdown b/2013-11-08-compiler.markdown new file mode 100644 index 0000000..7d69eb7 --- /dev/null +++ b/2013-11-08-compiler.markdown @@ -0,0 +1,498 @@ +--- +layout: post +title: "The Compiler" +category: "6" +date: "2013-11-08 10:00:00" +tags: article +author: "Chris Eidhof" +--- + +## What Does a Compiler Do? + +In this article we'll have a look at what a compiler does, and how we can use +that to our advantage. + +Roughly speaking, the compiler has two tasks: converting our Objective-C code +into low-level code, and analyzing our code to make sure we didn't make any +obvious mistakes. + +These days, Xcode ships with clang as the compiler. Wherever we write compiler, you can read it as clang. +clang is the tool that takes Objective-C code, analyzes it, and transforms it into a more low-level representation that resembles assembly code: LLVM Intermediate Representation. LLVM IR +is low level, and operating system independent. LLVM takes instructions and compiles them into native +bytecode for the target platform. This can either be done just-in-time or at the same time as compilation. + +The nice thing about having those LLVM instructions is that you can generate and run them on any platform that is supported by LLVM. For example, if you write your iOS app, it automatically runs on two very different architectures (Intel and ARM), and it's LLVM that takes care of translating the IR code into native bytecode for those platforms. + +LLVM benefits from having a three-tier architecture, which means it supports a lot of input languages (e.g. C, Objective-C, and C++, but also Haskell) in the first tier, then a shared optimizer in the second tier (which optimizes the LLVM IR), and different targets in the third tier (e.g. Intel, ARM, and PowerPC). If you want to add a language, you can focus on the first tier, and if you want to add another compilation target, you don't have to worry too much about the input languages. In the book *The Architecture of Open Source Applications* there's a great chapter by LLVM's creator, Chris Lattner, about the [LLVM architecture](http://www.aosabook.org/en/llvm.html). + +When compiling a source file, the compiler proceeds through several phases. To see the different phases, we can ask clang what it will do to compile the file *hello.m*: + + % clang -ccc-print-phases hello.m + + 0: input, "hello.m", objective-c + 1: preprocessor, {0}, objective-c-cpp-output + 2: compiler, {1}, assembler + 3: assembler, {2}, object + 4: linker, {3}, image + 5: bind-arch, "x86_64", {4}, image + +In this article, we'll focus on phases one and two. In [Mach-O Executables](/issue-6/mach-o-executables.html), Daniel explains phases three and four. + +### Preprocessing + +The first thing that happens when you're compiling a source file is preprocessing. The preprocessor handles a macro processing language, which means it will replace macros in your text by their definitions. For example, if you write the following: + + #import + +The preprocessor will take that line, and substitute it with the contents of that file. If that header file contains any other macro definitions, they will get substituted too. + +This is the reason why people tell you to keep your header files free of imports as much as you can, because anytime you import something, the compiler has to do more work. For example, in your header file, instead of writing: + + #import "MyClass.h" + +You could write: + + @class MyClass; + +And by doing that you promise the compiler that there will be a class, `MyClass`. In the implementation file (the `.m` file), you can then import the `MyClass.h` and use it. + +Now suppose we have a very simple pure C program, named `hello.c`: + + #include + + int main() { + printf("hello world\n"); + return 0; + } + +We can run the preprocessor on this to see what the effect is: + + clang -E hello.c | less + +Now, have a look at that code. It's 401 lines. If we also add the following line to the top: + + #import + +We can run the command again, and see that our file has expanded to a whopping 89,839 lines. There are entire operating systems written in less lines of code. + +Luckily, the situation has recently improved a bit. There's now a feature called [modules](http://clang.llvm.org/docs/Modules.html) that makes this process a bit more high level. + +#### Custom Macros + +Another example is when you define or use custom macros, like this: + + #define MY_CONSTANT 4 + +Now, anytime you write `MY_CONSTANT` after this line, it'll get replaced by `4` before the rest of compilation starts. You can also define more interesting macros that take arguments: + + #define MY_MACRO(x) x + +This article is too short to discuss the full scope of what is possible with the preprocessor, but it's a very powerful tool. Often, the preprocessor is used to inline code. We strongly discourage this. For example, suppose you have the following innocent-looking program: + + + #define MAX(a,b) a > b ? a : b + + int main() { + printf("largest: %d\n", MAX(10,100)); + return 0; + } + +This will work fine. However, what about the following program: + + + #define MAX(a,b) a > b ? a : b + + int main() { + int i = 200; + printf("largest: %d\n", MAX(i++,100)); + printf("i: %d\n", i); + return 0; + } + +If we compile this with `clang max.c`, we get the following result: + + largest: 201 + i: 202 + +This is quite obvious when we run the preprocessor and expand all the macros by issuing `clang -E max.c`: + + int main() { + int i = 200; + printf("largest: %d\n", i++ > 100 ? i++ : 100); + printf("i: %d\n", i); + return 0; + } + +In this case, it's an obvious example of what could go wrong with macros, but things can go also wrong in more unexpected and hard-to-debug ways. Instead of using a macro, you should use `static inline` functions: + + #include + + static const int MyConstant = 200; + + static inline int max(int l, int r) { + return l > r ? l : r; + } + + int main() { + int i = MyConstant; + printf("largest: %d\n", max(i++,100)); + printf("i: %d\n", i); + return 0; + } + +This will print the correct result (`i: 201`). Because the code is inlined, it will have the same performance as the macro variant, but it's a lot less error-prone. Also, you can set breakpoints, have type checking, and avoid unexpected behavior. + +The only time when macros are a reasonable solution is for logging, since you can use `__FILE__` and `__LINE__` and assert macros. + +### Tokenization (Lexing) + +After preprocessing is done, every source `.m` file now has a bunch of definitions. +This text is converted from a string to a stream of tokens. For example, in the case of a simple Objective-C hello world program: + + int main() { + NSLog(@"hello, %@", @"world"); + return 0; + } + +We can ask clang to dump the tokens for this program by issuing the following command: `clang -Xclang -dump-tokens hello.m`: + + int 'int' [StartOfLine] Loc= + identifier 'main' [LeadingSpace] Loc= + l_paren '(' Loc= + r_paren ')' Loc= + l_brace '{' [LeadingSpace] Loc= + identifier 'NSLog' [StartOfLine] [LeadingSpace] Loc= + l_paren '(' Loc= + at '@' Loc= + string_literal '"hello, %@"' Loc= + comma ',' Loc= + at '@' [LeadingSpace] Loc= + string_literal '"world"' Loc= + r_paren ')' Loc= + semi ';' Loc= + return 'return' [StartOfLine] [LeadingSpace] Loc= + numeric_constant '0' [LeadingSpace] Loc= + semi ';' Loc= + r_brace '}' [StartOfLine] Loc= + eof '' Loc= + +We can see that each token consists of a piece of text and a source location. The source location is from before macro expansion, so that if something goes wrong, clang can point you to the right spot. + +### Parsing + +Now the interesting part starts: our stream of tokens is parsed into an abstract syntax tree. Because Objective-C is a rather complicated language, parsing is not always easy. After parsing, a program is now available as an abstract syntax tree: a tree that represents the original program. Suppose we have a program `hello.m`: + + #import + + @interface World + - (void)hello; + @end + + @implementation World + - (void)hello { + NSLog(@"hello, world"); + } + @end + + int main() { + World* world = [World new]; + [world hello]; + } + + +When we issue `clang -Xclang -ast-dump -fsyntax-only hello.m`, we get the following result printed to our command line: + + @interface World- (void) hello; + @end + @implementation World + - (void) hello (CompoundStmt 0x10372ded0 + (CallExpr 0x10372dea0 'void' + (ImplicitCastExpr 0x10372de88 'void (*)(NSString *, ...)' + (DeclRefExpr 0x10372ddd8 'void (NSString *, ...)' Function 0x1023510d0 'NSLog' 'void (NSString *, ...)')) + (ObjCStringLiteral 0x10372de38 'NSString *' + (StringLiteral 0x10372de00 'char [13]' lvalue "hello, world")))) + + + @end + int main() (CompoundStmt 0x10372e118 + (DeclStmt 0x10372e090 + 0x10372dfe0 "World *world = + (ImplicitCastExpr 0x10372e078 'World *' + (ObjCMessageExpr 0x10372e048 'id':'id' selector=new class='World'))") + (ObjCMessageExpr 0x10372e0e8 'void' selector=hello + (ImplicitCastExpr 0x10372e0d0 'World *' + (DeclRefExpr 0x10372e0a8 'World *' lvalue Var 0x10372dfe0 'world' 'World *')))) + +Every node in the abstract syntax tree is annotated with the original source position, so that if there's any problem later on, clang can warn about your program and give you the correct location. + +##### See Also + +* [Introduction to the clang AST](http://clang.llvm.org/docs/IntroductionToTheClangAST.html) + +### Static Analysis + +Once the compiler has an abstract syntax tree, it can perform analyses on that tree to help catch you errors, such as in type checking, where it checks whether your program is type correct. For example, when you send a message to an object, it checks if the object actually implements that message. +Also, clang does more advanced analyses where it walks through your program to make sure you're not doing anything weird. + +#### Type Checking + +Any time you write code, clang is there to help you check that you didn't make any mistakes. One of the obvious things to check is if your program sends the correct messages to the correct objects and calls the correct functions on the correct values. If you have a plain `NSObject*`, you can't just send it the `hello` message, as clang will report an error. +Also, if you create a class `Test` that subclasses `NSObject`, like this: + + @interface Test : NSObject + @end + +And you then try to assign an object with a different type to that object, the compiler will help you and warn you that what you're doing is probably not correct. + +There are two types of typing: dynamic and static typing. Dynamic typing means that a type is checked at runtime, and static typing means that the types are checked at compile time. In the past, you could always send any message to any object, and at runtime, it would be determined if the object responds to that message. When this is checked only at runtime, it's called dynamic typing. + +With static typing, this is checked at compile time. When you use ARC, the compiler checks a lot more types at compile time, because it needs to know which objects it works with. For example, you can not write the following code anymore: + + [myObject hello] + +If there's no `hello` method defined anywhere in your program. + +#### Other Analyses + +There are a lot of other analyses that clang does for you. If you clone the clang repository and go to `lib/StaticAnalyzer/Checkers`, you'll see all the static checkers. For example, there's `ObjCUnusedIVarsChecker.cpp`, which checks if ivars are unused. Or there is `ObjCSelfInitChecker.cpp`, which checks if you called `[self initWith...]` or `[super init]` before you start using `self` inside your initializer. Some of the other checks happen in other parts of the compiler. For example, in line 2,534 of `lib/Sema/SemaExprObjC.cpp`, you can see the line that does the following: + + Diag(SelLoc, diag::warn_arc_perform_selector_leaks); + +Which produces the dreaded "performSelector may cause a leak because its selector is unknown" warning. + +## Code Generation + +Now, once your code is fully tokenized, parsed, and analyzed by clang, it can generate the LLVM code for you. To see what happens, we can have a look again at the program `hello.c`: + + #include + + int main() { + printf("hello world\n"); + return 0; + } + +To compile this into LLVM bitcode (which is, most of the time, represented in a binary format) we can issue the following command: + + clang -O3 -emit-LLVM hello.c -c -o hello.bc + +This generates a binary file that we can then inspect using another command: + + llvm-dis < hello.bc | less + +Which gives us the following output: + + ; ModuleID = '' + target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128" + target triple = "x86_64-apple-macosx10.8.0" + + @str = private unnamed_addr constant [12 x i8] c"hello world\00" + + ; Function Attrs: nounwind ssp uwtable + define i32 @main() #0 { + %puts = tail call i32 @puts(i8* getelementptr inbounds ([12 x i8]* @str, i64 0, i64 0)) + ret i32 0 + } + + ; Function Attrs: nounwind + declare i32 @puts(i8* nocapture) #1 + + attributes #0 = { nounwind ssp uwtable } + attributes #1 = { nounwind } + +You can see that the `main` function is only two lines: one to print the string and one to return `0`. + +It's also interesting to do the same thing for a very simple Objective-C program `five.m` that we compile and then view using `LLVM-dis < five.bc | less`: + + #include + #import + + int main() { + NSLog(@"%@", [@5 description]); + return 0; + } + +There are a lot more things going on around, but here's the `main` function: + + define i32 @main() #0 { + %1 = load %struct._class_t** @"\01L_OBJC_CLASSLIST_REFERENCES_$_", align 8 + %2 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_", align 8, !invariant.load !4 + %3 = bitcast %struct._class_t* %1 to i8* + %4 = tail call %0* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %0* (i8*, i8*, i32)*)(i8* %3, i8* %2, i32 5) + %5 = load i8** @"\01L_OBJC_SELECTOR_REFERENCES_2", align 8, !invariant.load !4 + %6 = bitcast %0* %4 to i8* + %7 = tail call %1* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to %1* (i8*, i8*)*)(i8* %6, i8* %5) + tail call void (%1*, ...)* @NSLog(%1* bitcast (%struct.NSConstantString* @_unnamed_cfstring_ to %1*), %1* %7) + ret i32 0 + } + + +The most important lines are line 4, which creates the `NSNumber` object, line +`7`, which sends the `description` message to the number object, and line 8, +which logs the string returned from the `description` message. + +### Optimizations + +To see which kind of optimizations LLVM and clang can do, it's interesting to look at a slightly more complicated C example, the recursively defined `factorial` function: + + #include + + int factorial(int x) { + if (x > 1) return x * factorial(x-1); + else return 1; + } + + int main() { + printf("factorial 10: %d\n", factorial(10)); + } + +To compile this without optimizations, run the following command: + + clang -O0 -emit-llvm factorial.c -c -o factorial.bc && llvm-dis < factorial.bc + +The interesting part is to look at the code generated for the `factorial` function: + + define i32 @factorial(i32 %x) #0 { + %1 = alloca i32, align 4 + %2 = alloca i32, align 4 + store i32 %x, i32* %2, align 4 + %3 = load i32* %2, align 4 + %4 = icmp sgt i32 %3, 1 + br i1 %4, label %5, label %11 + + ;