IOS block
2015-11-20 18:01
501 查看
准备走ios开发之一块,但是感觉C/C++的底子还是不够,既然之前学过,趁着刚刚学oc,还能做个对比什么的。
参考:
http://www.ithao123.cn/content-5613988.html
http://www.cnblogs.com/wustlj/archive/2013/08/12/3252152.html
http://www.dreamingwish.com/article/block%E4%BB%8B%E7%BB%8D%EF%BC%88%E5%9B%9B%EF%BC%89%E6%8F%AD%E5%BC%80%E7%A5%9E%E7%A7%98%E9%9D%A2%E7%BA%B1%EF%BC%88%E4%B8%8B%EF%BC%89.html
http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html
http://www.zhihu.com/question/30779258
http://blog.csdn.net/hherima/article/details/38586101?utm_source=tuicool&utm_medium=referral
block 被称为自动截获变量匿名函数
1。内存方面
普通声明的block是存在栈中的,通过copy得来的会存储在堆中。
int main(int argc, const char *argv[]){ @autoreleasepool{ int j = 1; __block int i = 1024; void (^block)(void) = ^{ printf("%d%d\n", i, j); } block(); void (^inheapblock)(void) = Block_copy(block); inheapblock(); i++; j++; block(); inheapblock(); block(); printf("%d %d\n", i, j); } }
结果会是
1024 1
1024 1
1025 1
1025 1
1025 2
首先,我们在栈上创建了变量ij,并赋予初始值,然后创建一个block变量名为block,但未赋值。
然后我们初始化这个block,赋值为一个只有一句printf的block,值得注意的是你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。,其引用到的常规变量会进行如下操作:
没有__block标记的变量,其值会被复制一份到block私有内存区 有__block标记的变量,其地址会被记录在block私有内存区 然后调用block,打印1024, 1很好理解.
接下来复制block到堆,名曰inheapblock(下面称为ihb),调用,打印1024, 1也很好理解.
接下来我们为ij增值,使其变为1025和2,此时再调用block或者ihb,会发现结果为1025, 1,这是因为变量j早已在创建原始的block时,被赋值进block的私有内存区,后续对i的操作并非操作的私有内存区的复制品,当调用block或者ihb时,其打印使用的是私有内存区的复制品,故而打印结果依旧为1;而变量j的修改会实时生效,因为block记录的是它的地址,通过地址来访问其值,使得外部对j的修改在block中得以生效.
我们再来看下源码剖析
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/Block_private.h
block定义无非两个东西
struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };
在Objective-C中,类都是继承NSObject的,NSObject里面都会有个isa,是一个objc_class指针。
我们可以通过clang-rewrite 去看下,我们的block到底怎样实现的。
^int(){printf("val"); return 111;};
这个block会被转化为
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __testBlock_block_impl_0 { struct __block_impl impl; struct __testBlock_block_desc_0* Desc; __testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
__testBlock_block_impl_0是block结构,他的第一个属性也是一个结构__block_impl,而第一个参数也是一个isa的指针。我们可以看到isa指针刚刚的值是&_NSConcreteStackBlock;,在栈上创建的。
在运行时,NSObject和block的isa指针都是指向在对象一个4字节。
isa指针一共有以下几种
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];
– 如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
void (^block) (void) = ^{ printf("Hello world\n"); }; int main(int argc, const char * argv[]) { ...... }clang
-rewrite 之后会发现是是 isa = _NSConcreteGlobalBlock;
copy 对 Global无效
在非ARC下执行如下代码:
//非MyBlock block = func(); NSLog(@"%d", block()); NSLog(@"%@", [block class]); MyBlock block2 = [block copy]; //Copy操作对__NSGlobalBlock__类型无效 NSLog(@"%d", block == block2);
123 __NSGlobalBlock__ 1 可以看到,copy后的Block和原来是同一个对象的。
而对于引用了外部变量的Block,如果没有对他进行copy,他的作用域只会在声明他的函数栈内(类型是NSStackBlock),
一种情况在非ARC下是无法编译的:
typedef int(^blk_t)(int); blk_t func(int rate){ return ^(int count){return rate*count;} }
而对于引用了外部变量的Block,如果没有对他进行copy,他的作用域只会在声明他的函数栈内(类型是NSStackBlock),如果想在非ARC下直接返回此类Block,Xcode会提示编译错误的
这是因为:block捕获了栈上的rate自动变量,此时rate已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下没有这个问题,因为ARC会自动加入copy操作(类型是NSMallocBlock)
3.有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:
-(id) getBlockArray{ int val =10; return [[NSArray alloc]initWithObjects: ^{NSLog(@"blk0:%d",val);}, ^{NSLog(@"blk1:%d",val);},nil]; } id obj = getBlockArray(); typedef void (^blk_t)(void); blk_t blk = (blk_t)[obj objectAtIndex:0]; blk();
这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。
4.不管block配置在何处,用copy方法复制都不会引起任何问题。在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。
注意:在栈上调用copy那么复制到堆上,在全局block调用copy什么也不做,在堆上调用block 引用计数增加
但是可以改变全局变量、静态变量、全局静态变量。 其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。 第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。 解决block不能保存值这一问题的另外一个办法是使用__block修饰符。
http://www.cnblogs.com/biosli/archive/2013/05/29/iOS_Objective-C_Block.html中有介绍关于copy的问题
相关文章推荐
- ios 本地推送
- IOS常用的设计模式以及对应的优势
- 使用.a文件遇到的错误
- 利用biostime()读取并设置BIOS的时钟
- iOS应用性能调优的25个建议和技巧
- iOS 利用平移缩放旋转手势对view实现对应的平移缩放旋转效果(一)
- IOS 截取部分图片并显示
- ios 调用系统的地图
- iOS上简单推送通知
- iOS 9 Auto Layout界面自动布局系列6-自适应布局
- iOS 之证书问题
- iOS PPI
- nagios监控
- ios h5 出现的问题
- iOS 标签字体加粗
- iOS 根证书和 P12 区分
- iOS开发 单手使触摸的视图旋转
- iOS- Masonry自动布局框架介绍与使用实践
- iOS 表视图的分区头部文字设置
- iOS之使用MapKit通过经纬度坐标画线