iOS - block - 存储域

[toc]

参考

block - 存储域

block

https://www.jianshu.com/p/a11337dbc8fd // 存储域

存储域 - 3种类型

(类型和存储域是一一对应的)

block.class __block_impl.isa 特征 存储域 生命周期 copy效果 持有对象
__NSGlobalBlock__ _NSConcreteGlobalBlock 没有访问auto变量(局部变量) 全局 .data 从创建到应用程序结束 什么也不做
__NSStackBlock__ _NSConcreteStackBlock 访问了auto变量 出栈时(出函数作用域)会被销毁 从栈复制到堆
__NSMallocBlock__ _NSConcreteMallocBlock __NSStackBlock__调用了copy 当引用计数为0时会被销毁 引用计数增加

可以通过打印出 block 对象来确定它存储的位置, 这3种类型的 block 对象打印出来的类分别是:

__NSGlobalBlock____NSStackBlock____NSMallocBlock__

通过调用 class 和 superclass 方法, 可查看 block 具体类型:

// __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
void (^block)(void) = ^{
    NSLog(@"Hello");
};

NSLog(@"%@", [block class]); // __NSGlobalBlock__
NSLog(@"%@", [block class].superclass); // __NSGlobalBlock
NSLog(@"%@", [block class].superclass.superclass); // NSBlock
NSLog(@"%@", [block class].superclass.superclass.superclass); // NSObject

可见: block 最终都是继承自 NSBlock 类型, 而 NSBlock 继承于 NSObjcet。

那么 block 其中的 isa 指针其实是来自NSObject中的, 这也更加印证了block 的本质就是OC对象


__NSGlobalBlock__

未访问任何变量的 全局block
// 定义在全局的block
void (^globalBlk)(void) = ^{
    printf("全局的block");
};
// 在viewDidLoad 中打印
NSLog(@"全局block变量-本身 %@", globalBlk);
NSLog(@"全局block变量-拷贝 %@", [globalBlk copy]);

// ARC/MRC下验证上述代码, 均输出如下:
全局block变量-本身 <__NSGlobalBlock__: 0x100018090>
全局block变量-拷贝 <__NSGlobalBlock__: 0x100018090>

结论:

普通全局block, 不论是ARC/MRC, 不论是否拷贝, block对象都是 __NSGlobalBlock__ 类, 存储区域都是.data区

当我们把Block作为全局变量使用时, 对应生成的Block将被设为 _NSConcreteGlobalBlock , 如:

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

该代码转换后的代码中, Block结构体的成员变量isa的初始化为: impl.isa = &_NSConcreteGlobalBlock;


访问全局变量的 全局block

(全局block只能访问全局变量)

// 全局变量
int globalNum = 1;

// 定义在全局的block
void (^globalBlk)(void) = ^{
    globalNum = 2;
};

// 在viewDidLoad 中打印
NSLog(@"全局block变量-本身 %@", globalBlk);
NSLog(@"全局block变量-拷贝 %@", [globalBlk copy]); // 未拷贝出新对象

// ARC/MRC下验证上述代码, 均输出如下:
全局block变量-本身 <__NSGlobalBlock__: 0x100098088>
全局block变量-拷贝 <__NSGlobalBlock__: 0x100098088>

结论:

访问了全局变量的全局 block, 和普通全局 block 性质完全一样

全局 block, 不论是ARC/MRC, 不论是否拷贝, 不论是否访问全局变量, block 对象都是 __NSGlobalBlock__ 类, 存储区域都是 .data区


未访问任何变量的 局部block
- (void)viewDidLoad {
    [super viewDidLoad];
    
    void (^localBlk)(void) = ^{
        printf("局部的");
    };

    NSLog(@"局部block变量-本身 %@", localBlk);
    NSLog(@"局部block变量-拷贝 %@", [localBlk copy]); // 未拷贝出新对象
    
    NSLog(@"局部block对象-本身 %@", ^{
        printf("局部的");
    });
    NSLog(@"局部block对象-拷贝 %@", [^{
        printf("局部的");
    } copy]); // 拷贝出了新对象
}

// ARC/MRC下验证上述代码, 均输出如下:
局部block变量-本身 <__NSGlobalBlock__: 0x1000a4098>
局部block变量-拷贝 <__NSGlobalBlock__: 0x1000a4098>
局部block对象-本身 <__NSGlobalBlock__: 0x1000a40d8>
局部block对象-本身 <__NSGlobalBlock__: 0x1000a4118>

结论:

未访问变量的局部block, 不论是ARC/MRC, 不论是否拷贝, block对象都是 __NSGlobalBlock__ 类, 存储区域都是 .data区


访问(静态)全局变量的 局部block
// 全局变量 (考虑加static的情况)
int globalNum = 1; 
- (void)viewDidLoad {
    [super viewDidLoad];
    
    void (^localBlk)(void) = ^{
        globalNum = 2;
    };
    
    NSLog(@"局部block变量-本身 %@", localBlk);
    NSLog(@"局部block变量-拷贝 %@", [localBlk copy]);
    
    NSLog(@"局部block对象-本身 %@", ^{
        globalNum = 3;
    });
    NSLog(@"局部block对象-拷贝 %@", [^{
        globalNum = 4;
    } copy]); // 拷贝出了新对象
}

// ARC/MRC 普通/静态全局变量, 验证上述代码, 均输出如下:
局部block变量-本身 <__NSGlobalBlock__: 0x100034090>
局部block变量-拷贝 <__NSGlobalBlock__: 0x100034090>
局部block对象-本身 <__NSGlobalBlock__: 0x1000340d0>
局部block对象-拷贝 <__NSGlobalBlock__: 0x100034110>

结论:

局部 block 访问全局变量, 不论是 ARC/MRC , 不论是否拷贝 , block 对象都是 __NSGlobalBlock__ 类, 存储区域都是 .data区


捕获静态局部变量的 局部block
- (void)viewDidLoad {
    [super viewDidLoad];
    
    static int localNum = 1;
    
    void (^localBlk)(void) = ^{
        localNum = 2;
    };
    
    NSLog(@"局部block变量-本身 %@", localBlk);
    NSLog(@"局部block变量-拷贝 %@", [localBlk copy]);
    
    NSLog(@"局部block对象-本身 %@", ^{
        localNum = 3;
    });
    NSLog(@"局部block对象-拷贝 %@", [^{
        localNum = 4;
    } copy]); // 拷贝出了新对象
}

// ARC/MRC下 验证上述代码, 均输出如下:
局部block变量-本身 <__NSGlobalBlock__: 0x1000d4070>
局部block变量-拷贝 <__NSGlobalBlock__: 0x1000d4070>
局部block对象-本身 <__NSGlobalBlock__: 0x1000d40b0>
局部block对象-拷贝 <__NSGlobalBlock__: 0x1000d40f0>

结论:

局部block访问静态局部变量, 不论是ARC/MRC, 不论是否拷贝, block对象都是 __NSGlobalBlock__ 类, 存储区域都是 .data区


__NSStackBlock__ / __NSMallocBlock__

捕获普通局部变量的 局部block (F) ★★
- (void)viewDidLoad {
    [super viewDidLoad];
    
    int localNum = 1;
    void (^localBlk)(void) = ^{
        printf("%d", localNum);
    };

    NSLog(@"局部block变量-本身 %@", localBlk); // ARC在堆上, MRC在栈上
    NSLog(@"局部block变量-拷贝 %@", [localBlk copy]); // ARC/MRC 经过copy, 都在堆上
    
    NSLog(@"局部block对象-本身 %@", ^{ 
         printf("%d", localNum);
    }); // ARC/MRC都在栈上 (该block未被强指针引用)

    NSLog(@"局部block对象-拷贝 %@", [^{  
         printf("%d", localNum);
    } copy]); // ARC/MRC都在堆上
}

// ARC下验证上述代码, 输出如下:
局部block变量-本身 <__NSMallocBlock__: 0x174240ae0>
局部block变量-拷贝 <__NSMallocBlock__: 0x174240ae0>
局部block对象-本身 <__NSStackBlock__: 0x16fd69e90>
局部block对象-拷贝 <__NSMallocBlock__: 0x174240990>

// MRC下验证上述代码, 输出如下:
局部block变量-本身 <__NSStackBlock__: 0x16fd05eb8>
局部block变量-拷贝 <__NSMallocBlock__: 0x170245520>
局部block对象-本身 <__NSStackBlock__: 0x16fd05e90>
局部block对象-拷贝 <__NSMallocBlock__: 0x170245580>

结论:

F0) 捕获局部变量的 Block, 不论是ARC/MRC, 不论是否被强指针引用, 只要主动调用copy方法, 都会被复制到堆上;

F1) MRC下:

截获局部变量的Block对象本身是存储在栈上的, 即使通过持有这个Block对象的变量来访问它, 仍然能顺利访问到存储在栈上的Block对象。

捕获局部变量的 Block 不管是否被强指针引用, 都存储在栈上;

捕获局部变量的 Block 只有主动调用copy方法, 才能将Block对象从栈上复制到堆上;

F2) ARC下:

截获局部变量的Block对象本身也是存储在栈上的, 只是当使用持有这个Block对象的变量来访问它时, 这个Block对象就会被从栈上复制到堆上, 这样变量访问到的就是存储在堆上的Block对象了。

捕获局部变量的 Block 未被强指针引用, 存储在栈上;

捕获局部变量的 Block 被强指针引用 (Block被赋值给__strong指针或者id类型), 会被从栈上复制到堆上;

F3) ARC下, 只有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的block。比较ARC/MRC下的区别, 可以发现: 使用变量访问Block对象时, 在不同编译模式下Block对象的存储区域不同, MRC下能访问到栈上的对象, ARC下访问到的是被复制到堆上的对象, 所以才会有 "<u>ARC下没有 _NSConcreteStackBlock</u>" 这种说法。


捕获__block局部变量的 局部block (G)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int localNum = 1;
    void (^localBlk)(void) = ^{
        localNum = 2;
    };
    
    NSLog(@"局部block变量-本身 %@", localBlk); // ARC在堆上, MRC在栈上
    NSLog(@"局部block变量-拷贝 %@", [localBlk copy]);  // ARC/MRC 经过copy, 都在堆上
    
    NSLog(@"局部block对象-本身 %@", ^{
        localNum = 3;
    });  // ARC/MRC都在栈上

    NSLog(@"局部block对象-拷贝 %@", [^{
        localNum = 4;
    } copy]);  // ARC/MRC都在堆上
}

// ARC下验证上述代码, 输出如下:
局部block变量-本身 <__NSMallocBlock__: 0x17405dfa0>
局部block变量-拷贝 <__NSMallocBlock__: 0x17405dfa0>
局部block对象-本身 <__NSStackBlock__: 0x16fdf5e58>
局部block对象-拷贝 <__NSMallocBlock__: 0x170240540>

// MRC下验证上述代码, 输出如下:
局部block变量-本身 <__NSStackBlock__: 0x16fd91e90>
局部block变量-拷贝 <__NSMallocBlock__: 0x17004b370>
局部block对象-本身 <__NSStackBlock__: 0x16fd91e58>
局部block对象-拷贝 <__NSMallocBlock__: 0x17004b220>

结论:

对比F和G, 可以发现 __block 不会影响block的存储域


Block声明在全局, 定义在局部, 访问局部变量
// 声明在全局
void (^block)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    int age = 10;
    // 定义在局部
    block = ^{
        NSLog(@"age is %d", age);
    };
    NSLog(@"block: %@", block);
    block();
}

// MRC 输出:
block: <__NSStackBlock__: 0x7ffeefbff3e8>
// ARC 输出:
block: <__NSMallocBlock__: 0x103a04ee0>

结论:

Block 定义在局部, 行为与局部block一致。


存储域 - 类型结论 ★★

没有访问局部变量的 block 就是 __NSGlobalBlock__

未访问任何外部变量、或仅访问了 (静态)全局变量 / 静态局部变量 的block, 不论该 block 定义在 全局 / 局部, 不论是 MRC / ARC , 不论是否被拷贝, 该block对象都是 __NSGlobalBlock__ 类, <u>存储在 .data区, 生命周期从创建到应用程序结束</u>。

② 也就是说, 只有访问了普通局部变量的block , 存储域才会改为 堆/栈 ; 该block必然是局部block。

③ 被强指针引用的 block 访问了局部变量:

  • 如果未手动copy, ARC 在堆上(自动copy了), MRC在栈上;
  • 经过copy, ARC/MRC 都在堆上。

④ 未被强指针引用的 block 访问了局部变量:

  • 如果未手动copy, ARC/MRC 都在栈上;
  • 经过copy, ARC/MRC 都在堆上。(案例F)

__block 不会影响 block 的存储域。


存储域 - 对持有对象的影响

只有堆上的block会持有对象 (产生强引用, 使retainCount +1)

__NSGlobalBlock__ 不持有对象
// 在MRC下执行
static NSObject *obj;
- (void)viewDidLoad {
    [super viewDidLoad];
    obj = [[NSObject alloc] init];
    NSLog(@"block定义前: obj引用 = %lu", (unsigned long)obj.retainCount);
    void (^localBlk)(void) = ^{
        NSLog(@"block内部一: obj引用 = %lu", (unsigned long)obj.retainCount);
    };
    NSLog(@"block定义后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
    localBlk();
    NSLog(@"block执行后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);
}

输出:
block定义前: obj引用 = 1
block定义后: obj引用 = 1, 局部block变量-本身 <__NSGlobalBlock__: 0x10005c070>
block内部一: obj引用 = 1
block执行后: obj引用 = 1, 局部block变量-本身 <__NSGlobalBlock__: 0x10005c070>

__NSStackBlock__ 不持有对象
// 在MRC下执行 (ARC下被强指针引用的block会直接copy到堆)
- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"block定义前: obj引用 = %lu", (unsigned long)obj.retainCount);

    void (^localBlk)(void) = ^{
        NSLog(@"block内部一: obj引用 = %lu", (unsigned long)obj.retainCount);
    };
    NSLog(@"block定义后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);

    localBlk();
    NSLog(@"block执行后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);

    // [localBlk release];  
}
// 输出:
block定义前: obj引用 = 1
block定义后: obj引用 = 1, 局部block变量-本身 <__NSStackBlock__: 0x16fdc1eb8>
block内部一: obj引用 = 1
block执行后: obj引用 = 1, 局部block变量-本身 <__NSStackBlock__: 0x16fdc1eb8>

__NSMallocBlock__ 持有对象
// 在MRC下执行
- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"block定义前: obj引用 = %lu", (unsigned long)obj.retainCount);

    void (^localBlk)(void) = [^{
        NSLog(@"block内部一: obj引用 = %lu", (unsigned long)obj.retainCount);   // 此时obj指针copy到堆, 但和包外的obj共同指向堆中的同一块内存地址, 导致其引用计数+1 <参考2.3C>
    } copy];
    
    NSLog(@"block定义后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);

    localBlk();
    NSLog(@"block执行后: obj引用 = %lu, 局部block变量-本身 %@", (unsigned long)obj.retainCount, localBlk);

    [localBlk release]; // 如果不销毁, obj.retainCount 还是 2
    NSLog(@"block销毁后: obj引用 = %lu", (unsigned long)obj.retainCount); 
}

block定义前: obj引用 = 1
block定义后: obj引用 = 2, 局部block变量-本身 <__NSMallocBlock__: 0x17405a970>
block内部一: obj引用 = 2
block执行后: obj引用 = 2, 局部block变量-本身 <__NSMallocBlock__: 0x17405a970>
block销毁后: obj引用 = 1

自动copy到堆 ★

ARC下, 编译器会根据以下情况自动将栈上的block 复制到堆上:

  1. Block 作为函数的返回值;
    由于_NSConcreteStackBlock 所属的变量域一旦结束, 该Block就会被销毁。所以编译器会自动将返回的block进行copy操作; 这样即使 Block 的变量作用域结束, 堆上的 Block 还可以继续存在。

  2. Block 被强引用, Block被赋值给 __strong 指针或者id类型 (有强指针引用block) ;

  3. 调用 Cocoa API 入参中含有 usingBlock 的方法;

  4. block 作为 GCD API 的入参;

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

推荐阅读更多精彩内容