有趣 IOS 开展 - block 使用具体解释
2015-09-20 09:58
495 查看
Block 它是iOS于4.0新的程序语法之后,于iOS SDK 4.0之后,block应用几乎无处不在。
在其他语言中也有类似的概念,称为闭包(closure),实例object C兄弟swift 中闭包(swift 闭包具体解释)的使用跟 OC的block一样重要。总的来说:
Block是C语言的
Block是一个数据类型
Block 是一个提前准备好的代码。在须要的时候运行
Block能够作为函数參数或者函数的返回值,而其本身又能够带输入參数或返回值。
苹果官方建议尽量多用block。在多线程、异步任务 、集合遍历、集合排序、动画转场用的非常多
在新的iOS API中block被大量用来代替传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:
A. 能够直接在block代码块中写等会要接着运行的代码,直接把block变成函数的參数传入函数中,这是新API最常使用block的地方。
B. 能够存取局部变量,在传统的callback操作时,若想要存取局部变量得将变量封装成结构体才干使用,而block则是能够非常方便地直接存取局部变量。
特点:
1. 类型比函数定义多了一个 ^
2. 设置数值,有一个 ^,内容是 {} 括起的一段代码
回传值 (^名字) (參数列);
假设要改动就要加关键字 __block (以下具体说明):
__block int sum =10;
定义函数指针
调用函数指针
定义Block
调用Blocks
block内部能够訪问外部变量。
默认情况下block内部不能
给局部变量加上关键字
block中能够訪问外部变量。可是
上面的特点是有原因滴:
A.
为了不给开发人员迷惑,干脆不让赋值。
道理有点像:函数參数。要用指针,不然传递的是副本(大家想起那个经典的两个数调换值的问题了吧)。
B.
静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。
所以block能够调用。
__block变量存储在
从优化角度考虑,block存储在栈上,假设block被拷贝(通过Block_copy或者copy),
__block变量还有两个限制,他们不能是可变数组(NSMutableArray),不能是结构体(structure)。
__block 变量的内部实现要复杂很多,__block 变量事实上是一个结构体对象,拷贝的是指向该结构体对象的指针
以下的样例就有点难度了。让我们看下block对指针变量的訪问
上面的样例搞定了,来让我们看下各种类型的变量与block之间的互动:
上面仅仅是简单演示下block引用成员变量,以下我们研究下block引用成员变量时出现的一个经典问题:
在block内部使用成员变量,例如以下:
在block创建中:
上面代码中block是会对内部的成员变量进行一次retain, 即self会被retain一次。
对于block 使用 成员变量self.string来说。block内部是直接强引用self的。也就是block持有了self,在这里bock又作为self的一个成员被持有,就会导致
改动方案非常easy:
新建一个
以下的样例中,block本身就是函数參数的一部分
主要是由于它们都是基于
高速迭代在处理的过程中须要多一次转换。当然也会消耗掉一些时间. 基于Block的迭代能够达到本机存储一样快的遍历集合. 对于字典同样适用。
注意”enumerateObjectsUsingBlock” 改动局部变量时。 你须要声明局部变量为 __block 类型.
enumerateObjectsWithOptions:usingBlock: 支持并发迭代或反向迭代,并发迭代时效率也非常高.
对于字典而言, enumerateObjectsWithOptions:usingBlock 也是唯一的方式能够并发实现恢复Key-Value值.
演示样例代码:
这里指的是内存的分配区域。
stack的空间由操作系统进⾏行分配。
在现代操作系统中,一个线程会分配⼀个stack. 当一个函数被调用,一个stack frame(栈帧)就会被压到stack里。
里包括这个函数涉及的參数,局部变量,返回地址等相关信息。当函数返回后,这个栈帧就会被销毁。
⽽这一切都是自己主动的,由系统帮我们进行分配与销毁。对于程序猿是透明的,我们不须要手动调度。
.
heap的空间须要手动分配。 heap与动态内存分配相关,内存能够随时在堆中分配和销毁。
我们须要明白请求内存分配与内存销毁。 简单来说,就
是malloc与free.
一个对象在alloc的时候,就在Heap分配了内存空间。 stack对象通常有速度的优势,⽽且不会发生内存泄露问题。那么为什么OC的对象都是分配在heap的呢? 原因在于:
stack对象的⽣生命周期所导致的问题。比如一旦函数返回,则所在的stack frame就会被摧毁。那么此时返回的对象也 会一并摧毁。这个时候我们去retain这个对象是无效的。由于整个stack frame都已经被摧毁了。简单⽽言就是stack 对象的⽣命周期不适合Objective-C的引用计数内存管理⽅方法。
.
stack对象不够灵活,不具备足够的扩展性。创建时⻓度已经是固定的,⽽stack对象的拥有者也就是所在的stack frame
我们知道block 在使用@property定义时,官方建议我们使⽤用copy修饰符
尽管在ARC时代已经不须要再显式声明了,使用strong是没有问题的,可是仍然建 议我们使⽤copy以显示相关拷贝⾏为。
在Objective-C语⾔言中,⼀一共同拥有3种类型的block:
_NSConcreteGlobalBlock 全局的静态block,不会訪问不论什么外部变量。
_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的block,当引⽤用计数为0时会被销毁。
这⾥我们主要基于
举个栗子,在手动管理引⽤计数时,假设在exampleD_getBlock方法返回block 时没有运行
复制到堆后,block的⽣命周期就与⼀般的OC对象⼀样了,我们通过引用计数来对其进行内存管理。
如今我们知道为么么要Copy了吧-_-
block在创建时是stack对象,假设我们须要在离开当前函数仍能够使用我们创建的block。我们就须要把它 拷⻉到堆上以便进行以引用计数为基础的内存管理。
在ARC模式下,系统帮助我们完毕了copy的⼯作。
在ARC下,即使你声明的修饰符是strong,实际上效果是与声明为copy一样的。
因此在ARC情况下,创建的block仍然是NSConcreteStackBlock类型,仅仅只是当
在ARC下,我们能够将block看做⼀一个正常的OC对象,与其它对象的内存管理没什么不同。
MRC下要使用 Block_copy()和 Block_release 来管理内存。
block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。
我们在viewDidLoad中创建一个_block:
而且在一个button的事件中调用了这个block:
此时假设按了button之后就会导致程序崩溃。解决问题的方法非常easy
在创建完block的时候须要调用
使用后,使用 Block_release。从堆中释放掉
改动代码例如以下:
同理。特别须要注意的地方就是在把block放到集合类当中去的时候,假设直接把生成的block放入到集合类中,是无法在其它地方使用block。必须要对block进行copy。
示比例如以下:
Q:为什么不使用简单的copy方法 而是 Blockcopy呢?
由于blcok是复杂的匿名函数。简单的copy在有些时候不能实现准确的copy,具体就要看各自的C源代码了
先说一下思想:
首先,创建两个视图控制器,在第一个视图控制器中创建一个UILabel和一个UIButton,当中UILabel是为了显示第二个视图控制器传过来的字符串,UIButton是为了push到第二个界面。
第二个界面的仅仅有一个UITextField,是为了输入文字,当输入文字,而且返回第一个界面的时候,当第二个视图将要消失的时候,就将第二个界面上TextFiled中的文字传给第一个界面,而且显示在UILabel上。
事实上核心代码就几行代码:
当中inputTF是视图中的UITextField。
第一个方法就是定义的那个方法,把传进来的Block语句块保存到本类的实例变量returnTextBlock(.h中定义的属性)中,然后寻找一个时机调用,而这个时机就是上面说到的,当视图将要消失的时候。须要重写:
在第一个视图中获得第二个视图控制器。而且用第二个视图控制器来调用定义的属性
例如以下:
能够看到代码中的凝视,系统告诉我们能够用
来获得新的视图控制器,也就是我们说的第二个视图控制器。
这时候上面(第一步中)定义的那个方法起作用了,假设你写一个[tfVC return Text按回车 ,系统会自己主动提示出来一个:
的东西。我们仅仅要在焦点上回车,就能够高速创建一个代码块了,大家能够试试。这在写代码的时候是非常方便的。
当须要在block 中改动外部变量时使用,当须要訪问内部成员变量时。
2.在block里面, 对数组运行加入操作, 这个数组须要声明成 __block吗?
当然不须要,由于数组能够理解为指针,在block中对数组进行加入操作。仅仅是改变了指针指向的值,而没有改动外部数组地址。具体參见block訪问成员变量演示样例3
3.在block里面, 对NSInteger进行改动, 这个NSInteger是否须要声明成__blcok
必须须要,NSInteger -> typedef long NSInteger; 这货披着OC的外衣,事实上就是一个基本类型。基本类型在没有static 等的保护下,当然须要__block
使用block开发遇到的问题
http://blog.csdn.net/hherima/article/details/3858610
这篇block 博客系列从C源代码的角度具体分析了blcok原理
http://mobile.51cto.com/iphone-446829.htm
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
巧叔的谈Objective-C Block的实现
在其他语言中也有类似的概念,称为闭包(closure),实例object C兄弟swift 中闭包(swift 闭包具体解释)的使用跟 OC的block一样重要。总的来说:
Block是C语言的
Block是一个数据类型
Block 是一个提前准备好的代码。在须要的时候运行
1. block作用:
Block用来封装一段代码,能够在不论什么时候运行;Block能够作为函数參数或者函数的返回值,而其本身又能够带输入參数或返回值。
苹果官方建议尽量多用block。在多线程、异步任务 、集合遍历、集合排序、动画转场用的非常多
在新的iOS API中block被大量用来代替传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:
A. 能够直接在block代码块中写等会要接着运行的代码,直接把block变成函数的參数传入函数中,这是新API最常使用block的地方。
B. 能够存取局部变量,在传统的callback操作时,若想要存取局部变量得将变量封装成结构体才干使用,而block则是能够非常方便地直接存取局部变量。
2. Block的定义:
定义时。把block当成数据类型特点:
1. 类型比函数定义多了一个 ^
2. 设置数值,有一个 ^,内容是 {} 括起的一段代码
(1)基本定义方式
/* *1.最简单的定义方式: *格式:void (^myBlock)() = ^ { // 代码实现; } */ void (^myBlock)() = ^ { NSLog(@"hello"); }; // 运行时。把block当成函数 myBlock(); /* *2.定义带參数的block: *格式:void (^block名称)(參数列表) = ^ (參数列表) { // 代码实现; } */ void (^sumBlock)(int, int) = ^ (int x, int y) { NSLog(@"%d", x + y); }; sumBlock(10, 20); /* *3.定义带返回值的block *格式:返回类型 (^block名称)(參数列表) = ^ 返回类型 (參数列表) { // 代码实现; } */ int (^sumBlock2)(int, int) = ^ int (int a, int b) { return a + b; }; NSLog(@"%d", sumBlock2(4, 8));
(2) block 指针
Block Pointer是这样定义的:回传值 (^名字) (參数列);
//声明一个名字为square的Block Pointer,其所指向的Block有一个int输入和int输出 int (^square)(int); //block 指针square的内容 square = ^(int a){ return a*a ; }; //调用方法,感觉是是不是非常像function的使用方法? int result = square(5); NSLog(@"%d", result);
(3) 用typedef先声明类型,再定义变量进行赋值
typedef int (^MySum)(int,int); MySum sum = ^(int a,int b) { return a + b; };
(4) block 訪问外部变量
可是block使用有个特点。Block能够訪问局部变量,可是不能改动:int sum = 10; int (^MyBlock)(int) = ^(int num) { sum++;//编译报错 return num * sum; };
假设要改动就要加关键字 __block (以下具体说明):
__block int sum =10;
(5) block 与函数指针
以下比較下函数指针与block异同:定义函数指针
int (*myFn)();
调用函数指针
(*myFn)(10, 20);
定义Block
int (^MyBlocks)(int,int);
调用Blocks
MyBlocks(10, 20);
3. block訪问外部变量
block 訪问外部变量有几个特点必须知道:block内部能够訪问外部变量。
默认情况下block内部不能
改动外面的局部变量;
给局部变量加上关键字
_block,这个局部变量就能够在block内部改动;
block中能够訪问外部变量。可是
不能改动它。否则
编译错误。可是能够改变全局变量、静态变量(static)、全局静态变量。
上面的特点是有原因滴:
A.
为何不让改动变量:这个是编译器决定的。理论上当然能够改动变量了。仅仅只是block捕获的是外部变量的副本,名字一样。
为了不给开发人员迷惑,干脆不让赋值。
道理有点像:函数參数。要用指针,不然传递的是副本(大家想起那个经典的两个数调换值的问题了吧)。
B.
能够改动静态变量的值。
静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。
所以block能够调用。
(1) __block存储类型
通过__block存储类型修饰符, 变量在block中可被改动。__block存储跟register、auto和static存储类型相似(可是之间相互排斥),用于局部变量。__block变量存储在
堆区,因此。这个block使用的外部变量,将会在栈结束被留下来。
从优化角度考虑,block存储在栈上,假设block被拷贝(通过Block_copy或者copy),
变量被复制到堆。因此__block变量的地址就会改变。
__block变量还有两个限制,他们不能是可变数组(NSMutableArray),不能是结构体(structure)。
__block 变量的内部实现要复杂很多,__block 变量事实上是一个结构体对象,拷贝的是指向该结构体对象的指针
(2) block訪问外部变量
上面已经说过,默认block 訪问的外部变量是仅仅读属性的,若要对外部变量进行读写,须要在定义外部变量时加一个 __block, 示比例如以下://演示样例1:block訪问外部变量 void demoBlock1() { int x = 10; NSLog(@"定义前 %p", &x);// 局部变量在栈区 // 在定义block的时候,假设引用了外部变量,默认是把外部变量当做是常量编码到block当中,而且把外部变量copy到堆中,外部变量值为定义block时变量的数值 // 假设兴许再改动x的值,默认不会影响block内部的数值变化! // 在默认情况下。不同意block内部改动外部变量的数值! 由于会破坏代码的可读性,不易于维护! void(^myBlock)() = ^ { NSLog(@"%d", x); NSLog(@"in block %p", &x); // 堆中的地址 }; //输出是10,由于block copy了一份x到堆中 NSLog(@"定义后 %p", &x); // 栈区 x = 20; myBlock(); }
//演示样例2:在block中改动外部变量 void demoBlock2() { // 使用 __block,说明不在关心x数值的具体变化 __block int x = 10; NSLog(@"定义前 %p", &x); // 栈区 // !定义block时,假设引用了外部使用__block的变量,在block定义之后, block外部的x和block内部的x指向了同一个值,内存地址同样 void (^myBlock)() = ^ { x = 80; NSLog(@"in block %p", &x); // 堆区 }; NSLog(@"定义后 %p", &x); // 堆区 myBlock(); NSLog(@"%d", x); //打印x的值为8,且地址在堆区中 }
以下的样例就有点难度了。让我们看下block对指针变量的訪问
//样例3:block对指针变量的訪问 void demoBlock3() { // !指针记录的是地址 NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"]; //strM是指针,其在堆中存储的是zhangsan这个string在内存中的的地址值 //&strM是指针strM在堆中的地址 NSLog(@"定义前 %p %p", strM, &strM); void (^myBlock)() = ^ { /*首先调用block会对strM(指针)进行一份copy,这份copy会在堆中创建 还有一个指针,这个指针存储的值同strM。都是zhangsan的地址。 即新copy的指针指向的内容没有变 */ // 注意以下的操作是改动strM指针指向的内容 [strM setString:@"lisi"]; NSLog(@"inblock %p %p", strM, &strM); //输出:strM没有变。由于存储的都是zhangsan的地址,&strM为堆中新地址 /* *这句代码是改动指针strM。由于strM copy过来后是仅仅读的,所以同样例2编译会报错,须要在定义strM时加__block strM = [NSMutableString stringWithString:@"wangwu"]; NSLog(@"inblock %p %p", strM, &strM); */ }; //大家想想使用__block输出会是什么呢 NSLog(@"定义后 %p %p", strM, &strM); myBlock(); NSLog(@"%@", strM); }
上面的样例搞定了,来让我们看下各种类型的变量与block之间的互动:
//演示样例4:各种类型的变量和block之间的互动 extern NSInteger CounterGlobal; static NSInteger CounterStatic; NSInteger localCounter = 42 ; __block char localCharacter; void (^aBlock)( void ) = ^( void ) { ++ CounterGlobal ; //能够存取。 ++ CounterStatic ; //能够存取。 CounterGlobal = localCounter; //localCounter在block 建立时就不可变了。 localCharacter = 'a' ; //设定外面定义的localCharacter 变数。 }; ++localCounter; //不会影响的block 中的值。 localCharacter = 'b' ; aBlock(); //运行block 的内容。 //运行完后,localCharachter 会变成'a'
(3) block 引用成员变量
OC对象。不同于基本类型。Block会引起对象的引用计数变化。若我们在block中引用到oc的对象。则对象的引用计数器会加1, 只是在对象前 加__block修饰,则參考计数不变。
- 若直接存取实例变量(instance variable),self的參考计数将被加1。 - 若透过变量存取实例变量的值,则变量的參考计数将被加1。 - 在对象前加 __block 则參考计数不会自己主动加1。
//样例1:定义一个变量间接给block调用,成员变量引用计数不变 dispatch_async (queue, ^{ // 由于直接存取实例变量instanceVariable ,所以self 的retain count 会加1 doSomethingWithObject (instanceVariable); }); //通过 id localVaribale = instanceVariable; dispatch_async (queue, ^{ //localVariable 是存取值,所以这时仅仅有localVariable 的retain count 加1 //self 的 return count 并不会添加。 doSomethingWithObject (localVaribale); });
上面仅仅是简单演示下block引用成员变量,以下我们研究下block引用成员变量时出现的一个经典问题:
循环引用。
在block内部使用成员变量,例如以下:
@interface ViewController : UIViewController { NSString *_string; } @end
在block创建中:
_block = ^(){ NSLog(@"string %@", self.string); };
上面代码中block是会对内部的成员变量进行一次retain, 即self会被retain一次。
对于block 使用 成员变量self.string来说。block内部是直接强引用self的。也就是block持有了self,在这里bock又作为self的一个成员被持有,就会导致
循环引用和内存泄露。
改动方案非常easy:
新建一个
__block scope的局部变量,并把self赋值给它,而在block内部则使用这个局部变量来进行取值,上面说过:__block标记的变量是不会被自己主动retain的。
__block ViewController *controller = self; _block = ^(){ NSLog(@"string %@", controller.string); };
4. block 基本使用
当block定义完毕后,我们除了能够像使用一般函数的方式来直接调用它以外,还能够有其它妙用。这些灵活的应用才是block最为强大的地方。(1) block 作为函数參数
我们能够像使用一般函数使用參数的方式将block以函数參数的型式传入函数中,在这样的情况下,大多数我们使用block的方式将不会倾向定义一个block。而是直接以内嵌的方式来将block传入,这也是眼下新版SDK中主流的做法
以下的样例中,block本身就是函数參数的一部分
char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" }; qsort_b (myCharacters, 3 , sizeof ( char *), ^( const void *l, const void *r) { //须要类型强转下 char *left = *( char **)l; char *right = *( char **)r; return strncmp (left, right, 1 ); } // 这里是block 的终点 ); // 最后的结果为:{"Charles Condomine", "George", "TomJohn"}
(2) Block当作方法的參数
// 全部的资料 NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ]; // 我们仅仅要这个集合内的资料 NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ]; BOOL (^test)( id obj, NSUInteger idx, BOOL *stop); test = ^ ( id obj, NSUInteger idx, BOOL *stop) { // 仅仅对前5 笔资料做检查 if (idx < 5 ) { if ([filterSet containsObject : obj]) { return YES ; } } return NO ; }; NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test]; NSLog ( @"indexes: %@" , indexes); // 结果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)] // 前5笔资料中,有4笔符合条件。它们的索引值各自是0-1, 3-4
(3)OC方法中block实例
A. sortedArrayUsingComparator:
//这里面block代码块直接内嵌作为方法的參数 NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) { //左边大于右边。降序 if ([obj1 integerValue] > [obj2 integerValue]) { return (NSComparisonResult)NSOrderedDescending; } //右边大于左边,升序 if ([obj1 integerValue] < [obj2 integerValue]) { return (NSComparisonResult)NSOrderedAscending; } //同样 return (NSComparisonResult)NSOrderedSame; }];
B. enumerateObjectsUsingBlock
通常enumerateObjectsUsingBlock: 和 (for(… in …)在效率上基本一致,有时会快些。主要是由于它们都是基于
NSFastEnumeration实现的。
高速迭代在处理的过程中须要多一次转换。当然也会消耗掉一些时间. 基于Block的迭代能够达到本机存储一样快的遍历集合. 对于字典同样适用。
注意”enumerateObjectsUsingBlock” 改动局部变量时。 你须要声明局部变量为 __block 类型.
enumerateObjectsWithOptions:usingBlock: 支持并发迭代或反向迭代,并发迭代时效率也非常高.
对于字典而言, enumerateObjectsWithOptions:usingBlock 也是唯一的方式能够并发实现恢复Key-Value值.
演示样例代码:
//定义一个可变数组 NSMutableArray *test = [NSMutableArray array]; //向数组中加入元素 for (int i= 0; i < 10000; i++) { [test addObject:@"i"]; } //迭代数组输出 [test enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog(@"%@",obj); }];
5. block内存管理
(1)堆(Stack)和栈(Heap)
heap和stack是内存管理的两个重要概念。这里指的是内存的分配区域。
stack的空间由操作系统进⾏行分配。
在现代操作系统中,一个线程会分配⼀个stack. 当一个函数被调用,一个stack frame(栈帧)就会被压到stack里。
里包括这个函数涉及的參数,局部变量,返回地址等相关信息。当函数返回后,这个栈帧就会被销毁。
⽽这一切都是自己主动的,由系统帮我们进行分配与销毁。对于程序猿是透明的,我们不须要手动调度。
.
heap的空间须要手动分配。 heap与动态内存分配相关,内存能够随时在堆中分配和销毁。
我们须要明白请求内存分配与内存销毁。 简单来说,就
是malloc与free.
(2)Objective-C中的Stack和Heap
首先全部的Objective-C对象都是分配在heap的。 在OC经典的内存分配与初始化:NSObject *obj = [[NSObject alloc] init];
一个对象在alloc的时候,就在Heap分配了内存空间。 stack对象通常有速度的优势,⽽且不会发生内存泄露问题。那么为什么OC的对象都是分配在heap的呢? 原因在于:
stack对象的⽣生命周期所导致的问题。比如一旦函数返回,则所在的stack frame就会被摧毁。那么此时返回的对象也 会一并摧毁。这个时候我们去retain这个对象是无效的。由于整个stack frame都已经被摧毁了。简单⽽言就是stack 对象的⽣命周期不适合Objective-C的引用计数内存管理⽅方法。
.
stack对象不够灵活,不具备足够的扩展性。创建时⻓度已经是固定的,⽽stack对象的拥有者也就是所在的stack frame
我们知道block 在使用@property定义时,官方建议我们使⽤用copy修饰符
// 定义一个块代码的属性。block属性须要用 copy @property (nonatomic, copy) void (^completion)(NSString *text);
尽管在ARC时代已经不须要再显式声明了,使用strong是没有问题的,可是仍然建 议我们使⽤copy以显示相关拷贝⾏为。
(3)为什么要使用copy?!
事实上Objective-C是有它的Stack object的。那就是block。在Objective-C语⾔言中,⼀一共同拥有3种类型的block:
_NSConcreteGlobalBlock 全局的静态block,不会訪问不论什么外部变量。
_NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的block,当引⽤用计数为0时会被销毁。
这⾥我们主要基于
内存管理的角度对它们进行分类。
NSConcreteGlobalBlock,这样的不捕捉外界变量的block是不须要内存管理的,这样的block不存在于Heap或是Stack⽽是作为代码片段存在,相似于C函数。 NSConcreteStackBlock,须要涉及到外界变量的block在创建的时候是在stack上⾯分配空间的,也就是⼀旦所在函数返回,运行弹栈,则会被摧毁。这就导致内存管理的问题,假设我们希望保存这个block或者是返回它,假设没有做进⼀步的copy处理,则必定会出现故障。
举个栗子,在手动管理引⽤计数时,假设在exampleD_getBlock方法返回block 时没有运行
[[block copy] autorelease]的操作,则方法运行完毕后,block就会被销毁, 返回block是无效的。
//定义了一个block typedef void (^dBlock)(); dBlock exampleD_getBlock() { char d = 'D'; return ^{ printf("%c\n", d); }; } void exampleD() { exampleD_getBlock(); }
NSConcreteMallocBlock,因此为了解决block作为Stack object的这个问题,我们终于须要把它拷⻉到堆上来。
复制到堆后,block的⽣命周期就与⼀般的OC对象⼀样了,我们通过引用计数来对其进行内存管理。
如今我们知道为么么要Copy了吧-_-
block在创建时是stack对象,假设我们须要在离开当前函数仍能够使用我们创建的block。我们就须要把它 拷⻉到堆上以便进行以引用计数为基础的内存管理。
在ARC模式下,系统帮助我们完毕了copy的⼯作。
在ARC下,即使你声明的修饰符是strong,实际上效果是与声明为copy一样的。
因此在ARC情况下,创建的block仍然是NSConcreteStackBlock类型,仅仅只是当
block被引用或返回时,ARC帮助我们完毕了copy和内存管理的工作。
总结
在ARC下,我们能够将block看做⼀一个正常的OC对象,与其它对象的内存管理没什么不同。
MRC下要使用 Block_copy()和 Block_release 来管理内存。
(4)再来一个栗子
上面讲到ARC下, block在被引用或返回时类型会由NSConcreteStackBlock转换为 NSConcreteHeapBlock,那在MRC环境下该怎么办呢。block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。
我们在viewDidLoad中创建一个_block:
- (void)viewDidLoad { [superviewDidLoad]; int number = 1; _block = ^(){ NSLog(@"number %d", number); }; }
而且在一个button的事件中调用了这个block:
- (IBAction)testDidClick:(id)sender { _block(); }
此时假设按了button之后就会导致程序崩溃。解决问题的方法非常easy
在创建完block的时候须要调用
Block_copy函数。它会把block从栈上移动到堆上。那么就能够在其它地方使用这个block了。
Block_copy实际上是一个宏。例如以下:
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
使用后,使用 Block_release。从堆中释放掉
改动代码例如以下:
_block = ^(){ NSLog(@"number %d", number); }; _block = Block_copy(_block);
同理。特别须要注意的地方就是在把block放到集合类当中去的时候,假设直接把生成的block放入到集合类中,是无法在其它地方使用block。必须要对block进行copy。
示比例如以下:
[array addObject:[[^{ NSLog(@"hello!"); } copy] autorelease]];
Q:为什么不使用简单的copy方法 而是 Blockcopy呢?
由于blcok是复杂的匿名函数。简单的copy在有些时候不能实现准确的copy,具体就要看各自的C源代码了
6. 视图控制器反向传值
使用Block的地方非常多,当中传值仅仅是当中的一小部分,以下介绍Block在两个界面之间的传值:先说一下思想:
首先,创建两个视图控制器,在第一个视图控制器中创建一个UILabel和一个UIButton,当中UILabel是为了显示第二个视图控制器传过来的字符串,UIButton是为了push到第二个界面。
第二个界面的仅仅有一个UITextField,是为了输入文字,当输入文字,而且返回第一个界面的时候,当第二个视图将要消失的时候,就将第二个界面上TextFiled中的文字传给第一个界面,而且显示在UILabel上。
事实上核心代码就几行代码:
在第二个视图控制器的.h文件里定义声明Block属性
typedef void (^ReturnTextBlock)(NSString *showText); @interface TextFieldViewController : UIViewController @property (nonatomic, copy) ReturnTextBlock returnTextBlock; - (void)returnText:(ReturnTextBlock)block; @end
第一行代码是为要声明的Block又一次定义了一个名字 ReturnTextBlock 这样,以下在使用的时候就会非常方便。 第三行是定义的一个Block属性 第四行是一个在第一个界面传进来一个Block语句块的函数,不用也能够,只是加上会降低代码的书写量
实现第二个视图控制器的方法
- (void)returnText:(ReturnTextBlock)block { self.returnTextBlock = block; } - (void)viewWillDisappear:(BOOL)animated { if (self.returnTextBlock != nil) { self.returnTextBlock(self.inputTF.text); } }
当中inputTF是视图中的UITextField。
第一个方法就是定义的那个方法,把传进来的Block语句块保存到本类的实例变量returnTextBlock(.h中定义的属性)中,然后寻找一个时机调用,而这个时机就是上面说到的,当视图将要消失的时候。须要重写:
- (void)viewWillDisappear:(BOOL)animated; 方法。
在第一个视图中获得第二个视图控制器。而且用第二个视图控制器来调用定义的属性
例如以下:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. TextFieldViewController *tfVC = segue.destinationViewController; [tfVC returnText:^(NSString *showText) { self.showLabel.text = showText; }]; }
能够看到代码中的凝视,系统告诉我们能够用
[segue destinationViewController]
来获得新的视图控制器,也就是我们说的第二个视图控制器。
这时候上面(第一步中)定义的那个方法起作用了,假设你写一个[tfVC return Text按回车 ,系统会自己主动提示出来一个:
tfVC returnText:<#^(NSString *showText)block#>
的东西。我们仅仅要在焦点上回车,就能够高速创建一个代码块了,大家能够试试。这在写代码的时候是非常方便的。
面试题:
__block什么时候用当须要在block 中改动外部变量时使用,当须要訪问内部成员变量时。
2.在block里面, 对数组运行加入操作, 这个数组须要声明成 __block吗?
当然不须要,由于数组能够理解为指针,在block中对数组进行加入操作。仅仅是改变了指针指向的值,而没有改动外部数组地址。具体參见block訪问成员变量演示样例3
3.在block里面, 对NSInteger进行改动, 这个NSInteger是否须要声明成__blcok
必须须要,NSInteger -> typedef long NSInteger; 这货披着OC的外衣,事实上就是一个基本类型。基本类型在没有static 等的保护下,当然须要__block
悄悄告诉你哦,block在iOS的面试中是非常重要的,假设你能把上面解说的内容理解了。那么就仰天长啸出门去了。
參考
/article/1227646.html使用block开发遇到的问题
http://blog.csdn.net/hherima/article/details/3858610
这篇block 博客系列从C源代码的角度具体分析了blcok原理
http://mobile.51cto.com/iphone-446829.htm
http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
巧叔的谈Objective-C Block的实现
相关文章推荐
- iOS开发之即时通讯之Socket(AsyncSocket)
- iOS 9的新内容
- 升级xcode7 和 iOS9 后遇到的问题以及解决方案
- iOS开发 关于SEL的简单总结
- iOS开发零基础教程之开发证书以及打包问题
- iOS-常见问题
- iOS8下,如何去掉系统自带的滑动返回手势?
- iOS延迟执行
- iOS:沙盒、偏好设置、归档、解归档
- iOS 多线程及其他补充
- ios属性定义
- 傻瓜式操作Nagios图解
- IOS9 最快速适配 五分钟适配ios9
- ios零碎知识点
- iOS 应用开发,用户密码存储技术--KeyChain
- iOS 限制图片的缩放比例 设置捏合手势的缩放比例
- 潜心俱乐部iOS一周问答20150919期
- iOS_22自定义键盘工具栏
- iOS9适配系列教程
- IOS 从当前页面直接跳转到首页建议用pop方法