10 tips for a reusable architecture
DESCRIPTION
Get some hints on how to write the code of your iOS apps in a more reusable way. This will make you more efficient and happier.TRANSCRIPT
10 tips for a reusable
architectureJorge D. Ortiz Fuentes
@jdortiz
A POWWAU production
Agenda
★Goal
★Principles
★Tips
★Q&A
Goal
Write code that can be used as
many times & in as many projects as
possible.
Principles
SOLID
★Single Responsibility
★Open/Closed
★ Liskov Substitution
★ Interface Segregation
★Dependency Inversion
Tips
Forgive me, because I am going to sin
★The following are tips, not rules.
★They might not apply to your code.
★But I would, at least, think about them.
1.
Don’t write reusable code
Don’t write start with reusable
code
Don’t start with reusable code
★Start with the right idea
★But don’t need the perfect code
★You can always refactor
★Shipped is better than perfect
2.
But don’t commit code that is clearly not reusable
Commit reusable code
★Do the Right Thing™ before committing to the development branch or any shared branches.
★Be a good boy scout: If you revisit the code and it can be improved, do so.
3.
Move all the business logic to
the model
Move to the model★Model > Data
★Business rules
• Behaviors
• Validation
• Importing & exporting
• Migration
Instead of- (IBAction) importPasteText:(id)sender { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; if ([pasteboard containsPasteboardTypes:@[@"public.utf8-plain-text", @"public.text"]]) { NSInteger rejectedItems = 0; CheckItem *newItem = nil; [self.undoManager beginUndoGrouping]; NSString *textToImport = [pasteboard.string copy]; NSArray *items = [textToImport componentsSeparatedByString:@"\n"]; NSUInteger objectNumber = 0; if ([[self.itemsTableViewDataSource.fetchedResultsController sections] count] > 0) { id <NSFetchedResultsSectionInfo> sectionInfo = [self.itemsTableViewDataSource.fetchedResultsController sections][0]; objectNumber = [sectionInfo numberOfObjects]; } for (NSString *item in items) { NSArray *itemComponents = [item componentsSeparatedByString:@":"]; NSString *itemName = [itemComponents[0] stringByTrimmingCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]]; if ([itemName length] > 0) { …
do- (IBAction) importPasteText:(id)sender { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; if ([pasteboard containsPasteboardTypes:@[@“public.utf8-plain-text", @"public.text"]]) { [self.undoManager beginUndoGrouping]; [self.model importItemsInChecklist: self.checklist fromString:pasteboard.string]; [self.undoManager endUndoGrouping]; } }
4.
Don’t put presentation logic
into the model
Presentation logic
★ It isn’t business logic, but “UI logic”
★ In the VC, it could be inconsistent, better “centralized”.
★ It shouldn’t be in the model.
★ It can contain UI dependent information (colors, fonts,…).
Instead of
// Set label to “3 days left” - (void) displayRemainingTime { self.remainingLabel.text = [self.task remainingTime]; }
do
// Set label to “3 days left” - (void) displayRemainingTime { TaskPresenter *tp = [[TaskPresenter alloc] initWithTask:self.task]; self.remainingLabel.text = [tp remainingTime]; }
5.
Put the network closer to the
model (instead of View Controller)
Network code★The view controller is responsible for responding events:
• lifecycle
• UI generated
★Controlling networking is OK
★Doing networking is wrong
★ In the model or a category:
• Provide business logic with network functionality
• Handle concurrency
6.
Make classes for Data Sources &
Delegates of table views
Data Source & Delegate Objects
★ It exceeds VC responsibilities
★Responsibility: View model
★They can be reused
Instead of
// @interface MyVC: UITableViewController
@implementation MyVC { //implicit (in super) // self.tableView.dataSource = self; // self.tableView.delegate = self; }
do// @interface MyVC: UITableViewController @interface MyVC @property (strong) MyDataSrc *myDataSrc; @end
@implementation MyVC - (void) viewDidLoad { [self assignDataSource]; }
- (void) assignDataSource { self.myDataSrc = [[MyDataSrc alloc] initWithMOC:self.moc]; self.myDataSrc.fetchedResourceController.delegate.self; self.tableView.dataSource = self; [self.myDataSource performFetch]; } @end
7.
But then, don’t let them take
care of the views
DataSource & Delegate responsibilities
★Metadata: How many rows, sections…
★Act on data: add or delete row
★Provide views, view info and present data
★Act on selections
Use a delegateMyVC MyDataSrc
numberOfSectionInTableView:tableView:numberOfRowsInSection:tableView:cellForRowAtIndexPath:
displayObj
ect:inCell
TableViewViewModelDelegate
@protocol TableViewViewModelDelegate <NSObject>
- (NSString *) cellIdentifier; - (CGFloat) cellHeight; - (UITableViewCell *) displayObject:(NSManagedObject *)object inCell:(UITableViewCell *)cell; - (NSString *) headerIdentifier; - (CGFloat) headerHeight; - (UITableViewHeaderFooterView *) displayObject:(NSManagedObject *)object inHeader:(UITableViewHeaderFooterView *)header; @optional - (void) didSelectObject:(NSManagedObject *)object; - (void) didSelectAccessoryForObject:(NSManagedObject *)object;
@end
8.
Test your code
Test
★Reusable code = code + tests
★Tests contain
• Validation
• Assumptions
• Documentation
Test your Core Data model
@implementation CoreDataTestCase - (void) setUp { [super setUp]; [self createCoreDataStack]; }
- (void) createCoreDataStack { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; model = [NSManagedObjectModel mergedModelFromBundles:@[bundle]]; coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; store = [coordinator addPersistentStoreWithType: NSInMemoryStoreType configuration: nil URL: nil options: nil error: NULL]; context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; context.persistentStoreCoordinator = coordinator; }
Test your view controllers
- (void) setUp { [super setUp];
[self createFixture]; [self createSut]; }
- (void) createSut { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil]; sut = [storyboard instantiateViewControllerWithIdentifier:viewControllerID]; sut.data = data; }
Test your view controllers (2)
- (void) testOnViewDidLoadTitleIsSetToChecklistName { [sut view];
XCTAssertTrue([sut.title isEqualToString:sut.checklist.name], @"View controller title must be set to the checklist name"); }
9.
Break up your Storyboards into
workflows
Huge StoryboardsSomething to avoid
Workflows★Huge Storyboards:
• Teams get conflicts in the Storyboards often
• Difficult to maintain
★Better Storyboards:
• One per workflow (sign in/sign up, statistics, preferences)
• Even combine with XIB (useful for cells)
Load another Storyboard
- (IBAction) displayReport:(id)sender { UIViewController *reportVC = [[UIStoryboard storyboardWithName:reportStoryboard bundle:nil] instantiateInitialViewController]; [self presentViewController:reportVC animated:YES completion:nil]; }
10.
Create your own modules
Modular code
★ iOS support
• Until iOS 8: only static libraries (no resources, separated headers)
• iOS 8: frameworks
★Cocoapods: allow to share open source code
Create a pod★ Isolate the code (use pod-template)
★Create .podspec & LICENSE
★pod lib create coolpod
★ In your Podfile:
• pod ‘coolpod', :git => 'https://server/repoURL/coolpod.git'
Thank you!
Images courtesy of Shutterstock