0.简单的Block定义和中间实现代码
先写一个简单的Block程序block.c:
#include <stdio.h>
typedef void(^TestBlock)(void);
int main()
{
TestBlock testBlock = ^(){ printf("Hello World!\n"); };
testBlock();
return 0;
}
在命令行下用Clang工具的-rewrite-objc编译选项编译:
clang -rewrite-objc block.c
得到一份block.cpp的C语言中间代码,其中关于Block的主要代码如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
typedef void(*TestBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello World!\n"); }
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
TestBlock testBlock = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
return 0;
}
1.函数调用的理解
对于这个简单的Block实现别人也讲过很多遍了,我只有一处看明白的比较慢就是下面这行函数调用的代码:
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
2.Block类型的理解
(1)在非ARC下,这里虽然isa指向的是NSConcreteStackBlock,但是在LLVM编译下没有访问局部变量的Block应该是NSConcreteGlobalBlock类型的,访问了局部变量的Block是NSConcreteStackBlock类型的。(2)在ARC下,访问了局部变量的Block是NSConcreteMallocBlock类型的,未访问局部变量的Block是NSConcreteGlobalBlock类型的。
3.Block对变量访问的理解
(1)普通栈变量 :通过传值实现一次性可读访问权限。(2)static变量 :通过传地址实现读写权限。
(3)全局变量 :本身就具有可读写权限。
(4)__block变量 :通过把普通变量打包成__Block_byref_*_0结构体来传递地址实现变量的读写权限。__Block_byref_a_0结构体如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
其中__forwarding在初始化的时候是指向自身的,当NSConcreteStackBlock类型的Block通过copy变为NSConcreteMallocBlock时,栈空间的Block的__forwarding指针会改变指向堆空间的Block。
4.Block中循环引用的解决
使用Block容易产生Retain Cycle。这是因为Block会自动retain他引用的对象。在使用SDWebImage时一个简单的Retain Cycle例子:
UIImageView *imgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType)
{
// ...
imgView.image = image; // warning: Capturing 'imgView' strongly in this block is likely to lead to a retain cycle
}];
self.imageView在调用setImageWithURL:completed:时会retain定义的这个Block,而Block用到了imgView又会自动retainimgView,所以就造成了Retain Cycle。解决办法:
(1)非ARC下
__block UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType)
{
// ...
weakImgView.image = image;
}];
添加:在不用__block的说明符的情况下,当Block发生copy的时候会retain Block内部用到的对象实例。在用了__block说明符的情况下,打包成的
struct __Block_byref_a_0
只是记录的用到的对象的指针,在copy的时候并没有retain结构体内部的对象实例。
(2)ARC下
ARC下也是可以用__block打破循环引用的:
__block UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType)
{
// ...
weakImgView.image = image;
//注意下面这句
weakImgView = nil;
}];
__block变量打包成的结构体内部对对象变量的引用是强引用,同样会产生循环引用,只不过是我们在Block执行最后的时候打破了(注释下面那句),但是如果我们不执行这个Block,循环引用还是存在的。所以最好用下面的方法。
__weak UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image,NSError *erroe,SDImageCacheType cacheType)
{
// ...
weakImgView.image = image;
}
为了异步操作回调的时候防止weakImgView为nil,我们可以这么做:
__weak UIImageView *weakImgView = self.imageView;
[self.imageView setImageWithURL:url completed:^(UIImage *image,NSError *erroe,SDImageCacheType cacheType)
{
UIImageView *strongImgView = weakImgView;
// ...
strongImgView.image = image;
}
另外注:
(1)NSNotificationCenter的addObserverForName:object:queue:usingBlock:会retainBlock。(2)NSTimer会retain目标Target。
(3)如果在iOS5.0之前,__unsafe_unretained 替代 __block
5.Block的使用
http://blog.csdn.net/joywii/article/details/9903095
6.参考链接
LLVM开源的Block实现源码:https://llvm.org/svn/llvm-project/compiler-rt/trunk/BlocksRuntime/
谈Objctive-C Block的实现:http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
iOS中Block实现的探究:http://blog.csdn.net/jasonblog/article/details/7756763
Objctive-C Blocks Quiz:http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/
对Objective-C中Block的追探:http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html
正确的使用Block http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/