1.KVO实现原理?
首先了解下什么是KVO(Key-Value Observing)?
文档解释如下:
Key-value observing is a mechanism that allows objects to be notified of changes to specified properties of other objects.
翻译:
他是提供一种机制,当指定的对象的属性被修改后,其观察者就会接受到通知
简单的说就是每次指定的被观察的对象(Key)的属性被修改后,
KVO
就会自动通知相应的观察者。主要依靠Objective-C
的动态性和runtime
。
我通过一个小demo显示来具体分析KVO,代码如下
准备工作:
- 创建一个模型和一个属性如下:
/* 测试属性 */
@property (nonatomic, copy) NSString *change;
- 在控制器中创建一个button,和label。按钮作为点击事件,label作为展示值更新(UI层)。
核心代码:
/*
首先注册showLabel对象为我们的被观察者
这里option 我们选择:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
*/
self.demoItem = [DemoItem new];
[self.demoItem addObserver:self forKeyPath:@"change" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
#pragma mark - 只要object的keyPath属性发生变化,就会调用此回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"change"] && object == self.demoItem) {
self.showLabel.text = [NSString stringWithFormat:@"%@",[change valueForKey:@"new"]];
//change字典中的新、旧值的这两个方法
NSLog(@"\noldDate:%@ newDate:%@",[change valueForKey:@"old"],[change valueForKey:@"new"]);
}
}
#pragma mark - 按钮点击
- (IBAction)buttonClick:(id)sender {
//每次添加一个你好
self.demoItem.change = [self.showLabel.text stringByAppendingString:@"你好啊"];
}
-
打印
如图
NSKeyValueObservingOptions
NSKeyValueObservingOptionNew = 0x01, : 提供更改前的值
NSKeyValueObservingOptionOld = 0x02, : 提供更改后的值
NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04, : 观察最初的值
NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08 : 分别在值修改前后触发方法
总结Demo步骤
- 1.注册观察者,对观察者实施监听。
- 2.在回调方法中处理属性发生的变化(更新UI等)。
- 3.移除观察者(注意)
基本的原理
当观察某对象 A 时,
KVO
机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath
的setter
方法。setter
方法随后负责通知观察对象属性的改变情况。
2.说说你理解的无痕埋点?
什么是无痕埋点?
移动端无痕埋点的自动化采集技术,降低开发成本、提高采集效率和数据质量;通过无痕埋点平台对埋点数据的集中管理,实现可配置、即埋即生 效的机制;并建立以资源位数据和透传数据自动采集为基础的引导效果体系。
为了解决前端埋点的准确性、及时性、开发效率等问题,业内各家公司从不同角度,提出了多种技术方案,这些方案大体上可以归为三类:
-
第二类是可视化埋点,即通过可视化工具配置采集节点,在前端自动解析配置并上报埋点数据,从而实现所谓的“无痕埋点”, 代表方案是已经开源的Mixpanel;
-
第三类是“无埋点”,它并不是真正的不需要埋点,而是前端自动采集全部事件并上报埋点数据,在后端数据计算时过滤出有用数据,代表方案是国内的GrowingIO。
这里推荐一个博客,值得看一下美团点评前端无痕埋点实践
3.消息转发机制原理?
-
利用下图来详细讲解下:
如图
- 如上图消息转发的过程中,我们看出来接受者在每一步中均有机会处理消息。步骤越往后,处理消息的代价越大。最好能在第一步处理完成,这样的话系统就可以将此方法缓存起来了。
第一阶段:首先征询接受者,所属类,看看其是否能动态的添加方法,以用来处理当前这个未知的选择子(unknown selector),这样也可以称为动态解析。
第二阶段:是完整的消息转发机制(full forwarding mechanism)。
- 动态方法解析:对象在收到无法解读的消息后,首先将调用所属类的下列方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
该方法的参数就是那个未知的选择子(SEL),其返回值为
Boolean
类型,表示这个类是否能新增一个实例方法用来处理此选择子(SEL)。再继续往下执行转发机制之前,本类有机会新增一个处理选择子的方法。假如不是实例方法是类方法的话那就用如下方法:
+ (BOOL)resolveClassMethod:(SEL)sel
- 备援接受者:当前接受者还有第二次机会能处理未知的选择子,在这一步中,运行期系统会问她:能不能把这条消息转发给其他接受者来处理。
- (id)forwardingTargetForSelector:(SEL)aSelector
如果找不到的话则返回nil。我们无法操作经由这一步所转发的消息。若是想在发给备援者之前先修改消息内容,那就得通过完整的消息转发机制来做。
- 完成的消息转发机制:如若转发的步骤已经走到这一步,那么只有通过完整的消息转发机制来完成。首先创建
NSInvocation
对象,把尚未处理的消息有关的细节都封于其中:选择子(SEL),目标(Target)以及参数。此对象出发后”消息派发系统“将把消息指派给目标对象:
- (void)forwardInvocation:(NSInvocation *)anInvocation
这个方法只需改变调用目标,让消息在新的目标上调用即可。如果调用操作不由本类处理,则需要用超类的同名方法。如用
NSObject
的方法和doesNotRecognizeSelector
抛出异常表明消息最终未得到处理。
4.说说你理解weak属性?
weak
关键字在OC
中属于比较基础的知识此特性表明该属性定义了一种关系“非拥有关系”(nonowning relationship)。为这种属性设置新值得时,设置方法既不保留新值,也不释放旧值。此特性同assign
类似,然后在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。
延伸什么情况使用 weak 关键字?
在ARC中,在可能出现循环引用的时候,往往要通过其中一端使用Weak来解决,比如说代理(delegate)。或者拖线
IBOutlet
一般也是用weak
来修饰。
一般我们还会把weak属性和assign两者相比较:assign可以用于非OC对象,而weak必须用于OC对象。
5.假如Controller太臃肿,如何优化?
说道Controller优化瘦身那我们首先得联想到老生常谈的设计模式:MVC;
MVC的的定义很容易造成将大量的代码是现在控制器(Controller)中。从而形成了控制器有个专业词汇Massive ViewControler(臃肿的视图控制器)。下面是我总结的几天关于控制器瘦身的技巧:
1.分离数据源(Data Source)等协议(Protocol)
2.把业务逻辑移至 Model
3.创建Store类
4.把视图相关的代码移至View层
这里推荐一个博客,值得看一下猿题库 iOS 客户端架构设计
这里顺带谈谈项目中一般考虑如何去优化
-
1.启动优化
- 启动过程中尽可能做少量的事(如果涉及到接口建议合并处理)
- 不要再UI线程上做耗时操作,数据处理放入子线程中,处理完返回数据到主线程并刷新界面。
- 尽量减少包的大小。
- 做好预加载,如果用户指引等.
-
2.操作流畅度上优化
- tableView的Cell的加载优化(重用机制)
- 控制器之间跳转优化(可以预先做好数据准备)
-
3.数据库优化
- 数据库上的重构优化
- 查询语句的优化
- 分库分表
-
4.代码质量上
- 重构
- 封装
6.项目中网络层如何做安全处理?
1.判断API的调用请求是否来自于经过授权的APP。如若不是则拒绝请求访问
2.在数据请求的过程中进行URL加密处理:防止反编译,接口信息被静态分析。
3.数据传输加密:对客户端传输数据提供有效的加密方案,以防止网络接口的拦截。
如果可以尽量使用HTTPS
,可以有效的避免接口数据在传输中被攻击。
7.main()之前的过程有哪些?
在iOS中
main.m
是我们所熟悉的程序入口。但是在在此之前其实程序以及做了很多事了。如系统会获取dyld
的路径,并加载。加载程序中的依赖库。调用所有的+ load
方法,并返回main
函数地址。
动态链接:在iOS中会用到系统framework
都是动态链接,好比插头和插排,静态链接的代码在编译成功后的过程是将插头和插排一个个插好,运行时直接执行二进制文件;而动态则不相同:需在程序启动时去完成”插插销“这个过程,所以在执行之前,动态链接需要完成准备工作。
-
这个是在 Xcode 中看到的 Link 列表,如下这些 framework 将会在动态链接过程中被加载。
如图
-
如下截图方法展示打印 link 的 framework,首先找到项目模拟器路径下找到项目APP,如案例上用的是KVODemo
路径 -
找到后cd 路径下再通过
otool
命令:
$ otool -L TestMain
-
如下截图打印信息
image.png
除了多了的CoreGraphics(被 UIKit 依赖)外,有两个默认添加的 lib:libobjc 即 objc 和 runtime,libSystem 中包含了很多系统级别 lib,列几个熟知的:
- libdispatch ( GCD )
- libsystem_c ( C语言库 )
- libsystem_blocks ( Block )
- libcommonCrypto ( 加密库,比如常用的 md5 函数 )
这些 lib 都是dylib格式(如 windows 中的 dll ),系统使用动态链接有几点好处:
- 代码共用:很多程序都动态链接了这些 lib,但它们在内存和磁盘中中只有一份
- 易于维护:由于被依赖的 lib 是程序执行时才 link 的,所以这些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升级直接换成 libSystem.C.dylib 然后再替换替身就行了
- 减少可执行文件体积:相比静态链接,动态链接在编译时不需要打进去,所以可执行文件的体积要小很多
dyld:动态连接器(the dynamic link editor)。系统kernel
做好启动程序的初始准备之后,交给dyld负责。dyld在github
上还是开源的,开源地址:dyld。
kernel对dyld的调用顺心如下:
- 从 kernel 留下的原始调用栈引导和启动自己
- 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制
- non-lazy 符号立即 link 到可执行文件,lazy 的存表里
- Runs static initializers for the executable
- 找到可执行文件的 main 函数,准备参数并调用
- 程序执行中负责绑定 lazy 符号、提供 runtime dynamic loading services、提供调试器接口
- 程序main函数 return 后执行 static terminator
- 某些场景下 main 函数结束后调 libSystem 的 _exit 函数
ImageLoader: 用于辅助加载特定可执行文件格式的类,程序中对应实例可简称为image(如程序可执行文件,Framework库,bundle文件)。
- 在程序运行时它先将动态链接的
image
递归加载 (也就是上面测试栈中一串的递归调用的时刻)。 - 再从可执行文件
image
递归加载所有符号。
网友评论