iOS 常见面试题总结

1. 空指针、野指针以及僵尸对象的理解,如何避免野指针错误。
解答:内存被释放的对象为僵尸对象(不能再使用的对象)。指向僵尸对象(不可用内存)的指针为野指针,给野指针发送消息会报EXC_BAD_ACCESS错吴。没有指向任何存储空间的指针为空指针,里面存的是nil,给空指针发送消息不会报错。一般在对象被销毁后,将指针对象的指针变为空指针来避免野指针错误。

2. nil、Nil、NULL、NSNULL的含义和区别?
解答:nil代表OC中对象的空指针、Nil代表OC中类的空指针、NULL代表C类型的空指针、NSNull基本数据类型的空指针。

//定义某一实例对象为空值
NSObject *obj;
if (nil == obj) {
    NSLog(@"obj is nil");
} else {
    NSLog(@"obj is not nil");
}

//定义某一个类为空
Class someClass = Nil;  
Class anotherClass = [NSString class];

//NULL是无类型的,只是一个宏,它代表为空。用于c语言的各种数据类型的指针为空
int *pointerToInt = NULL;
char *pointerToChar = NULL;
struct TreeNode *rootNode = NULL;

//集合对象无法包含 nil 作为其具体值,如NSArray、NSSet和NSDictionary。相应地,nil值用一个特定的对象 NSNull 来表示。NSNull 提供了一个单一实例用于表示集合对象属性中的的nil值。
NSDictionary *dic = @{@"testNull": [NSNull null]};
NSLog(@"%@", dic);

3. 在block内如何修改block外部变量?
解答:block可以直接修改全局变量,包括static修饰过的静态变量,因为全局变量内存是存放在堆中。不能直接修改局部变量,需要在局部变量前加标识位__block修饰,局部变量内存是存放在栈中的,__block就把内存由栈区放在堆区。

4.使用block时什么情况会发生引用循环,如何解决?
解答:当我们再代码中声明block时,声明的对象都会以copy的形式持有block,而block对于它内部的对象也会进行强引用,从而导致了循环引用。

self -> block -> self/实例变量
局部变量 -> block -> 局部变量
实例变量 -> block -> 实例变量

在使用block进行回调时,出现这样的情况是难免的,有时候可能还会在block内部强引用多个实例变量和self,解决的办法是一样的,即在block的内部使用弱引用修饰符__weak。

__weak typeof(self) weakSelf = self;

引深问题1:是不是所有的block中,使用self都会导致循环引用?
解答:并不是。使用系统自带的block不会引起循环引用。举个例子,使用系统自带的UIView动画block,block里直接使用self,控制器是能够被销毁的。说明没有造成循环引用。 原因是UIView的调用的是类方法,当前控制器不可能强引用一个类 ,所以循环无法形成, 动画block不会造成循环引用的原因。

5.block什么时候需要构造循环引用?有没有这样一个需求场景,block 会产生循环引用,但是业务又需要你不能使用 weak self? 如果有,请举一个例子并且解释这种情况下如何解决循环引用问题。
解答:需要不使用 weak self 的场景是,你需要构造一个循环引用,以便保证引用双方都存在。比如你有一个后台的任务,希望任务执行完后,通知另外一个实例。在我们开源的 AFN 网络库的源码中,就有这样的场景。
在 AFN3之前库中,我们的每一个网络请求 API 会持有回调的 block,回调的 block 会持有 self,而如果 self 也持有网络请求 API 的话,我们就构造了一个循环引用。虽然我们构造出了循环引用,但是因为在网络请求结束时,网络请求 API 会主动释放对 block 的持有,因此,整个循环链条被解开,循环引用就被打破了,所以不会有内存泄漏问题。代码其实很简单,如下所示:

block = nil;

总结来说,解决循环引用问题主要有两个办法:
第一个办法是「事前避免」,我们在会产生循环引用的地方使用 weak 弱引用,以避免产生循环引用。
第二个办法是「事后补救」,我们明确知道会存在循环引用,但是我们在合理的位置主动断开环中的一个引用,使得对象得以回收。

5.instancetype与id的区别?
解答:instancetype和id都表示未知类型的对象,本质也是指向一个对象。
(1)id在编译的时候不能判断对象的真实类型,instancetype在编译的时候可以判断对象的真实类型。
(2)id和instancetype除了一个在编译时不知道真实类型, 一个在编译时知道真实类型以外, 还有一个区别id可以用来定义变量,可以作为返回值,可以作为形参。而instancetype只能用于作为作为返回值。
(3)如果init方法的返回值是instancetype, 那么将返回值赋值给一个其它的对象会报一个警告, 如果init方法的返回值是id,那么将init返回的对象地址赋值给其它对象是不会报错的,自定义构造方法时,返回值尽量使用instancetype, 不要使用id。

6.Objective-C有GC(垃圾回收机制)吗?
解答:OC是支持垃圾回收机制的(Garbage collection简称GC)。macOS开发中是支持的,18.8之后弃用了GC。iOS开发中是不支持的。iOS开发只支持手动内存管理(MRC)和ARC,ARC是iOS5之后推出的新技术,它与GC的机制是不同的。


引深问题1:Objective-C回收机制的特点和区别与GC?
解答:ARC 的特点是自动引用技术简化了内存管理的难度,我们在编写代码时, 不需要向对象发送release或者autorelease方法,也可以不调用delloc方法,编译器会在合适的位置自动给用户生成release消息(autorelease)。
区别:ARC介于自动垃圾回收(GC)和手动内存管理(MRC)之间。就像垃圾回收,ARC让程序员不再需要书写retain/release/autorelease语句。但它又不同于垃圾回收,ARC无法处理retaincycles。在ARC里,如果两个对象互相强引用(strong references)将导致它们永远不会被释放,甚至没有任何对象引用它们。因此,尽管ARC能免去程序员大部分内存管理问题,但仍然要程序员自己避免retaincycles或手动打断对象之间的retain循环。

7.Objective-C是如何实现内存管理的?autorealease pool自动释放池是什么?autorelease的对象是在什么时候被release的?autorelease和release有什么区别?
  内存管理主要有三种方式ARC(自动引用计数)、MRR(手动内存计数)、内存池(自动释放池)。
  引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式,一般概念是:当创建一个新的对象时,初始的引用计数为1。为保证对象的存在,每当创建一个引用到该对象时,通过给对象发送retain消息,为引用计数加1;当不再需要对象时,通过给对象发送release消息,为引用计数减1;当对象的引用计数为0时,系统就知道这个对象不再使用了,通过给对象发送dealloc消息,销毁对象并回收内存。一般在retain方法之后,引用计数通常也被称为保留计数(retain count)。
  手动管理内存,即MRR(manual retain-release),是基于引用计数来实现的,通过自己跟踪对象来明确管理内存。它与ARC之间的唯一区别是:在MRR中,对象的保留和释放都是由我们手动处理,而在ARC中是自动处理的。
  自动释放池(autorealease pool):是一种半自动内存管理方式,具有延时释放的特性。即当我们创建了一个对象,并把他加入到了自动释放池中时,他不会立即被释放,会等到一次runloop结束或者作用域超出{}或者超出[pool release]之后再被释放。

8.在一个对象释放前,如果他被加到了notificationCenter 中,不在notificationcenter中remove这个对象可能会出现什么问题?
解答:对象被release,会导致出现野指针错误。

9.为什么很多内置类如UITableView的delegate属性都是assign而不是retain ?
  如果是retain会引起循环引用。
  所有的引用计数系统,都存在循环引用的问题。例如下面的引用关系:对象a创建并引用了对象b,对象b创建并引用了对象c,对象c创建并引用了对象b.这时候b和c的引用计数分别是2和1。当a不再使用b,调用release释放对b的所有权,因为c还引用了b。所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中。这种情况,必须打断循环引用,通过其他规则来维护引用关系。
  比如,我们常见的delegate往往是assign方式的属性而不是retain方式的属性,赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。如果一个UITableViewController对象a通过retain获取了UITableView对象b的所有权,这个UITableView对象b的delegate又是a,如果这个delegate是retain方式的,那基本上就没有机会释放这两个对象了。

你可能感兴趣的:(iOS 常见面试题总结)