【2018最新】iOS面试题(一)

1.为什么OC是一门动态语言?
动态类型:OC在运行时决定对象的类型,比如id;
动态绑定:程序在运行时判断需要调用的方法,而不是在编译时;
动态载入:程序在运行时根据需要再加载可执行的代码和资源。

2.设计模式是什么?你知道哪些设计模式?简要叙述。
设计模式是一种解决问题的思维,通过设计模式达到可复用、可拓展的目的,尽量实现高内聚低耦合。
代理模式:用于回调数据。
观察者模式:当对象的某个属性发生改变时,得到通知,进行处理。
单例模式:确保给定的类只有一个实例存在,通常用于懒加载的方式在第一次用到实例的时候再去创建它。
工厂模式:根据传入的参数,动态决定创建类的实例,比如NSNumber。

3.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
通过weak打破循环引用。
delegate是一个类委托另一个类实现某个方法,协议里面的方法主要是与操作相关的。
datasource一个类通过datasource将数据发送给需要接受委托的类,协议里面的方法主要是跟内容有关的。
代理和block的区别:代理和block的共同特性是回调机制。不同的是代理的方法比较多,block代码比较集中;代理的运行成本要低于block的运行成本,block的出站需要从栈内存拷贝到堆内存。公共接口比较多时,用代理解耦;简单回调和异步线程中使用block。

4.属性的实质是什么?包括哪几个部分?属性默认的关键字有哪些?@dynamic关键字和@synthesize关键字是做什么的?
属性的实质是成员变量+存取方法。包括三部分ivar+getter+setter。属性默认的关键字有strong、assgin、weak、unsafe_unretaind、nonatomic、atomic。使用@dynamic关键字,需要手动添加getter、setter方法,@synthesize自动生成setter、getter方法。

5.OC的类可以多继承吗?可以实现多个接口吗?Category是什么?重写一个类的方法用继承好还是Category好?为什么?
OC中的类只能单继承,可以通过协议实现多个接口。Category是分类,分类中的方法具有最高优先级。重写类的方法的时候分情况使用,如果只有你个人的类中需要重写某个方法,那用继承比较好,因为Category中方法优先级的原因,使用Category重写方法,可能导致团队合作中,其他成员引入该类后出现问题。如果该需求涉及到团队中多个任务,可以通过Category重写该方法。

6.可变集合类和不可变集合类的copy和mutablecopy有什么区别?如果是集合内容复制的话,里面的元素也会一起复制吗?
可变集合类中copy和mutablecopy都是深拷贝,不可变集合中copy是浅拷贝,mutablecopy是深拷贝。元素不会一起复制,如果需要拷贝的集合中,层级比较深,使用归档的方法,可以做元素的全部拷贝。

7.nonatomic和atomic的区别?atomic是绝对的线程安全吗?为什么?如果不是,那应该如何实现?
nonatomic和atomic是原子属性,nonatomic是不是线程安全的,atomic是线程安全的。atomic不是绝对的线程安全,假设有三个线程,一个线程进行写入操作,一个线程进行读取操作,一个线程进行写入操作。如果使用atomic,在读取完成之前,值可能会被篡改。使用栅栏块(dispatch_barrier_async)解决。

8.用storyboard开发界面有什么弊端?如何避免?
storyboard开发界面时速度较快,但是后期版本更新迭代时,维护成本过高;多人协作开发过程中,冲突问题较多。为了避免更好的维护代码,storyboard更适合用来描述viewcontroller之间的关系;xib进行简单视图绘制;手动编码绘制共同性较高的视图,这样可提高移植性和降低维护成本。

9.你是否接触过OC中的反射机制?简单聊一下概念和使用?
OC的反射机制分为Class反射和SEL反射两种,Class的反射是实现类名的字符串形式实例化对象和把类名变成字符串;SEL的反射是实现方法的字符串形式实例化方法和将方法变成字符串。
如果我们要实现一个场景,根据不同情况实现某个页面进行转场,用OC的反射机制,通过runtime实现该需求最好。

10.数据持久化的几个方案(fmdb用没用过)
沙盒、钥匙串、文件存储、数据库存储
小规模数据,弱业务相关的数据放在沙盒中;生成的UUID等需要加密的非常小的数据保存在钥匙串里面;文件存储包括了plist、archive、stream等方式,一般需要方便查询的数据,会以plist的方式存储、archive适合平时很少使用,但数据量很大的数据、stream用来存储图片等数据量不算太大的那种;当数据有状态和类别等强业务相关的时候,采用数据库方案比较好。

11.block的实质是什么?一共有几种block?都是什么情况下生成的?
block实质上是一个指向结构体的指针。一共有三种block,栈上的block,堆上的block,全局的block。在ARC中,block中如果对捕获的全局变量进行操作或是未对捕获的外部变量进行操作,则该block为全局block;block中如果对捕获的栈上或堆上的变量进行操作,则该block为堆上的block。block中如果既对全局变量进行操作,也对其他内存区变量进行操作,则该block为堆上的block。

12.objc在向一个对象发送消息时,发生了什么?
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到对象实际所属的类,然后在该类中的方法列表及父类方法列表中寻找方法并运行。如果在最顶层的父类中依然没找到方法,objc会调用resolveInstanceMethod:方法,让我们动态为该对象添加一个函数实现;如果在该方法中仍没有找到对应的方法,objc会调用forwardingTargetForSelector方法,将该消息转发给其他对象;如果在调用该方法后仍没找到对应方法,首先会调用methodSignatureForSelector方法,获得需要实现函数的参数和返回值类型,如果该消息返回nil则调用doesNotRecognizeSelector方法,程序崩溃,如果返回了函数签名,runtime会创建NSInvocation对象,并调用forwardInvocation方法给目标对象。

13.runtime如何实现weak变量自动置nil?
runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表是一个哈希表,key是所指对象的地址,value是所有指向该对象的weak指针的地址构成的数组。当该对象的引用计数为0时,则遍历指向该对象的weak指针的地址所构成的数组,并置为nil。

例如 {id __weak obj1 = obj}通过伪代码实现:
定义函数objc_storeWeak(value,key);第一个参数为__weak修饰的变量obj1的地址,第二个参数为赋值对象obj的地址,如果第二个参数为0,则将变量的地址从weak表中删除。
id obj1 = 5;
objc_storeWeak(&obj1,&obj);
objc_storeWeak(&obj1,0);

14.runloop是用来做什么的?runloop和线程有什么关系?主线程默认开启了runloop吗?子线程呢?
程序启动的时候会开辟一个主线程并将该线程添加到runloop中,保证主线程不被销毁,程序持续运行;处理应用中的各种事件;如果应用处于休眠状态,则释放资源,提高系统运行效率。
runloop和线程是一对一的关系,他们之间的关系保存在全局字典中;线程不会自动创建runloop;对应线程结束时,runloop会被销毁。
主线程默认开启了runloop。
子线程没有,需要我们手动获取runloop以后,将子线程添加到runloop中。

15.为什么把NSTimer对象以NSDefaultRunLoopMode添加到主运行循环以后,滑动scrollview的时候,NSTimer却不动了。
滑动scrollview时,苹果开发为了保证界面优先原则,runloop切换到UITrackingRunLoopMode模式。将模式设置为kCFRunloopCommonModes就可以保证滑动scrollview时,NSTimer也继续走动。

16.KVO的使用?实现原理?(为什么要创建子类来实现)
让A对象监听B对象的属性,当B对象的属性发生改变时,A对象收到通知。

KVO的实现原理:当观察对象B时,KVO会通过OC的runtime动态创建一个对象B当前类的子类,并为这个新的子类重写被观察属性的setter方法,setter方法随后负责通知观察对象属性的改变状况。KVO是通过setter方法实现的,需要使用self.来修改属性对象的成员变量才会有效,直接通过成员变量复制不会触发KVO机制。

17.KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符?能不能访问私有属性?能不能直接访问_ivar)
KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性或成员变量,通过valueForKey实现getter方法的读取,通过setValue:forKey:实现setter方法的赋值,以及衍生出的keyPath方法。

KVC的实现原理:
当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

你可能感兴趣的:(面试宝典)