Objective-C之Blocks

一、概念

Block是带有自动变量值的匿名函数。相当于其他语言里闭包Closure、Lambda的概念。

二、语法

范式

返回值类型 (^Block变量名) 参数列表 = ^返回值类型 参数列表 表达式
其中,返回值类型可省略;参数个数为0时,参数列表可省略。如下示例:

  int (^add)(int a, int b) = ^int (int a, int b) {return a + b;};
  int (^sum)(int, int) = ^int (int a, int b) {return a + b;};
  int (^cc)(void) = ^int (void) { return 0; };
  int (^dd)() = ^int () { return 0; };
  int (^ee)() = ^{return 0;};
  void (^ff)() = ^ { NSLog(@"ff");};

Block类型书写比较复杂,可以使用typedef进行类型定义

  typedef int (^TestBlock)(int c);
  TestBlock (^hh)(int a, int b) = ^TestBlock (int a, int b) {return ^int (int c) {return 123;};};

Block的执行方式:

  int aaa =hh(1,2)(3);
  int s = add(1, 2);
  ^{NSLog(@"abc");}();

三、__block修饰符

  • __block修饰符,准确地说叫__block存储类说明符(__block storage-class-specifier)。

  • C语言中存储域说明符包括:typedef、extern、static、auto、register。__block类似于static、auto、register,用于指定将变量值设置到哪个存储域中,比如static变量存储在数据区,auto变量存储在栈中,register变量存储在寄存器中。

  • __block修饰符只能修饰自动变量,不能修饰其他类型变量。使用__block修饰的自动变量,在Block内部可以修改该自动变量的值。

四、Block的实质

main.m文件源代码:

int main()
{
    void (^blk)(void) = ^{};
    blk();
}

使用clang(LLVM编译器)将OC代码转换为C++代码,转换命令:clang -rewrite-objc main.m,转换结果main.cpp代码如下:

//Block类,相当于类的结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//Block对象,相当于类的对象的结构体

struct __main_block_impl_0 {
  //struct __block_impl impl;展开等价于
  //void *isa;
  //int Flags;
  //int Reserved;
  //void *FuncPtr;
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //结构体构造函数
  //参数fp为C函数指针
  //参数desc为静态全局结构体__main_block_desc_0的实例指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
      //&_NSConcreteStackBlock相当于Block类的结构体指针,实现Block对象的isa指向Block类
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
  }
};

//OC语言Block块^{};对应的匿名函数
//通过Block使用的匿名函数被转换为简单的C函数,函数命名规则:__Block所在函数的名称_block_func_Block在函数中序号
//参数__cself为指向Block值的变量,相当于self和this的概念

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}

//Block描述信息结构体定义与其静态全局结构体实例__main_block_desc_0_DATA定义

static struct __main_block_desc_0 {
  size_t reserved;//保留字段
  size_t Block_size;//__main_block_impl_0结构体实例的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main()
{
  //对应的OC代码:void (^blk)(void) = ^{};
  //调用结构体__main_block_impl_0的构造函数创建结构体实例,并赋值给结构体指针blk
  //下面为等价代码:
  //struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
  //struct __main_block_impl_0 *blk = tmp;
  void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

  //对应的OC代码:blk();
  //调用Block,代码实质:(blk->FuncPtr)(blk);
  ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}

可见,Block的本质也是对象。

五、捕获变量

Block捕获的变量可以分为两类:

  • 没有使用__block修饰的变量,包括全局变量、静态全局变量、静态局部变量、C语言自动变量、强引用的OC对象、弱引用的OC对象。Block中访问不使用__block修饰的变量时,除全局变量和静态全局变量外,Block结构体中会自动追加相应的成员。
  • 使用__block修饰的自动变量,包括C语言变量和OC对象。Block中访问__block变量时,变量会自动被转为特定结构体的实例。

C语言自动变量

Block自动将Block内部使用的自动变量作为结构体成员追加到Block结构数据中。由于初始化Block结构体实例时,直接将自动变量的赋值给对应结构成员,属于传值方式,故Block内无法修改外部自动变量。

全局变量、静态全局变量

Block内部可以直接访问全局变量和静态全局变量,也可以进行修改,Block结构体中不会自动追加相应成员变量。

静态局部变量

Block内访问静态局部变量时,Block结构体中会自动追加对应指针类型的成员,属于传地址方式,增加的结构体成员为指针类型,指向静态局部变量,故Block内可以修改静态局部变量的值。

OC对象型自动变量

Block内访问OC对象时,Block结构体会自动追加一个对象所有权类型相同的OC对象成员,该成员会持有外部的OC对象,故在Block内可以调用该OC对象的方法,或向其发送消息。

__block修饰的C语言自动变量

对于使用__block修饰的自动变量,编译器会自动生成一个特定结构体,结构体中包含一个保存实际数据的成员字段,以及一个指向自身类型的指针,Block结构体会追加一个指向该特定结构体的实例的指针成员。

__block修饰的OC对象型变量

对于使用__block修饰的自动变量,编译器会自动生成一个特定结构体,结构体中包含一个保存实际数据的成员字段(对象所有权修饰符也保持一致),以及一个指向自身类型的指针、用于copy对象的函数指针,用于释放对象的函数指针,Block结构体会追加一个指向该特定结构体的实例的指针成员。

示例代码main.m文件源代码如下:
#import <Foundation/Foundation.h>
int global_val = 1;//全局变量
static int static_global_val = 2;//静态全局变量
void (^global_Block)(void) = ^{global_val += 1;};//全局Block
int main()
{
static int static_val = 3;//静态局部变量
int val1 = 10;//自动变量
const char fmt = "val = %d\n";//指针自动变量
__strong id obj1 = [[NSObject alloc] init];//强引用OC对象自动变量
__unsafe_unretained id obj2 = obj1;//__unsafe_unretained类型OC对象自动变量
__weak id obj3 = obj1;//弱引用OC对象自动变量
__block __strong id obj4 = obj1;//__block型强引用OC对象自动变量
__block __weak id obj5 = obj1;//__block型弱引用OC对象自动变量
__block int val2 = 10;//__block型C语言自动变量
//局部Block
void (^blk)(void) = ^{
global_val += 1;
static_global_val += 1;
static_val += 1;
val1;
fmt;
obj1;
obj2;
val2;
obj3;
obj4;
obj5;
};
blk();//执行Block
void (^blk2)(void) = ^{/
不截获任何变量的Block*/};
void (^blk_copy)(void) = [blk copy];//复制到堆上的Block
return 0;
}
使用clang(LLVM编译器)将OC代码转换为C++代码,转换命令:clang -fobjc-arc -framework Foundation -rewrite-objc -fobjc-runtime=macosx-10.11 main.m,转换结果main.cpp代码如下:

int global_val = 1;
static int static_global_val = 2;

//全局Block对应的结构体
struct __global_Block_block_impl_0 {
    struct __block_impl impl;
    struct __global_Block_block_desc_0* Desc;
    __global_Block_block_impl_0(void *fp, struct __global_Block_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock;//全局Block位于程序静态数据区
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
//global_Block全局Block对应的匿名函数
static void __global_Block_block_func_0(struct __global_Block_block_impl_0 *__cself) {
global_val += 1;}
//全局Block对应的描述信息结构体
static struct __global_Block_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __global_Block_block_desc_0_DATA = { 0, sizeof(struct __global_Block_block_impl_0)};
//创建全局Block对象,并赋值给该Block类型变量global_Block
static __global_Block_block_impl_0 __global_global_Block_block_impl_0((void *)__global_Block_block_func_0, &__global_Block_block_desc_0_DATA);
void (*global_Block)(void) = ((void (*)())&__global_global_Block_block_impl_0);

//只要声明了__block类型的变量,转换的C++代码中自动为其创建的结构体
//强引用型OC对象__block变量obj4对应的结构体
struct __Block_byref_obj4_0 {
    void *__isa;
    __Block_byref_obj4_0 *__forwarding;//指向结构体自身,或栈上的__block变量被复制到堆上后,栈上的__block变量的该指针指向堆上的__block变量
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);//copy函数的指针
void (*__Block_byref_id_object_dispose)(void*);//释放函数的指针
__strong id obj4;//实际封装的数据,所有权类型保持一致
};
//弱引用型OC对象__block变量obj5对应的结构体
struct __Block_byref_obj5_1 {
    void *__isa;
    __Block_byref_obj5_1 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    __weak id obj5;
};
//C语言__block自动变量val2对应的结构体
struct __Block_byref_val2_2 {
    void *__isa;
    __Block_byref_val2_2 *__forwarding;
    int __flags;
    int __size;
    int val2;
};
//blk对应的Block结构体
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *static_val;//对应静态自动变量static_val
    int val1;//对应自动变量val1
    const char *fmt;//对应自动变量fmt
    __strong id obj1;//对应OC自动变量obj1
    __unsafe_unretained id obj2;//对应OC自动变量obj2
    __weak id obj3;//对应OC自动变量obj3
    __Block_byref_val2_2 *val2; //对应__block自动变量val2
    __Block_byref_obj4_0 *obj4; //对应__block自动变量obj4
    __Block_byref_obj5_1 *obj5; //对应__block自动变量obj5
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int _val1, const char *_fmt, __strong id _obj1, __unsafe_unretained id _obj2, __weak id _obj3, __Block_byref_val2_2 *_val2, __Block_byref_obj4_0 *_obj4, __Block_byref_obj5_1 *_obj5, int flags=0) : static_val(_static_val), val1(_val1), fmt(_fmt), obj1(_obj1), obj2(_obj2), obj3(_obj3), val2(_val2->__forwarding), obj4(_obj4->__forwarding), obj5(_obj5->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;//位于栈上
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
//栈上Block对应的匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_val2_2 *val2 = __cself->val2; // bound by ref
    __Block_byref_obj4_0 *obj4 = __cself->obj4; // bound by ref
    __Block_byref_obj5_1 *obj5 = __cself->obj5; // bound by ref
    int *static_val = __cself->static_val; // bound by copy
    int val1 = __cself->val1; // bound by copy
    const char *fmt = __cself->fmt; // bound by copy
    __strong id obj1 = __cself->obj1; // bound by copy
    __unsafe_unretained id obj2 = __cself->obj2; // bound by copy
    __weak id obj3 = __cself->obj3; // bound by copy

    global_val += 1;
    static_global_val += 1;
    (*static_val) += 1;
    val1;
    fmt;
    obj1;
    obj2;
    (val2->__forwarding->val2);
    obj3;
    (obj4->__forwarding->obj4);
    (obj5->__forwarding->obj5);
}

//copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj1, (void*)src->obj1, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->obj2, (void*)src->obj2, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->val2, (void*)src->val2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj3, (void*)src->obj3, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->obj4, (void*)src->obj4, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj5, (void*)src->obj5, 8/*BLOCK_FIELD_IS_BYREF*/);}

//释放函数
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj1, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->obj2, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->val2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj3, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->obj4, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj5, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};

//blk2对应的Block结构体
struct __main_block_impl_1 {
    struct __block_impl impl;
    struct __main_block_desc_1* Desc;
    __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;//位于栈上
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
}

static struct __main_block_desc_1 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
int main()
{
    static int static_val = 3;
    int val1 = 10;
    const char *fmt = "val = %d\n";
    __attribute__((objc_ownership(strong))) id obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    __attribute__((objc_ownership(none))) id obj2 = obj1;
    __attribute__((objc_ownership(weak))) id obj3 = obj1;
    __attribute__((__blocks__(byref))) __attribute__((objc_ownership(strong))) __Block_byref_obj4_0 obj4 = {(void*)0,(__Block_byref_obj4_0 *)&obj4, 33554432, sizeof(__Block_byref_obj4_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, obj1};
    __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_obj5_1 obj5 = {(void*)0,(__Block_byref_obj5_1 *)&obj5, 33554432, sizeof(__Block_byref_obj5_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, obj1};
    __attribute__((__blocks__(byref))) __Block_byref_val2_2 val2 = {(void*)0,(__Block_byref_val2_2 *)&val2, 0, sizeof(__Block_byref_val2_2), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val, val1, fmt, obj1, obj2, obj3, (__Block_byref_val2_2 *)&val2, (__Block_byref_obj4_0 *)&obj4, (__Block_byref_obj5_1 *)&obj5, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    void (*blk2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));
    void (*blk_copy)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)blk, sel_registerName("copy"));
    return 0;
}

通常C语言结构体中不能包含OC对象型成员,因为编译器不能很好地对C语言里OC对象进行内存管理,但是,Block结构体中可以包含OC对象,因为编译自动生成了copy和dispose函数实现对OC对象成员的内存管。copy函数在Block从栈上复制到堆上时被调用,dispose函数在堆上的Block被释放回收时被调用。

六、存储域

Block的实质:Block的结构体实例自动变量,Block也是OC对象。__block变量的实质:栈上__block变量的结构体实例自动变量。根据Block存储域,Block对应的类有三种:

  • _NSConcreteStackBlock:存储域为栈的Block对象可以看作该类的实例,通常在函数内部定义的Block即存储在栈上。
  • _NSConcreteGlobalBlock:存储域为程序静态数据区的Block对象可以看作该类的实例,全局形式的Block、函数内不使用应截获的自动变量的Block存储在静态数据区。
  • _NSConcreteMallocBlock:存储域为堆的Block对象可以看作该类的实例,作为函数返回值的Block、copy得到的Block存储在堆上。

Block从栈复制到堆上时,栈上的__block变量被复制到堆上并被堆上Block持有,栈上被Block捕获的OC对象自动变量也会被堆上Block持有。所以,copy后,栈上的OC对象自动变量和__block变量可以超过作用域而存在。Block从栈上被复制到堆上本质都是调用copy方法,有四种情况:

  • 调用Block的copy方法时
  • Block作为函数的返回值时
  • 将Block赋值给__strong修饰的id类型的类或Block类型成员变量时
  • 在方法名包含usingBlock的Cocoa框架方法或GCD的API中传递Block时

七、Block循环引用

如果在Block中使用了附有__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆上时,该对象会被堆上Block所持有,如果堆上的Block又由该对象所直接或间接持有,这样就导致循环引用。避免循环引用的三种方式:

  • 使用__weak修饰符

  • 使用__unsafe_unretained修饰符

  • 使用__block修饰符并手动置nil,优点是可以控制对象的持有期,缺点是必须执行Block,并执行到置nil的语句。

    @interface MyObject : NSObject
    @end
    @implementation MyObject
    {
        void (^_blk1)(void);
        void (^_blk2)(void);
        void (^_blk3)(void);
        void (^_blk4)(void);
        void (^_blk5)(void);
        void (^_blk6)(void);
        id _obj;
        id _obj2;
    }
    - (id)init
    {
        self = [super init];
        _obj = [NSObject new];
        _obj2 = [NSObject new];
    
        _blk1 = ^{ NSLog(@"%@", self); };//Block内捕获并强引用对象self,而self又强引用Block,产生循环引用,导致内存泄漏
        _blk2 = ^{ NSLog(@"%@", _obj); };//等效于self->_obj,Block实际截获了self,对于编译器,_obj不过是结构体成员,同样产生循环引用,导致内存泄漏
    
        __weak typeof(self) weakSelf = self;
        _blk2 = ^{ NSLog(@"%@", weakSelf); };//使用__weak修饰符避免循环引用
    
        __weak typeof(_obj) weakObj = _obj;
        _blk4 = ^{ NSLog(@"%@", weakObj); };//使用__weak修饰符避免循环引用
    
        __unsafe_unretained typeof(_obj) weakObj = _obj;
        _blk4 = ^{ NSLog(@"%@", weakObj); };//在不支持__weak的时候,可使用__unsafe_unretained修饰符避免循环引用
    
        __block id tmp = self;
        _blk6 = ^{ NSLog(@"%@", tmp); tmp = nil;};//使用__block变量避免循环引用,要求程序必须能执行到该block中,才能通过将tmp置为nil,断开循环引用
        return self;
    }
    @end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容