iOS面试题答案 --- 底层

1. KVO的实现原理

当咱们对A类添加监听的时候,系统会自动生成一个NSKVONotifying_A的子类,这个类重写了A的class、superclass、deealloc方法和该属性的Set方法,同时A类的对象的isa指针指向了该虚拟子类。当监听属性改变的时候系统调用NSSetobjectValueandNotify,这个方法的执行流程是(willchangeValueforkey->改变父类的值->didchangeValueforkey->observeValueForKey:ofObject:change:context:),若是设置automaticallyNotifiesObserversForKey:(NSString *)key为NO的时候则须要手动触发KVO即手动调用willchangeValueforkey和didchangeValueforkey.html

func _NSSetObjectValueAndNotify {
    ...
    willchangeValueforkey
    ...
    " objc_msgSendSupper '改变父类的值(猜想这样实现) "
    ...
    didchangeValueforkey
    ...
    observeValueForKey:ofObject:change:context: 
}

复制代码

Q1: 为何重写系统的class/superclass、deealloc方法

由于该子类为系统自动生成苹果想假装成并无这个类 因此重写class/superclass ,可是调用 objc_getClass()这个方法时候依然会暴露,由于这个方法是调用调用对象的isa指针指向。dealloc则是系统还有一些其余的事情处理c++

Q2:为何KVO要实现一个子类

2. KVC是什么,他是如何实现的

KVCKVC是由NSKeyValueCoding非正式协议实现的一种机制,对象采用该协议来提供对其属性的间接访问。当一个对象符合键值编码时,它的属性能够经过一个简洁、统一的消息传递接口经过字符串参数来寻址。这种间接访问机制补充了实例变量及其关联的访问方法所提供的直接访问。json

valueForkey的查找流程

  1. 在实例中搜索找到的类里面的相关方法,其名称依次为get<Key>, <key>, is<Key>, or _<key>。若是找到,则调用它,并继续执行步骤5的结果。不然继续下一步。
  2. 若是没有找到那么搜索countOf<Key> and objectIn<Key>AtIndex:,若是找到了其中的第一个和另外两个中的至少一个,则建立一个collection代理对象,该对象响应全部NSarray方法并返回该对象, 若是没有找到数组相关方法那么搜索countOf<Key>, enumeratorOf<Key>, and memberOf<Key>:,若是实现了其中一个则该对象响应全部Set方法的,
  3. 若是上述方法都没有找到那么判断accessInstanceVariablesDirectly 属性是否为yes,若是为yes则查找**_<key>, _is<Key>, <key>, or is<Key>**属性,若是找到直接返回变量值
  4. 若是都没找到那么且accessInstanceVariablesDirectly为false,那么执行valueForUndefinedKey:方法,调用valueForUndefinedKey:,默认状况下,这会引起异常,但NSObject的子类可能提供关键的特定行为

setValueForkey的实现

set value:for key:的默认实现:给定key和value参数做为输入,尝试在接收调用的对象内,使用如下过程将名为key的属性设置为value(对于非对象属性,则设置未包装的value版本,如表示非对象值:设计模式

  1. 按此顺序查找名为set<key> 和 **_set<key>**的第方法。若是找到了,使用value值调用它并完成。
  2. 若是找不到简单访问器,而且类方法accessInstanceVariablesDirectly返回Yes,则按该顺序查找名为 _<key>、_is<key>、<key>或is<key>的实例变量。若是找到,直接用value设置。
  3. 在找不到方法或实例变量时,调用setValue:ForUndefinedKey:。默认状况下,这会引起异常,但NSObject的子类可能提供关键的特定行为。

3. 请简单介绍一下Runtime,以及它的原理和应用(消息发送机制,动态解析。应用防止崩溃)

OC的代码分为编译时和运行时,Runtime是运行时处理oc的各个事件,好比动态添加类添加属性,以及他的底层是一套C和C++还有汇编的api. OC的消息调用底层调用时objc_msgSend,这个消息流程是有两种方式一种是快速发送一种是慢速发送,类的本质是结构体,他的构成有 isa和class_rw_t和class_data_bits_t的结构体、superclass,以及cache类,类结构里的cache_t缓存存储方法的Selector和IMP,它们组成一张哈希表,经过哈希表查找很是快,当查找的时候若是cache里面存在则会直接返回,不存在的话则会进入慢速发送,找到了会在缓存里面存一份。
快速查找:objc_msgSend是由汇编进行的他的过程是 先是优化isa指针,优化完毕后执行CacheLookup Normal,在CacheLookup里进行CacheHit若是缓存里有则会返回imp,没有的话则会继续走 checkmiss(checkmiss发送objc_msgSend_uncached)消息,处理完后执行add,,将查找到的消息放到缓存里面,
慢速查找objc_msgSend_uncached是一个结构体里面调用了 MethodTableLookup 方法列表查找,而后经过br x 17 返回当前imp,MethodTableLookup内部方法是执行了 这个方法里面有一个**__class_lookupMethodAndLoadCache3**,这个方法是_class_lookupMethodAndLoadCache3的汇编方法,当调用这个方法的时候的到了c++的lookUpImpForward,这个方法显示声明了一系列的初始化操做,经过递归先查找自身类若是自身类存在则返回,若是不存在则查找父类,直道为nil为止,中间若是查找到则返回并缓存一份经过log_and_fill_cache,若是都没有则进入动态方法解析流程
动态消息解析: 当调用者自身和父类没有实现方法时候会地用 _class_resolveMethod 方法这个方法里面若是是类方法则执行resolveClassmethod(这个方法实际上是元类的resolveinstancemethod),而后一直调到根源类的父类NSObject。若是是实例方法则直接调用resolveinstancemethod,若是有处理则处理,若是未处理则调用__objc_msgForward_impcache,由于苹果的这里面实现是闭源的,能够经过instrumentObjcMessageSends这个函数来进行查找,找到对应路径会发现调用顺讯就是 forwardTragemethodSignutre 最后未处理执行doesnotGesture。若是没有处理的话汇编后面还会调用 __objc_forward_handler来进行处理这个方法是打印出了出错的堆栈信息。api

4. 什么是block?(堆block,栈block,全局block)

block匿名函数,分为堆block,栈block和全局block,若是block未引用任何变量那么他是一个全局block,若是未出做用域则是栈block,若引用了外界属性,那么他是一个堆block,block默认是存储在堆里的,ARC环境下回 自动执行copy操做。block的本质是 __main_block_impl_0 结构体里面的结构是isa指针指向堆内存或者栈内存或者全局内存和 flag参数,isa指针指向本身的类(global malloc stack),有desc结构体描述block的信息,数组

Q1:Block是如何从栈区拷贝到堆区的呢。

Block不容许修改外部变量的值。这里所说的外部变量的值,指的是栈中指针的内存地址。__block所起到的做用就是只要观察到该变量被block所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也能够修改外部变量的值。 block对一个值进行引用时他会拷贝到一个新的内存地址,所以他修改的值不是原来的外部变量地址,而是修改了外部变量copy到的新的内存地址,不影响旧的。block以后再访问其实访问的其实访问的是拷贝完以后的内存地址。
那么 __block作的什么操做,__Block_byref_a_0 这个指针获取到了外部参数(栈区)的地址,而后下面的block传入的是__Block_byref_a_0类型的指针地址,他再也不是原来的栈区的地址,所以能够在闭包里访问到外部变量,那么出block做用域后 __Block修饰的变量内存地址已经为新的了因此在此访问就是访问的新内存空间缓存

5. weak的实现原理

Runtime维护了一个weak表,weak是一个哈希表,key是对象地址,value是weak指针的数组 当对一个属性进行weak修饰时 会先调用objc_initweak,初始化一个新的weak指针指向地址,添加引用时调用objcstroeweak函数,这个函数是更新指针指向,释放时调用cleardeallocing函数,该函数会搜索以该对象内存地址为key,对应的全部value(weak指针数组)并将其置为nil,后从entry从weak表中删除。清理对象记录。安全

6. 通知和代理,block的区别。

通知一对多,代理一对一,通知基于观察者实现。bash

通知只能通知不能接受回调,代理能够反向通知数据结构

delegate运行成本低,block的运行成本高。**block出栈须要将使用的数据从栈内存拷贝到堆内存,固然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多作了一个查表动做。

delegate更安全些,好比: 避免循环引用。使用 block 时稍微不注意就造成循环引用,致使对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,所以会更安全些。

7. 说说你读过的第三方库工做流程。

SwiftyJSON

这个类是一个JSON结构体初始化的时候能够传入object、string、dictionary等其余操做单最后的数据结构都是有一个 ojbect 来进行初始化,而且定义有各个类型dictiorary string array bool int double,同时还支持路径访问,经过merge操做合并json,若是对象是数组则直接进行append操做,若是是字典则对字典的key进行赋值,原有则覆盖,若是value是数组则进行递归调用

KingFisher

图片加载框架

Alamofire

8. OC类的本质是什么?他的结构是什么,他是如何初始化的

oc类的本质是结构体,他的结构是里面有 isa指针,supeprcalss,cache类,data,class_rw_t,class_ro_t, data里面是这个类全部的数据,包括方法列表属性列表接口列表,由class_rw_t,和class_ro_t进行维护。

他初始化经过oc源码NSObject.mm 文件能够看到 当进行初始化操做的时候先执行 _objc_rootAlloc ==》callAlloc ==》 class_createInstance ==》 _class_createInstanceFromZone ==》_objc_constructOrFree 得到该对象,完成后还会执行init操做,而init方法调用**_objc_rootInit**,返回了自身

是由于,oc对象的init方法采用了 简单工厂设计模式,他只须要设计一个init功能,由子类进行初始化从而获得不一样的对象。

9. Swift类的初始化发生了什么?

这个须要经过Swift的源码进行源码分析,这个我暂时还没找到相关方面。

相关文章
相关标签/搜索