note:文明看帖转载是对自己的尊重也是对学者的鼓励,欢迎批评讨论
iOS-Block揭秘
学习Block的时候认为它很神秘,其实只要用C语言的思想去理解就会觉的非常简单,计算机编程语言其实就是一个标准,而编译器就是这个标准的解释实现者,任何语言的设计实现都可以映射到C语言的数据和函数中,C语言是功能函数和数据是分离的,通过参数的传递来达到数据的加工,c++是面向对象编程它的数据和函数是有关联的,java和C#又加入了运行时,而这些语言的源代码通过编译之后都会最终编程计算机具体CPU架构的指令,既然大家编译的结果都是一样的,那么它们的源代码元素都可以映射成为C语言的结构体和函数,所以计算机语言的不同是编译器的解释不同,编译器在编译面向对象编程的语言时也是把它映射为结构体和函数,而为了设计的面向对象的编程思想,在源代码编写的时候做了函数调用和数据访问的各种限制,从而达到编码阶段的面向对象的设计,说白了如果你知道编译器怎么编译并知道某个类的私有函数地址会编译在什么地方,你完全可以直接调用,从而打破面向对象编程的各种限制达到向C语言编程。下面就用C语言的思想解释iOS中神秘的Block,注意只是对其思想的简单说明实现,Xcode的具体实现不得而知,通过思想提高Block编程技能;尤其是在GCD多线程编程中。
一.Block的简单使用
1.
void(^testBlcok)(void) = ^(void) {
NSLog(@"你好我是testBlcok");
};
testBlcok();
运行结果:
2015-01-03 15:00:53.610 BlockTest[722:40106]你好我是testBlcok
通过其声明和使用我们可以把它映射到C语言的结构和函数上:
a.void (^)void:可以看成是一个类型,这个类型可以声明变量,变量编译之后就是一个虚拟地址,指向的类型为函数指针
b.
^(void) {
NSLog(@"你好我是testBlcok");
};
这个可以看成是一个值,当编译器编译到这个代码时,它会为它声明成一个函数,并把它的函数入口地址赋值给testBlock
c.testBlock()这个语句就是简单的函数调用
2.用C语言的方式实现Block的效果
static void c_function(){<span style="white-space:pre"> </span>//类的外部声明了一个静态函数
NSLog(@"你好,我是在c函数中调用的");
}
</pre><pre name="code" class="objc">typedef void(*p_funvoid)(void);<span style="white-space:pre"> </span>//声明一个返回值类型为void参数也为void的函数指针类型
p_funvoid p = c_function;<span style="white-space:pre"> </span>//声明一个函数指针类型为p_funvoid的变量p,并把c_function函数的地址赋给p
p();<span style="white-space:pre"> </span>// 通过函数指针实现函数的调用
2015-01-03 15:20:06.541 BlockTest[775:45308]你好,我是在c函数中调用的
3.两种方法的对比关系
二,第二中用法
一中简单的用法只使用了简单的NSLog语句,要是Block中用到了外部的变量或者经过__block修饰的变量又会是什么情况了,下面将做解答;
1.
------------------------------------
值类型
int b=9;
void(^testBlcok)(void) = ^(void) {
NSLog(@"%d",b);
};
testBlcok();
--------------------------------------------
运行结果:
2015-01-03 16:10:14.007 BlockTest[826:56728] 9
-------------------------------------------
引用类型
NSMutableString *hello=[[NSMutableString alloc]initWithString:@"hello"];
void(^testBlcok)(void) = ^(void) {
NSLog(@"在block中%@",hello);
[hello appendString:@"world"];
};
testBlcok();
NSLog(@"在block外部%@",hello);
运行结果:
2015-01-03 16:35:28.549 BlockTest[966:63819] 在block中hello
2015-01-03 16:35:28.550 BlockTest[966:63819] 在block外部helloworld
block中可以使用外部的变量,但是不能修改,其实外部的变量b和block内部的变量b是不一样的,当block中包含有外部的变量是它会为block生成一个包含变量的struct结构体,把block中的用到的变量赋值给struct中的变量,在block调用是把struct的变量传给编译器为block生成的函数,如果你修改b的值,编译器认为你想通过block改变外部值类型的变量并传递出来,但是block往往是多线程或者后台运行的,外部的变量的作用域有可能已经结束,当block运行完成变量b已经不在了这会造成崩溃,如果你通过应用类型来修改b的值是可以做到的,但是一般不这样做,因为不知道b什么时候会消失,这很容易引起程序崩溃。
所以,在block中使用外部的值类型的变量就把它看成是一个block作用域的变量,在block调用的时候当作参数传递给编译器为block生成的函数,而对于应用类型的变量,因为它是通过变量的地址来修改访问变量,所要注意的问题是该变量作用域要比使用它的block大长,要不然block对已经释放的变量修改会造成崩溃。
2.__block修饰符有什么用?
-------------------
__block int b=9;
void(^testBlcok)(void) = ^(void) {
NSLog(@"%d",b);
b++;
};
testBlcok();
NSLog(@"%d",b);
------------------------运行结构:
2015-01-03 16:46:09.317 BlockTest[996:66336] 9
2015-01-03 16:46:09.317 BlockTest[996:66336] 10
从结果中可以看出来,外部的变量被修改了,因为变量被__block修饰符修饰,如果不用__block修饰,看我又怎么可以实现它的效果,如下
------------------------结构体声明----------------------
typedef struct{
int b;
}blockierStruct;
---------------------使用代码-------------------------
blockierStruct *b= malloc(sizeof(blockierStruct));
b->b =100;
void(^testBlcok)(void) = ^(void) {
NSLog(@"%d",b->b);
b->b++;
};
testBlcok();
NSLog(@"%d",b->b);
---------------------运行结果------------------------
2015-01-03 16:56:42.034 BlockTest[1025:69633] 100
2015-01-03 16:56:42.035 BlockTest[1025:69633] 101
所以,加上__block修饰的赋值变量你可以把它当作一个结构体的指针。
至于,
Block_release();
Block_copy();这两个函数无非就是block与NSObject的相似,用来对block的引用复制释放;如果想让struct结构体变量和NSObject 对象有关联,可以把struct的字节存储在NSData中,由此猜想Block_release();Block_copy();这两个函数的实现思想也差不多。
总结,以上的Block的理解主要是对其的跟人理解使用总结,写这篇文章主要是想用一种常见的思想去说明实现它,从而提高对block的理解,为coding服务,它的具体实现只有官方知道,欢迎讨论。