iOS block

block的数据结构

先来一个最简单的block,看看这个block到底执行了什么

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        void(^block)(void) = ^{
            NSLog(@"this is block");
        };
        block();
    }
    return 0;
}

cdmain.m的目录下,执行:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

我们可以看到上述的代码,转换为c++代码为:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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

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

//  main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

block声明简化后为:

*block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));

全局搜索__main_block_impl_0,我们在结构体__main_block_impl_0里面找到了同名构造函数。

结论:block是包含isa指针的对象。

block的类型

通过打印classsuperclass可以看出继承关系如下所示,class__NSGlobalBlock__

image.png

当我们传递一个局部变量进block的时候,发现class发生改变,变成__NSMallocBlock__
image.png

ARC环境转成MRC之后,
image.png

执行相同的代码,发现blockclass变成__NSStackBlock__
image.png

结论:block是对象类型,有完整的继承链,当声明block时,会转化成__NSGlobalBlock____NSStackBlock____NSMallocBlock__三种类型。

1、创建一个不包含外部变量的block的时候,则该block在全局区,是__NSGlobalBlock__类型。
2、block包含局部变量的时候,MRC环境下,则该block在栈区,是__NSStackBlock__类型。
3、block包含局部变量的时候,ARC环境下,则该block被拷贝到堆区,是__NSMallocBlock__类型。
ARC环境下成为__NSMallocBlock__条件:有需要自己处理对象生命周期的时候,会将栈区的__NSStackBlock__类型copy到堆区,生成__NSMallocBlock__类型。

经典问题:如下代码打印结果是什么,以及为什么(涉及到__block

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        int a = 10;
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

打印结果为10,现在开始解析原因,将上述代码重新编译成C++代码之后,代码为:

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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_cf236d_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(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

在创建block的时候,__main_block_impl_0结构中,会创建一个int a;,在block执行构建函数的时候,将外面的a作为参数传进去,再对__main_block_impl_0结构中,创建的int a;参数进行赋值操作,此时,block内部的a,其初始值即为外面a的值,为10,后面执行a = 20;的操作实际上是对外面的a的操作,并没有影响到内部的结果,所以打印结果为:a = 10,打印的实际上是捕获到的值。

那么我们如何处理a值不变的问题呢?

方法一:使用局部静态变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int a = 10;
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

底层代码实现原理如下:

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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_8dcf0c_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(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

构建函数的时候,传递了static int a = 10;的地址,block内部用一个int *a接收地址,所以调用的时候,实际上是捕获静态局部变量的地址,所以可以值会同步打印。【局部静态变量,block获取指针地址】

方法二:使用全局变量

static int a = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

底层代码实现原理如下:

static 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_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_7445b2_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(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

因为是全局变量,所以直接调用全局变量。

方法三(重要):使用__block
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a = 10;
        void(^block)(void) = ^{
            NSLog(@"a = %d" , a);
        };
        a = 20;
        block();
    }
    return 0;
}

底层代码实现原理如下:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_bc1a46_mi_0 , (a->__forwarding->a));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        (a.__forwarding->a) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

当我们执行__block int a = 10;的时候,实际底层代码转换为:

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

__main_block_impl_0中会出现一个__Block_byref_a_0 *a;,是包含isa指针的对象,__Block_byref_a_0中包含int a;,我们在__main_block_desc_0结构中,多出了__main_block_copy_0__main_block_dispose_0函数,

  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

copy操作实际上是调用_Block_object_assign函数,进行copy(拷贝)操作,dispose操作实际上是调用_Block_object_dispose函数,进行dispose(销毁)操作。

结论:当__block作用于局部变量时,会将局部变量封装成一个__Block_byref_XXX_0类型的对象,局部变量的值复制给了__Block_byref_XXX_0类型对象中的同名成员变量中,此时暴露在外面的__block int a = 10;实际上已经封装成了一个对象,当我们对a进行赋值操作的时候,实际上执行的是(a.__forwarding->a) = 20;操作,__forwarding存储的地址就是__Block_byref_a_0 *a的地址,目的是为了保证栈区的block copy到堆区的时候依然可以找到相对应的地址

block循环引用

申明一个BlockObject对象

@interface BlockObject : NSObject
@property (nonatomic, assign) int num;
@property (nonatomic, copy) void(^block)(void);
@end

@implementation BlockObject
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

//  main.m
#import <Foundation/Foundation.h>
#import "BlockObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BlockObject *objc = [[BlockObject alloc] init];
        objc.num = 10;
        objc.block = ^{
            NSLog(@"%d", objc.num);
        };
    }
    NSLog(@"-----------");
    return 0;
}

运行结果显示,没有执行dealloc里面的方法,造成了循环引用。

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)objc, sel_registerName("num")));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 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; 
        BlockObject *objc = ((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)((BlockObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("BlockObject"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)objc, sel_registerName("setNum:"), 10);
        ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)objc, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, objc, 570425344)));
    }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_d9_7__3nyzn011cc36bjx_tzkkh0000gn_T_main_a023f6_mi_1);
    return 0;
}

block里面包含对象类型时,__main_block_impl_0包含了BlockObject *objc;对象,__main_block_copy_0objc对象执行了copy操作,objcretain+1。当执行__main_block_dispose_0的时候,则出现:objc对象中引用了block属性,在block对象中,有了一个被强引用的objc属性,当执行dispose的时候,则会发生互相等待被销毁的循环引用问题。

解决方法1:使用__weak
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BlockObject *objc = [[BlockObject alloc] init];
        objc.num = 10;
        __weak typeof(objc) weakObjc = objc;
        objc.block = ^{
            NSLog(@"%d", weakObjc.num);
        };
    }
    NSLog(@"-----------");
    return 0;
}

由于__weak会用到runtime相关的东西,OC转为C++可以使用如下方式:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

可以看到如下数据结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  BlockObject *__weak weakObjc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, BlockObject *__weak _weakObjc, int flags=0) : weakObjc(_weakObjc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__weak实际上是对block里面的对象执行了weak操作,结构为:BlockObject *__weak weakObjc;__main_block_copy_0objc对象执行了copy操作,实际上也只是进行了一次weak引用,所以不会造成循环引用。

解决方法2:使用__block
//   BlockObject.h
@interface BlockObject : NSObject

@property (nonatomic, assign) int num;
@property (nonatomic, copy) void(^block)(void);

- (void)test;

@end

//   BlockObject.m
@implementation BlockObject

- (void)dealloc {
    NSLog(@"%s", __func__);
}

- (void)test {
    __block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p", blockSelf);
        blockSelf = nil;
    };
    self.block();
}
@end

//  main.m
#import <Foundation/Foundation.h>
#import "BlockObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BlockObject *objc = [[BlockObject alloc] init];
        objc.num = 10;
        [objc test];
    }
    NSLog(@"-----------");
    return 0;
}

这个方法一般用的比较少,实际上实现原理是形成三角关系,由于__block创建了一个__Block_byref_XXX_0类型的对象,block内部执行了blockSelf = nil;的操作实际上就是切断三角关系,达到去除循环引用的效果。

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

推荐阅读更多精彩内容