×
Copy
Open
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
No content
Slide 2
Slide 2 text
ƛavericks @darkproger
Slide 3
Slide 3 text
Functional Programming with Objective-C
Slide 4
Slide 4 text
Objective-C (the good parts) Static typing Dynamic binding Blocks ARC
Slide 5
Slide 5 text
Objective-C (really) Boring Verbose Low-level OOP
Slide 6
Slide 6 text
-[NSBitmapImageRep initWithBitmapDataPlanes:(unsigned char **)planes pixelsWide:(NSInteger)width pixelsHigh:(NSInteger)height bitsPerSample:(NSInteger)bps samplesPerPixel:(NSInteger)spp hasAlpha:(BOOL)alpha isPlanar:(BOOL)isPlanar colorSpaceName:(NSString *)colorSpaceName bitmapFormat:(NSBitmapFormat)bitmapFormat bytesPerRow:(NSInteger)rowBytes bitsPerPixel:(NSInteger)pixelBits]; AppKit
Slide 7
Slide 7 text
[headerView addConstraint:[NSLayoutConstraint constraintWithItem:excessivelyBigView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationLessThanOrEqual toItem:headerView attribute:NSLayoutAttributeHeight multiplier:0.0 constant:45]]; AutoLayout
Slide 8
Slide 8 text
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"state"]) { /* ... */ } } KVO
Slide 9
Slide 9 text
NSMutableArray *result = [NSMutableArray arrayWithCapacity:mutableOperations.count]; [mutableOperations enumerateObjectsUsingBlock:^(AFHTTPRequestOperation *op, NSUInteger idx, BOOL *stop) { [result addObject:change_op(op)]; }]; return result;
Slide 10
Slide 10 text
NSMutableArray *result = [NSMutableArray arrayWithCapacity:mutableOperations.count]; [mutableOperations enumerateObjectsUsingBlock:^(AFHTTPRequestOperation *op, NSUInteger idx, BOOL *stop) { [result addObject:change_op(op)]; }]; return result; state change state return state!
Slide 11
Slide 11 text
NSMutableArray *result = [NSMutableArray arrayWithCapacity:mutableOperations.count]; [mutableOperations enumerateObjectsUsingBlock:^(AFHTTPRequestOperation *op, NSUInteger idx, BOOL *stop) { [result addObject:change_op(op)]; }]; return result; state change state return state! also, way too much code. Check AFIncrementalStore
Slide 12
Slide 12 text
68: if (isDataRetrieved == NO) { 86: if(line.Id == tripId){ 110: if([[Config ArrPsgWaitingTrips] count]+[[Config ArrPsgPastTrips] count] > 0) 120: if([[Config ArrPsgWaitingTrips] count]+[[Config ArrPsgPastTrips] count] > 0) 133: if([[Config ArrPsgWaitingTrips] count]+[[Config ArrPsgPastTrips] count] == 0) 163: if (aTrip.Id == pushedTrip.Id) { 189: if (line.Id == aTrip.Id) { 197: if (isFound == NO) { 200: if (line.Id == aTrip.Id) { 214: if (isFound == YES) { +
Slide 13
Slide 13 text
68: if (isDataRetrieved == NO) { 86: if(line.Id == tripId){ 110: if([[Config ArrPsgWaitingTrips] count]+[[Config ArrPsgPastTrips] count] > 0) 120: if([[Config ArrPsgWaitingTrips] count]+[[Config ArrPsgPastTrips] count] > 0) 133: if([[Config ArrPsgWaitingTrips] count]+[[Config ArrPsgPastTrips] count] == 0) 163: if (aTrip.Id == pushedTrip.Id) { 189: if (line.Id == aTrip.Id) { 197: if (isFound == NO) { 200: if (line.Id == aTrip.Id) { 214: if (isFound == YES) { + state? state? state? state? state? state?
Slide 14
Slide 14 text
…state?
Slide 15
Slide 15 text
No content
Slide 16
Slide 16 text
you ok? you good? you need anything? you sure you’re fine? argh!!
Slide 17
Slide 17 text
you ok? you good? you need anything? you sure you’re fine? argh!!
Slide 18
Slide 18 text
you ok? you good? you need anything? you sure you’re fine? argh!! STATE SUCKS!
Slide 19
Slide 19 text
singletons too (dispatch_once story here)
Slide 20
Slide 20 text
Functional no state!
Slide 21
Slide 21 text
Functional HOFs
Slide 22
Slide 22 text
Functional HOFs HOFs > OOP patterns
Slide 23
Slide 23 text
Functional immutable data no side effects .. thread-safe
Slide 24
Slide 24 text
Functional simple
Slide 25
Slide 25 text
No content
Slide 26
Slide 26 text
> let numbers = [1,2,3,4,5] > map (+1) numbers [2,3,4,5,6] > filter even numbers [2,4] > foldl1 (+) numbers 15 > sum numbers 15 > zip numbers numbers [(1,1),(2,2),(3,3),(4,4),(5,5)]
Slide 27
Slide 27 text
Functional declarative rather than imperative
Slide 28
Slide 28 text
NSMutableArray
Slide 29
Slide 29 text
NSArray
Slide 30
Slide 30 text
NSArray *newMaps = [maps map:^id(CXMap *map) { return [map changeSomehow]; }]; http://underscorem.org/ https://github.com/mogeneration/functionalkit and more!
Slide 31
Slide 31 text
NSArray *newMaps = [maps map:^id(CXMap *map) { return [map changeSomehow]; }]; entirely new array! mapping function (HOF) returns a new object
Slide 32
Slide 32 text
Function Composition
Slide 33
Slide 33 text
No content
Slide 34
Slide 34 text
> let numbers = [1,2,3,4,5] > map (+1) numbers [2,3,4,5,6] > filter even numbers [2,4] > foldl1 (+) numbers 15 > sum numbers 15
Slide 35
Slide 35 text
> let numbers = [1,2,3,4,5] > map (+1) numbers [2,3,4,5,6] > filter even numbers [2,4] > foldl1 (+) numbers 15 > sum numbers 15 ! ! > foldl1 (+) . filter even . map (+1) $ numbers 12
Slide 36
Slide 36 text
> let numbers = [1,2,3,4,5] > map (+1) numbers [2,3,4,5,6] > filter even numbers [2,4] > foldl1 (+) numbers 15 > sum numbers 15 ! ! > foldl1 (+) . filter even . map (+1) $ numbers 12 composition ^ composition ^
Slide 37
Slide 37 text
> let f = foldl1 (+) . filter even . map (+1) > :type f f :: [Integer] -> Integer > f numbers 12 int f(int array[])
Slide 38
Slide 38 text
> let f = foldl1 (+) . filter even . map (+1) > :type f f :: [Integer] -> Integer > f numbers 12 int f(int array[]) NSInvocation a block
Slide 39
Slide 39 text
Functors http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Slide 40
Slide 40 text
No content
Slide 41
Slide 41 text
> let numbers = [1,2,3,4,5] > fmap (+1) numbers [2,3,4,5,6] ! > fmap (+1) (Just 2) Just 3
Slide 42
Slide 42 text
No content
Slide 43
Slide 43 text
No content
Slide 44
Slide 44 text
Concurrency
Slide 45
Slide 45 text
[client logInWithSuccess:^{ [client loadCachedMessagesWithSuccess:^(NSArray *messages) { [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { NSLog(@"Fetched all messages."); } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }];
Slide 46
Slide 46 text
-> * login * load data from cache * load data from network * send stats
Slide 47
Slide 47 text
report_stats( load_network( load_cached( login(user)))
Slide 48
Slide 48 text
dashboard = login . load_cache . load_network . report_stats
Slide 49
Slide 49 text
scripts != apps
Slide 50
Slide 50 text
Objective-C concurrency -scheduleInRunLoop:
Slide 51
Slide 51 text
Objective-C concurrency NSThread @synchronized
Slide 52
Slide 52 text
Objective-C advanced concurrency dispatch(3)
Slide 53
Slide 53 text
No content
Slide 54
Slide 54 text
Qs! units of work NSRunLoop dispatch_queue NSOperationQueue
Slide 55
Slide 55 text
Qs! actors (queues associated with an execution context) ! -performSelector: https://code.google.com/p/plankton-platform/ https://code.google.com/p/plactorkit/
Slide 56
Slide 56 text
No content
Slide 57
Slide 57 text
No content
Slide 58
Slide 58 text
true for motivational training losers different story in (user-oriented) programming
Slide 59
Slide 59 text
ReactiveCocoa
Slide 60
Slide 60 text
pod 'ReactiveCocoa', '~> 2.1.3'
Slide 61
Slide 61 text
- self.title = [map name]; + RAC(self, title) = RACObserve(map, name);
Slide 62
Slide 62 text
Qs! RACStream
Slide 63
Slide 63 text
Streams composable lazy/eager ops (eg throttling)
Slide 64
Slide 64 text
Streams Collections (arrays, etc) Values over time Asynchronous ops
Slide 65
Slide 65 text
Streams! no more: callbacks notifications delegates boredom
Slide 66
Slide 66 text
[client logInWithSuccess:^{ [client loadCachedMessagesWithSuccess:^(NSArray *messages) { [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { NSLog(@"Fetched all messages."); } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }];
Slide 67
Slide 67 text
__block BOOL stop = NO; __block NSError *topError = nil; CXParse *client = [CXParse clientWithApplicationId:@"my application” RESTKey:@"yeah right"]; RACSignal *login = [client login:@"johndoe" password:@"password"]; RACSignal *maps = [client GETClass:@"Map" parameters:nil]; [[[login concat:maps] takeLast:1].logAll subscribeNext:^(NSArray *maps) { NSLog(@"maps: %@", maps); } error:^(NSError *error) { topError = error; stop = YES; } completed:^{ stop = YES; }]; while (!stop && [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]) ;
Slide 68
Slide 68 text
operation vs execution
Slide 69
Slide 69 text
__block BOOL stop = NO; __block NSError *topError = nil; CXParse *client = [CXParse clientWithApplicationId:@"my application" RESTKey:@"yeah right"]; RACSignal *login = [client login:@“johndoe" password:@"password"]; RACSignal *maps = [client GETClass:@“Map" parameters:nil]; [[[login concat:maps] takeLast:1].logAll subscribeNext:^(NSArray *maps) { NSLog(@"maps: %@", maps); } error:^(NSError *error) { topError = error; stop = YES; } completed:^{ stop = YES; }]; while (!stop && [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]) ;
Slide 70
Slide 70 text
chaining operations
Slide 71
Slide 71 text
__block BOOL stop = NO; __block NSError *topError = nil; CXParse *client = [CXParse clientWithApplicationId:@"my application" RESTKey:@"yeah right"]; RACSignal *login = [client login:@"johndoe" password:@"password"]; RACSignal *maps = [client GETClass:@"Map" parameters:nil]; [[[login concat:maps] takeLast:1].logAll subscribeNext:^(NSArray *maps) { NSLog(@"maps: %@", maps); } error:^(NSError *error) { topError = error; stop = YES; } completed:^{ stop = YES; }]; while (!stop && [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]) ;
Slide 72
Slide 72 text
testing ready
Slide 73
Slide 73 text
__block BOOL stop = NO; __block NSError *topError = nil; CXParse *client = [CXParse clientWithApplicationId:@"my application" RESTKey:@"yeah right"]; RACSignal *login = [client login:@"johndoe" password:@"password"]; RACSignal *maps = [client GETClass:@"Map" parameters:nil]; [[[login concat:maps] takeLast:1].logAll subscribeNext:^(NSArray *maps) { NSLog(@"maps: %@", maps); } error:^(NSError *error) { topError = error; stop = YES; } completed:^{ stop = YES; }]; while (!stop && [NSRunLoop.currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture]) ;
Slide 74
Slide 74 text
gathering async ops
Slide 75
Slide 75 text
NSArray *otherUsers; return [[RACSignal concat:[otherUsers.rac_sequence map: ^RACSignal *(id user) { return [[client GETUser:user] catchTo:RACSignal.empty]; }]] collect];
Slide 76
Slide 76 text
monads!
Slide 77
Slide 77 text
[[[login flattenMap:^RACStream *(NSDictionary *login) { return [client GETRelatedTo:login className:@“_User" byKey:@"maps" parameters:nil]; }] flattenMap:^RACStream *(NSDictionary *mapsResult1) { return [client GETObjectsRelatedToCurrentUserByKey:@“maps" parameters:nil]; }] subscribeNext:^(NSDictionary *mapsResult2) { NSLog(@"maps for user: %@", @(mapsResult2.count)); } error:^(NSError *error) { topError = error; stop = YES; } completed:^{ stop = YES; }];
Slide 78
Slide 78 text
-flattenMap: = http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Slide 79
Slide 79 text
folds with hacks
Slide 80
Slide 80 text
RACTupleUnpack(NSArray *implicit, NSArray *explicit) = [results.rac_sequence foldLeftWithStart:RACTuplePack(NSMutableArray.array, NSMutableArray.array) reduce: ^id(RACTuple *tuple, NSDictionary *map) { NSMutableArray *receiver = [map[@"ACL"] objectForKey:acl] == nil ? tuple.first : tuple.second; [receiver addObject:map]; return tuple; }];
Slide 81
Slide 81 text
notifications
Slide 82
Slide 82 text
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [[NSNotificationCenter.defaultCenter rac_addObserverForName:AFNetworkingOperationDidStartNotification object:nil] subscribeNext:^(NSNotification *note) { AFHTTPRequestOperation *op = note.object; NSLog(@“%@", [TTTURLRequestFormatter cURLCommandFromURLRequest:op.request]); }]; });
Slide 83
Slide 83 text
composing nofitications
Slide 84
Slide 84 text
RACSignal *fetch = /* ... */; ! RACSignal *save = [NSNotificationCenter.defaultCenter rac_addObserverForName: NSManagedObjectContextDidSaveNotification object:CXAppDelegate.shared.managedObjectContext]; RACSignal *storeFetch = [NSNotificationCenter.defaultCenter rac_addObserverForName: AFIncrementalStoreContextDidFetchRemoteValues object:nil]; RACSignal *storeSave = [NSNotificationCenter.defaultCenter rac_addObserverForName: AFIncrementalStoreContextDidSaveRemoteValues object:nil]; RACSignal *anyUpdate = [[RACSignal merge:@[save, storeFetch, storeSave]] take:1]; ! RACSignal *rest = [anyUpdate then:^RACSignal *{ return fetch; }]; return [rest repeat];
Slide 85
Slide 85 text
composing KVO
Slide 86
Slide 86 text
self.btLogin.enabled = NO; RAC(self.btLogin.enabled) = [RACSignal combineLatest:@[ RACObserve(self, tfLogin.text), RACObserve(self, tfPassword.text) ] reduce:^(NSString *login, NSString *password) { return @(login.length > 0 && password.length > 0); }];
Slide 87
Slide 87 text
encapsulating I/O
Slide 88
Slide 88 text
- (RACSignal *)thumbnail { NSURL *url = self.thumbnailURL; if ([NSFileManager.defaultManager fileExistsAtPath:url.path] == NO) { return [[SomeViewController drawMap:self] flattenMap:^RACStream *(UIImage *m) { [UIImagePNGRepresentation(m) writeToURL:url options:NSDataWritingAtomic error:nil]; return [RACSignal return:m]; }]; } else { return [RACSignal return:[UIImage imageWithContentsOfFile:url.path]]; } }
Slide 89
Slide 89 text
op cancelation
Slide 90
Slide 90 text
[self.thumbnailCancelation sendError:[NSError errorWithDomain:NSPOSIXErrorDomain code:EINTR userInfo:nil]]; self.thumbnailCancelation = RACSubject.subject; ! [[RACSignal merge:@[[self.map thumbnail], self.thumbnailCancelation]] subscribeNext:^(UIImage *image) { self.thumbnailView.image = image; self.thumbnailView.contentMode = UIViewContentModeScaleAspectFit; self.thumbnailCancelation = nil; } error:^(NSError *error) { if (error.domain != NSPOSIXErrorDomain && error.code != EINTR) { NSLog(@"thumb error: %@ (map: %@)", error, self.map); } }];
Slide 91
Slide 91 text
tinkering
Slide 92
Slide 92 text
RACSignal *wait = [[[RACObserve(self, p12) doNext:^(id x) { NSLog(@"new p12! %@", self.p12); }] filter:^BOOL(id value) { return value != NSNull.null && value != nil; }] take:1]; ! RACSignal *access = [rq accessRequest]; access = [access doNext:^(id response) { NSLog(@"access: %@", response); }]; access = [access doError:^(NSError *error) { NSLog(@"error: %@", error); }]; ! access = [access catchTo:[RACSignal empty]]; access = [access delay:2].repeat; ! RACSignal *op = [wait then:^RACSignal *{ return access; }]; ! [op subscribeNext:^(id x) { NSLog(@"next %@", x); } error:^(NSError *error) { NSLog(@"err: %@", error); } completed:^{ NSLog(@“completed"); }];
Slide 93
Slide 93 text
return [[[self requestAuth] flattenMap:^RACStream *(NSDictionary *args) { return [[self startTunnel:args] flattenMap:^RACStream *(NMSSHSession *session) { return [[self startPortForwarding:args session:session] flattenMap:^RACStream *(NSValue *channelPointer) { LIBSSH2_CHANNEL *channel = [channelPointer pointerValue]; ! dispatch_async(self.ssh_queue, ^{ libssh2_session_set_blocking(session.session, 0); rdps_proxyloop(session.sock, channel); }); return [self rac_sessionControllerWithArgs:args]; }]; }]; }] subscribeOn:self.ssh_scheduler];
Slide 94
Slide 94 text
rac_ APIs
Slide 95
Slide 95 text
Pods/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoa/NSData+RACSupport.h 20:+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL options:(NSDataReadingOptions)options scheduler:(RACScheduler *)scheduler; ! Pods/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoa/NSFileHandle+RACSupport.h 17:- (RACSignal *)rac_readInBackground; ! Pods/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoa/NSNotificationCenter+RACSupport.h 16:- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object; ! Pods/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoa/NSString+RACSupport.h 20:+ (RACSignal *)rac_readContentsOfURL:(NSURL *)URL usedEncoding:(NSStringEncoding *)encoding scheduler:(RACScheduler *)scheduler; ! Pods/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoa/NSURLConnection+RACSupport.h 23:+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request; ! Pods/ReactiveCocoa/ReactiveCocoaFramework/ReactiveCocoa/NSObject+RACSelectorSignal.h 55:- (RACSignal *)rac_signalForSelector:(SEL)selector; 77:- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol; & many more
Slide 96
Slide 96 text
[[[controller rac_signalForSelector:@selector(sessionDidDisconnect:)] take:1] subscribeNext:^(id x) { [controller dismissViewControllerAnimated:NO completion:nil]; }];
Slide 97
Slide 97 text
debuggable!
Slide 98
Slide 98 text
https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Instruments
Slide 99
Slide 99 text
beyond/without ReactiveCocoa
Slide 100
Slide 100 text
dispatch_group_create(3) libdispatch is actually pretty awesome
Slide 101
Slide 101 text
__block dispatch_group_t group = dispatch_group_create(); ! NSArray *operationsArray = /* ... */; [operationsArray enumerateObjectsUsingBlock:^(AFHTTPRequestOperation *op, NSUInteger idx, BOOL *stop) { dispatch_group_enter(group); }]; ! dispatch_group_notify(group, dispatch_get_main_queue(), ^{ [self notifyManagedObjectContext:context aboutRequestOperations:[NSArray arrayWithArray:mutableOperations] forSaveChangesRequest:saveChangesRequestCopy]; }); https://github.com/proger/AFIncrementalStore/blob/a38c0566f96525586e64639144a315f5ea0848a2/ AFIncrementalStore/AFIncrementalStore.m#L500
Slide 102
Slide 102 text
debugging dispatch (with DTrace!)
Slide 103
Slide 103 text
No content
Slide 104
Slide 104 text
# mv -v /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/usr/lib/system/ libdispatch.dylib{,.backup} ! # cp -v /Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/usr/lib/system/ {introspection/,}libdispatch.dylib ! # dtrace -p $(pgrep CXParseApp) -s ~/dev/darwinkit/sys/dispatch_trace.d https://github.com/proger/darwinkit/blob/master/sys/dispatch_trace.d
Slide 105
Slide 105 text
more stuff
Slide 106
Slide 106 text
-valueForKeyPath: , @keypath NSPredicate (LINQ without compile checks) create new controllers instead of reusing
Slide 107
Slide 107 text
derive state instead of keeping it
Slide 108
Slide 108 text
reason of program behaviours by isolating state impacts in your head
Slide 109
Slide 109 text
No content
Slide 110
Slide 110 text
@darkproger ! http://kirillov.im kthxbai!
Slide 111
Slide 111 text
http://icomputerdenver.com/wp-content/uploads/2013/09/Mac-OS-X-Mavericks-Logo.png http://eatalkalinefoods.com/wp-content/uploads/2012/04/file0001508919007.jpg http://custom-sharepoint-training.com/wp-content/uploads/2012/02/bigstock_Crossing_Out_Reactive_And_Writ_6135701.jpg http://wallchips.com/wp-content/uploads/2013/07/Banana-Minion-mi-villano-favorito-Despicable-Me-Images.jpg http://i4.mirror.co.uk/incoming/article96108.ece/ALTERNATES/s2197/top-gun-with-tom-cruise-pic-rex-103184563-96108.jpg