Slide 1

Slide 1 text

Architecting Modular Codebases Istanbul Tech Talks 2014 Peter Steinberger @steipete

Slide 2

Slide 2 text

Modular what?

Slide 3

Slide 3 text

2011 50 files 25k LOC

Slide 4

Slide 4 text

2012 150 files 100k LOC

Slide 5

Slide 5 text

2013 350 files 150k LOC

Slide 6

Slide 6 text

2014

Slide 7

Slide 7 text

Architecting Modular Codebases » Application Components » Dependency Injection » View Controller Decoupling » Plugin Infrastructure » Aspect Oriented Programming

Slide 8

Slide 8 text

Flexibility

Slide 9

Slide 9 text

Clarity

Slide 10

Slide 10 text

Performance

Slide 11

Slide 11 text

Quality

Slide 12

Slide 12 text

What it won't do for you.

Slide 13

Slide 13 text

Application Components

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Application Components Structure classes after responsibility. PSPDFKit |- Gallery |- Networking |- DataExporter |-|- DataModel |- PSPDFUIKit |-|- PSPDFFoundation

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Application Components Use class extensions. @interface PSPDFViewController (EmbeddedFileSupport) @end @implementation PSPDFViewController (EmbeddedFileSupport) - (void)embeddedFilesController:(PSPDFEmbeddedFilesViewController *)controller didSelectFile:(PSPDFEmbeddedFile *)embeddedFile sender:(id)sender { // delegate implementation. }

Slide 19

Slide 19 text

Application Components Extract features into sub-controllers. * PSPDFSearchHighlightViewManager * PSPDFPresentationController * PSPDFHalfModalController objc.io/issue-1/lighter-view- controllers.html

Slide 20

Slide 20 text

Dependency Injection

Slide 21

Slide 21 text

Hollywood Principle

Slide 22

Slide 22 text

Dependency Injection - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // Stop further caching the document. PSPDFCache *cache = PSPDFCache.sharedCache; [cache stopCachingDocument:self.document]; }

Slide 23

Slide 23 text

Dependency Injection - (PSPDFCache *)cache { return PSPDFCache.sharedCache; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // Stop further caching the document. [self.cache stopCachingDocument:self.document]; }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Dependency Injection // In class extension @property (nonatomic, strong) PSPDFCache *cache; - (id)initWithCache:(PSPDFCache *)cache { NSParameterAssert(cache); if (self = [super init]) { _cache = cache; } return self; }

Slide 26

Slide 26 text

Dependency Injection @property (nonatomic, strong) PSPDFCache *cache; objection_requires_sel(@selector(cache)) // Create instance id vc = [JSObjection.defaultInjector getObject:ViewController.class];

Slide 27

Slide 27 text

Dependency Injection JSObjectionInjector *injector = [JSObjection createInjector]; [JSObjection setDefaultInjector:injector]; // Register singleton. [JSObjection registerClass:PSPDFCache.class scope:JSObjectionScopeSingleton];

Slide 28

Slide 28 text

Dependency Injection // Define implementation for a protocol. [self bindClass:YahooWeatherService.class toProtocol:@protocol(WeatherService)];

Slide 29

Slide 29 text

Dependency Injection Popular Frameworks » Spring, Guice » Objection » Typhoon Framework » DYI

Slide 30

Slide 30 text

View Controller Decoupling

Slide 31

Slide 31 text

View Controller Decoupling with Routes /user -> ISTUserController /user/1 -> ISTUserDetailController with ID 1

Slide 32

Slide 32 text

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; }];

Slide 33

Slide 33 text

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]; }

Slide 34

Slide 34 text

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; }];

Slide 35

Slide 35 text

View Controller Decoupling with Routes » JLRoutes https://github.com/joeldev/JLRoutes » SOCKit https://github.com/NimbusKit/sockit

Slide 36

Slide 36 text

Plugin Infrastructure

Slide 37

Slide 37 text

The Apple way » CFPlugin CFPlugInAddInstanceForFactory » NSBundle builtInPlugInsPath, principalClass

Slide 38

Slide 38 text

Anatomy of a Loadable Bundle MyLoadableBundle.bundle Contents/ Info.plist MacOS/ MyLoadableBundle Resources/ Lizard.jpg MyLoadableBundle.icns en.lproj/ MyLoadableBundle.nib InfoPlist.strings

Slide 39

Slide 39 text

"iOS Static Libraries Are, Like, Really Bad, And Stuff" 15800975

Slide 40

Slide 40 text

tl;dr » Hacky "static framework" workarounds using lipo » Resource Loading » Debugging and Symbolication » Dependent frameworks » Two-level Namespacing

Slide 41

Slide 41 text

Plugin Infrastructure The Xcode way

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

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)]; }]; }

Slide 44

Slide 44 text

Implementing Stylus Support

Slide 45

Slide 45 text

Implementing Stylus Support So many different models! » Adonit JotTouch » WACOM » Pogo (Ten One Design) » HEX3 Jaja » Unnamed Vendor

Slide 46

Slide 46 text

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:

Slide 47

Slide 47 text

Implementing Stylus Support Places to customize: » PSPDFDrawView » PSPDFAnnotationStateManager » PSPDFFlexibleAnnotationToolbar » PSPDFViewController

Slide 48

Slide 48 text

Implementing Stylus Support New classes: » PSPDFStylusManager » PSPDFStylusViewController » PSPDFStylusStatusButton

Slide 49

Slide 49 text

Messy!

Slide 50

Slide 50 text

SDK == Driver

Slide 51

Slide 51 text

Plugin Infrastructure SDK == Driver @protocol PSPDFStylusDriver - (BOOL)enableDriver:(NSError **)error options:(NSDictionary *)options; - (void)disableDriver; - (NSDictionary *)connectionInfo; @property (nonatomic, weak, readonly) id delegate; @optional - (id)touchInfoForTouch:(UITouch *)touch; - (UIViewController *)settingsController; @end

Slide 52

Slide 52 text

Plugin Infrastructure @protocol PSPDFPlugin // 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;

Slide 53

Slide 53 text

Discovery

Slide 54

Slide 54 text

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]; }

Slide 55

Slide 55 text

Plugin Infrastructure Discovery // Static method declared in + (NSDictionary *)pluginInfo { return @{PSPDFPluginNameKey : @"com.pspdfkit.stylus.driver.wacom", PSPDFPluginProtocolVersionKey : @(PSPDFPluginProtocolVersion_1), PSPDFStylusDriverNameKey : @"Wacom", PSPDFStylusDriverSDKVersionKey : WacomManager.getManager.getSDKVersion, PSPDFStylusDriverVersionKey : @(PSPDFStylusDriverProtocolVersion_1), PSPDFStylusDriverPriorityKey : @(80)}; }

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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; }

Slide 58

Slide 58 text

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]; } }];

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Plugin Infrastructure Selector Hooking Inspired by ReactiveCocoa's signals. NSObject+RACSelectorSignal.m _objc_msgForward + forwardInvocation + objc_setClass + class_replaceMethod

Slide 61

Slide 61 text

Aspect Oriented Programming

Slide 62

Slide 62 text

Aspect Oriented Programming "AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns."

Slide 63

Slide 63 text

Aspect Oriented Programming Advice types: » Add code before a specific method. » Add code after a specific method. » Add code instead a specific method.

Slide 64

Slide 64 text

Aspect Oriented Programming » Optional Features » Logging » Analytics » Tutorial Code

Slide 65

Slide 65 text

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"}, ] }; }

Slide 66

Slide 66 text

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)

Slide 67

Slide 67 text

Architecting Modular Codebases » Application Components » Dependency Injection » View Controller Decoupling » Plugin Infrastructure » Aspect Oriented Programming

Slide 68

Slide 68 text

Architecting Modular Codebases Q/A

Slide 69

Slide 69 text

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