OC面试题 二、Block

329 阅读6分钟

Block的本质是什么?

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象

循环引用的几种场景

  1. delegate循环引用 如果delegate使用strong修饰容易引起循环引用

  2. block循环引用 block和self的相互持有会造成循环引用

  3. NSTimer循环引用 timer和self的相互引用造成循环引用 在删除对象之前,需要调用timer的invalidate方法

  4. NSMutableArray加入self造成循环引用

@interface SomeObj : NSObject
@property (nonatomic, strong)   NSMutableArray *array;
@end

[_array addObject:self];
  1. 使用类别添加属性 比如:有一个类A,给A动态添加属性p。如果p中再引用类A,容易造成循环引用

delegate属性为什么要用weak修饰?

  • 如果用strong修饰,会对监听代理的对象造成强引用,从而对该对象的生命周期产生影响。
  • strong:修饰对象是对该对象进行强引用,外界不能销毁该对象,会导致循环引用(Retain Cycles)
  • weak:修饰对象只是指明该对象,并不负责持有这个对象,对象的销毁是由外部控制的。

为什么__weak 可以解决循环引用?

__weak 声明了一个可以自动 nil 化的弱引用,声明而不持有对象。 self强持有block,block里又强持有self,造成循环引用,__weak声明弱引用对象 __weak只能在ARC模式下使用,也只能修饰对象(如NSString),不能修饰基本数据类型(如int)。

  1. 初始化一个weak对象时,runtime会调用一个objc_initWeak函数初始化一个新的weak指针指向该对象的地址

  2. 在objc_initWeak函数中会继续调用objc_storeWeak函数,在这个过程是用来更新weak指针的指向,同时创建对应的弱引用表WeakTable

  3. 在对象释放时,会调用clearDeallocating函数,这个函数会根据对象地址获取所有weak指针数组,然后遍历这个数组置为nil。最后把该条对象的记录从weak表中删除

  4. weak指针指向对象,不会让对象的引用计数增加,所以block内部就不会持有self对象,破解循环引用。

为什么__Block也可以解决循环引用?

  • 因为通过__block的修饰,这时候Block在编译的过程中就会获取外部变量的指针,通过指针来修改变量。
  • __block的作用就是将变量复制到堆上去,自己管理生命周期!
  • 加了__block关键字之后,会将原本存储在栈区的指针,放到了堆中,而不是复制了一个副本到堆中,所以(block内部)和(结束)指针的地址是相同的且都存在于堆中。

block的变量捕获

image.png

block的属性修饰词为什么是copy?使用block有哪些使用注意?

Block 引用了普通外部变量,都是创建在栈区的;对于分配在栈区的对象,我们很容易会在释放之后继续调用,导致程序奔溃,所以我们使用的时候需要将栈区的对象移到堆区,来延长该对象的生命周期。

为什么__block可以修改局部变量

  • 相当于加了个标识位,遇到__block就把内存由栈区放在推区。
  • 对于__block修饰的局部变量,Block捕获时,记录了该变量的地址。所以后续该变量的值改变了,block调用时,通过地址获取到的值仍然是最新的值。
  • __block将局部变量内存的值传递到block内部

block有哪些类型,区别是什么?

block类型执行copy操作后的效果
全局block(NSGlobalBlock)什么也不做,不会产生新的block
栈block(NSStackBlock)复制一份栈block到堆区
堆block(NSMallocBlock)仅仅是block的引用计数加1,不会产生新的block

Block和Protocol的区别?

  • 代理和block的共同特性是回调机制,不同的是,代理的方法比较多,比较分散,公共接口,方法较多也选择用delegate进行解耦
  • 使用block的代码比较集中统一,异步和简单的回调用block更好

Block是为了解决什么问题而使用的?

  • block为了多线程之间调度产生的;
  • block 也是一个OC对象,可以当参数传递,使用方便简单,灵活,很少的代码就可以实现代码回调.比协议省很多代码

isEquel和hash的关系?

  1. isEquel 用于比较2个对象是否相等, 与==(地址比较)不一样, 可以重写isEquel方法,来进行2个对象的比较
  2. hash 是一个类方法,任何类都可以调用这个方法,返回的结果是一个NSInteger值(如果两个对象相等,那么他们的hash值一定相等,但是,如果两个对象的哈希值相等是不能一定推出来这两个对象是相等的)

isEquel 和 isEquelToString

  • isEquel 比较的是2个NSObject的方法
  • isEquelToString是比较2个字符串值是否相等
  • isEquel 首先比较2个对象地址,如果相同就返回YES,如果不同,就比较对象类型,以及属性的值,一般重写 isEquel 来比较2个对象

容错处理你们一般是注意哪些?

  1. 字典

  2. 数组;

  3. 野指针;

  4. NSNull

项目开始容错处理没做?如何防止拦截潜在的崩溃?

例:

1、category给类添加方法用来替换掉原本存在潜在崩溃的方法

2、利用runtime方法交换技术,将系统方法替换成类添加的新方法。

3、利用异常的捕获来防止程序的崩溃,并且进行相应的处理。

4、使用 @try__Catch__方法进行拦截

总结:

1、不要过分相信服务器返回的数据会永远的正确。

2、在对数据处理上,要进行容错处理,进行相应判断之后再处理数据,这是一个良好的编程习惯。

函数指针和 Block区别

相同点:

  • 二者都可以看成是一个代码片段
  • 函数指针类型和 Block 类型都可以作为变量和函数参数的类型

不同点:

  • 函数指针只能指向预先定义好的函数代码块,函数地址是在编译链接时就已经确定好的。从内存的角度看,函数指针只不过是指向代码区的一段可执行代码
  • block 本质是 OC对象,是 NSObject的子类,是程序运行过程中在栈内存动态创建的对象,可以向其发送copy消息将block对象拷贝到堆内存,以延长其生命周期。

__weak 和 _Unsafe_Unretain 的区别?

  • __weak 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,在离开作用域的时候会将指针赋值null,自动置为 nil

  • _Unsafe_Unretain 不会置为 nil,在对象离开作用域的时候就会释放,容易出现 悬垂指针,发生 崩溃 。但是 _Unsafe_Unretain 比 __weak 效率高

__strong在Block内部中的使用

防止self被提前释放,暂时持有对象,出了作用域就会释放该对象。

__weak修饰的对象被Block引用,不会影响对象的释放,而__strong在Block内部修饰的对象,会保证,在使用这个对象在作用域内,这个对象都不会被释放,出了作用域,引用计数就会-1,且__strong主要是用在多线程运用中,如果只使用单线程,只需要使用__weak即可。