iOS开发常用技术底层实现(精简概述)
(1)RunTime总结:
oc动态性, 运行时将代码转化为runtime的C代码
RunTime运行流程:
生成对应objc_msgSend方法 isa指针查看当前类有没有这个方法, 之后寻觅父类, 每个方法SEL(方法选择器)对应IMP(相似于一个编号,是函数指针,指向函数实现,找到内存里对应函数), ?直到NSObeject, 假如找不到IMP, 会进入消息转发机制, resolveClassMethod, resolveInstanceMethod, forwardingTargetForSelector, forwardInvocation 第一个方法所属类方法动态方法解析, 第二个和第一个相似,是对应实例方法的, 第三个是备援接受者, 第四个方法是消息重定向, 真正消息转发,也是Aspects的核心操作, 假如都找不到调用doesNotRecognizeSelector:方法抛出异常
RunTime的实际应用
交换方法(黑魔法.hook,让SEL1->IMP2,SEL2->IMP1),为系统类增加自己设置方法,三方Aspects?
①用方法交换增加保护, 如数组赋值时增加越界判断等等 ?②统计页面点击数用? ③多继承? ④自动化归档(kvo)? ⑤NSTime内存泄漏(vc被释放通过消息转发找回vc)? ⑥系统类增加自己设置方法, 写少量更便捷的代码,比方控件加手势,字典加加密方法,代码更简洁?
isa指针当前类的方法,找不到寻觅元类能否有该方法,到NSObject找不到就进入消息转发
消息转发机制, 消息重定向在第三步
OC对象的本质<二> 实例对象,类对象,元类对象
runtime – iOS类对象、实例对象、元类对象
(2)KVO总结:
KVO是OC的一种观察者设计模式,另一种是通知机制, 是基于runtime机制实现的, 也是一种响应式编程(kvo,block,代理商,通知,定时器等)
KVO运行流程:
当观察对象A时,KVO动态创立了新的名为NSKVONotifying_A的新类,该类时为对象A的子类(根据父类—>子类, 创立类名,开拓内存空间. 利用RunTime拿到父类的函数实现,用黑魔法isa-swizzling交换父类方法调用)? 并且KVO重写了新类的观察属性的setter方法,setter方法负责在调用原setter方法之前和之后,通知所有观察者该属性的变化情况(利用消息转发,子类—>父类)?
子类重写setter方法:
KVO的键值观察通知依赖于NSObject的两个方法:willChangeValueForKey:和didChangeValueForKey:,在存取数值的前后分别调用2个方法;中间利用消息转发,之后,observeValueForKey:ofObject:change:context:也会被调用。?
KVO注意事项:
①没有set方法无法观察,例如成员变量(类外部不可访问,不可赋值,类内部可以通过self->属性名或者者属性名访问和赋值)? ②观察可变数组方法不一样
KVO实际应用:
①监听属性(模型)变化? ②MVVM双向绑定? ③网络,断点续传?
(3)MVC, MVP, MVVM总结:
MVC是运用最广泛的架构模式,MVP和MVVM是基于MVC衍生的新框架, 可以实现解耦, 真正实现高内聚低耦合的特性. 但架构没有最好的, 只有最合适的!!!
MVC, MVP, MVVM区别:
MVC缺点: 厚重的VC, 代码可读性差, 逻辑混乱, 基本无法测试.?
在日常开发使用MVC中,经常为了减少代码量,冗余将Model写在View,这样View的移植性差, 添加了耦合性. MVP是面向协议编程,使用代理商完成双向绑定, 但少量推迟性操作难以管理(如请求接口数据). MVVM是MV(X)系列最好的架构模式, 双向绑定,面向需求增加方法, 随掉随用, MVC的代码抽离也是一种MVVM思想
MVVM优点: 低耦合, 可重用性, 独立开发(业务逻辑通过VM和UI分离), 可测试(可以针对viewModel测试)
MVVM缺点: 界面异常BUG难调试, 代码不直观, 企业应用存在学习成本
(4)Block总结: ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 最常用没有之一, 敲黑板?
将OC语言block改写成C语言的block, 动态生成.cpp文件(c++代码). ?block是可以%@打印的, 说明也是一个对象. 可以了解为特殊格式, 带函数回调的对象. block的灵活之处也在于可以将block作为一个属性封装, 保存一段代码块, 可以在任意时候调用
block分类:
NSGlobalBlock(全局)、NSStackBlock(栈)、NSMallocBlock(堆)
当block回调对外部变量操作时, 将外部变量copy到堆上,?
block应用场景:
①传值 ?②MVVM ?③封装回调代码块
__block:
block对外部变量是只读的,要变成可读可写,就需要加上__block, ?将栈中的block复制到堆上一份,从而避免了循环引用这个情况
__block原理: 能够将观察到int a=0的值copy到堆里, 对a的指针地址进行修改, block回调里a指针地址和外部变量a的指针地址相同(相当于浅拷贝, 拷贝指针地址) ? ??栈/常量 -> copy -> 堆 ?
block处理循环引用(面试总问:多种处理方法):
循环引用起因: A持有B,B又持有A, 就形成了互相持有, 形成了闭环. 从引用计数分析是B想释放, 但A还持有B, 同理A也无法释放(A,B就是self和block)
处理方案: ①弱引用__weak typeof(self) weakSelf = self;(原理强弱共舞) ? ?②__block ViewController ?*bWeakSelf = self; ?同时block回调里 bWeakSelf = nil; (原理把self至nil)
③self.block = ^(ViewController *obj){ };(原理以参数形式传入self)
block里Cope和Strong的区别:
由于在MRC下,block在创立的时候,它的内存是分配在栈(stack)上的,而不是在堆(heap)上,可能被随时回收。他本身的作用域是属于创立时候的作用域,一旦在创立时候的作用域外面调用block将导致程序崩溃。通过copy可以把block拷贝(copy)到堆,保证block的公告域外使用。在ARC下写不写都行,编译器会自动对block进行copy操作
在ARC下, 没区别, Strong在ARC也会自动将block拷贝到堆上, MRC需要使用Copy
(5)NSTime
两种初始化方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
注:不用scheduled方式初始化的,需要手动addTimer:forMode: 将timer增加到一个runloop中。而scheduled的初始化方法将以默认mode直接增加到当前的runloop中.
可能存在的问题:
?(1)带有RunLoop的定时器发生内存泄漏 ? ??
起因:RunLoop->timer->self 形成循环引用,无法在页面离开时释放
处理方案:①在生命周期代码里加上timer的中止代码(最简单方式) ? ②思路:不让RunLoop持有timer ? ? 实现:一种是使用runtime方法处理 ? 另一种是使用NSProxy
错误方案:用weakSelf处理, 其实定时器内部是有strongSelf强引用的, 所以用weak无法处理定时器循环引用问题
(2)页面滑动操作定时器中止
起因:默认的NSDefaultRunLoopMode在滑动视图时会暂停定时器
处理方案:使用NSRunLoopCommonModes模式的Runloop就可处理
(3)NSTimer加到了RunLoop中但迟迟的不触发事件
起因:①每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动 ? ②timer增加的时候,我们需要指定一个mode,由于同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会
处理方案:要让timer生效,必需保证该线程的runloop已启动,而且其运行的runloopmode也要匹配
(3)NSTimer不准时触发事件(定时器不准!!!)
起因:程序是多线程的,而你的timer只是增加在某一个线程的runloop的某一种指定的runloopmode中,因为多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化
处理方案:①纳秒级精度的Timer,用mach_absolute_time()来实现更高精度的定时器?? ?②CADisplayLink是一个频率能达到屏幕刷新率的定时器类。iPhone屏幕刷新频率为60帧/秒,也就是说最小间隔可以达到1/60s ? ? ③GCD定时器代替.由于GCD定时器不受RunLoop影响.?
(4)检测内存泄漏方案
①静态检测方法 ? ?②动态检测方法instrument ? ③delloc ? ④腾讯三方库MLeaksFinder
(6)性能优化
优化cpu占有率, 提高客户体验(缩短加载时间, 确保帧数不会出现卡慢)
①重用池和懒加载 ? ?②少用离屏渲染(如设置圆角不用Lab.layer.masksToBounds = true, 使用Core Graphics绘制, 还有设置阴影和光栅化也会触发离屏渲染) ? ?③CADisplayLink来测量帧率(大于60fpz即没有卡慢感) ? ?④Instuments, 静态分析等方法检测内存泄漏 ? ⑤减少UIWebView的使用? ?⑥不阻塞主线程, 使用GCD等多线程技术 ? ⑦tableView预加载, 滑动流畅 ?⑧MLeaksFinder检测内存泄漏 ?
(7)GCD
同步? 阻塞当前线程? 不会开拓新线程?dispatch_sync(queue, ^{ ?//回调 ?});
异步? 不会阻塞当前线程? 开拓新线程?dispatch_async(queue, ^{ ?//回调 ?});
串行? 一个个执行 ??dispatch_queue_t?queue?=?dispatch_queue_create(“标识符”,?DISPATCH_QUEUE_SERIAL);
并发? 多个同时执行 ?dispatch_queue_t?queue?=?dispatch_queue_create(“标识符”,?DISPATCH_QUEUE_CONCURRENT);
主队列? 异步队列不会开拓线程? 需要等到主线程中的任务执行完? 才会执行主队列中的任务
GCD 栅栏方法:dispatch_barrier_async (例任务123,栅栏,456 能控制任务执行顺序)
GCD 延时执行方法:dispatch_after (推迟调用,使用便捷)
GCD 一次性代码(只执行一次):dispatch_once (写单例等使用)
GCD 快速迭代方法:dispatch_apply?(for循环增加异步并发)
GCD 的队列组:dispatch_group (监听 group 中任务的完成状态,可以当上面所有的任务都执行完成后,才执行任务dispatch_group_notify)
GCD 信号量:dispatch_semaphore (保证线程安全,为线程加锁)
(8)链式编程,函数式编程,响应式编程
链式:msonry使用链式编程,先执行A方法,再执行B方法…? 核心是点语法调用方法, 点语法传参通过返回值block实现, 所以也使用了函数式编程
函数式:需要什么block返回什么
响应式:a=b+c 赋值之后 b 或者者 c 的值变化后,a 的值不会跟着变化.? 响应式编程,目标就是,假如 b 或者者 c 的数值发生变化,a 的数值会同时发生变化; 标准应用:RAC框架(对KVO等效果的封装)
(9)KVO,KVC,代理商,通知区别
KVC,即Key-Value-Coding,是一个非正式协议,使用字符串(key)来访问一个对象实例变量的机制
KVO,即Key-Value-Observing,它提供一种机制,当被观察者的属性值更改时,观察者就会接收到通知
通知监听不局限于属性的变化,还可以是状态的变化,监听范围广,例如键盘的出现、app进入后端等,使用也更灵活方便
KVO和通知都负责发送和接收通知,剩下的事情都由系统来完成,所以不用返回值,而delegate则需要协议和代理商对象来关联
delegate适用于一对一,KVO和通知则适用于一对多情况, 代理商效率更高
KVC和KVO实现的根本是OC语言的动态性和运行时runtime,以及访问器方法的实现
(10)weak 关键字, 相比 assign 有什么不同?
weak 在属性所指的对象遭到摧毁时,系统会将 weak 修饰的属性对象的指针指向 nil , 尽管assign不会添加引用计数但也不会自动至nil
assign内部还是增加了一层强引用
assign可以用于修饰非 OC 对象,而 weak 必需用于 OC 对象
(11)属性@property的实质
@property = ivar + getter + setter;
@synthesize的语义是假如你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
@dynamic告诉编译器,属性的setter与getter方法由客户自己实现。
(12)深浅拷贝的区别(Copy与MutableCopy) ? ? ? ? ? ? ? ?这里面绕弯弯的地方很多,想理解最好自己写点逻辑测试,特别是浅拷贝的了解
简单来说:浅拷贝复制容器,深拷贝复制容器及其内部元素
有两种类型的对象拷贝,浅拷贝和深拷贝。正常的拷贝,生成一个新的容器,但却是和原来的容器共用内部的元素(即内存地址相同),这叫做浅拷贝。深拷贝不仅生成新的容器,还生成了新的内部元素(即内部元素尽管和原对象内部元素数值相同,但生成新的内存地址,新内部元素指向新地址,和原地址元素无任何关系),这叫深拷贝?
浅拷贝指向同一块内存地址, 深拷贝指向不同地址
误会:浅拷贝就是用copy,深拷贝就是用mutableCopy。假如有这样的误会,肯定要更正过来。copy只是不可变拷贝,而mutableCopy是可变拷贝。比方,NSArray *arr = [modelsArray copy],那么arr是不可变的。而NSMutableArray *ma = [modelsArray mutableCopy],那么ma是可变的
这里需要注意浅拷贝尽管是指针拷贝,但只需copy就会生出新容器,不会随原内容改变而改变
注意:①对可变对象(mutable,model)无论使用copy还是mutableCopy(包括等号),都会深拷贝! 会生成新的内存地址 ? ? ②使用mutableCopy无论是可变还是不可变都是深拷贝!?会生成新的内存地址 ? ③对不可变对象使用copy,是浅拷贝,内部元素指向同一地址,容器类(数组,model)假如修改原数据的值,copy出来的值也会改变! ?④copy和mutableCopy都是单层深拷贝,如数组套模型,只会copy数组元素,里面的模型由于没有生成新的容器,指向相同内存地址,所以改变modelA会改变modelB , 但是单层拷贝层假如发生改变不会改变另一个
处理单层拷贝的问题(内部元素一律copy出来)
让一个对象有copy功能:要想自己设置对象可以复制,那么该类就必需遵守NSCopying 或者 NSMutableCopying协议, ?实现协议中copyWithZone或者者mutableCopyWithZone方法
copyWithZone方法
(13)nonatomic 和?atomic
对于线程的安全,有nonatomic,这样效率就更高了,但是不是线程安全的。假如要线程安全,可以使用atomic,这样在访问是就会有线程锁。但atomic只能保证set方法的线程安全(加了锁,效率会变低),并不是绝对的线程安全,所以在实际开发中很少使用,假如没有增加系统默认是使用atomic和strong类型
(14)SDWebImage底层原理
SDWebImage是一个图片加载和缓存的框架,通过三级缓存机制很好处理了图片缓存问题
SDWebImage加载图片的流程:
1、sd_setImageWithURL:placeholderImage:options: 会先把placeholderImage显示,而后SDWebImageManager根据URL开始解决图片
2、先从内存图片缓存查找能否有图片。若有显示图片
3、假如内存缓存中没有,生成NSInvocationOperation增加到队列开始从硬盘查找图片能否已经缓存。若有显示图片
4、假如从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,生成一个下载器SDWebImageDownloader开始下载图片(异步下载)
5、下载完成后显示图片,且内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独NSInvocationOperation完成,避免拖慢主线程。
SDWebImage使用过程中可能存在的问题:
?(1)Url链接的图片改变,但未显示新图片(Url地址本身未变)
问题起因:图片的缓存是根据url来设置的,在加载过程中找到了该Url对应的缓存,所以显示以前的图片
处理方法:①根据http,每个Url有一个ETag参数,是通过哈希编码得到的,当资源发生变更时,那么ETag也随之发生变化,用户端可以判断NSURLCache来判断该地址下图片能否发生改变(sd对应的方法options:SDWebImageRefreshCached,注意是比正常请求多消耗性能的)? ? ? ?②不使用缓存
?(2)图片显示错乱的问题
问题起因:因为cell的重用导致,客户下拉或者者上拉,当网络不好的情况,该cell的图片还没有被加载,但是对应的cell已经被显示,就会显示cell被重用之前的数据,造成数据混乱
处理方法:设置每个cell中image为nil或者者设置默认图片
SDImageCache是怎样做数据管理的?
SDImageCache分两个部分,一个是内存层面的,一个是硬盘层面的。
内存层面的相当于是个缓存器,以Key-Value的形式存储图片,当内存不够的时候回清理所有缓存图片。
用搜索文件系统的方式做管理,文件替换方式是以时间为单位,剔除时间大于一周的图片文件
当SDWebImageManager向SDImageCache要资源时,先搜索内存层面的数据,假如有直接返回,没有的话就去访问磁盘,将图片从磁盘读取出来,而后做Decoder,将图片对象放到内存层面做备份,再返回调用层。
SDWebImage详解博客:
iOS开发之SDWebImage原理 – 简书
iOS-SDWebimage底层实现原理 – 木子沉雨 – 博客园
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS开发常用技术底层实现(精简概述)