内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

深度理解iOS的Block

2018-05-15 16:18 出处:清屏网 人气: 评论(0

一、内存分区

内存区域

栈区(stack):由系统自动分配,一般存放函数参数值、局部变量的值等。由编译器自动创建与释放。其操作方式类似于数据结构中的栈,即后进先出、先进后出的原则。

堆区(heap):一般由程序员申请并指明大小,最终也由程序员释放。如果程序员不释放,程序结束时可能会由OS回收。对于堆区的管理是采用链表式管理的,操作系统有一个记录空闲内存地址的链表,当接收到程序分配内存的申请时,操作系统就会遍历该链表,遍历到一个记录的内存地址大于申请内存的链表节点,并将该节点从该链表中删除,然后将该节点记录的内存地址分配给程序。

全局区/静态区:顾名思义,全局变量和静态变量存储在这个区域。只不过初始化的全局变量和静态变量存储在一块,未初始化的全局变量和静态变量存储在一块。程序结束后由系统释放。

文字常量区:这个区域主要存储字符串常量。程序结束后由系统释放。

程序代码区:这个区域主要存放函数体的二进制代码。

二、闭包

闭包(Closure)简单理解就是一个函数,加上这个函数相关的引用环境。block实际是Objc对闭包的实现。

三、Block中变量的复制与修改

  • 对于block外的变量引用,block默认是将其复制到其数据结构中来实现访问的
  • 通过block进行引用的变量是const的。也就是说不能在block中直接修改这些变量
  • 用__block关键字来声明变量,这样就可以在block中修改变量了,对于用__block修饰的外部变量引用,block是复制其引用的地址来实现访问的

四、Block的类型

block主要有三种类型,三种类型也说明了其在内存中存储的方式:

__NSGlobalBlock__:全局block并不是指全局声明的block,而是指不会访问任何外部变量,不会涉及到任何拷贝的block,比如一个不执行任何代码的block

__NSStackBlock__:保存在栈中的block,当函数返回时被销毁

__NSMallocBlock__:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由__NSStackBlock__类型的block从栈中复制到堆中形成的。

五、Block从栈拷贝到堆上的时机

当我们创建一个block的时候,block是分配在栈上的(__NSGlobalBlock__例外),类型为__NSStackBlock__,但是当下面的一些情形时会把栈上的block拷贝到堆上变成__NSMallocBlock__

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch 的API中传递Block时

六、属性Block用copy还是strong?

对于当前的ARC对于block的处理,当把一个block赋值给一个strong类型的属性时,会自动把block拷贝到堆上,并产生对该block的一个强引用。同样地,copy类型的block会在被赋值的时候进行一次copy操作,如果原来的block是在栈上,那么也会被复制到堆上。所以对于block类型的属性,用strong和copy并没有本质上的区别,推荐使用copy,可以显式地指定拷贝的动作。当然,如果block原本是堆block,那么内部并没有copy,只是进行了一次retain操作;如果block原本是全局的block,那么也没有copy,只是一次简单的赋值引用的操作

- (void)testBlock {
    
    //1.没有引用外部变量,block1为__NSGlobalBlock__
    void(^block1)() = ^{
        NSLog(@"1");
    };
    NSLog(@"%@",block1);
    
    //2.引用了外部变量,默认生成在栈上,该block为__NSStackBlock__类型
    int num = 0;
    NSLog(@"%@",^{
        NSLog(@"%d",num);
    });
    
    //3.生成在栈上的block赋值给强引用,拷贝到堆上,block2为__NSMallocBlock__
    void(^block2)() = ^{
        NSLog(@"%d",num);
    };
    NSLog(@"%@",block2);
    
    
    //4.全局block可以执行
    NSArray *globalBlocks = [self getGlobalBlocks];
    for (void(^block)() in globalBlocks) {
        NSLog(@"%@",block);
        block();
        
        // 对于全局的block进行copy操作,并不会生成新的block,使用的其实是相同的block
        void(^copyBlock)() = [block copy];
        NSLog(@"%d", copyBlock == block);
    }
    
    
    //5.堆上的block可以在外部执行
    NSArray *mallocBlocks = [self getMallocBlocks];
    for (void(^block)() in mallocBlocks) {
        NSLog(@"%@",block);
        block();
        
        // 和全局block一样,堆上的block进行copy操作也不会生成新的block
        void(^copyBlock)() = [block copy];
        NSLog(@"%d", copyBlock == block);
    }
    
    // 6.栈上的block在外部使用(野指针错误)
    NSArray *stackBlocks = [self getStackBlocks];
    for (void(^block)() in stackBlocks) {
        NSLog(@"%@",block);
        block();
    }
}

- (NSArray *)getGlobalBlocks {
    // 没有对外部变量进行应用的block为全局block,类型是__NSGlobalBlock__
    // 全局block不需要copy或者retain处理
    return [NSArray arrayWithObjects:^{NSLog(@"1");},
            ^{NSLog(@"2");},
            ^{NSLog(@"3");},
            nil];
}

- (NSArray *)getMallocBlocks {
    // 栈上的block赋给一个强引用,会自动从栈上copy到堆上
    // 当返回一个block数组的时候注意要先把block拷贝到堆上,然后再加入的数组
    int num = 1;
    void(^block1)(void) = ^{NSLog(@"%i",num);};
    void(^block2)(void) = ^{NSLog(@"%i",num);};
    void(^block3)(void) = ^{NSLog(@"%i",num);};
    return [NSArray arrayWithObjects:block1,block2,block3,
            nil];
}


- (NSArray *)getStackBlocks {
    
    /* 
    这里有个奇怪的问题,网上没有发现对应的解释。下面数组中的第一个为__NSMallocBlock__,后面的却为__NSStackBlock__,
    在方法外执行数组中的block在执行到第二个block的时候会发生崩溃
    
    个人的理解是这样的:
    - 首先需要明确,栈block只能在作用域中执行,出了作用域就会被销毁,而堆block的销毁取决于自身的引用计数,可以在作用域外调用
    - block默认创建在栈上,但数组对栈上的对象强引用无效,因为栈上的block的销毁是作用域决定的,强引用只能对堆上的对象起作用,所以数组的block在方法返回以后就被销毁了。
    - 在数组的内部应该保存了队首的位置,在添加元素的时候,这个队首的引用应该是指向第一个元素,这样就产生了一次赋值操作,所以把第一个
    栈block拷贝到了堆上转换成堆block,并进行了一次retain操作
    */
    int num = 1;
    NSArray *arr =  [NSArray arrayWithObjects:^{NSLog(@"%i",num);},
            ^{NSLog(@"%i",num);},
            ^{NSLog(@"%i",num);},
            nil];
    NSLog(@"%@",arr);
    
    /*
    注:
    // block1为__NSMallocBlock__,后面的block2和block3还是__NSStackBlock__,
    // block4,block5为__NSMallocBlock__
    void(^block1)() = arr[0];
    void(^block2)() = arr[1];
    void(^block3)() = arr[2];
    
    // block2赋值给强引用block4,把栈上的block拷贝到堆上
    // block2进行copy操作,把栈上的block拷贝到堆上
    void(^block4)() = block2;
    void(^block5)() = [block2 copy];
    NSLog(@"\n%@\n%@\n%@\n%@\n%@",block1,block2,block3,block4,block5);
    */
    
    return arr;
}
分享给小伙伴们:
本文标签: BlockiOS

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号