说明:

<1>阅读本文,最好阅读之前的block文章加以理解;

<2>本文内容:三种block类型的copy情况(MRC)、是否深拷贝、错误copy;

一、MRC模式下,三种block类型的copy情况

//代码

void test1()
{
int age = ; void(^block1)(void) = ^{
NSLog(@"-----");
}; void(^block2)(void) = ^{
NSLog(@"-----%d", age);
}; id block3 = [block2 copy]; NSLog(@"%@ %@ %@", [block1 class], [block2 class], [block3 class]);
NSLog(@"%@ %@ %@", [[block1 copy] class], [[block2 copy] class], [[block3 copy] class]);
}

//打印

-- ::06.902974+ MJ_TEST[:] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__
-- ::06.903260+ MJ_TEST[:] __NSGlobalBlock__ __NSMallocBlock__ __NSMallocBlock__
Program ended with exit code:

分析:

<1>只有stack类型block实例对象copy后的类型变为malloc,这个前面文章已经讨论过,没有问题;

<2>global类型实例对象存储在数据区,copy操作其实什么也没做;malloc在堆区,copy之后肯定还是在堆区,但不会开辟新的内存,只是引用计数加1——此处分析,可以通过clang和地址、引用计数打印来查看,此处不再赘述;

结论:

补充:上述copy的操作是针对block实例对象,那么类对象是存在哪个区呢?往下看

//代码

int a = ;

void test2()
{
int b = ; void(^block1)(void) = ^{
NSLog(@"-----");
}; void(^block2)(void) = ^{
NSLog(@"-----%d", b);
}; id block3 = [block2 copy]; id block1Cls = object_getClass(block1);
id block2Cls = object_getClass(block2);
id block3Cls = object_getClass(block3); NSLog(@"a--global--%p", &a);
NSLog(@"b--auto place--%p", &b);
NSLog(@"alloc----%p", [[NSObject alloc] init]);
NSLog(@"Person----%p", [Person class]); NSLog(@"------block---instance---");
NSLog(@"block1----%@ %p", [block1 class], block1);
NSLog(@"block2----%@ %p", [block2 class], block2);
NSLog(@"block3----%@ %p", [block3 class], block3); NSLog(@"------block---Class---");
NSLog(@"block1Cls----%@ %p", block1Cls, block1Cls);
NSLog(@"block2Cls----%@ %p", block2Cls, block2Cls);
NSLog(@"block3Cls----%@ %p", block3Cls, block3Cls);
}

//打印

-- ::29.922125+ MJ_TEST[:] a--global--0x100002520
-- ::29.922498+ MJ_TEST[:] b--auto place--0x7ffeefbff59c
-- ::29.922525+ MJ_TEST[:] alloc----0x100526420
-- ::29.922561+ MJ_TEST[:] Person----0x1000024f8
-- ::29.922585+ MJ_TEST[:] ------block---instance---
-- ::29.922639+ MJ_TEST[:] block1----__NSGlobalBlock__ 0x1000020c0
-- ::29.922666+ MJ_TEST[:] block2----__NSStackBlock__ 0x7ffeefbff560
-- ::29.922699+ MJ_TEST[:] block3----__NSMallocBlock__ 0x102812000
-- ::29.922717+ MJ_TEST[:] ------block---Class---
-- ::29.922736+ MJ_TEST[:] block1Cls----__NSGlobalBlock__ 0x7fffb33c3460
-- ::29.922756+ MJ_TEST[:] block2Cls----__NSStackBlock__ 0x7fffb33c3060
-- ::29.922777+ MJ_TEST[:] block3Cls----__NSMallocBlock__ 0x7fffb33c3160
Program ended with exit code:

分析:

<1>Person类对象:打印出的类对象Person的地址跟全局变量a和global类型block实例对象的地址类似度极高(都以"0x100002"开头),我们知道全局变量a和global类型block实例变量都是存放在数据区(全局区),那么可以肯定类对象也是存放在数据区中;

<2>block类对象:通过runtime的API我们拿到了三种类型block类对象,发现类对象的地址并不以"0x100002"开头———其中的原因我就懵逼了(内存地址不是很了解),但是可以推断应该也是在数据区,为什么呢?往下看

//代码

typedef void(^Block)(void);
Block block1; void test3()
{
int b = ; block1 = ^{
NSLog(@"-----%d", b);
}; NSLog(@"%p %p", block1, object_getClass(block1)); }

//设置全局断点

//打印

-- ::06.281146+ MJ_TEST[:] 0x7ffeefbff538 0x7fffb33c3060
-- ::06.281455+ MJ_TEST[:] ------
-- ::06.281477+ MJ_TEST[:] 0x7ffeefbff538 0xf9552b000e0
-- ::06.281496+ MJ_TEST[:] 0x7ffeefbff588 0x7fffb33c3060

分析:

1)作为auto类型的局部变量,age的作用域仅限于test3()函数内,所以在main函数中再去回调block时,age已经被自动释放(所占内存被回收),所以age的值显示乱码;而同时block1其实也被销毁了,为什么?往下看

<1>object_getClass(block1)每次返回的值都不同,而其他只都保持不变(已经反复run了多次);

<2>当我们第二次去回调block1时,如上报出一个很经典的错误——野指针调用,即指针所指向的内存空间已经被回收(即被释放),但是此时并没有对该指针赋值一个新的内存地址或者nil值,该指针变成了一个野指针,指向不明确;

补充:内存泄露:是指指针一直指向某一片内存空间,但是程序已经不需要再用该内存空间了,但其他的程序又无法调用该内存空间(只能开辟新的内存空间),这样很容易导致内存爆增;所以内存泄露跟野指针调用是完全相反的;

内存溢出:是指系统分配给程序的内存空间不够用,这样也很容易导致野指针调用的问题;

<3>对block1进行copy的情形:

//代码

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

//打印

-- ::36.144529+ MJ_TEST[:] 0x100526670 0x7fffb33c3160
-- ::36.144849+ MJ_TEST[:] -----
-- ::36.144864+ MJ_TEST[:] 0x100526670 0x7fffb33c3160
-- ::36.144893+ MJ_TEST[:] 0x7ffeefbff588 0x7fffb33c3060
-- ::36.144916+ MJ_TEST[:] -----
-- ::36.144934+ MJ_TEST[:] -----
-- ::36.144950+ MJ_TEST[:] -----
Program ended with exit code:

分析:copy之后,block1一直没有被释放(堆区需要手动管理),即block1一直指向了合法的内存空间,因此不会出现野指针调用的bug;

综上:block1是一个指针变量,其指向等号右边的代码块本质是一个oc对象,存放在栈区中,当回调该代码块时,其已经被自动释放,但是block1因为没有重新赋值而变成了野指针,所以block1指向的代码块是已经被销毁了的;

2)block1销毁后,新创建的bl打印出的类对象的地址跟block1销毁前打印出的地址都是0x7fffb33c3060,因为类对象在内存中只有一份,据此,block1的类对象并没有随着block1的销毁而销毁,所以block的类对象不可能存在于栈区,同一个block类对象供所有创建的block实例对象的isa指针访问并且类对象是系统自动创建并管理的,因此也不可能存在于堆区,也不会存在于代码区

————结论:block类对象跟其他OC实例对象的类对象一样,都只存在于数据区!!!

二、block拷贝是否深拷贝

//代码

void test4()
{
int age = ;
int *agePtr = &age;
NSLog(@"age---1:\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr); block1 = [^{
NSLog(@"age----2:\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
} copy]; }

//打印

-- ::33.399468+ MJ_TEST[:] age---:
0x7ffeefbff59c 0x7ffeefbff59c 0x7ffeefbff590
-- ::33.399735+ MJ_TEST[:] age----:
0x100400238 0x7ffeefbff59c 0x100400230
Program ended with exit code:

分析:

<1>copy后,age、agePtr自身的地址值都发生了变化,说明两个变量都从栈区拷贝到了堆区;

<2>指针变量的值不再是10而是1(乱码),因为指针变量依然指向age拷贝前的内存区域,而该内存区随时可能被释放;

我们再看看对nsstring字符串的深拷贝(mutableCopy)和浅拷贝(copy)操作

//代码

void test5()
{
NSString *strSource = @"abc"; NSLog(@"source:\n%@ %p %p", strSource, strSource, &strSource); NSString *str1 = [strSource copy]; NSLog(@"str1:\n%@ %p %p", str1, str1, &str1); NSString *str2 = [strSource mutableCopy]; NSLog(@"str2:\n%@ %p %p", str2, str2, &str2); }

//打印

-- ::26.299400+ MJ_TEST[:] source:
abc 0x1000023a0 0x7ffeefbff598
-- ::26.299783+ MJ_TEST[:] str1:
abc 0x1000023a0 0x7ffeefbff590
-- ::26.299897+ MJ_TEST[:] str2:
abc 0x100507170 0x7ffeefbff588
Program ended with exit code:

分析:

<1>很明显,浅拷贝只拷贝了指针变量str1(从代码区(常量区)到堆区),该指针依然指向代码区常量abc的内存区;

<2>深拷贝不仅指针变量被拷贝到堆区,而且常量abc也被拷贝到了堆区;

说明:深拷贝和浅拷贝区别,见参考链接:https://www.jianshu.com/p/63239d4d65e0;

//代码

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;
int age;
}; int age = 10;
typedef void(^MyBlock)(void);
void(^deptCopyBlock)(void); void test2()
{
MyBlock stackBlock;
NSString *name = @"yy"; //栈区block
stackBlock = ^{
NSLog(@"-----%@", name);
};
//堆区block
MyBlock mallocBlock = [stackBlock copy]; NSLog(@"stackBlock--%p---%p\n%@", stackBlock, &stackBlock, [stackBlock class]);
NSLog(@"mallocBlock--%p---%p\n%@", mallocBlock, &mallocBlock, [mallocBlock class]); struct __main_block_impl_0 *stackStruct = (__bridge struct __main_block_impl_0 *)stackBlock;
struct __main_block_impl_0 *mallocStruct = (__bridge struct __main_block_impl_0 *)mallocBlock; //打断点
NSLog(@"----");
}

//打印

综上所述:

1)从上述test5()方法看出:block的拷贝均拷贝了指针和该指针指向的值到堆区,但是新的指针却依然指向拷贝前的内存区域;

2)从上述test2()方法,根据打印isa和FuncPtr指针本身地址,及指向地址:在栈区的指针及其值均被拷贝到堆区(如isa),为深拷贝;而其他指针指不在栈区的(如FuncPtr),仅拷贝了指针本事,指针值没有拷贝,为浅拷贝;

所以,block从栈区拷贝到堆区,既有深拷贝,也有浅拷贝;

说明:此处拷贝:针对的是block内部成员变量,而非block指针;所以,&stackBlock和&mallocBlock都在栈区(自定义auto局部变量);

三、错误copy

//代码

void(^block)(void);

void test6()
{
int age = ;
NSLog(@"age----%p", &age); block = ^{
NSLog(@"age----%p", &age);
NSLog(@"----%d", age);
}; NSLog(@"block--1---%p", block);
NSLog(@"block class--1---%p", [block class]);
id coBlock = [block copy];
NSLog(@"%@", [coBlock class]);
NSLog(@"block--2---%p", coBlock);
NSLog(@"block class--2---%p", [coBlock class]);
}

//打印

-- ::32.767665+ MJ_TEST[:] age----0x7ffeefbff59c
-- ::32.767975+ MJ_TEST[:] block-----0x7ffeefbff578
-- ::32.768027+ MJ_TEST[:] block class-----0x7fff8e0fe060
-- ::32.768075+ MJ_TEST[:] __NSMallocBlock__
-- ::32.768094+ MJ_TEST[:] block-----0x100729380
-- ::32.768111+ MJ_TEST[:] block class-----0x7fff8e0fe160
-- ::32.768127+ MJ_TEST[:] age----0x7ffeefbff598
-- ::32.768141+ MJ_TEST[:] -----
Program ended with exit code:

分析:

<1>block:copy前后,block的地址发生了变化,因为block从栈区被拷贝到堆区了,这一点没问题;那么block的类对象地址也发生了变化,因为copy前block的类型为stack类型,之后是malloc类型(系统会自动创建一个类对象),前者存放在栈区,后者存放在堆区,所以也没问题;

<2>age:并没有被copy 到堆区,block回调时,已经被释放,其值为乱码,这点没问题;但是age的地址值这么发生变化了?我们再往下看

//代码

void test7()
{
int age = ;
int *agePtr = &age;
NSLog(@"1----\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr); block = ^{
NSLog(@"1----\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
}; id coBlock = [block copy];
}

//打印

-- ::30.119695+ MJ_TEST[:] ----
0x7ffeefbff59c 0x7ffeefbff59c 0x7ffeefbff590
-- ::30.119992+ MJ_TEST[:] ----
0x7ffeefbff588 0x7ffeefbff59c 0x7ffeefbff580
Program ended with exit code:

分析:

<1>不论是age还是agePtr,block回调时,本身的地址都会发生变化,因为所占内存都被释放,内存地址不回固定,系统会重新编排(个人YY,具体不清楚);

<2>但是,尽管age的值变成乱码,而指针变量agePtr的值却没变依然是原age的地址值——为什么指针变量的内存值不是乱码呢?也许是因为代码区(常量区)跟栈区、堆区的存储规则的区别,指针变量本身已经被释放,其值变与不变好像没有多大的意义——但是,从代码规范角度,被释放后,应当将指针变量置为nil,防止野指针调用!

结论:所谓错误的copy只是copy了block指针变量(等号左边),并非block代码块本身(等号右边)——因此,被引用的外部auto类型的局部变量不会被copy到堆区;

GitHub

block本质探寻四之copy的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. block本质探寻三之block类型

    一.oc代码 提示:看本文章之前,最好按顺序来看: //代码 void test1() { ; void(^block1)(void) = ^{ NSLog(@"block1----&quo ...

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

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

  9. kvo本质探寻

    一.概述 1.本文章内容,须参照本人的另一篇博客文章“class和object_getClass方法区别”加以理解: 2.基本使用: //给实例对象instance添加观察者,监听该实例对象的某个属性 ...

随机推荐

  1. Excel2010取消隐藏的工作簿

    背景 Excel 2010文件,其中包含针对业务需要涉及的计算器等,其中一个Worksheet用于存放计算器用到的常量,针对业务人员(即此Excel文件的用户)是隐藏的,并有密码保护. 现象 因业务变 ...

  2. 网络I/O模型--03非阻塞模式(ServerSocket与Socket的超时处理)--解除accept()、 read()方法阻塞

    对于阻塞方式的一种改进是在应用程序层面上将 “一直等待 ”的状态主动打开: 这种模式下,应用程序的线程不再一直等待操作系统的 I/O状态,而是在等待一段时间后就解除阻塞.如果没有得到想要的结果,则再次 ...

  3. Android设备网络、屏幕尺寸、SD卡、本地IP、存储空间等信息获取工具类

    Android设备网络.屏幕尺寸.SD卡.本地IP.存储空间.服务.进程.应用包名等信息获取的整合工具类. package com.qiyu.ddb.util; import android.anno ...

  4. 【Android】RxJava的使用(四)线程控制 —— Scheduler

    并没有关系的图 前言 经过前几篇的介绍,对RxJava对模式有了一定的理解:由Observable发起事件,经过中间的处理后由Observer消费.(对RxJava还不了解的可以出门左拐)之前的代码中 ...

  5. android之画板功能之橡皮擦 画笔大小和画笔颜色

    第一展示设置画笔颜色的功能,第二展示设置画笔大小的颜色,而第三则展示橡皮擦的功能,这节将图标颜色设置为了蓝色,并且,增加了最左边的按钮(其实,就是在gridview中多增加了一个item). 下面分别 ...

  6. PS制作gif动图以及背景透明与消除残影

    摘要: 用Photoshop制作gif动画的要点:在窗口菜单中找到“时间轴”选中打开时间轴,单击一帧,设置该帧显示持续时间在图层里将该帧要显示的图层显示,并将不该显示的层隐藏,新建一帧,接下来就是重复 ...

  7. svn 同步资源库时忽略某些文件类型和文件夹

    项目开发中,开发人员经常用SVN来管理代码,在和服务器同步时,每次都看到一堆.class,.log,target等文件,这样很不舒服. 解决方法: 打开:window-->preferences ...

  8. Python 中单双引号

    TODO, 在python中, 其实单双引号还是有分别的, 具体是什么?

  9. Jmeter入门--Badboy使用教程(转)

    一.Badboy下载安装 感谢smxwn分享,转载地址:http://blog.csdn.net/wn_68/article/details/45872269 下载地址:http://www.badb ...

  10. Script" References MACLEAN‘s post Speed ​​up the index creation.

    alter session set workarea_size_policy=MANUAL; alter session set db_file_multiblock_read_count=512; ...