转载

iOS 进阶原理知识笔记

KVO实现原理

KVO基本原理:

  1. kvo是基于runtime机制实现的

  2. 当某个类的属性对象第一次被观察时,系统就会在运行期动态的创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法,派生类在被重写的setter方法内实现真正的通知机制

  3. 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

  4. 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的派生类的setter方法

  5. 键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:和didChangeValueForKey:在一个被观察属性发生改变之前,willChangeValueForKey:会被调用,这就会记录旧的值,而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context: 也会被调用。

KVO深入原理:

  1. Apple使用了isa混写(isa-swizzling)来实现KVO,当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用原setter方法之前和之后,通知所有观察对象属性值的更改情况。

  2. NSKVONotifying_A类剖析:在这个过程,被观察对象的isa指针从只想原来的A类,被KVO机制修改为指向系统新创建的自雷NSKVONotifying_A类,来实现当前类属性值改变的监听。

  3. 所以当我们从应用层面上来看,完全没有意识到有新的类的除夕拿,这是系统“隐藏”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为NSKVONotifying_A的类,就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。

  4. (isa指针的作用:每个对象都有isa指针,指向该对象的类,它告诉Runtime系统这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇的变成新子类的对象(或实例)了)因而在该对象上对setter的调用就会调用已重写的setter,从而激活键值通知机制。

  5. 子类setter方法剖析:KVO的键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:和didChangeValueForKey:,在存取数值的齐纳后分别调用2个方法,被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该keyPath的属性值即将变更,当改变发生后,didChangeValueForKey:被调用,通知系统该keyPath的属性值已经变更,之后observeValueForKey:ofObject:change:context: 也会被调用,且重写观察属性的setter方法这种集成法师的注入实在运行时而不是在编译时实现的。

iOS 进阶原理知识笔记

消息转发机制原理

消息转发机制基本分为三个步骤:

  1. 动态方法解析

  2. 备用接受者

  3. 完整转发

iOS 进阶原理知识笔记

week属性

weak实现原理

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针,weak表其实是一个hash(哈希)表,key时所指对象的地址,Value时weak指针的地址(这个地址的值时所指对象的地址)数组。

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

  2. 添加引用时,objc_initWeak函数会调用objc_storeWeak()函数,objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表。

  3. 释放时,调用clearDeallocating函数,clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

实现weak后,为什么对象释放后会自动为nil

runtime对注册的类,会进行布局,对于weak对象会放入一个hash表中,用weak指向的对象内存地址作为key,当此对象的应用能够技术为0 的时候会dealloc,加入weak指向的对象内存地址为a, 那么就会以a为键,在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢

1调用objc_release2因为对象的引用计数为0,所以执行dealloc3 在dealloc中,调用了_objc_rootDealloc函数4在_objc_rootDealloc中,调用了object_dispose函数5调用objc_destructInstance6最后调用objc_clear_deallocating:详细过程如下:a.从weak表中获取废弃对象的地址为键值的记录b将包含在记录中的所有附有weak修饰符变量的地址,赋值为nil c将weak表中该记录删除 d从引用计数表中删除废弃对象的地址为键值的记录。

如何优化过于臃肿的Controller

1、将网络请求抽象到单独的类中

方便在基类总处理公共逻辑 方便在基类中处理缓存逻辑,以及其他一些公共逻辑 方便作对象的持久化

2、将界面的封装抽象到专门的类中

构造专门的UIview的自雷,来负责这些控件的拼装,这是最彻底和优雅的方式,不过稍微麻烦一些的是,你需要把这些空间的事件回调先接管,再都一一暴露回controller

3 构造viewmodel

借鉴MVVM,具体做法将VIewConttroller给view传递数据这个过程,抽象成构造viewmodel的过程。

4 专门构造存储类

专门来出来本地数据的存取

5 整合常量

项目中网络层如何做安全处理

1、尽量使用https

https可以过滤掉大部分的安全问题,https在证书申请,服务器配置,性能优化,客户端配置上都需要投入精力,所以缺乏安全意识的开发人员容易跳过https,或者拖到以后遇到问题再优化,https除了性能优化麻烦一些以外其他都比想象中的简单,如果没有精力优化性能,至少在注册登录模块需要启用https,这部分业务对性能要求比较低。

2、不要传输明文密码

不知道现在还有多少app后台是明文存储密码的。无论客户端,server还是网络传输都要避免明文密码,要使用hash值。客户端不要做任何密码相关的存储,hash值也不行。存储token进行下一次的认证,而且token需要设置有效期,使用refresh token去申请新的token

3、post并不比get安全

事实上,post和get一样不安全,都是明文,参数放在queryString或者body没有任何安全上的差别,在http的环境下,使用post或者get都需要做加密和签名处理。

4、不要使用301跳转

301跳转很容易被http劫持攻击,移动端http使用301比桌面端更危险,用户看不到浏览器地址,无法察觉到被重定向到了其他地址,如果一定要使用,确保跳转发生在https的环境下,而且https做了证书绑定校验。

5、http请求都带上MAC

所有客户端发出的请求,无论是查询还是写操作,都带上MAC(Message Authentication Code)。Mac不但能保证请求没有被篡改,还能保证请求确实来自你的合法客户端。当然前提是你客户端的key没有被泄露,如何保证客户端key的安全是另一话题,mac值的计算可以简单的处理为hash(request+params+key)。带上mac后,服务器就可以过滤掉绝大部分的非法请求,mac虽然带有签名的功能,和rsa证书的电子点名方式却不一样,原因是mac签名和签名验证使用的是同一个key。而rsa是使用私钥签名,公钥验证,mac的签名并不具备法律效应。

6、http请求使用临时密钥

高延迟的网络环境下,不经优化https的体验确实会明显不如http。在不具备https条件或对网络性能要求较高且缺乏https优化经验的场景下,http的流量也应该使用AES进行加密。AES的密钥可以由客户端来临时生成,不过这个临时的AES key需要使用服务器的公钥进行加密,确保只有自己的服务器才能解开这个请求的信息,当然服务器的response也需要使用同样的AES key进行加密。由于http的应用场景都是由客户端发起,服务器响应,所以这种由客户端单方生成密钥的方式可以一定程度上便捷的保证通信安全。

7、AES使用CBC模式

不要使用ECB模式,记得设置初始化向量,每个block加密之前要和上个block的秘文进行运算。

main()之前的过程有哪些?

main之前的加载过程:

  • 1、dyld开始将程序二进制文件初始化

  • 2、交有ImageLoader读取image,其中包含了我们的类、方法等各种符号

  • 3、由于runtime 向dyld 绑定了回调,当image加载到内存后,dyld会通知runtime进行处理

  • 4、runtime 接手后调用map_images做解析和处理

  • 5、接下来load_images 中调用call_load_methods方法,遍历所有加载进来的Class,按继承层次依次调用Class的+load和其他Category的+load方法

  • 6、至此 所有的信息都被加载到内存中

  • 7、最后dyld调用真正的main函数

注意:dyld会缓存上一次把信息加载内存的缓存,所以第二次比第一次启动快一点

正文到此结束
Loading...