• 注册
当前位置:1313e > 默认分类 >正文

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.

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

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

因为该子类为系统自动生成苹果想伪装成并没有这个类 所以重写class/superclass ,但是调用 objc_getClass()这个方法时候依然会暴露,因为这个方法是调用调用对象的isa指针指向。dealloc则是系统还有一些其他的事情处理

Q2:为什么KVO要实现一个子类

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

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

valueForkey的查找流程

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

setValueForkey的实现

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

  1. 按此顺序查找名为set 和 **_set**的第方法。如果找到了,使用value值调用它并完成。
  2. 如果找不到简单访问器,并且类方法accessInstanceVariablesDirectly返回Yes,则按该顺序查找名为 _、_is或is的实例变量。如果找到,直接用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来进行处理这个方法是打印出了出错的堆栈信息。

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的区别。

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

通知只能通知不能接受回调,代理可以反向通知

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的源码进行源码分析,这个我暂时还没找到相关方面。

转载于:https://juejin.im/post/5ca47f57f265da308c199870

本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 162202241@qq.com 举报,一经查实,本站将立刻删除。

最新评论

欢迎您发表评论:

请登录之后再进行评论

登录
相关推荐