Lightweight View Controllers and viewcontrollers
Iew controllers is usually the largest file in iOS projects, and they contain a lot of unnecessary code. Therefore, the code in View controllers is almost always the least reusable. We will see the technology for slimming view controllers so that code can be reused and code can be moved to a more appropriate place.
You can obtain sample projects about this issue on Github.
Separate Data Source from other Protocols
SetUITableViewDataSourceThe code is extracted and put into a separate class, which is one of the powerful technologies to slim down the view controller. When you do this several times, you can sum up some patterns and create reusable classes.
For example, in the example project, there isPhotosViewControllerClass, which has 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;}
These codes basically do some things around arrays. More specifically, they do things around the photos array managed by the view controller. We can try to move the array-related code to a separate class. We can use a block to set the cell, or use delegate to do this, depending on your habits.
@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
Now, you can remove the three methods in view controller. Instead, you can createArrayDataSourceClass instance as the data source of table view.
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 ing an index path to the position in the array. Every time you want to display this array to a table view, you can reuse this code. You can also implement some additional methods, suchtableView:commitEditingStyle:forRowAtIndexPath:, Which is shared between table view controllers.
The advantage is that you can test this class independently and do not need to write it again. This principle applies to objects other than arrays.
In an application we made this year, we used a lot of Core Data. We have created a similar class, but it is different from the previously used array. It uses a fetched results controller to obtain data. It implements all animation updates, section headers processing, and deletion operations. You can create an instance of this class, and then assign a fetch request and block used to set the cell. The rest will be processed, so you don't have to worry about it.
In addition, this method can be extended to other protocols. The most obvious one isUICollectionViewDataSource. This gives you great flexibility. If you want to useUICollectionViewReplaceUITableViewYou almost do not need to modify the view controller. You can even allow your data source to support both Protocols at the same time.
Move business logic to Model
The following is the sample code in view controller (from other projects) to find a list of users' current priorities:
- (void)loadPriorities { NSDate* now = [NSDate date]; NSString* formatString = @"startDate = %@"; NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now]; NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate]; self.priorities = [priorities allObjects];}
Move the codeUserClass category will become clearer. After processingView Controller.mIt looks like this:
- (void)loadPriorities { self.priorities = [user currentPriorities];}
InUser+Extensions.mMedium:
- (NSArray*)currentPriorities { NSDate* now = [NSDate date]; NSString* formatString = @"startDate = %@"; NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now]; return [[self.priorities filteredSetUsingPredicate:predicate] allObjects];}
Some Code cannot be easily moved to the model object, but it is obviously closely related to the model Code. In this case, we can useStore:
Create a Store class
In our first example program, some code loads the file and parses it. The code in view controller is as follows:
- (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];}
However, the view controller does not need to know this, so we createdStoreObject. With separation, We can reuse the code, test them separately, and keep the view controller small. Store objects care about data loading, caching, and setting data stacks. It is also often calledService LayerOrWarehouse.
Move network request logic to Model layer
Similar to the above topic: Do not make network request logic in view controller. Instead, you should encapsulate them into another class. In this way, your view controller can then request the network by using a callback (such as a completion block. The advantage is that cache and error control can also be completed in this class.
Move the View code to the View layer
Complex view hierarchies should not be built in view controller. You can use Interface Builder or encapsulate views intoUIViewSubclass. For example, if you want to create a date Selection control, put it inDatePickerViewClass will be much better than doing everything in view controller. Again, this increases reusability and keeps it simple.
If you like Interface Builder, you can also do it in Interface Builder. Some people think that IB can only be used with view controllers, but in fact you can also load a separate nib file to a custom view. In the example program, we createPhotoCell.xib, Including the layout of photo cell:
As you can see, we created properties on the view (we didn't use the File's Owner object on this nib) and connected to the specified subviews. This technology also applies to other custom views.
Communication
Other common events in view controllers are communication with other view controllers, models, and views. This is of course what the controller should do, but we still want to complete it with as few code as possible.
There are many well-described technologies (such as KVO and fetched results controllers) about message transmission between view controllers and model objects ). However, the message transmission between view controllers is slightly less clear.
This problem occurs when a view controller wants to pass a status to multiple other view controllers. It is better to put the state in a separate object, and then pass the object to other view controllers to observe and modify the state. The advantage is that the message is transmitted in one place (the observed object), and we do not need to tangle nested delegate callbacks. This is actually a complicated topic. We may discuss it with a complete topic in the future.
Summary
We have seen some techniques for creating smaller view controllers. We don't want to apply these technologies to every possible corner, but we have a goal: To write maintainable code. Once we know these patterns, we are more likely to clean up those bulky view controllers.