IOS基础知识-block原理篇

面试题

block的原理是怎样的?本质是什么?
__block的作用是什么?有什么使用注意点?
block的属性修饰词为什么是copy?使用block有哪些使用注意?
block在修改NSMutableArray,需不需要添加__block?

首先对block有一个基本的认识
block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
block中各个结构体之间的关系如下图:


1434508-1ecebe9ad79c8c11.png

block底层数据结构如下图:


1434508-b6f59468ae71b22b.png

为了保证block内部能够正常访问外部的变量,block有一个变量捕获机制:
局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获;
block最终都是继承自NSBlock类型,而NSBlock继承于NSObjcet。那么block其中的isa指针其实是来自NSObject中的。这也更加印证了block的本质其实就是OC对象。
block调用copy会改变block类型吗?
1434508-74811a740c972d8e.png

所以在平时开发过程中MRC环境下经常需要使用copy来保存block,将栈上的block拷贝到堆中,即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。而在ARC环境下回系统会自动copy,是block不会被销毁。
什么情况下?ARC会对block做自动copy操作

block作为返回值时,比如使用“=”;
将block赋值给__strong指针时;
block作为Cocoa API中方法名含有usingBlock的方法参数时;
block作为GCD API的方法参数时;

总结

一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。
当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰还是__weak修饰,都不会对变量产生强引用。
如果block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)做出相应的操作,形成强引用或者弱引用
如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量

  1. 下列代码person在何时销毁 ?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",person);
    });
    NSLog(@"touchBegin----------End");
}
分析:在block执行完毕后被释放;在ARC环境中,block作为GCD API的方法参数时会自动进行copy操作,因此block在堆空间,并且使用强引用访问person对象,因此block内部copy函数会对person进行强引用。当block执行完毕需要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。

2、下列代码person在何时销毁 ?

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    Person *person = [[Person alloc] init];
    
    __weak Person *waekP = person;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",waekP);
    });
    NSLog(@"touchBegin----------End");
}
分析:person先销毁,才执行block,打印为空;block中对waekP为__weak弱引用,因此block内部copy函数会对person同样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。因此block块执行的时候打印null。

block中修改变量的值:
方式一:age使用static修饰。
前文提到过static修饰的age变量传递到block内部的是指针,在__main_block_func_0函数内部就可以拿到age变量的内存地址,因此就可以在block内部修改age的值。
方式二:__block
__block用于解决block内部不能修改auto变量值的问题,__block不能修饰静态变量(static) 和全局变量
__block为什么能修改变量的值?
__block将变量包装成对象,然后在把age封装在结构体里面,block内部存储的变量为结构体指针,也就可以通过指针找到内存地址进而修改变量的值。
3、以下代码是否可以正确执行

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [NSMutableArray array];
        Block block = ^{
            [array addObject: @"5"];
            [array addObject: @"5"];
            NSLog(@"%@",array);
        };
        block();
    }
    return 0;
}
分析:可以正确执行,因为在block块中仅仅是使用了array的内存地址,往内存地址中添加内容,并没有修改arry的内存地址,因此array不需要使用__block修饰也可以正确编译。因此当仅仅是使用局部变量的内存地址,而不是修改的时候,尽量不要添加__block,通过上述分析我们知道一旦添加了__block修饰符,系统会自动创建相应的结构体,占用不必要的内存空间。

解决循环引用问题 - ARC
使用__weak 和 __unsafe_unretained修饰符可以解决循环引用的问题;
__weak不会产生强引用,指向的对象销毁时,会自动将指针置为nil。因此一般通过__weak来解决问题。

__unsafe_unretained不会产生前引用,不安全,指向的对象销毁时,指针存储的地址值不变。
解决循环引用问题 - MRC
使用__unsafe_unretained解决;

你可能感兴趣的:(IOS基础知识-block原理篇)