IOS block

来源:互联网 发布:js中undefined怎么赋值 编辑:程序博客网 时间:2024/06/05 19:49
准备走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];

  1. – 如果是定义在函数外面的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),

  1. 一种情况在非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的问题

0 0
原创粉丝点击