聊一聊Block(二)

函数内定义的变量叫做局部变量,函数外部定义的变量叫做全局变量。

block封装了函数调用,以及函数调用环境的oc对象

- (void)viewDidLoad {
    [super viewDidLoad];
    
    int age = 10;
    void (^blockName)(void) = ^{
        NSLog(@"%d",age);
    };
    blockName();
    
}

源码如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int age;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_zs_r4g726_d3rd7y0hvw0_7ckym0000gn_T_ViewController_3bd104_mi_0,age);
    }


static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    int age = 10;
    void (*blockName)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, age));
    ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);

}
image.png

自动变量截获值不截获指针,是因为自动变量随时可能销毁,如果解惑指针,有可能会在访问自动变量的时候指针已经销毁
代码举例:

void (^block)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    test(); 
    block();// 作用域外age已经被销毁,所以如果age是指针传递,则此时age的地址已经被销毁,会造成坏内存访问
}

void test() {
    int age = 10;
    static int height = 110;
    block = ^{
        NSLog(@"%d, %d", age, height);
    };
    age = 10; 
    height = 120;
}

如何证明self是一个局部变量

void test () {
     void (^blockName)(void) = ^{
        NSLog(@"%@",self);
    };
    blockName();
}

编译之后的代码:
struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main__block_desc_0;
      XSYPerson *self; // 会被捕获,所以说明self是局部变量
}

test的未省略方法为:
void test(XSYPerson *self ,SEL _cmd)
 {
    void (^blockName)(void) = ^{
        NSLog(@"%@",self);
    };
    blockName();
 }

能被block捕获的变量都是局部变量

全局变量不会捕获,局部变量会捕获。

void test () {
     void (^blockName)(void) = ^{
        NSLog(@"%@",_name );
    };
    blockName();
}

编译之后的代码:

struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main__block_desc_0;
      XSYPerson *self; // 依然捕获的是self对象
}
因为
_name 是 self->_name这样来访问的

block的内存布局

image.png
image.png

isa决定了属于哪个类型的block

为什么说block是对象

  1. superclass最终是nsobject
  2. c++编译成功是一个带有isa的结构体,这跟nsobject的结构相同

block的类型

image.png

在arc环境下,一下情况会自动调用copy将blockcopy到堆上

  1. block作为函数返回时
  2. 将block赋值给__strong指针时
  3. block作为Cocoa API中方法名含有usingBlock的方法参数时
  4. block作为GCDAPI的方法参数时
    拷贝到堆上,就是为了防止栈block被销毁,从而引发坏内存访问。
typedef void (^XSYBlock)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    XSYBlock block;

    {
        XSYPerson *person = [[XSYPerson alloc] init];
        block = ^{
        // 此处堆person有一个retain的操作。
            NSLog(@"%@",person);
        };
    }
    block();
    
}

// person不会被释放的原因:
因为person是自动变量,所以会被block捕获,又因为是对象,则连通修饰符一起被捕获,所以person的引用计数加1。所以在块外部,因为block没有释放,所以person也不会被释放。

如果block是在栈上,block内部不会对person产生强引用。

    ^{
        // 此处堆person有一个retain的操作。
            NSLog(@"%@",person);
        };
此处block对person没有强引用,即便person自己是强引用。

解释:block自己都保不住自己的命,person更保不住。所以无需强引用。

block copy的操作流程

如果block被拷贝到堆上:
会调用block内部的copy函数,
copy函数会调用_Block_object_assign函数
_block_object_assign函数会根据auto变量的修饰符(__strong, __weak, __unsafe_retain)做出相应的操作,类似于retain(强引用,弱引用)
如果block从堆上移除:
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量,类似于release


image.png

__block

不能修饰可以解决block内部无法修改auto变量的问题
__block不能修饰全局变量和静态变量。
编译器将__block变量包装称为一个对象。


image.png
image.png

__block修饰的自动变量的本质

__block int age = 10;
    void (^arrBlockName)(void) = ^ {
        age = 11;
    };
arrBlockName();
NSLog(@"%d",age);
输出:11

struct __Block_byref_age_0 {
    void *isa;
    __Block_byref_age_0 *__forwarding;
    int flags;
    int __size;
    int age;
}

struct __main_block_impl_1 {
    void *isa;
    struct _block_impl *impl;
    struct __main_block_desc_0 *Desc;
    struct __Block_byref_age_0 *age;
}

__block的内存管理

__block int age = 10;
void (^arrBlockName)(void) = ^ {
    age = 11;
};

因为__block对象是自动变量,所以在栈上
block被强引用修饰,被拷贝到堆上
这个时候,__block也会被拷贝到堆上,block内部堆__block对象是强引用,引用计数加一。
当block时放的时候,__block会通过dispose函数销毁

当block使用__block对象和NSObject对象的区别

相同点:

  1. 当block在栈上,block对两种对象都不会产生强引用
  2. 两个对象都是通过copy方法拷贝到堆上
  3. 两个对象都是通过dispost方法销毁
    不同点:
  4. NSObject对象会通过修饰符来进行强引用或弱引用
  5. block对 __block对象只存在强引用

当__block变量在栈上时,不会对__block变量产生强引用

  • 当__block变量被copy到堆时,
    会调用__block变量内部的copy函数,
    copy函数会调用_Block_object_assign函数
    _Block_object_assign会根据所指向的对象的修饰符(__strong,__weak, __unsafe_unretain)做出相应的操作,形成强引用或弱引用(注意:这里仅限于ARC时retain,MRC时不会retain)
    • 如果__block变量从堆上移除
      会调用__block变量内部的dispose函数
      dispose函数内部会调用_Block_object_dispose函数
      _Block_object_dispose函数会自动释放指向的对象(release)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容