Objective-C Block一步一步分析

Block作为OC语言对于闭包的实现,使用频率是非常高的。所有有必要深入理解。


一个简单的block

首先敲入一个简单的block,如:

//
//  main.m
//  MyConsole3

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");

    
        void (^TestBlock1)() = ^{ NSLog(@"empty block"); };
    }
    return 0;
}

我们可以使用CLang生成c语言中间代码。

如:


这样就会生成一个c语言的中间代码,当前目录下main.cpp,如:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_2c51a5_mi_1); }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_2c51a5_mi_0);


        void (*TestBlock1)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
        ((void (*)(__block_impl *))((__block_impl *)TestBlock1)->FuncPtr)((__block_impl *)TestBlock1);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

我们可以看到里面有个结构,__main_block_impl_0,这个结构有2个数据成员和1个构造函数。其中第一个成员impl有个成员叫做isa,那么可以肯定那家伙是NSObject的子类,也就是说Block本身也是一个oc对象(NSObject子类)。

__main_block_func_0就是block的执行函数。这个例子只是打一个log。

__main_block_desc_0是对block的一个描述。

然后看main里面的代码:

void (*TestBlock1)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

上面这个是创建一个block,基本上可以看出这是创建了一个结构对象,通过调用构造函数,把block内容(函数)和一个描述给传了进去。这样就有了一个block对象。然后看这一行:

((void (*)(__block_impl *))((__block_impl *)TestBlock1)->FuncPtr)((__block_impl *)TestBlock1);

其实就是通过block对象的FuncPtr函数指针来调用函数,注意这个函数的第一个参数是block对象本身。如果看一下block对象里面的函数的原型,

static void __main_block_func_0(struct __main_block_impl_0 *__cself),就会发现它的第一个参数__cself,类似与c++的this,或者oc的self吧。

(注意:像这种没有引用任何局部变量的block,会被运行时转换成Global的,在中间代码里面我们看到的是Stack,但是通过打印block对象信息,会发现是global的,看最后面)


Global Block

上面的block里面isa = &_NSConcreteStackBlock,可想而知,还有其他类型的block,我们把block当作一个全局的,又如何呢?简单的把block对象改成全局变量,如:

#import <Foundation/Foundation.h>


void (^TestBlock_Global)() = ^{ NSLog(@"empty block"); };

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

        TestBlock_Global();
    }
    return 0;
}
这样,再使用clang(clang -rewrite-objc main.m),就可以得到中间代码如:

struct __TestBlock_Global_block_impl_0 {
  struct __block_impl impl;
  struct __TestBlock_Global_block_desc_0* Desc;
  __TestBlock_Global_block_impl_0(void *fp, struct __TestBlock_Global_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __TestBlock_Global_block_func_0(struct __TestBlock_Global_block_impl_0 *__cself) {
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_18b7cc_mi_0); }

static struct __TestBlock_Global_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __TestBlock_Global_block_desc_0_DATA = { 0, sizeof(struct __TestBlock_Global_block_impl_0)};
static __TestBlock_Global_block_impl_0 __global_TestBlock_Global_block_impl_0((void *)__TestBlock_Global_block_func_0, &__TestBlock_Global_block_desc_0_DATA);
void (*TestBlock_Global)() = (void (*)())&__global_TestBlock_Global_block_impl_0;

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        ((void (*)(__block_impl *))((__block_impl *)TestBlock_Global)->FuncPtr)((__block_impl *)TestBlock_Global);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
和前面一个中间代码相比较,唯一的差别就是
impl.isa = &_NSConcreteGlobalBlock;
说明这是一个global的block了。而前面的block存在于栈上。


栈block获取局部变量

block可以接收局部变量,如下面的代码。

#import <Foundation/Foundation.h>


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

        int a = 10;
        void (^TestBlock)() = ^{ NSLog(@"value: %d", a); };
        TestBlock();
        
        a = 20;
        TestBlock();
    }
    return 0;
}
上面的例子代码调用block两次,那么a的值分别是多少呢?

运行一下,会发现都是10,为什么呢?同样,我们再用clang产生中间代码看看:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_5d8937_mi_0, a); }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        int a = 10;
        void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
可以清楚的看到block结构里面多了一个成员int a,然后构造函数也多了一个参数int _a,看main里面的block对象创建,可以清楚的看到把局部变量a通过构造函数传进去了,也就是说等block对象创建完的时候,对象里面的数据成员已经保存了a的值。那么尽管第二次调用的时候,a改成了20,但是block对象里面保存的值还是10, 所以,当调用block主体的时候,还是显示10,看int a= __cself->a。明显block主体调用的时候,a的值是从block对象里面取出来的。所以,两次调用都是10.

那么如果我们通过地址来访问呢?又如何?

#import <Foundation/Foundation.h>


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

        int a = 10;
        int* p = &a;
        
        void (^TestBlock)() = ^{ NSLog(@"value: %d", *p); };
        TestBlock();
        
        a = 20;
        TestBlock();
    }
    return 0;
}
两次调用输出什么呢?运行结果是10和20.那这又是如何解释呢?同样,搬出clang

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *p;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *p = __cself->p; // bound by copy
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_260c4b_mi_0, *p); }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        int a = 10;
        int* p = &a;

        void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p);
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
区别就是block结构里面的数据成员变成了int*,这样的话,等于在创建block对象的时候,a的地址就传给了block对象进行保存,那么这个block对象里面的保存的a的地址就在创建的时候确定了。然后等第二次调用的时候,我们修改了a的值,block主体运行的时候,通过a的地址读取a的值,所以是20了。但是这么做有个问题,a的值是放在stack上的,如果block的运行是在出了a作用域以外的地方(比如block当作回调的时候),会发生什么事情?那个时候a对象已经释放了,然后又通过地址去访问,鬼知道的会发生什么,所以一定要注意。


静态,全局变量

如果我们使用静态变量或者全局变量的?如:

#import <Foundation/Foundation.h>


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

        static int a = 10;
        
        void (^TestBlock)() = ^{ NSLog(@"value: %d", a); };
        TestBlock();
        
        a = 20;
        TestBlock();
    }
    return 0;
}
运行结果,输出10和20.为什么加了个static,就不一样了呢?

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_dc2044_mi_0, (*a)); }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        static int a = 10;

        void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a);
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
明白了,原来是静态局部变量的时候,创建block对象的时候,传的是地址,block结构里面保存的也是int*,block主体运行的时候是通过指针来获取的,所以第二次调用就是20了。

那么如果把a改成全局变量呢?直接看中间代码:

int a = 10;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_d24d32_mi_0, a); }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 




        void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
当block主体__main_block_func_0调用的时候,直接访问全局变量a,而不是像局部变量一样通过int a = __cself->a,所以,第二次调用是20.

到目前为止我们访问的都是int简单类型,那么对于oc类型又如何呢?看下面的例子:


Block访问oc对象

把代码改成:

#import <Foundation/Foundation.h>

@interface Car : NSObject

@property(nonatomic, readwrite, retain) NSString* name;

@end

@implementation Car

-(id) retain
{
    return [super retain];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Car* car = [[Car alloc] init];
        car.name = @"BMW";
        
        
        void (^TestBlock)() = ^{ NSLog(@"static value: %@", car.name); };
        TestBlock();
        
        car.name = @"Benz";

        TestBlock();
    }
    return 0;
}
结果就是输出BMW和Benz,用clang分析一下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Car *car;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Car *_car, int flags=0) : car(_car) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Car *car = __cself->car; // bound by copy
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_0a5585_mi_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)car, sel_registerName("name"))); }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->car, (void*)src->car, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->car, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        Car* car = ((Car *(*)(id, SEL))(void *)objc_msgSend)((id)((Car *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Car"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)car, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_0a5585_mi_0);


        void (*TestBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, car, 570425344);
        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)car, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_gv_tf2w19ts4tng6sn76lzwzqsh0000gn_T_main_0a5585_mi_2);

        ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);
    }
    return 0;
}
所有的oc对象都是在heap 上的,也就是都是通过指针的方式来使用的,在创建block对象的时候,清楚的看到传递的是car,它就是一个指针,而block对象里面保存的也是Car* car,所以,跟int*一样,都是通过地址来访问的,那么地址上的内容变化了,它当然也可以访问到新的数据。所以输出BMW和Benz.

前面介绍传递a地址的时候讲到一个问题,就是万一当block运行的时候,a对象已经被释放了,会怎么样?结果是不可预测的。那么对于oc对象呢,也一样的问题。

比如:

#import <Foundation/Foundation.h>

@interface Car : NSObject

@property(nonatomic, readwrite, retain) NSString* name;

@end

@implementation Car

-(id) retain
{
    return [super retain];
}

- (oneway void) release
{
    [super release];
}


- (void) dealloc
{
    [super dealloc];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Car* car = [[Car alloc] init];
        car.name = @"BMW";
        
        
        void (^TestBlock)() = ^{ NSLog(@"static value: %@", car.name); };
        TestBlock();
        
        [car release];
        
  //      car.name = @"Benz";

        TestBlock();
    }
    return 0;
}
我们第一次调用TestBlock后,把car给释放了,那么第二次调用TestBlock会怎么样呢?很明显这个时候car对象已经被释放了,然后第二次调用又通过地址去访问,肯定有问题。至于什么问题,就不一定了,如果那块内存被系统回收了,用于其他什么地方了,那么可能crash,也可能引发其他的异常。如果还没有回收,那么就运行正常。
所以当我们在block里面引用oc对象的时候,一定要注意周明周期,如果说当block被调用的oc对象已经被释放了,将会发生不可预测的问题。


Global Block & Stack Block

一般情况下,block都是stack block,如果block在函数外面或者block里面没有引用局部变量等,那么就是global的。global block在编译的时候就产生了,而stack block是动态在stack上创建的。如:第一个block只是打印一个log,没有任何局部变量引用,所以是global block,第二个引用了一个局部变量,所以是stack bloc。

还有另外一种block,heap block,待续。













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值