OC-精简解读 block

  网上关于block的解释已经很多了,在此,本文仅对block的精简解读。

一、block是什么?

  block在OC中是对象,在运行时会被转化成函数。但是block与OC中其他对象最大的不同是:一般来说,block初始化是储存在栈区的。关于block在内存中的储存位置我们在下面的内容中探讨。因为情况是在是太多了。

  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;  //isa指针,这是OC对象的证明
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);  //这个invoke指针指向block被转化成的函数
    struct Block_descriptor *descriptor;
    /* Imported variables. */  //如果引用了外部变量,从这里开始列在后面
};

 

二、block内存地址研究

  在开始研究之前,需要说明的是,block中可以访问并且修改全局变量和static变量(无论全局还是局部的)。如果访问非static局部变量,block会被转换成函数,那么就是值传递,并且不能在block中修改传进来的值,编译器会报错的。如果一定要修改非static局部变量的值,请加__block,这样的话,就是传递变量的指针,当然可以修改。

  下面简单研究下block的内存地址。

int a=1;
static int b=2;

- (void)viewDidLoad {
    [super viewDidLoad];
    int c=3;
    static int d=4;
    
    void(^myBlock)()=^{
        NSLog(@"myBlock");
    };
    NSLog(@"myBlock   %p",myBlock);//没有访问任何外部变量,block储存在全局区(常量区)
    
    NSArray * blockArr=[NSArray arrayWithObjects:^{a=a+c;},        //访问全局变量和局部变量,block储存在堆区
                                       ^{int ss= b +1;}, //访问了全局静态变量,block储存在全局区(常量区)
                              ^{int dd=c+1;}, //访问了局部变量,block储存在栈区
                              ^{d=5+c;},nil];  //访问了局部静态变量和局部变量,block储存在栈区 NSLog(@"%@",blockArr);

 打印结果如下:

2015-11-03 21:21:40.936 NSRunloopTest[3668:302834] myBlock   0x10c350310
2015-11-03 21:21:40.937 NSRunloopTest[3668:302834] (
    "<__NSMallocBlock__: 0x7feef1732480>",
    "<__NSGlobalBlock__: 0x10c350370>",
    "<__NSStackBlock__: 0x7fff538b3a08>",
    "<__NSStackBlock__: 0x7fff538b39e0>"
) 

 解释: 为了打印出三种不同类型的block,就随便组合了几种外部变量访问的方式,先说类型,

__NSMallocBlock__:内存地址在堆区的block类型

__NSGlobalBlock__:内存地址在全局区的block类型

__NSStackBlock__: 内存地址在栈区的block类型

比较对应的内存地址,也确实如此,当然,英文名称也说明了一切。block是否访问外部变量,外部变量的类型不同,同时访问多种变量,ARC和MRC,种种条件组合起来,情况是不一样的,下面是一些常见组合的结论:

在Block中, 如果只使用全局或静态变量或不使用外部变量, 那么Block块的代码会存储在全局区;
如果使用了外部变量, 在ARC中, Block块的代码会存储在堆区;
在MRC中, Block快的代码会存储在栈区;
block默认情况下不能修改外部变量, 只能读取外部变量:
在ARC中, 外部变量存在堆中, 这个变量在Block块内与在Block块外地址相同;
外部变量存在栈中, 这个变量会被copy到为Block代码块所分配的堆中;
在MRC中, 外部变量存在堆中, 这个变量在Block块内与Block块外相同;
外部变量存在栈中, 这个变量会被copy到为Block代码块所分配的栈中;
如果需要修改外部变量, 需要在外部变量前面声明 __block
在ARC中, 外部变量存在堆中, 这个变量在Block块内与Block块外地址相同;
外部变量存在栈中, 这个变量会被转移到堆区, 不是复制, 是转移.
在MRC中, 外部变量存在堆中, 这个变量在Block块内与Block块外地址相同;
外部变量存在栈中, 这个变量在Block块内与Block块外地址相同; 
 
好吧,其他情况就待君探索了。
 
三、声明block属性的时候,为什么用copy
  一般而言,block初始化时是储存在栈区的(感觉这么说没有底气,你懂的。。。),此时的block和局部变量一样,代码块结束,就被释放掉了,不用copy的话,就是野指针;用copy拷贝block到堆区来,这样可以长久的调用对应的block,block成为属性值才有意义。
  是不是有人在心里这么问了:为什么NSString声明成属性的时候也用copy ?
  因为copy做了这么两件事:
  1.对原来的NSString对象做了一次retain
  2.返回原来NSString对象的指针
  并且,copy过来是只读的,不能改,我猜官方是为了某种意义上的统一吧。
 
如果有不足之处,请指出,多多交流学习。在此谢过!
 

 

 

  

posted on 2015-11-03 22:13  磐玉  阅读(579)  评论(0编辑  收藏  举报

导航