iOS中的闭包(二)

作者: 纪小衰 | 来源:发表于2018-05-11 17:02 被阅读52次

    一、内存分区

    内存区域

    栈区(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;
    }
    

    相关文章

      网友评论

        本文标题:iOS中的闭包(二)

        本文链接:https://www.haomeiwen.com/subject/fxtqdftx.html