IOS block

准备走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的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值