Block

  • block本质上也是一个OC对象,它内部也有一个isa指针。
  • block是封装了函数调用以及函数调用环境的OC对象。
    Block底层结构.png
  • block底层结构就是__main_block_impl_0结构体,内部包含了impl结构体和Desc结构体以及外部需要访问的变量,block将需要执行的代码放到一个函数里,impl内部的FuncPtr指向这个函数的地址,通过地址调用这个函数,就可以执行block里面的代码了。Desc用来描述block,内部的reserved作保留,Block_size描述block占用内存。

block的变量捕获(capture)

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

  • 由于作用域的问题,全局变量不用捕获到block内部,block执行代码的函数可直接访问
  • 局部变量的定义和需要执行的代码在不同的函数里,如果想跨函数访问局部变量,需要把局部变量捕获到block中存起来,再在执行代码的函数从block中取出捕获的局部变量进行访问。


    block的变量捕获.png
auto int age = 10;
static int height = 10;    
void (^block)(void) = ^{
    NSLog(@"age is %d,height is %d",age,height);
};        
age = 20;
height = 20;        
block();
-------------------------------------------------
output: age is 10,height is 20
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age; // 值传递
  int *height; // 指针传递
}

auto变量和static变量访问方式的不同,是由于auto变量随时可能自动销毁,通过值传递访问;而static变量会一直在内存中,可通过指针(地址)传递访问。

  • 同样的,self也会被block捕获,是因为所有的OC方法转化成C语言函数,底层会默认传递两个参数,self_cmd,也是局部变量。
- (void)test
{
    void(^block)(void) = ^{
        NSLog(@"----%p",self);
    };
    block();
}
-------------------------------------------------
static void _I_YCPerson_test(YCPerson * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__YCPerson__test_block_impl_0((void *)__YCPerson__test_block_func_0, &__YCPerson__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

struct __YCPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __YCPerson__test_block_desc_0* Desc;
  YCPerson *self;
};

block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都继承自NSBlock类型。

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBlock调用了copy

每一种类型的block调用copy后的结果如下所示

Block的类 副本源的配置存储域 复制效果
_NSConcreteGlogalBlock 程序的数据区域 什么也不做
_NSConcreteStackBlock 从栈复制到堆
_NSConcreteMallocBlock 引用计数器增加
  • 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • block作为GCD API的方法参数时

对象类型的auto变量

  • 在使用clang转换OC代码为C++代码时,可能会遇到以下问题:
    cannot create __weak reference in file using manual reference
    解决方案:支持ARC、指定运行时系统版本,比如
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc-fobjc-arc -fobjc-runtime=ios-8.0.0main.m
YCPerson *person = [[YCPerson alloc] init];
person.age = 10;        
__weak YCPerson *weakPerson = person;
    MyBlock block = ^{
    NSLog(@"-----%d",weakPerson.age);
};
-------------------------------------------------
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  YCPerson *__weak weakPerson;
}
YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
//        __weak YCPerson *weakPerson = person;
MyBlock block = ^{
    NSLog(@"-----%d",person.age);
};
-------------------------------------------------
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  YCPerson *__strong person;
}

当block内部访问了对象类型的auto变量时

  • 如果block是在栈上,不论是ARC还是MRC环境,或者对外部的auto变量是强引用还是弱引用,都不会对auto变量产生强引用。
  • 如果block被拷贝到堆上
    1、会调用block内部的copy函数
    2、copy函数内部会调用_Block_object_assign函数
    3、_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak)产生强引用还是弱引用。
  • 如果block从堆上移除
    1、会调用block内部的dispose函数
    2、dispose函数内部会调用_Block_object_dispose函数
    3、_Block_object_dispose函数会自动释放引用的auto变量,类似于release
函数 调用时机
copy函数 栈上的Block复制到堆上
dispose函数 堆上的Block被废弃时

__block修饰符

  • 一般情况下,对被截获对象进行赋值操作需要添加__block修饰符(赋值≠使用)
  • 对变量进行赋值时
    • 对局部变量(基本数据类型对象类型),需要__block修饰符
    • 静态局部变量全局变量静态全局变量,不需要__block修饰符
  • __block可以用来解决block内部无法修改auto变量的问题
  • 编译器会将__block变量包装成一个对象(__Block_byref_age_0结构体),结构体内部__forwarding是指向自身的指针,内部还存储着外部auto变量的值
    __block修饰基本数据类型
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 三种auto变量
        int no = 20; // 基本数据类型的auto变量,不需要内存管理,不会生成copy函数和dispose函数

        __block int age = 10; // __block修饰基本数据类型变量,需要内存管理,会生成copy函数和dispose函数

        NSObject *object = [[NSObject alloc] init]; // 对象类型的auto变量,需要内存管理,不会生成copy函数和dispose函数
        __weak NSObject *weakObject = object;
     
        // 刚开始block内存在栈上,在ARC环境下,一旦block被强引用着,会对栈上的block进行copy操作,会拷贝到堆上
        // block如果是在栈上,对象类型的auto变量object和__block变量age产生的都是弱引用,不是强引用;如果block被copy到堆时,都会通过copy函数来处理它们
        MyBlock block = ^{ 
            age = 20; // 修改age变量
            NSLog(@"%d",no);
            NSLog(@"%d",age);
            NSLog(@"%p", weakObject);
        };
        block();
    }
    return 0;
}
-------------------------------------------------
struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

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

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age;
};

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age; // 结构体内部会存储着age值
};

  // 此处block会捕获三个auto变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no; // 将no值直接存储
  NSObject *__weak weakObjc; // 访问对象类型的auto变量,会在内部存储该类型的指针变量,__weak or __strong取决于外部如何访问
  __Block_byref_age_0 *age; // 访问__Block变量,会将age包装到__Block_byref_age_0结构体中,block内部保留一个引用这个结构体的指针,指针会指向__Block_byref_age_0结构体
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObjc, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObjc(_weakObjc), age(_age->__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_age_0 *age = __cself->age; // 通过block中age指针拿到指向结构体的指针
  int no = __cself->no; // bound by copy
  NSObject *__weak weakObjc = __cself->weakObjc; // bound by copy
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_0,no);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_1,(age->__forwarding->age));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_1_y0148j4xd12nhm7z0r2gj00000gn_T_main_161189_mi_2,weakObjc);
        }

// block从栈拷贝到堆时调用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
// __block,第三个参数传递8(BLOCK_FIELD_IS_BYREF),__block变量不存在强弱引用之分,就是强引用
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
// 对象类型的auto变量object,第三个参数传递3(BLOCK_FIELD_IS_OBJECT),如果外部通过弱(强)引用访问OC对象,那_Block_object_assign对OC对象产生的就是弱(强)引用
_Block_object_assign((void*)&dst->weakObjc, (void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// block从堆中移除调用
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 访问对象类型的auto变量,会生成以下两个函数,对内部访问的对象进行内存管理操作,访问基本数据类型不会生成这两个函数
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};  

__block的forwarding指针

__block的forwarding指针
(age->__forwarding->age) = 20;
  • 上,__block结构体中的__forwarding指针指向自己,一旦复制到上,栈上的__block结构体中的__forwarding指针会指向堆上的__block结构体,堆上__block结构体中的__forwarding还是指向自己。假设age是栈上的变量,age->__forwarding会拿到堆上的__block结构体,age->__forwarding->age会把20赋值到堆上,不论是栈上还是堆上的__block结构体,都能保证20赋值到堆的结构体里。

block循环引用问题

  • 用__weak、__unsafe_unretained解决
// __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil,若再次访问此变量,不会产生错误。
__weak typeof(self) weakSelf = self;
   self.block = ^{
   NSLog(@"%d",weakSelf.age);
};
// __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,若再次访问此变量,容易产生野指针错误。
__unsafe_unretained typeof(self) weakSelf = self;
   self.block = ^{
   NSLog(@"%d",weakSelf.age);
};
__weak、__unsafe_unretained解决循环引用.png
  • 用__block解决
__block YCPerson *person = [[YCPerson alloc] init];
person.age = 10;
person.block = ^{
    NSLog(@"%d",person.age);
    person = nil;
};
person.block(); // 必须要调用block,执行block内代码,将对象置为nil
用__block解决循环引用.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 一、Block的底层结构及本质 (1)block本质: 从代码可以看出,Block的本质就是NSObject. 也...
    王的for阅读 442评论 0 2
  • 参考篇:iOS-Block浅谈 前言:本文简述Block本质,如有错误请留言指正。 第一部分:Block本质 Q:...
    梦蕊dream阅读 60,722评论 41 321
  • 第一部分:Block本质 Q:什么是Block,Block的本质是什么? block本质上也是一个OC对象,它内部...
    sheldon_龙阅读 549评论 0 0
  • 提示:下面会把OC相应的类转化为C++代码,OC代码转C++代码的生成 一、block 知识回顾block 是一个...
    IIronMan阅读 608评论 0 2
  • 今天早晨有些冷,走在满是落叶的小树林里,戴着口罩,穿着过膝的羽绒服,带着长长毛领的大帽子,快步走向学校,情不自禁再...
    张峰读书阅读 170评论 0 2