Slide 1

Slide 1 text

SOME CODE PATTERNS

Slide 2

Slide 2 text

STORYBOARDS

Slide 3

Slide 3 text

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Settings" bundle:nil];

Slide 4

Slide 4 text

// .h extern NSString * const StoryboardNameSettings; // .m NSString * const StoryboardNameSettings = @"Settings"; // use new constant UIStoryboard *storyboard = [UIStoryboard storyboardWithName:StoryboardNameSettings bundle:nil];

Slide 5

Slide 5 text

@implementation UIStoryboard (Additions) + (UIStoryboard *)pch_settingsStoryboard { return [UIStoryboard storyboardWithName:@"Settings" bundle:nil]; } @end

Slide 6

Slide 6 text

@implementation UIStoryboard (Additions) + (UIStoryboard *)pch_settingsStoryboard { if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) return [UIStoryboard storyboardWithName:@"Settings~iPad" bundle:nil]; } else { return [UIStoryboard storyboardWithName:@"Settings~iPhone" bundle:nil]; } } @end

Slide 7

Slide 7 text

CUSTOM ERRORS

Slide 8

Slide 8 text

#define ERROR_DOMAIN @"com.mikezornek.json-parser" @implementation NSError (MZJSONParserErrors) + (NSError *)mdz_invalidJsonError { return [NSError errorWithDomain:ERROR_DOMAIN code:1001 userInfo:@{NSLocalizedDescriptionKey : @"JSON was malformed and could not be parsed."}]; } @end

Slide 9

Slide 9 text

BLOCK SAFETY

Slide 10

Slide 10 text

- (void)tellClientToDoItWithCompletion:(CompletionType)completion { [self.client doIt:^(NSError *error) { completion(); }]; }

Slide 11

Slide 11 text

- (void)tellClientToDoItWithCompletion:(CompletionType)completion { [self.client doIt:^(NSError *error) { if (completion) { completion(); } }]; }

Slide 12

Slide 12 text

// In even a simple network call this can get out of hand... - (void)downloadTracksFromURL:(NSURL *)url completionHandler:(void (^)(NSArray *tracks, NSError *error))completion { NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { NSError *trackError; NSArray *tracks = [self tracksFromData:data error:&trackError]; if (tracks) { if (completionHandler) { completionHandler(tracks, nil); } } else { if (completionHandler) { completionHandler(nil, trackError); } } } else { if (completionHandler) { completionHandler(nil, error); } } }]; [dataTask resume]; }

Slide 13

Slide 13 text

// Look how nicer it gets when you can assume the block is present... - (void)downloadTracksFromURL:(NSURL *)url completionHandler: (void (^)(NSArray *tracks, NSError *error))completion { // Some code or method to verify the completion block is not null NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { NSError *trackError; NSArray *tracks = [self tracksFromData:data error:&trackError]; tracks ? completionHandler(tracks, nil) : completionHandler(nil, trackError); } else { completionHandler(nil, error); } }]; [dataTask resume]; }

Slide 14

Slide 14 text

MAKE A MACRO

Slide 15

Slide 15 text

MACROS #define BLOCK_CALL(block, ...) ({ if (block) block(__VA_ARGS__); else nil; })

Slide 16

Slide 16 text

PROVIDE A DEFAULT BLOCK BEHAVIOR

Slide 17

Slide 17 text

DEFAULT BLOCK - (void)processPhoneList:(NSArray *)list completion: (PhoneListCompletionHandler)incomingCompletion { PhoneListCompletionHandler completion = (incomingCompletionHandler != NULL) ? incomingCompletionHandler : ^{ NSLog(@"processPhoneList: did complete"); }; // When the time comes, look ma' no need to check for nil! completion() }

Slide 18

Slide 18 text

MAKE THE BLOCK REQUIRED

Slide 19

Slide 19 text

USING ASSERTS NSParameterAssert(block != nil);

Slide 20

Slide 20 text

NEW IN XCODE 6.31 // as long as the type is a simple object or block pointer. - (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name; - (NSInteger)indexOfItem:(nonnull AAPLListItem *)item; 1 https://developer.apple.com/swift/blog/?id=25

Slide 21

Slide 21 text

NEW IN XCODE 6.31 @property (copy, nullable) NSString *name; @property (copy, readonly, nonnull) NSArray *allItems; 1 https://developer.apple.com/swift/blog/?id=25

Slide 22

Slide 22 text

TELL, DON'T ASK (DEPENDENCY INJECTION)

Slide 23

Slide 23 text

The core idea of the Tell, Don't Ask pattern is that any *ViewController or *Service or *Store you build should, when initialized or otherwise "prepared", have all the information it needs to do its job. It should not ask the global state for information. -- Mike Zornek, right now

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

SOME MORE EXAMPLES: ▸ +[NSDate date] ▸ NSNotificationCenter ▸ NSUserDefaults ▸ SessionManager ▸ *Store / *Service

Slide 27

Slide 27 text

But I don't want to manually pass around all these state objects! — Someone complained

Slide 28

Slide 28 text

SOLUTIONS ▸ prepareForSegue:sender: ▸ pushViewController:animated: ▸ ... or a dedicated framework

Slide 29

Slide 29 text

Frameworks ▸ http://typhoonframework.org/ ▸ http://objection-framework.org/ Articles ▸ http://www.objc.io/issue-15/dependency-injection.html ▸ https://www.bignerdranch.com/blog/dependency-injection-ios/

Slide 30

Slide 30 text

LAUNCH MENU

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

struct LaunchMenuItem { let title: String let details: String let runAction: () -> () } struct LaunchMenuSection { let title: String let items: [LaunchMenuItem] } struct LaunchMenu { let sections: [LaunchMenuSection] }

Slide 33

Slide 33 text

WEATHERDEMO https://github.com/zorn/WeatherDemo

Slide 34

Slide 34 text

DATASOURCES FOR *Service OBJECTS

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

WEATHERDEMO https://github.com/zorn/WeatherDemo

Slide 41

Slide 41 text

The End?