iOS Block 您了解多少?

首先抛出来一些问题

block{}内部会捕捉哪些类型的变量 ?
block{}内部是怎样捕获变量的?整个copy的流程是怎样实现的 ?
block{}内部捕获到"__block NSObject *obj;"时 obj的引用计数为什么会是1 ?
block{}内部捕获到"NSObject *obj;" 时 obj的引用计数为什么会是3 ?
block{}内部如何调用NSObject (NSDelayedPerforming)的api (performSelector:withObject: afterDelay:) ?

【 block源码请移步 opensource.apple.com "libclosure-xx" 】


block怎么使用?

(1) block的基本用法:

{
    void (^hello)(char *);
    hello = ^(char *str) {        
        NSLog(@"hello:%s", str);
    };  // 不要忘记这个分号
    hello("Avery~");
}

{
    int multiplier = 7;
    int (^myBlock)( int ) = ^( int num ) {
        return num * multiplier;
    };
    int result = myBlock(3);
}

{
    typedef void (^CompletionBlock)(NSDictionary * _Nullable info);

    - (void)testMethod:(CompletionBlock)completion {
        // do somthing ...
        NSDictionary *info = [NSDictionary dictionary];
        if (completion) {
            completion(info);
        }
    }

    TestObject *object = [TestObject new];
    [object testMethod:^(NSDictionary * _Nullable info) {
        // do somthing...
    }];
}

(2) 稍微复杂一点的用法:

(1) TestObject:
- (void)testMethodWithAllow:(BOOL(^)(void))allow
                 completion:(void(^)(void))completion
                   canceled:(void(^)(NSString *reason))canceled
                     failed:(void(^)(NSError *error))failed;

- (void)testMethodWithAllow:(BOOL(^)(void))allow
                 completion:(void(^)(void))completion
                   canceled:(void(^)(NSString *reason))canceled
                     failed:(void(^)(NSError *error))failed {
    if (allow) {
        if (allow() == YES) {
            NSLog(@"allowed");
            // Do your thing...
            
            if (completion) {
                completion();
            }
            
            /**
             if (canceled) {
                canceled(@"canceled msg");
             }
             
             if (failed) {
                NSDictionary *errorInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"error-message", @"info", nil];
                NSError *error = [NSError errorWithDomain:@"Wrong-Domain" code:-1 userInfo:errorInfo];
                failed(error);
             }
             */
        }
        else {
            NSLog(@"not allowed");
        }
    }
    else {
    }
}

(2) ViewController:
TestObject *object = [TestObject new];
[object testMethodWithAllow:^BOOL{
        return [self isAllowed];
    } completion:^{
        NSLog(@"completion ~~~");
    } canceled:^(NSString * _Nonnull reason) {
        NSLog(@"canceled!");
    } failed:^(NSError * _Nonnull error) {
        NSLog(@"failed!");
}];

- (BOOL)isAllowed {
    // 在这里加入你的判断逻辑
    return YES;
}
- (void)searchText:(NSString * _Nonnull)text
   resetResultInfo:(NSDictionary * _Nullable (^_Nullable)(void))searchResultInfo;

 - (void)searchTexts:(NSArray * _Nonnull)texts resetSearchResultInfo:(NSDictionary * _Nullable (^_Nullable)(void))searchResultInfo {
    if (!texts || texts.count == 0) {
        return;
    }
    NSMutableArray *searchRanges = [NSMutableArray array];
    [self.attributedText searchTexts:texts saveWithRangeArray:&searchRanges];
    if (searchRanges.count > 0) {
        NSDictionary *info = searchResultInfo();
        self.attributedText.searchRanges = searchRanges;
        self.attributedText.searchAttributeInfo = info;
 
        if (info && [info isKindOfClass:[NSDictionary class]] && info.count > 0) {
            QAAttributedLayer *layer = (QAAttributedLayer *)self.layer;
            [layer drawHighlightColorInRanges:searchRanges attributeInfo:info];
        }
    }
 }
// 详细的使用可以在这里找到:https://github.com/Avery-AN/TableView

(3) 用block{}实现链式编程:

(1) ViewController:
Person *person = [Person new];
person.studyBlock().exerciseBlock(2).studyBlock();

(2) Person:
- (Person * (^)(int))exerciseBlock;
- (Person * (^)(void))studyBlock;

- (Person * (^)(int))exerciseBlock {
    Person * (^averyBlock)(int) = ^(int count) {
        NSLog(@"++++++ exercise: %d", count);
        return self;
    };
    return averyBlock; 
}
- (Person * (^)(void))studyBlock {
    Person * (^averyBlock)(void) = ^()  {
        NSLog(@"++++++ study");
        return self;
    };
    return averyBlock;
}


block{}可以取消吗?

用下面这两种方式创建的block有什么优势吗?如果没有那么为什么要用这些api呢?
本篇暂不详细讲解、不了解的同学还请自行查找相关知识点。
dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags,
dispatch_qos_class_t qos_class, int relative_priority,
dispatch_block_t block);

取消block{}的api (只能取消尚未执行的任务):
dispatch_block_cancel();
dispatch_block_testcancel();

那么正在执行中的block{}怎么取消?看下面的代码:

__block BOOL blockCanceledFlag = NO;

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    for (NSUInteger i = 0; i < 10; i++) {
        NSLog(@"正在执行第'%d'次",i);
        [NSThread sleepForTimeInterval:1];
        if (blockCanceledFlag == YES) {
            NSLog(@"block被终止");
            return;
        }
    };
});

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
    NSLog(@"准备停止block~~~");
    blockCanceledFlag = YES;
});


编译器对block{}做了哪些事?

int main(int argc,const char *argv[]) {
    @autoreleasepool {
        // insert code here...

        void(^averyBlock)(void) = ^{
            NSLog(@"hello Avery");
        };
        averyBlock();
    }
    return 0;
}
/**
 struct __block_impl {
   void *isa;  // (表明block本身也是一个OC对象) _NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
   int Flags;  // 按位存储一些 block 的附加信息
   int Reserved;
   void *FuncPtr;  // 函数指针 (指向 Block 要执行的函数、即__main_block_func_0)
 };
 */

// block结构体 (包含两个成员变量、一个构造函数)
struct __main_block_impl_0 {
    struct__block_impl impl;    //block实现结构体
    struct __main_block_desc_0* Desc;
    __main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,intflags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// block代码块中的实现
static void__main_block_func_0(struct__main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_v2_jnjsfbtj5qq7x5wc059cl8n00000gn_T_main_b244b8_mi_0);
}

// block描述结构体
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;  // 结构体大小
}__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0)};

int main(intargc,const char *argv[]) {
/* @autoreleasepool */
{
    __AtAutoreleasePool __autoreleasepool;
    // block的实现:
    void(*averyBlock)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));

    // block的调用:
    ((void(*)(__block_impl *))((__block_impl *)averyBlock)->FuncPtr)((__block_impl *)averyBlock);
   }
    return 0;
}


block{}内部会捕捉哪些类型的变量?

int globaleA = 1;
static int staticGlobaleB = 2;

int main(int argc,const char *argv[]) {
    int tmpC =3;
    static int staticD =4;
    NSMutableString *string = [[NSMutableString alloc] initWithString:@"Hello "];
    void(^averyBlock)(void) = ^{
        globaleA++;
        staticGlobaleB++;
        staticD++;
        [string appendString:@"avery~"];
        NSLog(@"globaleA: %d;  staticGlobaleB: %d;  tmpC: %d;  staticD: %d;  string: %@", globaleA, staticGlobaleB, tmpC, staticD, string);
    };
    averyBlock();
    return 0;
}
int globaleA = 1;
static int staticGlobaleB = 2;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *staticD;
    NSMutableString *__strong string;
    int tmpC;
    __main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,int*_staticD, NSMutableString *__strong _string,int_tmpC,intflags=0) : staticD(_staticD), string(_string), tmpC(_tmpC) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct__main_block_impl_0 *__cself) {
    int *staticD = __cself->staticD;  // bound by copy
    NSMutableString *__strong string = __cself->string;  // bound by copy
    int tmpC = __cself->tmpC;  // bound by copy
    globaleA++;
    staticGlobaleB++;
    (*staticD)++;
    ((void(*)(id, SEL, NSString *_Nonnull__strong))(void*)objc_msgSend)((id)string, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_v2_jnjsfbtj5qq7x5wc059cl8n00000gn_T_main_9a6e9f_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_v2_jnjsfbtj5qq7x5wc059cl8n00000gn_T_main_9a6e9f_mi_2, globaleA, staticGlobaleB, tmpC, (*staticD), string);
}

static void __main_block_copy_0(struct__main_block_impl_0*dst,struct__main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->string, (void*)src->string,3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct__main_block_impl_0*src) {
    _Block_object_dispose((void*)src->string,3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void(*copy)(struct__main_block_impl_0*,struct__main_block_impl_0*);
  void(*dispose)(struct__main_block_impl_0*);
}__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc,const char *argv[]) {
    int tmpC =3;
    static int staticD =4;
    NSMutableString *string = ((NSMutableString *(*)(id, SEL, NSString *_Nonnull__strong))(void*)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void*)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_v2_jnjsfbtj5qq7x5wc059cl8n00000gn_T_main_9a6e9f_mi_0);
    void(*averyBlock)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, &staticD, string, tmpC,570425344));
    ((void(*)(__block_impl *))((__block_impl *)averyBlock)->FuncPtr)((__block_impl *)averyBlock);
    return 0;
}



通过__main_block_impl_0结构体中的构造函数可知:自动变量tmpC & string和静态变量staticD被截获为block的成员变量:

图片 1

截获时机:在main函数中实现block时,会将栈参数传入__main_block_impl_0的构造函数中进行初始化,所以block会在实现的地方截获变量,而截获的变量的值也是实现时刻的变量值:


图片 2

那么block{}为什么没有捕获全局变量呢?
因为全局变量存储在内存的.data全局区、并不是存储在栈上的、其作用域是全局的。
让我们用一张图来标示一下这几个结构体之间的关系:


关系图


block{}内部是怎样捕获变量的?整个copy的流程是怎样实现的?

我们用下面的代码来讲解,具体流程请看代码处的注释:

- (void)testBlockAction {
    /**
     "__block NSObject *testObj = [NSObject new];": {
        (1) "[NSObject new]":
            在堆上开辟一块内存来存放NSObjec通过new生成的对象;
        (2)"__block NSObject *testObj":
            指针testObj存放在栈空间上;
            编译器会对testObj指针的数据类型做封装:"__block NSObject *testObj;" -> "__Block_byref_object_0 *testObj;";
            由于testObj是用__block修饰的,编译器会将指针testObj的数据类型NSObject封装成结构体__Block_byref_testObj_0、
            并在这个结构体内部创建强引用类型的指针NSObject *__strong testObj;
        (3) 对保存在栈空间上的testObj的结构体__Block_byref_testObj_0 *testObj进行初始化赋值:
            ((void*)0,(__Block_byref_testObj_0 *)&testObj, 33554432, sizeof(__Block_byref_testObj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, testObj);
            结构体__Block_byref_testObj_0中的testObj指针指向了保存数据的那块内存地址;
     }
     */

    __block TestObject *testObj = [TestObject new];
    NSLog(@"testObj (block out): %p; %@",&testObj, testObj);
    NSLog(@"retainCount (block out): %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (testObj)));  // 1
    /**
     "averyBlock = ^(void) {}":
        ARC下对block的赋值操作会使block被拷贝到堆空间;
        (1) 当block{}内部捕捉到用__block修饰的变量时、编译器会合成用于Block_copy和Block_release的助手函数,称为copy和dispose助手:
        (__AveryBlockManager__testBlockAction_block_copy_0 & __AveryBlockManager__testBlockAction_block_dispose_0);
        同样当block{}内部捕捉到用__block修饰的变量时、编译器会根据捕捉到的变量来决定Block_byref结构体中的flags的值,以及在
        __Block_byref_testObj_0结构体内部是否包含"void (*__Block_byref_id_object_copy)(void*, void*);" & "void (*__Block_byref_id_object_dispose)(void*);"两个函数;
        (2) 在ARC下这种赋值操作会使block被拷贝到堆空间;
        _Block_copy函数是负责block的拷贝的、会申请堆空间的block并用栈block进行初始化;
        block拷贝的倒数第二步会调用_Block_call_copy_helper函数、此函数会触发"__AveryBlockManager__testBlockAction_block_copy_0"函数 随后会再触发函数"_Block_object_assign((void*)&dst->testObj, (void*)src->testObj, 8);" 随后会再触发函数"_Block_byref_copy(src->testObj)",如果已经被拷贝到堆空间则会直接返回、不再对__Block_byref_testObj_0进行拷贝;
        在该函数中会malloc内存空间并完成对src->testObj的栈空间到堆空间的拷贝,_Block_byref_copy这个函数中还有一个重要的操作、那就是通过下面这个操作
        "copy->forwarding = copy; src->forwarding = copy;"完成栈上的forwarding指向堆、堆上的forwarding指向自己。
        _Block_byref_copy方法的最后一步操作是如果(src->flags(src->flags的值是在编译器确定的) & BLOCK_BYREF_HAS_COPY_DISPOSE)值为YES、 即__Block_byref_testObj_0结构体内部包含有__Block_byref_id_object_copy & __Block_byref_id_object_dispose两个函数、 那么则调用__Block_byref_id_object_copy(copy, src);对__Block_byref_testObj_0内部的"TestObject *__strong testObj;"进行相应的内存管理,
        调用函数__Block_byref_id_object_copy_131即_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); 会根据结构体__Block_byref_testObj_0内部的"TestObject *__strong testObj;"的修饰符来决定该结构体对其进行强引用还是弱引用。
        (3) 整个block copy的最后一步操作是"copyBlock->isa = _NSConcreteMallocBlock;"即把新copy的block标记为堆block,并返回这个堆block。
     "averyBlock();": {
        调用block、执行block内部的相关操作;
        "[testObj testMethod];":
            block{}内部使用testObj指针、会使block捕捉到testObj指针;
            然后执行testMethod方法中的相关操作。
     }
     */

    void(^averyBlock) (void);
    averyBlock = ^(void) {
        [testObj testMethod];
        NSLog(@"testObj->isa: %@",object_getClass(testObj));
        NSLog(@"testObj (in block): %p; %@",&testObj, testObj);
        NSLog(@"retainCount (in block): %ld", (long)CFGetRetainCount((__bridgeCFTypeRef) (testObj)));  // 1
    };
    NSLog(@"averyBlock类型: %@",averyBlock);  // __NSMallocBlock__
    averyBlock();
    NSLog(@"testObj (after call block): %p; %@", &testObj, testObj);
    NSLog(@"retainCount (after call  block): %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (testObj)));  // 1
}


不用__block 或者 __weak修饰对象时、引用计数为什么会是3?

我们用下面的代码来讲解,具体流程请看代码处的注释:

/**
 将block{}拷贝到堆空间的步骤:
 _Block_copy -> _Block_call_copy_helper -> xxx_block_copy_0 -> _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3);
 _Block_object_assign函数中会触发两个操作"_Block_retain_object(object);" & "*dest = object;"

 综上:
 当block{}内部捕获到临时变量NSObject *obj时会生成一个指针指向obj;
 当给averyBlock赋值时会将block{}copy到堆区,在堆中又持有了一个指向obj的指针;
 所以在block{}内部obj的retainCount的值为3;
 block{}执行完毕后、block{}所在的堆空间并不会被立即释放,所以after call block后obj的retainCount的值仍为3;
 */

int main(int argc,const char *argv[]) {
    /**
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
     */
    NSObject *obj = [NSObject new];
    NSLog(@"(block out) obj: %p; obj: %@", &obj, obj);
    NSLog(@"(block out) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1

    /**
     void (*averyBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
     */
    void(^averyBlock)(void) = ^{
        NSLog(@"(in block) obj: %p; obj: %@", &obj, obj);
        NSLog(@"(in block) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 3
    };
    averyBlock();
    NSLog(@"(after call block) obj: %p; obj: %@", &obj, obj);
    NSLog(@"(after call block) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 3
    return 0;
}


用__block修饰对象时、引用计数为什么会是1?

我们用下面的代码来讲解,具体流程请看代码处的注释:

/**
 将block{}拷贝到堆空间的步骤 ("void *"表示通用指针类型):
 _Block_copy -> _Block_call_copy_helper -> xxx_block_copy_0 -> _Block_object_assign((void*)&dst->obj, (void*)src->obj, 8);
 -> _Block_byref_copy(src->obj) -> __Block_byref_id_object_copy(copy, src); -> __Block_byref_id_object_copy_131
 -> _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); -> *dest = obj;

 综上:
 obj在初始化之后其引用计数为1;
 block{}内部捕捉到obj时:获取到的是__Block_byref_obj_0结构体的地址;(__main_block_impl_0->obj保存的是指向obj的地址)、所以不会使obj的引用计数加1;
 当给averyBlock赋值时会将block{}copy到堆区、会将__Block_byref_obj_0结构体一同拷贝到堆区、并将栈区中保存的obj的地址赋值给堆区的指针*dest;
 整个过程中都没有对obj进行强引用、所以obj的引用计数仍然为1。
 */

int main(int argc,const char *argv[]) {
    /**
     __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
     */
    __block NSObject *obj = [NSObject new];
    NSLog(@"(block out) obj: %p; obj: %@", &obj, obj);
    NSLog(@"(block out) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1

    /**
     void (*averyBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
     */
    void(^averyBlock)(void) = ^{
        NSLog(@"(in block) obj: %p; obj: %@", &obj, obj);
        NSLog(@"(in block) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1
    };
    averyBlock();
    NSLog(@"(after call block) obj: %p; obj: %@", &obj, obj);
    NSLog(@"(after call block) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1
    return 0;
}
AveryBlock[65473:21936013] (block out) obj: 0x7ffeefbff4f8; obj: <NSObject: 0x1005269b0>
AveryBlock[65473:21936013] (block out) retainCount: 1
AveryBlock[65473:21936013] (in block) obj: 0x1005074f8; obj: <NSObject: 0x1005269b0>
AveryBlock[65473:21936013] (in block) retainCount: 1
AveryBlock[65473:21936013] (after call block) obj: 0x1005074f8; obj: <NSObject: 0x1005269b0>
AveryBlock[65473:21936013] (after call block) retainCount: 1
Program ended with exit code: 0


用__weak修饰对象时的情况

int main(int argc,const char *argv[]) {
    /**
     NSObject *tmp = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
     __attribute__((objc_ownership(weak))) NSObject * obj = tmp;
     */
    NSObject *tmp = [NSObject new];
    __weak NSObject *obj = tmp;
    NSLog(@"(block out) obj: %p; obj: %@", &obj, obj);  // obj在栈空间
    NSLog(@"(block out) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1 (2)

    /**
     void (*averyBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
     */
    void(^averyBlock)(void) = ^{
        NSLog(@"(in block) obj: %p; obj: %@", &obj, obj);  // 堆空间新申请了__weak指针指向保存obj的那块内存
        NSLog(@"(in block) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1 (2)
    };
    averyBlock();
    NSLog(@"(after call block) obj: %p; obj: %@", &obj, obj);  // obj仍然在栈空间
    NSLog(@"(after call block) retainCount: %ld", (long)CFGetRetainCount((__bridge CFTypeRef) (obj)));  // 1 (2)
    return0;
}


block{}内部如何使用"performSelector:withObject:afterDelay:" ?

当然你也可以用dispatch_source_t来实现相关的需求。
至于如何使用NSObject (NSDelayedPerforming)的api、请参考另外一篇博文:iOS RunLoop中你应该知道的那些事

block{}与函数指针

int addNumber(int x, int y) {
    return x + y;
}

// 声明函数指针:
int (*fun) (int x, int y);

int main(int argc, const char * argv[]) {
    fun = &addNumber;
    
    int a = 1;
    int b = 2;
    int result = (*fun) (a, b);
    NSLog(@"result: %d", result);
    
    return 0;
}

函数指针本质是一个指针,其指向代码区的一段可执行代码,函数地址是在编译链接时就已经确定下来了;
block本质就是一个Objective-C对象;

block{}与delegate相比有哪些优缺点 ?

(1) delegate运行成本较低,block的运行成本比delegate要高一些;
block出栈需要将使用的数据从栈内存拷贝到堆内存,有可能会涉及到数据类型的转换或者操作引用计数;delegate只是保存了一个对象指针,回调的时候没有额外的消耗;
(2) delegate相比block有时会关注事件的处理过程、而block大多数时候只是关注结果、是否完成、是否已取消、是否失败等;
比如发起一个网络请求,我们需要知道请求是否已经开始、是否收到了数据、已收到多少数据(请求进度)、数据是否已接受完毕、数据是否接收失败等,这时候还是用delegate比较好;
(3) block的代码相比于delegete显得更加的紧凑、实现方式更简练,不需要写protocol、函数等等;
(4) 使用block的时候需要更加注意防止循环引用的发生;

我们来聊一个老生常谈的话题-循环引用

关于什么是循环引用以及如何去避免发生循环引用本篇不再赘述、下面我们来看一段代码:

// ViewController:
- (void)blockAction {
    self.obj = [[TestObject alloc] init];
    [self.obj testMethodSuccess:^(NSDictionary *info) {
        [self methodAction];
    } failed:^(NSError* error) {
        NSLog(@"   error : %@", error.description);
    }];
}
- (void)methodAction {
}

// TestObject:
@implementation TestObject
- (void)testMethodSuccess:(_Nullable TestObjectSuccessBlock)successBlock
                   failed:(_Nullable TestObjectFailedBlock)failedBlock {
    [self doSomething];
    NSLog(@"successBlock: %@",successBlock);
    
    if (successBlock) {
        NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:@"success", @"info", nil];
        successBlock(info);
    }
}
- (void)doSomething {
}
@end

// Log:
AveryTest[15194:2053884] successBlock: <__NSStackBlock__: 0x7ffedfcea958>

如上所示上面的这段中在self.obj的实例方法中的block{}内部强引用了self、那么这段代码会引起循环引用吗?其实并不会引起循环引用的,因为successBlock是一个NSStackBlock出了作用域之后就被释放了,虽然在block{}内部会使self的retainCount加1、但是出了作用域之后位于栈上的block{}就会被回收、self的retainCount也就会减1,所以在block{}前后self的retainCount始终保持一致、也就不会引起循环引用。
所以说不要一见到类似于"[self.obj method:^{[self otherAction];}];"这样的代码就觉得会引起循环引用,还需要确认下block{}是否被copy (如果在TestObject中对successBlock进行了copy操作、那么这么写就会引起循环引用)。


【结束语】那么到此、你搞明白了Block是怎么回事没有

如下所示的代码:
block{}内部中的where_Obj是__Block_byref_testObj_0类型的还是TestObject类型的?
block{}内部中的where_Obj保存在栈上还是堆上?
block{}内部中的where_Obj的生命周期是谁来管理的?

- (void)testBlockAction {
    __block TestObject *testObj = [TestObject new];
    void (^averyBlock) (void);
    averyBlock = ^(void) {
        TestObject *where_Obj = testObj;   // Who am I?  where am I?
    };
    averyBlock();
}



下面的代码中的number_b的值是多少?

NSInteger number_a = 1;
void (^averyBlock)(void) = ^{
    NSInteger number_b = number_a + 10;
    NSLog(@"number_b: %ld",number_b);
};
number_a = 10;
averyBlock();



下面的代码中的number_c的值是多少?

static NSInteger number_a = 1;
void (^averyBlock)(void) = ^{
    NSInteger number_c = number_a + 10;
    NSLog(@"number_c: %ld",number_c);
};
number_a = 10;
averyBlock();


下面两组代码的输出和你想象的一样吗?

typedef void (^AveryBlock)(void);

NSString *string = @"avery_default";
AveryBlock block = ^(void){
  // 在非主队列中执行主线程上的调用
  dispatch_sync(dispatch_queue_create("com.avery.queue", DISPATCH_QUEUE_CONCURRENT), ^{
        NSLog(@"string: %@",string);
        NSLog(@"currentThread: %@",[NSThread currentThread]);
    });
};
string = @"avery_update";
block();
typedef void (^AveryBlock)(void);

NSMutableString *string = [[NSMutableString alloc] initWithString:@"avery_default"];
AveryBlock block = ^(void) {
    // 在非主队列中执行主线程上的调用
    dispatch_sync(dispatch_queue_create("com.avery.queue", DISPATCH_QUEUE_CONCURRENT), ^{
        [string appendString:@"world"];
        NSLog(@"string: %@",string);
        NSLog(@"currentThread: %@",[NSThread currentThread]);
    });
};
[string appendString:@"hello"];
block();



下面的两组代码中为什么第一组可以正常执行、而第二组就编译不通过呢?

NSMutableString *string = [[NSMutableString alloc] initWithString:@"Avery"];
void (^averyBlock) (void);
averyBlock = ^(void) {
    [string appendString:@"_append"];
};
averyBlock();
NSMutableString *string = [[NSMutableString alloc] initWithString:@"Avery"];
void (^averyBlock) (void);
averyBlock = ^(void) {
    string = @"Avery_update";
};
averyBlock();


【 请勿直接转载 - 节约能源从你我做起 】
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容