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

Fun with blocks in ObjC

Fun with blocks in ObjC

Cyril Lashkevich

March 13, 2014
Tweet

More Decks by Cyril Lashkevich

Other Decks in Programming

Transcript

  1. const char *s = "abc" "cde"; ! NSString *ns1 =

    @"abc" @"cde"; ! NSString *ns2 = @"abc" "cde"; ! NSString *ns1 = "abc" @"cde";
  2. History First appeared as ObjC/C/C++ extension in iOS 4 and

    Mac OS 10.6 Was implemented in gcc 4.2 (with bugs) and clang 3.1 First blocks-oriented API is the Grand Central Dispatch iOS 5 ARC (changed __block semantics) iOS 7 no need to call copy for blocks in many cases
  3. Similar concepts First class functions GCC nested functuons extension C++11

    lambdas Ruby blocks Proc’s and lambdas Python lambdas Lua/JavaScript functions
  4. Syntax As a local variable: returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

    As a property: @property (nonatomic, copy) returnType (^blockName)(parameterTypes); As a method parameter: - (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName; As an argument to a method call: [someObject someMethodThatTakesABlock: ^returnType (parameters) {...}]; As a typedef: typedef returnType (^TypeName)(parameterTypes); TypeName blockName = ^returnType(parameters) {...}; http://fuckingblocksyntax.com
  5. Syntax In the implementation return type is optional and can

    be inferred by compiler:
 ^(void) { if(something) return 42; else return 43; } No parameters, parentheses are optional:
 ^ { if(something) return 42; else return 43; } Type can be deduced with typeof instruction:
 typeof(^ { if(something) return 42; else return 43; })
  6. Blocks as first class citizens Blocks are special kind of

    objects compatible with id Have *isa as first member Managed with retain/release/copy Can be passed as parameters and retuned from functions and methods Can be stored in containers Can be used in C++ algorithms as functors
  7. vector<m_off_t> transfers; ... NSMutableArray *transferHist = 
 [[NSMutableArray alloc]
 initWithCapacity:transfers.size()];

    for_each(transfers.begin(), transfers.begin(), ^(const int &x) { [transferHist addObject:@(x)]; }) NSDictionary *commands = @{ @"foo" : ^{ [self foo]; }, @"bar" : ^{ [self bar]; } };
  8. Blocks are closures They can capture values form lexical scope

    and prolong their lifetime By default values are captured as const, pointer to objects are referenced so using of self in block creates retain cycle if block somehow stored in the object __block changes semantics of capturing (different with and without ARC)
  9. Block from stack to heap .text invoke() Locals Stack *isa

    *invoke Scope locals NSStackBlock NSMallocBlock __block Locals *isa *invoke Scope locals
  10. Reference to self self is captured by strong reference in

    block Access to ivar is done throw implicit self
 ^ { NSLog(@"%@", _ivar); };
 ^ { NSLog(@"%@", self->_ivar); }; Retain cycle when block is saved as class member
  11. __weak typeof(self) weakSelf = self; [obj doWithBlock:^ { [weakSelf doThing];

    [weakSelf doOtherThing]; }]; __weak typeof(self) weakSelf = self; [obj doWithBlock:^ { __strong typeof(self) strongSelf = weakSelf; [strongSelf doThing]; [strongSelf doOtherThing]; }]; self still can be used and create retain cycle (for example when using RACObserve)
  12. Goals Safe using of self in the block: weak until

    the block is called, strong during call self should called self Checking for ivar access in the block 
 [RACObserve(self, pttState) subscribeNext:@weakselfnotnil(^(NSNumber *state)) { self.isRecordingPTT = !!state.intValue; } @weakselfend];
  13. Block can be split arguments part:
 ^(NSHTTPURLResponse *response, NSData *data,

    NSError *error) body:{ if (error) NSLog(@"Error: %@", error); else { self->statusCode = [response statusCode]; NSLog(@"HTTP status %d", statusCode); [self saveData:data forResponse:response]; } }
  14. #define weakself(ARGS, BODY) weakself(^(NSHTTPURLResponse *response, NSData *data, NSError *error), {

    … }) ARGS { \ __strong typeof(weakSelf) self = weakSelf; return ^ BODY (); }
  15. ^(NSHTTPURLResponse *response, NSData *data, NSError *error) { __strong typeof(weakSelf) self

    = weakSelf; return ^{ if (error) { NSLog(@"Error: %@", error); } else { self->statusCode = [response statusCode]; NSLog(@"HTTP status %d", statusCode); [self saveData:data forResponse:response]; } } (); } ARGS { \ __strong typeof(weakSelf) self = weakSelf; return ^ BODY (); }
  16. ^(__weak typeof(self) weakSelf) { return ARGS { \ __strong typeof(weakSelf)

    self = weakSelf; return ^ BODY (); } } (self) ^(__weak typeof(self) weakSelf) { return ^(NSHTTPURLResponse *response, NSData *data, NSError *error) { __strong typeof(weakSelf) self = weakSelf; return ^{ if (error) { NSLog(@"Error: %@", error); } else { self->statusCode = [response statusCode]; NSLog(@"HTTP status %d", statusCode); [self saveData:data forResponse:response]; } } (); } (self)
  17. self.searchBar = ({ UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:({ CGRect

    frame = self.tableView.frame; frame.size.height = 50.0f; frame; })]; searchBar.delegate = self; searchBar; }); Example from NSHipster
  18. #define weakself(ARGS, BODY) ({ __weak typeof(self) weakSelf = self; ARGS

    { \ __strong typeof(weakSelf) self = weakSelf; return ^ BODY (); }; }) #define weakself(ARGS, BODY) ^(__weak typeof(self) weakSelf) { return ARGS { \ __strong typeof(weakSelf) self = weakSelf; return ^ BODY (); } } (self)
  19. Compare reference count for self #define weakself(ARGS, BODY) ({ __weak

    typeof(self) weakSelf = self; ARGS { \ __strong typeof(weakSelf) self = weakSelf; return ^ BODY (); }; }) Here And here
  20. How to do something after return? @onExit form libextobjc CFMutableDictionaryRef

    processedObjects = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &kCFTypeDictionaryValueCallBacks); if (processedObjects == NULL) return nil; @onExit { CFRelease(processedObjects); };
  21. ({ __weak typeof(self) weakSelf = self; NSUInteger refBefore = CFGetRetainCount((__bridge

    CFTypeRef)self); @onExit { NSUInteger refAfter = CFGetRetainCount((__bridge CFTypeRef)weakSelf); assert(refBefore == refAfter); } ARGS { __strong typeof(weakSelf) self = weakSelf; return ^ BODY (); }; })
  22. struct RefCountCheckerData { CFTypeRef weakSelf; NSUInteger refCountBefore; }; ! static

    inline void vbr_CheckRefCountForWeakSelf(struct RefCountCheckerData *data) { const NSUInteger refCountAfter = CFGetRetainCount(data->weakSelf); const NSUInteger countOfSelfRefInBlock = refCountAfter - data->refCountBefore; assert(countOfSelfRefInBlock == 0); } ! #define weakself(ARGS) \ "weakself should be called as @weakself" @"" ? \ ({ __weak typeof(self) _private_weakSelf = self; \ __attribute__((cleanup(vbr_CheckRefCountForWeakSelf), unused)) \ struct RefCountCheckerData _private_refCountCheckerData = { \ .weakSelf = (__bridge CFTypeRef)self, \ .refCountBefore = CFGetRetainCount((__bridge CFTypeRef)self), \ };\ ARGS { \ __strong typeof(_private_weakSelf) self __attribute__((unused)) = \ _private_weakSelf; \ return ^ (void) { ! #define weakselfend \ try {} @finally {} } (); }; \ }) : nil
  23. Bonus
 Cool macro with @ #define weakify(...) \ try {}

    @finally {} \ metamacro_foreach_cxt(mtl_weakify_,, __weak, __VA_ARGS__) #define weakify(...) \ autoreleasepool {} \ metamacro_foreach_cxt(mtl_weakify_,, __weak, __VA_ARGS__)
  24. #define coolmacro(ARGS) \ 1 ? ({ ... }) : nil

    ! @coolmacro(111) -> @1 ? ({ ... }) : nil coolmacro(111) -> 1 ? ({ ... }) : nil #define coolmacro(ARGS) \ [@1] ? ({ ... }) : nil ! @coolmacro(111) -> @[@1] ? ({ ... }) : nil coolmacro(111) -> [@1] ? ({ ... }) : nil
  25. const char *s = "abc" "cde"; ! NSString *ns1 =

    @"abc" @"cde"; ! NSString *ns2 = @"abc" "cde"; ! NSString *ns1 = "abc" @"cde";
  26. #define coolmacro(ARGS) \ "@ missed" @"" ? ({ ... })

    : nil ! @coolmacro(111) -> @"@ missed" @"" ? ({ ... }) : nil coolmacro(111) -> "@ missed" @"" ? ({ ... }) : nil