Save 37% off PRO during our Black Friday Sale! »

Architecting Modular Codebases

Architecting Modular Codebases

Here I'm presenting 5 techniques to create more modular codebases, presented at Istanbul Tech Talks 2014.

832ece085bfe2c7c5b0ed6be62d7e675?s=128

Peter Steinberger
PRO

April 28, 2014
Tweet

Transcript

  1. Architecting Modular Codebases Istanbul Tech Talks 2014 Peter Steinberger @steipete

  2. Modular what?

  3. 2011 50 files 25k LOC

  4. 2012 150 files 100k LOC

  5. 2013 350 files 150k LOC

  6. 2014

  7. Architecting Modular Codebases » Application Components » Dependency Injection »

    View Controller Decoupling » Plugin Infrastructure » Aspect Oriented Programming
  8. Flexibility

  9. Clarity

  10. Performance

  11. Quality

  12. What it won't do for you.

  13. Application Components

  14. Find large files find . -name "*.m" -exec wc -l

    "{}" \; | sort -rn | head 4351 ./TextParser/PSPDFFontFileDescriptor.m 3651 ./Controller/PSPDFViewController.m 2889 ./Views/PSPDFPageView.m 2189 ./Parser/PSPDFDocumentParser.m 2017 ./XFDF/PSPDFXFDFParser.m 2004 ./Model/PSPDFDocument.m 1783 ./TextParser/PSPDFTextParser.m 1776 ./Annotations/PSPDFAnnotation.m 1649 ./Views/PSPDFPageView+AnnotationMenu.m 1469 ./Gallery/PSPDFGalleryViewController.m
  15. Application Components Structure classes after responsibility. PSPDFKit |- Gallery |-

    Networking |- DataExporter |-|- DataModel |- PSPDFUIKit |-|- PSPDFFoundation
  16. Application Components Use private headers within components. PSPDFEmbeddedFile.h/m PSPDFEmbeddedFile+Private.h @interface

    PSPDFEmbeddedFile (Private) // Parses the PDF dictionary and returns an `PSPDFEmbeddedFile` object if found. + (instancetype)embeddedFileWithPDFDictionary:(CGPDFDictionaryRef)pdfDictionary; // Index of the file in the original document. @property (nonatomic, copy) NSString *streamPath; @end
  17. Application Components Use class extensions. @interface UIView (UIViewHierarchy) - (void)layoutSubviews;

    @end @interface UIView (UIViewRendering) - (void)drawRect:(CGRect)rect; @end @interface UIView (UIViewGestureRecognizers) - (void)addGestureRecognizer:(UIGestureRecognizer*)recognizer @end @interface UIView (UIViewMotionEffects) - (void)addMotionEffect:(UIMotionEffect *)effect @end
  18. Application Components Use class extensions. @interface PSPDFViewController (EmbeddedFileSupport) <PSPDFEmbeddedFilesViewControllerDelegate> @end

    @implementation PSPDFViewController (EmbeddedFileSupport) - (void)embeddedFilesController:(PSPDFEmbeddedFilesViewController *)controller didSelectFile:(PSPDFEmbeddedFile *)embeddedFile sender:(id)sender { // delegate implementation. }
  19. Application Components Extract features into sub-controllers. * PSPDFSearchHighlightViewManager * PSPDFPresentationController

    * PSPDFHalfModalController objc.io/issue-1/lighter-view- controllers.html
  20. Dependency Injection

  21. Hollywood Principle

  22. Dependency Injection - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // Stop further

    caching the document. PSPDFCache *cache = PSPDFCache.sharedCache; [cache stopCachingDocument:self.document]; }
  23. Dependency Injection - (PSPDFCache *)cache { return PSPDFCache.sharedCache; } -

    (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // Stop further caching the document. [self.cache stopCachingDocument:self.document]; }
  24. Dependency Injection @property (nonatomic, strong) PSPDFCache *cache; - (void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated]; // Stop further caching the document. [self.cache stopCachingDocument:self.document]; }
  25. Dependency Injection // In class extension @property (nonatomic, strong) PSPDFCache

    *cache; - (id)initWithCache:(PSPDFCache *)cache { NSParameterAssert(cache); if (self = [super init]) { _cache = cache; } return self; }
  26. Dependency Injection @property (nonatomic, strong) PSPDFCache *cache; objection_requires_sel(@selector(cache)) // Create

    instance id vc = [JSObjection.defaultInjector getObject:ViewController.class];
  27. Dependency Injection JSObjectionInjector *injector = [JSObjection createInjector]; [JSObjection setDefaultInjector:injector]; //

    Register singleton. [JSObjection registerClass:PSPDFCache.class scope:JSObjectionScopeSingleton];
  28. Dependency Injection // Define implementation for a protocol. [self bindClass:YahooWeatherService.class

    toProtocol:@protocol(WeatherService)];
  29. Dependency Injection Popular Frameworks » Spring, Guice » Objection »

    Typhoon Framework » DYI
  30. View Controller Decoupling

  31. View Controller Decoupling with Routes /user -> ISTUserController /user/1 ->

    ISTUserDetailController with ID 1
  32. View Controller Decoupling with Routes // Set up routes [JLRoutes

    addRoute:@"/user/:userID" handler:^BOOL(NSDictionary *parameters) { NSUInteger userID = [parameters[@"userID"] integerValue]; UserDetailController *controller = [[UserDetailController alloc] initWithID:userID]; [self.navigationController pushViewController:controller animated:YES]; return YES; }];
  33. View Controller Decoupling with Routes // Open the user controller.

    NSUInteger userID = 42; NSString *URLString = [NSString stringWithFormat:@"myapp://user/%tu?animated=NO", userID]; [UIApplication.sharedApplication openURL:[NSURL URLWithString:URLString]]; // Install the URL handler. - (BOOL)application:(UIApplication *)app openURL:(NSURL *)URL sourceApplication:(NSString *)source annotation:(id)annotation { return [JLRoutes routeURL:URL]; }
  34. View Controller Decoupling with Routes // Set up route and

    also process the animation property. [JLRoutes addRoute:@"/user/:userID" handler:^BOOL(NSDictionary *parameters) { NSUInteger userID = [parameters[@"userID"] integerValue]; BOOL animated = parameters[@"animated"] ? [parameters[@"animated"] boolValue] : YES; UserDetailController *controller = [[UserDetailController alloc] initWithID:userID]; [self.navigationController pushViewController:controller animated:animated]; return YES; }];
  35. View Controller Decoupling with Routes » JLRoutes https://github.com/joeldev/JLRoutes » SOCKit

    https://github.com/NimbusKit/sockit
  36. Plugin Infrastructure

  37. The Apple way » CFPlugin CFPlugInAddInstanceForFactory » NSBundle builtInPlugInsPath, principalClass

  38. Anatomy of a Loadable Bundle MyLoadableBundle.bundle Contents/ Info.plist MacOS/ MyLoadableBundle

    Resources/ Lizard.jpg MyLoadableBundle.icns en.lproj/ MyLoadableBundle.nib InfoPlist.strings
  39. "iOS Static Libraries Are, Like, Really Bad, And Stuff" 15800975

  40. tl;dr » Hacky "static framework" workarounds using lipo » Resource

    Loading » Debugging and Symbolication » Dependent frameworks » Two-level Namespacing
  41. Plugin Infrastructure The Xcode way

  42. None
  43. Plugin Infrastructure The Xcode way + (void)pluginDidLoad:(NSBundle *)plugin { //

    Initialize class } // BBUDebuggerTuckAway - (void)swizzleDidChangeTextInSourceTextView { id sourceTextView = [objc_getClass("DVTSourceTextView") new]; [sourceTextView yl_swizzleSelector:@selector(didChangeText) withBlock:^void(id _self) { [self toggleDebuggersIfNeeded]; // call original [_self yl_performSelector:@selector(didChangeText)]; }]; }
  44. Implementing Stylus Support

  45. Implementing Stylus Support So many different models! » Adonit JotTouch

    » WACOM » Pogo (Ten One Design) » HEX3 Jaja » Unnamed Vendor
  46. Implementing Stylus Support Different approaches! » UIApplication subclass/hook sendEvent: »

    Register/deregister views » Custom touch delivery » Touch classification » Manually forwarding touches from touchesBegan:/Moved:/Ended:/Cancelled:
  47. Implementing Stylus Support Places to customize: » PSPDFDrawView » PSPDFAnnotationStateManager

    » PSPDFFlexibleAnnotationToolbar » PSPDFViewController
  48. Implementing Stylus Support New classes: » PSPDFStylusManager » PSPDFStylusViewController »

    PSPDFStylusStatusButton
  49. Messy!

  50. SDK == Driver

  51. Plugin Infrastructure SDK == Driver @protocol PSPDFStylusDriver <PSPDFPlugin> - (BOOL)enableDriver:(NSError

    **)error options:(NSDictionary *)options; - (void)disableDriver; - (NSDictionary *)connectionInfo; @property (nonatomic, weak, readonly) id<PSPDFStylusDriverDelegate> delegate; @optional - (id<PSPDFStylusTouch>)touchInfoForTouch:(UITouch *)touch; - (UIViewController *)settingsController; @end
  52. Plugin Infrastructure @protocol PSPDFPlugin <NSObject> // Designated initializer. Will be

    called upon creation. - (id)initWithRegistry:(PSPDFPluginRegistry *)registry options:(NSDictionary *)options; // Plugin details for auto-discovery. + (NSDictionary *)pluginInfo; @end extern NSString * const PSPDFPluginNameKey; extern NSString * const PSPDFPluginEnabledKey; extern NSString * const PSPDFPluginInitializeOnDiscoveryKey; extern NSString * const PSPDFPluginSaveInstanceKey; extern NSString * const PSPDFPluginProtocolVersionKey;
  53. Discovery

  54. Plugin Infrastructure Discovery - (NSDictionary *)discoverPluginClasses { NSMutableDictionary *pluginClasses =

    [NSMutableDictionary dictionary]; // Interate all classes. unsigned int count = 0; Class *classList = objc_copyClassList(&count); for (int index = 0; index < count; index++) { Class class = classList[index]; if (class_conformsToProtocol(class, @protocol(PSPDFPlugin)) && [self isValidPluginClass:class]) { NSDictionary *info = [class pluginInfo]; pluginClasses[info[PSPDFPluginNameKey]] = class; } } free(classList); return [pluginClasses copy]; }
  55. Plugin Infrastructure Discovery // Static method declared in <PSPDFPlugin> +

    (NSDictionary *)pluginInfo { return @{PSPDFPluginNameKey : @"com.pspdfkit.stylus.driver.wacom", PSPDFPluginProtocolVersionKey : @(PSPDFPluginProtocolVersion_1), PSPDFStylusDriverNameKey : @"Wacom", PSPDFStylusDriverSDKVersionKey : WacomManager.getManager.getSDKVersion, PSPDFStylusDriverVersionKey : @(PSPDFStylusDriverProtocolVersion_1), PSPDFStylusDriverPriorityKey : @(80)}; }
  56. Plugin Infrastructure Implementation @interface PSPDFAnnotationStateManagerStylusSupport : NSObject <PSPDFStylusDriverDelegate> @end @interface

    PSPDFAnnotationStateManager (StylusSupport) // Accessing this class will enable stylus support. - (PSPDFAnnotationStateManagerStylusSupport *)stylusSupport; @property (nonatomic, readonly) UIBarButtonItem *stylusStatusButton; @end
  57. Plugin Infrastructure Implementation // Lazily initialize our stylus support class.

    - (PSPDFAnnotationStateManagerStylusSupport *)stylusSupport { PSPDFAnnotationStateManagerStylusSupport *stylusSupport = objc_getAssociatedObject(self, _cmd); if (!stylusSupport) { stylusSupport = [[PSPDFAnnotationStateManagerStylusSupport alloc] initWithStateManager:self]; objc_setAssociatedObject(self, _cmd, stylusSupport, OBJC_ASSOCIATION_RETAIN); } return stylusSupport; }
  58. Plugin Infrastructure Implementation // Hook into didMoveToWindow for view registration.

    [PSPDFDrawView.class pspdf_hookSelector:@selector(didMoveToWindow) withBlock:^(PSPDFDrawView *view, NSArray *args) { if (view.window) { [PSPDFStylusManager.sharedInstance registerView:view]; }else { [PSPDFStylusManager.sharedInstance unregisterView:view]; } }];
  59. Plugin Infrastructure Implementation @interface NSObject (PSPDFMessageHooks) - (BOOL)pspdf_hookSelector:(SEL)selector withBlock:(void (^)(id

    object, NSArray *arguments))block; + (BOOL)pspdf_hookSelector:(SEL)selector withBlock:(void (^)(id object, NSArray *arguments))block; @end
  60. Plugin Infrastructure Selector Hooking Inspired by ReactiveCocoa's signals. NSObject+RACSelectorSignal.m _objc_msgForward

    + forwardInvocation + objc_setClass + class_replaceMethod
  61. Aspect Oriented Programming

  62. Aspect Oriented Programming "AOP is a programming paradigm that aims

    to increase modularity by allowing the separation of cross-cutting concerns."
  63. Aspect Oriented Programming Advice types: » Add code before a

    specific method. » Add code after a specific method. » Add code instead a specific method.
  64. Aspect Oriented Programming » Optional Features » Logging » Analytics

    » Tutorial Code
  65. Aspect Oriented Programming - (NSDictionary *)analyticsConfiguration { return @{ @"trackedScreens"

    : @[ @{@"class" : @"ISTMainViewController", @"label" : @"Main screen"} ], @"trackedEvents" : @[ @{@"class" : @"ISTMainViewController", @"sel" : @"loginWithFacebook:", @"label" : @"Login with Facebook"}, @{@"class" : @"ISTFacebookViewController", @"sel" : @"failedToConnect:", @"label" : @"Facebook connection failure"}, ] }; }
  66. Aspect Oriented Programming » AOP-for-Objective-C https://github.com/ndcube/AOP-for- Objective-C » NSProxy »

    ReactiveCocoa https://github.com/ReactiveCocoa/ ReactiveCocoa » method_exchangeImplementations (DIY)
  67. Architecting Modular Codebases » Application Components » Dependency Injection »

    View Controller Decoupling » Plugin Infrastructure » Aspect Oriented Programming
  68. Architecting Modular Codebases Q/A

  69. Architecting Modular Codebases Thanks! Resources: * http://www.typhoonframework.org * https://github.com/atomicobject/objection *

    http://objc.io/issue-1/lighter-view-controllers.html * https://github.com/joeldev/JLRoutes * http://ddeville.me/2014/04/dynamic-linking/ * http://dev.hubspot.com/blog/architecting-a-large-ios-app-with-cocoapods * http://landonf.bikemonkey.org/code/ios/Radar_15800975_iOS_Frameworks.20140112.html * https://github.com/neonichu/BBUDebuggerTuckAway * http://albertodebortoli.github.io/blog/2014/03/25/ an-aspect-oriented-approach-programming-to-ios-analytics/ * http://codeshaker.blogspot.co.at/2012/01/aop-delivered.html * https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/ ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.m * https://github.com/ndcube/AOP-for-Objective-C * http://codeshaker.blogspot.com.tr/2012/01/aop-delivered.html Image Credits: * Hollywood, Gavin Johnson, flickr.com/photos/23629211@N04/4569636150