一、oc代码

提示:看本文章之前,最好按顺序来看;

//代码

void test1()
{
int age = ; void(^block1)(void) = ^{
NSLog(@"block1----");
}; void(^block2)(void) = ^{
NSLog(@"block2----%d", age);
}; NSLog(@"block1-----\n%@ %@ %@ %@", [block1 class], [[block1 class] superclass], [[[block1 class] superclass] superclass], [[[[block1 class] superclass] superclass] superclass]); NSLog(@"block2-----\n%@ %@ %@ %@", [block2 class], [[block2 class] superclass], [[[block2 class] superclass] superclass], [[[[block2 class] superclass] superclass] superclass]); NSLog(@"block-----\n%@ %@ %@ %@", [^{
NSLog(@"block----%d", age);
} class], [[^{
NSLog(@"block----%d", age);
} class] superclass], [[[^{
NSLog(@"block----%d", age);
} class] superclass] superclass], [[[[^{
NSLog(@"block----%d", age);
} class] superclass] superclass] superclass]);
}

//打印

-- ::04.290317+ MJ_TEST[:] block1-----
__NSGlobalBlock__ __NSGlobalBlock NSBlock NSObject
-- ::04.290608+ MJ_TEST[:] block2-----
__NSMallocBlock__ __NSMallocBlock NSBlock NSObject
-- ::04.290652+ MJ_TEST[:] block-----
__NSStackBlock__ __NSStackBlock NSBlock NSObject
Program ended with exit code:

分析:

1)三个block的类型分别为:__NSGlobalBlock__、__NSMallocBlock__、__NSStackBlock__,什么原因,往下看;

2)上述三种类型最终都是继承自NSBlock,而NSBlock又是继承自NSObject:此处又进一步说明block其实就是一个OC对象(前面的文章已经证明过);

说明:上述结果是在ARC模式下打印的结果,现在我们看看MRC的打印情况

//设置

//打印

-- ::50.667948+ MJ_TEST[:] block1-----
__NSGlobalBlock__ __NSGlobalBlock NSBlock NSObject
-- ::50.668257+ MJ_TEST[:] block2-----
__NSStackBlock__ __NSStackBlock NSBlock NSObject
-- ::50.668279+ MJ_TEST[:] block-----
__NSStackBlock__ __NSStackBlock NSBlock NSObject
Program ended with exit code:

分析:发现MRC模式下,三种block类型:__NSGlobalBlock__、__NSStackBlock__、__NSStackBlock__,为什么中间的类型由malloc变成了stack?这是因为ARC系统自动帮助我们对block进行了copy操作;

补充一下:clang成C++代码,我们看下

struct __test1_block_impl_0 {
struct __block_impl impl;
struct __test1_block_desc_0* Desc;
__test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int flags=) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; struct __test1_block_impl_1 {
struct __block_impl impl;
struct __test1_block_desc_1* Desc;
int age;
__test1_block_impl_1(void *fp, struct __test1_block_desc_1 *desc, int _age, int flags=) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}; struct __test1_block_impl_2 {
struct __block_impl impl;
struct __test1_block_desc_2* Desc;
int age;
__test1_block_impl_2(void *fp, struct __test1_block_desc_2 *desc, int _age, int flags=) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

分析:发现都是_NSConcreteStackBlock类型,不能正确反应block的实质类型(据说是LLVM编译器版本的问题,而clang又是LLVM的一部分);

二、原因分析

1)程序内存结构

<1>首先,程序的内存结构分为:程序区(代码区)、数据区(全局区)、堆区、栈区;

<2>全局变量、static类型的局部变量存放在数据区,内存直到程序结束才自动释放;auto类型的局部变量、参数(形/实参)存放在栈区,离其最近的大阔结束时自动内存自动被释放;通过malloc\alloc\copy等手动开辟内存的,则存放在堆区,需要程序员手动予以释放;程序区可以看成非变量区,除变量外,程序的一些其他代码即常量存放在该区;

说明:

<1>除了堆区需要程序员手动管理内存外,其他区都由系统自动管理;

<2>等号左右:

{
//等号左边:auto形局部变量,存放在栈区;等号右边:常量,存放在数据区;
int age = ;
//等号左边:auto形局部变量(指针),存放在栈区;等号右边:auto形局部变量地址,存放在栈区;
int *agePtr = &age;
//等号左边:auto形局部变量(指针),存放在栈区;等号右边:alloc开辟的对象,存放在堆区;
NSObject *objc = [[NSObject alloc] init];
}

2)block类型

<1>三种block类型(global、malloc、stack),从字面理解,可以推断依次存放在数据区、堆区、栈区;

<2>我们发现,blcok1没有访问任何变量,后两个block都访问量变量age,而age是一个auto类型的局部变量;似乎block的类型跟访问的变量有关系?往下看;

//代码

int weight = ;

void test2()
{
static int age = ; void(^block1)(void) = ^{
NSLog(@"-----%d", age);
}; void(^block2)(void) = ^{
NSLog(@"-----%d", weight);
}; NSLog(@"%@ %@", [block1 class], [block2 class]);
}

//打印

-- ::56.366509+ MJ_TEST[:] __NSGlobalBlock__ __NSGlobalBlock__
Program ended with exit code:

分析:

<1>如果age是static修饰的局部变量,或者访问全局变量,则block的类型都是__NSGlobalBlock__,那么我们基本上可以肯定,block的类型取决于其访问的变量的属性;

<2>这里带来了一个新的问题:

因为auto类型的局部变量是存放在栈区的,而block要访问该变量,经前述文章分析,block会讲该变量捕获到block结构体内部,即重新开辟内存来存放该局部变量(相当于copy操作,但不是copy),那么此时的block自己是存放在哪个区呢?

前面说了,auto类型的局部变量一定是存放在栈区的,这点毋庸置疑,而block虽然新开辟内存来存放该变量,但改变不了该变量是一个auto类型的局部变量的属性,因此此时的block也只能存放在栈区;

既然存放在栈区,则访问的变量作用域仅限于离其最近的大括号范围内,超出则被自动释放,我们来验证下

//代码

void(^block)(void);

void test3()
{
int age = ; block = ^{
NSLog(@"----%d", age);
};
} int main(int argc, const char * argv[]) {
@autoreleasepool {
// test1();
// test2();
test3();
block();
}
return ;
}

//打印

-- ::19.489782+ MJ_TEST[:] -----
Program ended with exit code:

分析:

<1>age是一个auto类型的局部变量,作用域仅限于test3()函数,该函数一旦调用完毕,age则被自动释放(变成垃圾内存,值不确定);

<2>根据打印结果,age的值不是10而是一堆乱码,说明age已经被自动释放,block再次调用时,访问的是被废弃的内存;

那么如何才能不被自动释放?往下看

//代码

void test4()
{
int age = ; block = [^{
NSLog(@"----%d", age);
} copy];
} int main(int argc, const char * argv[]) {
@autoreleasepool {
// test1();
// test2();
// test3();
test4();
block();
NSLog(@"%@", [block class]);
    }
return ;
}

//打印

2019-01-11 09:37:16.563329+0800 MJ_TEST[776:26631] ----10

2019-01-11 09:37:16.563935+0800 MJ_TEST[776:26631] __NSMallocBlock__

Program ended with exit code: 0

分析:

<1>通过copy操作能达到auto类型的局部变量的值正确,为什么?因为copy是把age的值直接拷贝到了一块新的内存区域,而我们知道copy操作开辟的内存必定是在堆区(同时,block的类型由之前的__NSStackBlock__类型变为__NSMallocBlock__类型);

<2>因此,防止一个auto类型的局部变量自动释放的方法,就是将其copy到堆区进行手动管理,达到对其生命周期可控的目的(所以记得要释放[block release])——这是MRC模式下须手动管理内存,而在ARC模式下系统会自动管理内存(copy和release);

说明:block此处的copy是深拷贝还是浅拷贝,以及深拷贝和浅拷贝的区别——后面文章会写到!

三、结论

1)blcok的类型取决于其访问的变量的类型,跟是否有强指针持有block对象无关(但ARC下,会从栈区拷贝到堆区):

【1】global:没有访问auto类型局部变量——包括:没有访问任何变量、访问了static类型的局部变量、访问了全局变量(包括static和auto类型);

【2】stack:访问了auto类型的局部变量;

【3】malloc:对block进行了copy操作;

2)存储位置:

GitHub

block本质探寻三之block类型的更多相关文章

  1. block本质探寻五之atuto类型局部实例对象

    说明:阅读本文章,请参考之前的block文章加以理解: 一.栈区block分析 //代码 //ARC void test1() { { Person *per = [[Person alloc] in ...

  2. block本质探寻二之变量捕获

    一.代码 说明:本文章须结合文章<block本质探寻一之内存结构>和<class和object_getClass方法区别>加以理解: //main.m #import < ...

  3. block本质探寻四之copy

    说明: <1>阅读本文,最好阅读之前的block文章加以理解: <2>本文内容:三种block类型的copy情况(MRC).是否深拷贝.错误copy: 一.MRC模式下,三种b ...

  4. block本质探寻一之内存结构

    一.代码——命令行模式 //main.m #import <Foundation/Foundation.h> struct __block_impl { void *isa; int Fl ...

  5. block本质探寻七之内存管理

    说明: <1>阅读本问,请参照block前述文章加以理解: <2>环境:ARC: <3>变量类型:基本数据类型或者对象类型的auto局部变量: 一.三种情形 //代 ...

  6. block本质探寻八之循环引用

    说明:阅读本文,请参照之前的block文章加以理解: 一.循环引用的本质 //代码——ARC环境 void test1() { Person *per = [[Person alloc] init]; ...

  7. block本质探寻六之修改变量

    说明: <1>阅读本文章,请参照前面的block文章加以理解: <2>本文的变量指的是auto类型的局部变量(包括实例对象): <3>ARC和MRC两种模式均适用: ...

  8. OC3大回调模式使用总结(三)block回调

    OC 3大回调模式使用总结(三)block回调 block 又称 代码块,闭包等 是一个匿名的函数,它能够当做一个对象来使用,仅仅只是这个对象非常特殊,是一段代码,他能够保存你写的一段预备性质代码,待 ...

  9. iOS开发系列-Block本质篇

    概述 在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题. Block底层结构窥探 ...

随机推荐

  1. C# 2个List<T>比较内部项是否相等(全部相等则相等,反之不相等)

    static void Main(string[] args) { List<string> a = new List<string>() { "a", & ...

  2. excel导入 服务器忘了装组件了。。。

    excel导入 本地没问题 一直在找权限问题  最后发现服务器忘了装组件了... 郁闷 记录下 http://www.microsoft.com/zh-cn/download/confirmation ...

  3. arcgis英文版补丁下载地址

    http://support.esri.com/zh-cn/downloads/patches-servicepacks/list/productid/160

  4. Android可伸缩布局-FlexboxLayout(支持RecyclerView集成)

    Android可伸缩布局-FlexboxLayout(支持RecyclerView集成) 1 . 前言 前几天看到Google官方的博客介绍了Google开源的一个强大的布局-FlexboxLayou ...

  5. 软工读书笔记 week 7 ——《构建之法》

    总时长近两周的结对项目终于算是结束了,马上要重新开启团队项目.于是这几天决定对<构建之法>一书中与团队项目及需求分析有关的章节进行重点阅读,希望能够从中得到启发,并运用到接下来的团队项目中 ...

  6. 登录MySQL提示ERROR 1045 (28000)错误解决方法

    今天,登录服务器准备修改数据库的一些东西.但输入密码,却进不了数据库并提示一个错误,如下图 再确认密码没错的情况下,还是进不了数据库.便在网上找到了解决方法,记录下来,供参考学习. 解决方法: 总体思 ...

  7. windows下php使用zerophp

    官网地址:http://zeromq.org/ 下载windows版本安装(不过php可以不用安装,直接使用扩展包就可以了) 然后下载php的zmq扩展包:https://pecl.php.net/p ...

  8. 3.如何在Maven项目中引入自己的jar包

    1.一般情况下jar包都可以使用pom.xml来配置管理,但也有一些时候,我们项目中使用了一个内部jar文件,但是这个文件我们又没有开放到maven库中. 我们会将文件放到我们项目中.(以下以java ...

  9. 如何将同一 VNET 下的虚拟机从经典部署模型迁移到 Azure Resource Manager

    本文内容 适用场景 解决方案 适用场景 用户拥有多个云服务但是在同一个 VNET 下,希望将这些虚拟机从经典部署模型(以下简称:ASM)迁移到 Azure Resource Manager(以下简称: ...

  10. 【Kettle】4、SQL SERVER到SQL SERVER数据转换抽取实例

    1.系统版本信息 System:Windows旗舰版 Service Pack1 Kettle版本:6.1.0.1-196 JDK版本:1.8.0_72 2.连接数据库 本次实例连接数据库时使用全局变 ...