Block学习总结(三)

关于block的存储域

一、 block变量存储域

1. ARC和MRC不同的存储情况

通过对block本质的探究,发现block内部也是有一个isa指针指向它所属的类,所以block其实也是一个对象。

// ARC 环境下
    void (^blk)() = ^{
    
    };
    NSLog(@"%@",[blk class]);

其打印结果为 " _ _NSGlobalBlock _ _ ",可想而知block是属于一个叫NSGlobalBlock类的对象。其实 block的类有三种。分别为 NSGlobalBlock ,NSStackBlock ,NSMallocBlock。从它们的名字能猜测到,这三种类型的block对应着block不同的存储区域。相继为 global(全局数据区域) stack (栈区) malloc(堆区)。

分别在ARC和MRC的情况下看看哪些情况下对应的block存储区域,首先是ARC的情况下

    NSLog(@"未赋值 未引用外部变量 %@", [^void() {
    
    } class]);
    
    void (^blk)() = ^{
    
    };
    NSLog(@"赋值 未引用外部变量 %@",[blk class]);
    
    int a = 1;
    void (^blk1)() = ^{
        a;
    };
    NSLog(@"赋值 引用外部变量 %@",[blk1 class]);
    
    NSLog(@"未赋值 引用外部变量  %@", [^void() {
        a;
    } class]);

最后的输出结果为:

2017-01-08 21:49:15.389 block存储域[10956:1104789] 未赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:49:15.389 block存储域[10956:1104789] 赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:49:15.389 block存储域[10956:1104789] 赋值 引用外部变量 __NSMallocBlock__
2017-01-08 21:49:15.390 block存储域[10956:1104789] 未赋值 引用外部变量  __NSStackBlock__

可以对ARC的情况下做一个总结:

  • NSGlobalBlock === 在block未引用外部变量的时候
  • NSMallocBlock === 在block引用了外部变量的时候,且将block赋值给了block类型的变量
  • NSStackBlock === 在block引用了外部变量的时候, 没有将block赋值给block类型的变量时。

接下来是对在MRC的情况下,block的不同存储区域

    NSLog(@"未赋值 未引用外部变量 %@", [^void() {
    
    } class]);
    
    void (^blk)() = ^{
    
    };
    NSLog(@"赋值 未引用外部变量 %@",[blk class]);
    
    int a = 1;
    void (^blk1)() = ^{
        a;
    };
    NSLog(@"赋值 引用外部变量 %@",[blk1 class]);
    
    NSLog(@"未赋值 引用外部变量  %@", [^void() {
        a;
    } class]);

输出结果为:

2017-01-08 21:56:21.789 block存储域[10994:1108230] 未赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:56:21.790 block存储域[10994:1108230] 赋值 未引用外部变量 __NSGlobalBlock__
2017-01-08 21:56:21.790 block存储域[10994:1108230] 赋值 引用外部变量 __NSStackBlock__
2017-01-08 21:56:21.791 block存储域[10994:1108230] 未赋值 引用外部变量  __NSStackBlock__

我们可以看到无论是否引用外部变量和是否赋值给了block类型的变量,都没有出现mallocBlock,那么MRC的情况下如何才会生成mallocBlock呢。这时我们需要调用对象的copy方法。

    void (^blk)() = ^{
    
    };
    NSLog(@"copy 未引用外部变量 %@",[[blk copy] class]);
   
    int a = 1;
    void (^blk1)() = ^{
        a;
    };
    NSLog(@"copy 引用外部变量 %@",[[blk1 copy] class]);

2. ARC情况下什么时候会进行copy

block引用了外部变量就会存储在栈区,在ARC环境下大多数情况会自动copy操作后复制到堆区。但是有些情况下编译器不会自动进行copy操作:

  • block作为函数参数的时候不会自动进行copy操作,除了以下情况之外:

    (1) Cocoa框架的方法切方法名中又usingBlock。

    (2) GCD的API。

通过以下代码验证:

  - (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *tempArray = [self getBlockArray];
    blk_t blk = tempArray[0];
    blk();
}

- (id)getBlockArray {
    int a = 10;
    return [[NSArray alloc] initWithObjects:^{
        NSLog(@"blk1 %d",a);
    },^{
        NSLog(@"blk2 %d",a);
            }, nil];
}

因为作为参数传递的block的存储区域为栈区,而出了 getBlockArray 函数后,block的作用域结束了,当调用函数取得数组时,访问已经释放了的block,就会造成了crash。要是想正确访问block ,可以做以下修改,对作为参数传递的block进行copy操作。

- (id)getBlockArray {
    int a = 10;
    return [[NSArray alloc] initWithObjects:[^{
        NSLog(@"blk1 %d",a);
    } copy],[^{
        NSLog(@"blk2 %d",a);
            }copy], nil];
}

二、 __block变量存储域

我们知道对于block内部使用的外部变量,不允许在block内部修改外部变量的值。以下代码是编译不过的。

    int a = 10;
    void (^blk)() = ^(){
    a = 20;
    } ;
    blk();

因为在block进行copy的同时,会将block内使用的外部变量也进行一次copy操作,如果这个变量是在栈区的,则会被拷贝到堆区,并被block持有。而如果之前这个变量是在堆区的则不会有影响,只是被block持有。

    int a = 10;
    NSLog(@"定义block前 %p",&a);
    void (^blk)() = ^(){
        NSLog(@"block内部 %p",&a);
    } ;
    blk();
    NSLog(@"定义block后 %p",&a);

打印结果为:

2017-02-08 14:20:06.716 block存储域_test[23117:147534] 定义block前 0x7fff5b635a4c
2017-02-08 14:20:06.717 block存储域_test[23117:147534] block内部 0x60800005a1b0
2017-02-08 14:20:06.717 block存储域_test[23117:147534] 定义block后 0x7fff5b635a4c

16进制转换为10进制,block外部为 140734726625868 ,block内部使用的时候为 106102872449456,2者相差1532868764个字节,转换为1461 mb。因为堆地址远小于栈中的地址,又因为iOS中一个进程的栈区内存只有 1mb,所以在block内部 的变量已经在堆区内了。

__block 变量

对于block内部使用 _ _block变量时,和普通的变量一样,如果这个变量是在栈区的,则会被拷贝到堆区,并被block持有。而如果之前这个变量是在堆区的则不会有影响,只是被block持有。但是为什么能在block内部修改 _ _block修饰的变量。

    __block int a = 10;
    NSLog(@"定义block前 %p",&a);
    void (^blk)() = ^(){
        a++;
        NSLog(@"block内部 %p",&a);
    } ;
    NSLog(@"定义block后 %p",&a);
    blk();

打印的结果为:

2017-02-08 14:44:01.933 block存储域_test[26207:167214] 定义block前 0x7fff569eca48
2017-02-08 14:44:01.934 block存储域_test[26207:167214] 定义block后 0x60800003eb38
2017-02-08 14:44:01.934 block存储域_test[26207:167214] block内部 0x60800003eb38

与普通的外部变量不同的是,定义block后,变量的地址也指向了堆中的那个地址。而不是定义之前的栈中地址。还记得__block内部那个指向 变量自身的那个forwading指针吗,当 _ _block变量赋值到堆区时,栈区的变量的forwading指针也改为了指向堆中的那个变量的地址,所以在block外部使用变量时,它访问的是堆中的那个变量

a++  <===> a._forwarding ->a)++

三、 截获的对象存储域

通常的一个变量,如下,当它超过了作用域之后,就会被释放了。

- (void)test5 {
    NSMutableArray *tempArray = [NSMutableArray array];
}

但是对于block中使用的外部变量,在某些情况下,却能超过变量的作用域存在。

(该代码 是在ARC环境下运行)
blk_t globalblk;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    globalblk();
}

- (void)test5 {
    NSMutableArray *tempArray = [NSMutableArray array];
    globalblk = ^{
        id obj = [[NSObject alloc] init];
        [tempArray addObject:obj];
        NSLog(@"count == %ld a == %d", tempArray.count,a);
    };
}

该代码中,tempArray变量出了 test5这个函数,就超出了作用域,但是该代码却能正确的打印出来。说明: 在ARC环境下,当给block赋值给一个block变量时,将会默认的对block进行一次copy操作。如果在MRC环境下,也就是没有对block进行手动copy的话,这段代码就会crash。

四、 __block和对象

关于__block修饰的对象,被block使用的时候,在ARC和MRC情况下,是有很大的不同的。当在ARC环境下,block内部使用 _ _block修饰的对象的时候,它和正常的外部变量一样,当block从栈区copy到堆区时,会被block所持有,因此能超出变量总用域存在。而在MRC的情况下,当 _ _block修饰的变量被block使用时,不会随着block从栈区copy到堆区而被block所持有。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    globalblk();
}

- (void)test5 {
    __block NSMutableArray *tempArray = [NSMutableArray array];
    globalblk = [^{
        id obj = [[NSObject alloc] init];
        [tempArray addObject:obj];
        NSLog(@"count == %ld", [tempArray count]);
    } copy];
}

以上的代码,在ARC环境下能正常运行,在MRC情况下会因为野指针的访问而crash。

五、 block的循环引用

block持有_ _strong修饰的外部变量时,block会持有该对象,而如果该对象又持有block时,就会造成循环引用的问题。

typedef void (^blk_t)();
@interface Person ()
{
    blk_t blk;
}
@end

@implementation Person
- (instancetype)init {
    if (self = [super init]) {
        blk = [^(){
            NSLog(@"%@",self);
        } copy];
    }
    return self;
}

- (void)dealloc {
    NSLog(@"dealloc");
}

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p = [[Person alloc] init];
}

该段代码的dealloc一定不会被打印,因为在调用 Person的init函数是,成员变量blk使用了 self,使得blk对 self 持有。而blk作为 Person对象的成员变量,Person对象就对 blk持有。造成了这种互相持有的关系后,该对象就无法释放了。

循环引用问题.png

解决方法(1): 通过对block内部使用的变量,改为weak修饰。

- (instancetype)init {
    if (self = [super init]) {
        __weak typeof(self) weakSelf = self;
        blk = [^(){
            NSLog(@"%@",weakSelf);
        } copy];
    }
    return self;
}

解决方法(2): 之前说过,在MRC环境下,用_ _block修饰变量时,block不会对该变量进行持有。

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

推荐阅读更多精彩内容