iOS-block

一. 查看block内部实现

1.编写block代码

void (^DemoBlock)(int, int) = ^(int a, int b){
    NSLog(@"%d",a+b);
};
DemoBlock(1,3);

输出:2019-01-14 13:01:47.104 iOSWorld[1324:103701] 4
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc BlockViewController.m命令查看c++源码,搜索DemoBlock.
void (*DemoBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int, int))((__block_impl *)DemoBlock)->FuncPtr)((__block_impl *)DemoBlock, 1, 3);

简化:((void (*)(int, int))是一个函数指针,也就是把后面的代码强转为一个函数指针.删减后得到(从下往上看)

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

static struct __main_block_desc_0 {
 size_t reserved;//为0
 size_t Block_size;//block的大小
} __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和结构体同名, 是一个构造函数,  返回结构体对象
 __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, int a, int b){
  NSLog(a+b);
}
//定义DemoBlock,&__main_block_impl_0是一个函数,有两个参数,第一个为包装的方法,第二个为描述信息
void (*DemoBlock)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
//执行DemoBlock
DemoBlock->FuncPtr(DemoBlock, 1, 3);

代码解释:
1.调用__main_block_impl_0()函数

  • 参数1为__main_block_func_0,也就是我们block里面要做的事情,也就是匿名函数 --> NSLog(@"%d",a+b);
  • 参数2为__main_block_desc_0_DATA,__main_block_desc_0_DATA是一个struct __main_block_desc_0的结构体,结构体参数reserved=0,结构体参数Block_size=sizeof(struct __main_block_impl_0);
  1. 调用后会来到__main_block_impl_0这个结构体的构造函数,将内部impl结构体设置impl.isa = &_NSConcreteStackBlock; impl.Flags = flags;impl.FuncPtr = fp;将内部__main_block_desc_0结构体的Desc = desc;

所以: block是封装了函数调用以及函数调用环境的对象
底层:block是一个包含了函数指针和变量的结构体

3.分析(注意!!!!!标注的代码)

  • impl.isa = &_NSConcreteStackBlock;
    block有一个isa指针,说明block也是一个OC对象.
  • impl.FuncPtr = fp
    记录一下block里面的函数
  • Desc = desc;
    记录该block的描述

4.拓展

- (void)test
{
   void(^DemoBlock)(void) = ^{
   };
   
   NSLog(@"%@",[DemoBlock class]);
   NSLog(@"%@",[[DemoBlock class] superclass]);
   NSLog(@"%@",[[[DemoBlock class] superclass] superclass]);
   NSLog(@"%@",[[[[DemoBlock class] superclass] superclass] superclass]);
}

输出:2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock__
    2019-01-14 16:16:15.953 iOSWorld[1542:128655] __NSGlobalBlock
    2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSBlock
    2019-01-14 16:16:15.954 iOSWorld[1542:128655] NSObject

block有superclass,说明就是OC对象.

二.block变量捕获

  • 对基本数据类型的局部变量截获其值.
  • 对对象类型的局部变量连同所有权修饰符一起截获,如果以strong修饰,捕获的时候底层就retain一次,如果是weak修饰的,就不retain
    self也是局部变量,所以会捕获self
  • 以指针形式截获静态局部变量
    虽然static修饰的局部变量也是永驻内存,但因为它是局部的,不像全局变量一样谁都可以访问,所以捕获它的指针才能找到它.
  • 不截获全局变量、静态全局变量,编译的时候就写死其地址了。

2.1 block捕获局部变量

demo1

- (void)test
{
   int a = 10;
   void(^DemoBlock)(void) = ^{
       NSLog(@"%d", a);
   };
   a = 20;
   DemoBlock();
}

输出:2019-01-14 15:00:38.339 iOSWorld[1446:119920] 10

解释:定义完a=10后,编译器开始编译DemoBlock,这时候DemoBlock内部已经捕获了a的值.
继续执行a=20,继续执行DemoBlock()会打印出已经捕获的a=10,所以打印为10.

总结: 对于局部变量,一出作用域就会被销毁了,所以block会及时捕获局部变量并copy一份值到block内部.

2.2 block捕获全局变量

demo2

int a = 10;
- (void)test
{
   void(^DemoBlock)(void) = ^{
       NSLog(@"%d", a);
   };
   a = 20;
   DemoBlock();
}

输出:2019-01-14 15:26:38.208 iOSWorld[1478:122759] 20

解释:int a = 10,接下来编译DemoBlock,继续执行a=20,继续执行DemoBlock()的时候会调用NSLog(@"%d", a),因为a是全局变量,这时候只需要拿到a打印就行了.

总结: 对于全局变量,即使出了作用域也不会被销毁,所以block不会捕获全局变量,啥时候用啥时候取就行.下划线_age其实是self->age,所以会先捕获self

三.3种block的内存存放区域

block分类
_NSConcreteStackBlock:在栈上创建的Block对象
_NSConcreteMallocBlock:在堆上创建的Block对象
_NSConcreteGlobalBlock:全局数据区的Block对象(data区)

  • 没有捕获局部变量的block为GlobalBlock
  • 捕获了局部变量但是没有进行block复制则为StackBlock---栈block容易出错,因为有可能被销毁了,StackBlock不会强引用对象
  • 其他的block基本都为MallocBlock(堆block)

block复制
block从栈复制到堆的时候,会调用_Block_object_assign()强引用
block在堆上销毁的时候,会调用_Block_object_dispose()release引用的对象

在ARC有效时,大多数情况下编译器会进行判断,自动生成将Block从栈上复制到堆上的代码,以下几种情况栈上的Block会自动复制到堆上:

  • 调用Block的copy方法 globalBlock copy仍为globalBlock,stackBlock copy后为mallocBlock,mallocBlock copy后引用计数+1
  • 将Block作为函数返回值时
  • 将Block赋值给__strong修饰的变量时
  • 向Cocoa框架含有usingBlock的方法 或者 GCD的API传递Block参数时

所以如果不怕被销毁,比如该block当函数用一次,那么block不一定非得用copy.
__block可以解除循环引用

四.block修改变量

???为什么不可以在block内部修改局部变量

因为:局部变量出了大括号就会销毁,有可能你修改的时候它已经是nil了.
但是:我们可以使用__block修饰局部变量.

__block int a = 10;
void (^DemoBlock)(void) = ^(){
    a = 20;
    NSLog(@"%d",a);
};
DemoBlock()
输出:---------> 20
  1. 使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc main.m命令查看c++源码,搜索DemoBlock.
void (*DemoBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
  1. 发现__main_block_impl_0实现里面多了一个__Block_byref_a_0开头的变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
}
  1. 看看__Block_byref_a_0,发现它里面有个a,还有个自己类型的__forwarding-->__Block_byref_a_0 *__forwarding
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

4.看看__main_block_func_0这个参数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//创建了一个__Block_byref_a_0类型的和我们定义一样的同名变量a
  __Block_byref_a_0 *a = __cself->a; 
//将新a的__forwarding里面的a设置为20
(a->__forwarding->a) = 20;
//打印新a的__forwarding里面的a
NSLog(a->__forwarding->a);
}

5.将自己(__Block_byref_a_0)的地址值传到自己的第二个参数__forwarding里面,也就是说__forwarding指向的还是自己

__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a

__forwarding存在的意义:不论在栈上还是堆上,都可以顺利的访问同一个__block变量.

总结:__block原理编译器会将__block变量包装成一个对象,并把局部变量赋值给该对象,这样就能修改对象的变量
你以为你修改的还是那个局部变量,殊不知已经是__block自己生成的对象啦

拓展:__block __weak Person *p = [Person new]
上面代码:生成一个自己的对象block_p包裹了p,
而且block强引用了block_p,但是因为有weak的存在,block_pp是弱引用的.

{
        TestBlock block1;
        {
            __block BlockReferDemoObject *objc = [[BlockReferDemoObject alloc] init];
            block1 = ^{
                NSLog(@"%@", objc);
            };
        }//默认强引用
        block1();
    }
    
    NSLog(@"-----------------");

    {
        TestBlock block2;
        {
            BlockReferDemoObject *objc = [[BlockReferDemoObject alloc] init];
            __block __weak BlockReferDemoObject *weakObjc = objc;
            block2 = ^{
                NSLog(@"%@", weakObjc);
            };
        }//__weak修饰,没有强引用objc,导致出了大括号就被销毁了
        block2();
    }

五.block的内存管理

  • stackBlock对所有的局部变量都是弱引用
  • mallocBlock :
    1.对__block或者__Strong使用_Block_object_assign()进行强引用.
    2.对__weak使用_Block_object_assign()进行弱引用.
    当block移除时,使用_Block_object_dispose()来释放局部变量.

六.解决循环引用的新方法

将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露。
即原来使用__weak的写法:

__weak typeof(self) weakSelf = self;
self.blk = ^{
    __strong typeof(self) strongSelf = weakSelf;
    NSLog(@"Use Property:%@", strongSelf.name);
    //……
};
self.blk();

改为Block传参写法后:

self.blk = ^(UIViewController *vc) {
    NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);

__weak typeof(self)      weakSelf = self;
__weak UIViewController *weakSelf = self;

使用__block也能解除循环引用

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

推荐阅读更多精彩内容

  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,766评论 5 61
  • 在了解Block之前,我们有必要先了解一下一些基础知识。我们都知道,Objective-C是由C语言扩展而来的。在...
    SmithJackyson阅读 702评论 0 8
  • 前言 ios4.0系统已开始支持block,在编程过程中,block被Obj-C看成是对象,它封装了一段代码,这段...
    GitHubPorter阅读 804评论 2 4
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,812评论 10 69
  • 《小强升职记》是我读的第一本关于时间管理的书籍,它不像其他同类书籍讲述大段大段的方法论,而是通过故事的形式,潜移默...
    石子鑫阅读 385评论 2 0