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