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

ITT 2014 - Peter Steinberger - Architecting Modular Codebases

ITT 2014 - Peter Steinberger - Architecting Modular Codebases

Everyone knows the pain of convoluted code as an application grows and feature after feature is being added. In this talk Peter lets you explore ideas how to grow your project in a healthy, maintainable way, how to manage dependencies, how to design code around testability, how to write plugins and even some practical solutions around the idea of aspect oriented programming. This is all based on a large-scale 150k lines project and Peter shows some production code as well.

Istanbul Tech Talks

May 12, 2014
Tweet

More Decks by Istanbul Tech Talks

Other Decks in Programming

Transcript

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

    View Controller Decoupling » Plugin Infrastructure » Aspect Oriented Programming
  2. 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
  3. Application Components Structure classes after responsibility. PSPDFKit |- Gallery |-

    Networking |- DataExporter |-|- DataModel |- PSPDFUIKit |-|- PSPDFFoundation
  4. 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
  5. 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
  6. 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. }
  7. Dependency Injection - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // Stop further

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

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

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

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

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

    Register singleton. [JSObjection registerClass:PSPDFCache.class scope:JSObjectionScopeSingleton];
  13. 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; }];
  14. 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]; }
  15. 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; }];
  16. Anatomy of a Loadable Bundle MyLoadableBundle.bundle Contents/ Info.plist MacOS/ MyLoadableBundle

    Resources/ Lizard.jpg MyLoadableBundle.icns en.lproj/ MyLoadableBundle.nib InfoPlist.strings
  17. tl;dr » Hacky "static framework" workarounds using lipo » Resource

    Loading » Debugging and Symbolication » Dependent frameworks » Two-level Namespacing
  18. 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)]; }]; }
  19. Implementing Stylus Support So many different models! » Adonit JotTouch

    » WACOM » Pogo (Ten One Design) » HEX3 Jaja » Unnamed Vendor
  20. 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:
  21. 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
  22. 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;
  23. 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]; }
  24. 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)}; }
  25. 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
  26. 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; }
  27. 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]; } }];
  28. 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
  29. Aspect Oriented Programming "AOP is a programming paradigm that aims

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

    specific method. » Add code after a specific method. » Add code instead a specific method.
  31. 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"}, ] }; }
  32. 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)
  33. Architecting Modular Codebases » Application Components » Dependency Injection »

    View Controller Decoupling » Plugin Infrastructure » Aspect Oriented Programming
  34. 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