Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Architecting Modular Codebases

Architecting Modular Codebases

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

Peter Steinberger
PRO

April 28, 2014
Tweet

More Decks by Peter Steinberger

Other Decks in Technology

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/[email protected]/4569636150