ios一些杂散知识点记录2
dispatch_barrier_sync : 多读单写,数据库可以使用
dispatch_group_async : 队列组,如多图片下载全部完成后拿到结果
死锁:串行队列依次执行,当有同步任务添加到这个串行队列时要注意,FMDB因为使用串行队列保证线程安全,有可能出现这种情况。可以采用异步,或多个串行队列避免死锁
线程同步:
加锁:
信号量:
NSOperationQueue设置最大并发数为1. (AFN)
串行队列: (FMDB)
dispatch_barrier_async: 多读单写,让写入操作线程同步
NSOperationQueue 优点:
可以设置依赖,最大并发数,优先级等,最重要的是可以通过kvo获取任务最新状态,取消任务等
对象的引用计数为0是否会马上释放:
假如不是马上释放,那就是在runloop进入休眠时再释放,那在一个大的for循环中创建对象岂不是内存很容易崩溃?所以应该是马上释放。对象在哪个线程引用计数减为0则对象在哪个线程释放。
GCD底层:推测是系统维护的一个线程池,系统决定线程数量,可复用。按照YYKit框架中建议,线程池线程数量跟CPU的核数相关,建议不超过32个。 可以使用32个串行队列,当任务太多时则将任务添加到队列尾部依次执行。
离屏渲染: 个人认为,可能是GPU渲染时,觉得某些如切圆等需要多次剪切,工作较为复杂,为了加速渲染效率, 将其放到屏幕外的另外一块空间单独渲染,完成后再合并到屏幕缓冲区。 类似多线程完成总体任务。
GPU渲染任务太重可以将部分任务交给CPU,如使用Core graphics绘制则是典型的CPU绘制任务。根据情况平衡CPU及GPU的双方压力,避免掉帧
异步绘制:系统的layer有一个代理方法displayLayer,一旦实现了则会走自定义渲染流程,在该代理方法中一般实现是,开启子线程使用coregraphics绘制上下文,然后得到bitmap位图,回到主线程赋值给layer.contents。
ASDK: 将布局计算,文本绘制,图片解码,对象创建销毁等移到子线程处理,对必须主线程的内容提交到全局容器,注册runloop监听,当其即将休眠时取出全局容器中的内容进行绘制显示。
例如uitableview,当用户快速滑动时,必须在主线程完成cell绘制才能显示出来,一旦任务过重就会出现卡顿现象,使用ASDK处理后cell的很多操作都放到子线程异步处理,就不会堵塞用户滑动,但是有可能出现滑动出来的cell是复用的那个cell,数据依旧显示的是复用的数据,得等待一会才能刷新成正确的内容。针对这种ASDK做了预加载。预加载就是在runloop空闲的时候,把屏幕外cell分批次的(指的是每个runloop循环处理多少条数据,由cpu核心数决定,如6核cpu则6*5=30,每批次处理30个cell数据, 避免大量任务堵塞同一个runloop循环),如布局/渲染等工作提前在子线程算好,等真正显示时需要的计算量/渲染等就少了,上述现象明显减少。
通知: 发送端发出通知时,接收端会马上处理回调事件,如果接收端在这里处理耗时任务会堵塞发送端。
通知接收端线程会和发送端保持一致, 在子线程发送通知,也会在子线程接收通知
KVO: 执行了对象属性的setter方法才会触发kvo。 kvc因为底层也是调用setter方法所以也会触发。当对readonly属性kvc时,按理说readonly属性没有setter方法,kvc时是直接给成员变量赋值不会触发kvo才会,但是现实是会触发,应该是kvc底层代码还有做别的处理,可能内部调用了willChangeValueForKey和didChangeValueForKey。
手动KVO,通过调用willChangeValueForKey和didChangeValueForKey触发,这两个方法一般需要搭配使用,单独使用大部分情况无效,跟添加观察者时使用的options有关。
KVO底层也是用通知的方式通知观察者回调,所以跟通知一样,在子线程触发kvo则会在子线程收到回调,主线程触发KVO时是同步回调,子线程则是异步回调。
死锁: 竞争资源导致的僵局
往当前串行队列中压入同步任务,为串行队列相互等待导致死锁,可以采用异步或者让其不在同一个串行队列中解决
非递归锁在同一线程多次加锁,比如递归调用test方法,进入方法就加锁然后再次调用时发现该锁被锁住,进入休眠状态等待。因为休眠所以该方法不再往下执行无法结束,锁无法释放,导致死锁, 采用递归锁解决。递归锁允许同一线程对锁多次加锁,原理可能是类似信号量那种,每次上锁只是计数值加一,解锁就减一
多线程线程数量控制
信号量
NSOperationQueue
多个异步串行队列(YYKit)
kCFRunLoopCommonModes: 只是一个字符串标记, 调用方法将timer,sources,observers加入指定模式时,runloop内部发现是kCFRunLoopCommonModes模式则会进行特殊处理,将这些timer,sources,observers加入到nscommonmodeitems数组之下,并且将元素加入到被标记为common的所有mode中 。 默认被标记为common的mode有UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
UITableView的优化
列表滚动时执行的代理方法,主要有heightForRow和cellForRow方法,需要在主线程将cell完全渲染完成才能展示,否则出现卡顿。
前者优化,高度不固定是需要较复杂计算的cell高度有必要缓存
后者优化,先看该方法中主要做的事情
1、读取数据源对应的model
2、从reuse池中dequeue一个cell
3、将model装配到cell当中去
4、model数据处理,cell 布局,渲染(文字和图片都很耗时),显示
以上都在主线程中完成。针对该流程优化如下
1、model数据提前处理成cell需要的类型
2、cell 中避免频繁add/remove内容,优先使用显示和隐藏控制
3、避免切割图片圆角,设置阴影等会导致离屏渲染的操作
4、uiimageView最好跟图片一样大,避免图片拉伸。图片等采用子线程解码,不在屏幕外的cell的图片加载要及时断开,使用sdwebimage就可以了
5、多图片的cell时,现实的图片是缩略图,直到用户点击了才下载高清的图片
6、判断列表滑动速度,达到一定值的时候,cellForRow方法可以提前返回cell,而不是让其完整渲染完毕,因为反正此时用户也看不清楚内容
7、必要时可以滑动不加载图片而是显示占位图,停止滑动才加载图片
8、异步绘制,cell中实现代理方法displayLayer,开辟子线程自己绘制cell样式再回到主线程设置cell.layer.content,避免主线程堵塞。因为是异步加载,当快速滚动时,可能出现cell还没渲染完成就已经显示在屏幕中,甚至滚出屏幕外。此时会出现cell依旧显示的是上次复用cell的现象,而且滚出屏幕外的cell不再需要继续绘制,要提前结束绘制流程。异步绘制需要手动调用[cell.layer setNeedsDisplay]才会执行代理方法生效
9、ASDK,主要也是异步绘制,尤其的,针对异步时可能出现的cell来不及绘制进行了预加载
TCP协议是怎么保证数据的可靠不丢失的
1、连接管理机制,保证双方在线
2、检验和,就是双方都对接收到的数据进行完整正确验证
3、ACK应答机制,如果接收到消息且验证正确,则会发送相应报文确认
4、序列号,每个报文编号,有效保证数据正确行,避免重复
5、超时重传
6、滑动窗口,根据双方缓冲区大小,处理能力动态设置滑动窗口,保证传输效率以及避免对方无法处理消息导致缓冲区溢出无法接收数据异常。
7、拥赛控制和慢启动,慢启动是刚开始发送一个字节试探网络和对方接受情况,正常则按指数递增流量直到窗口下限值,然后缓慢递增直到发生拥堵(表征为超时)时立刻大幅下调发送字节数避免拥塞。正常后再走上面的流程
HTTP
Get请求,不会使服务器端发生变化,多次请求结果一致,等幂的,可被一些代理服务器缓存,一般的请求时发现第二次请求往往比第一次快,可能跟get方法被缓存,或者http1.1版本的连接复用有关
状态码:2xx成功,206 断点续传, 3xx重定向,4xx请求链接等出错,5xx服务端错误,比如权限不通过
http为短连接,默认情况下先三次握手,再传输数据,再断开连接。 底层采用的tcp传输数据,为了避免每次请求都发起连接,http1.1版本之后默认有连接复用机制,在一段时间内发起的请求都可以复用该连接,叫做http长连接,保证多次请求效率。通过设置请求头中的Connection: Keep-Alive, timeout=30, max=100等保持连接有效。当想要关闭该连接时Connection: Close。 长连接适用于频繁发起的请求,例如视频播放。
一个长连接可以发起、返回多个请求,如何判断某一个请求返回的数据是否完成?要求服务端设置返回数据的大小Content-Length字段。
session和cookie是对http无状态的一个补偿,让其能记住一些东西,比如登陆状态
http 1.0,每个请求建立连接,请求完毕关闭连接
http 1.1,支持连接复用,一个tcp连接建立成功后会维持一段时间,直到超出时间没人使用才会断开。一定程度的长连接。 多个任务串行排队,前一个请求完成后面一个任务才发起,
http 2.0, 多路复用,可以同时多个请求并发发出,利用如ID类似的标识符表明是哪一个请求的返回内容,区分返回结果
DNS解析:
默认是UDP协议解析,明文传输,不安全,容易被劫持
1、采用httpdns,向一个权威网站发起http请求获得ip地址,防止劫持
2、本地缓存,app启动时由服务端下发域名和 IP 的对应列表,客户端来进行缓存,发起网络请求的时候直接根据缓存 IP 来进行业务访问。
topK问题
k=1,2,直接用一个/二个变量存储,遍历数组即可
局部排序,例如冒泡,每次冒泡可得到1个最大值,k次冒泡可得到topK,O(n*k)
堆排序, O(n*logK), 空间复杂度O(k)
快排,O(nlogn),空间复杂度O(1), 该算法主要问题是最坏时时间复杂度会达到O(n2)级别
UIView和CALayer的关系
CALayer是UIView的一个属性,专门负责视图绘制显示方面
UIView是CALayer的代理,其实现的代理方法displayLayer是异步绘制的入口。
UIView是UIResponder子类,能相应事件等
Masonry是基于系统的autoLayout的封装,效率相比frame要低,是基于解方程组的方式计算布局,会监听约束的变化,当发生变化时则调用 setNeedLayout,在runloop即将休眠时重新计算frame布局。
iOS热重载/热编译
InjectionIII 分为server 和 client部分,client部分在你的项目启动的时候会作为 bundle load 进去,server部分在Mac App那边,server 和 client 都会在后台发送和监听 Socket 消息,
InjectionIII 服务端会监听源代码文件的变化,如果文件被改动了,server 就会通过 Socket 通知 client 进行 rebuildClass 重新对该文件进行编译,打包成动态库,也就是 .dylib 文件。
然后通过 dlopen 把动态库文件载入运行的 App 里,接下来 dlsym 会得到动态库的符号地址,然后就可以处理类的替换工作。当类的方法被替换后,我们就可以开始重新绘制界面了。整个过程无需重新编译和重载 App,使用动态库方式极速调试的目的就达成了。
dlopen是一个函数,获取动态库的句柄
dlsym是一个函数,获取动态库句柄中的某个具体方法地址
.dSYM,符号表,是一个文件,是内存地址与函数名、文件名、行号的映射表。符号表元素如下所示:
<起始地址> <结束地址> <函数> [<文件名:行号>]
LinkMap
是一个记录链接相关信息的纯文本文件,里面记录了可执行文件的路径、CPU架构、目标文件、符号等信息。主要是用于分析每个文件代码段的大小,常量,静态字符串等信息。
文本从上到下依次记录的是Object files,section,符号表
Object files:参与编译的所有文件序号及路径(.o文件)
section表:是按照 mach-o 文件映射的Segment和section信息。如__objc_classrefs是引用到的类,_objc_classname是所有类名,通过分析两者之间的差别,就可以知道哪些类没有用到。
符号表:按照Object files表中的文件序号依次列出了所有的方法,地址和占用的空间大小,常量等,可分析每一个.o或者framework文件的内存占用大小,对于减小包大小有很大帮助。
APP启动优化
启动步骤:
将app加载进内存,链接动态库。
初始化runtime相关,创建类对象,合并分类,调用+load方法等
启动runloop,注册自动释放池,初始化appdelegate
执行didFinishLaunchingWithOptions(主要优化地方)
homepage创建。(viewDidAppear之前的方法都要优化)
启动时间优化
Main之前: 少用+load,分类合并等,但是实际操作主要是删除没有引用的类,合并动态库等。
Main之后:原则是任务能延后就延后,不能延后就放后台,比如分享,支付,地图等SDK可以延后初始化,DB,日志等可以放到后台初始化,跟homepage首屏无关的延后等
Time Profiler找到具体耗时方法,针对性的优化
自己实现一个NSNotificationCenter
模仿系统类定义接口,内部用一个字典,以添加的通知为key,对应是一个数组,数组内部为封装target和回调的对象。target弱引用。当外界target销毁时指针清空。发出通知时,根据名字查找字典得到对应数组,遍历数组对象,如果target指针为nil则删除该对象,否则调用target的回调方法
实现一个线程池
YYDispatchQueuePool
线程池,根据传入优先级NSQualityOfService创建跟CPU核心数一样多的串行队列。 有五种优先级,所以最多可以创建30个串行队列。 结构类似一个二维数组。获取队列时通过轮询,例如后台优先级所创建的Pool共有6个队列,那么会依次取出,如取7次,那么第一个队列会返回两次。这里有个问题就是假如队列1中上一个任务没有完成,就算其他队列中任务已经完成了,该Pool也会返回队列1,没有充分利用好。可以如下优化。
使用一个可变数组,内部装串行队列。 接到任务时检查数组中是否存在元素,存在则取出数组末尾队列,开辟线程执行任务,否则创建队列再执行任务,任务结束将队列重新添加到数组尾部。设置线程最大数量,例如上限6条,则当发现已经创建了6队列,再接受到任务时,将任务放入另外一个FIFO的队列中等待。当正在执行的任意一个任务结束,检查等待队列中是否存在任务,有则取出来放到当前队列中执行,否则将队列添加回数组。**
1、创建串行队列的消耗,内存有多少,当数组中队列使用不足时是否需要销毁一些队列。
2、线程池最好是直接返回线程而不是串行队列,比如数组中直接存储线程,但是线程需要有任务才能存活,否则会直接退出,不知道如何保存。
NSDictionary的key必须实现NSCopying协议
字典底层是hashtable,使用key的hash作为表索引。会对key进行copy操作,所以key必须实现NSCopying协议。 为什么要copy?猜测是防止key是可变类型的,如果不copy,那么key可能会被改变,由key hash得到的索引就会失效。
同时自定义key最好自己实现hash和isequal两个方法,保证hash函数性能和均匀分布
NSMapTable和NSMutableDictionary
区别在于前者支持弱引用对象
实现原理: 一种可能的实现是前者就是采用字典实现,但是在保存对象A时,不是保存A,而是保存一个中间类B,让B的value属性弱引用A。
这样的问题是假如A销毁,B无法及时在集合中删除,我想到下面两点删除时机。
1、根据key查找时,如果发现B存在但是A是nil,那么将保存的B删除。
2、集合触发动态扩容时,先遍历集合查看是否存在A是nil的现象,有则删除B
因为需要对保存对象进行包装,而且当A对象销毁时无法及时清理B,效率稍低。
静态库和动态库
静态库:这类库的名字一般是libxxx.a;静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,优点即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。
动态库:这类库的名字一般是libxxx.tbd,libxxx.dylib;相对于静态函数库,动态函数库在编译的时候 并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要改静态库;而动态库在程序编译时并不会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在。
系统的大多数库为动态库,可以多个app共享,静态库不行
静态库很适合做模块开发,保密,编译速度更快
swift方法派发
1、直接派发,在编译阶段即决定,类似c语言的方法调用,struct的方法调用,类的extension都是这种类型。编译器必要时可inline化,速度最快
2、函数表派发,类似c++虚函数表,解决直接派发无法多态调用问题,主要是类中的方法调用。struct无法继承,所以不必使用函数表派发,直接派发即可
3、动态派发,使用动态关键字,或者oc语言的方法走这个方式,最是灵活多变
SDWebImage之RunLoop
在早期的版本中,采用的是NSURLConnection在子线程发起请求,那么请求发送完毕线程就退出了,无法在该线程接收到回调,所以采用runloop让该线程活下去,知道回调完成再退出runloop销毁线程
最新版本中已经采用nsurlsession,不再使用runloop
Ios进程间的通信方式
UIPasteboard (粘贴板), URL Scheme,Keychain(如多个app共享同一个登陆账户), App Groups等
实用举例,微信分享如何分享大图?
图片数据很大,首先是看下能否压缩变小,再次将图片数据携带到urlScheme,或者粘贴板上面带给微信,如果还是不行则就带一个图片链接,到微信里面再去下载图片。
iOS 线程间通信
GCD,信号量,通知。。。
pod install和pod update
初次安装,或者在Podfile中删除/添加某一个库都是使用pod install,此命令只会安装/删除有变更的那个库,一般都是使用此命令。
当想更新某一个库的版本号时,例如使用pod update AFNetworking,则只会更新指定库的版本号,其他不受影响。
如何hook一个对象的方法,而不影响其它对象
个人推测的一种可能:
比如hook一个Person对象p的test方法,动态生成Person的子类对象Person_sub,修改p的isa指针指向子类,然后子类动态添加test方法,hook掉。
@property (assign, atomic) NSInteger age;
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
for (NSInteger i =0; i<1000; i++) {
self.age++;
}
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
for (NSInteger i =0; i<1000; i++) {
self.age++;
}
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"%zd",self.age);
});
age属性是atomic修饰,主要是防止多线程修改同一个变量时导致崩溃。其实由于int是基本数据类型,占据字节很少,cpu一般能一次性完成付值,所以用nonatomic修饰也没啥问题。
这里会输出1000-2000之间的某一个值,由于多线程同时读和写数据导致数据错乱所致。比如A线程读到self.age为1,此时B线程读的也是1,然后A线程self.age++写为2,B线程也写为2,那么对self.age++执行了2次但是只加了1,导致结果变小
要避免这种多线程同时读写现象,可以加锁避免
子线程的autorelease对象管理
子线程autorelease对象由用户自己创建的自动释放池管理,否则系统会自动创建一个用来管理,当线程结束时释放 。
当子线程通过runloop实现常驻时,测试如下
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool { // ------@1
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSThread *th = [ViewController networkRequestThread];
[self performSelector:@selector(test) onThread:th withObject:nil waitUntilDone:YES];
}
- (void)test{
for (NSInteger i = 0; i < 1100000; i++) {
__autoreleasing Person *p = [Person person];
}
sleep(5); // Person对象直到sleep结束才会释放
}
Person的dealloc方法调用堆栈
thread #8, name = 'AFNetworking', stop reason = breakpoint 2.1
* frame #0: 0x000000010bfa5e50 testww222`-[Person dealloc](self=0x0000600001782a00, _cmd="dealloc") at Person.m:25:1
frame #1: 0x00007fff50bbea16 libobjc.A.dylib`objc_object::sidetable_release(bool) + 174
frame #2: 0x00007fff50bc0077 libobjc.A.dylib`AutoreleasePoolPage::releaseUntil(objc_object**) + 147
frame #3: 0x00007fff50bbff96 libobjc.A.dylib`objc_autoreleasePoolPop + 199
frame #4: 0x00007fff23e5e016 CoreFoundation`_CFAutoreleasePoolPop + 22
frame #5: 0x00007fff2594feb8 Foundation`__NSThreadPerformPerform + 327
frame #6: 0x00007fff23da0d31 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #7: 0x00007fff23da0c5c CoreFoundation`__CFRunLoopDoSource0 + 76
frame #8: 0x00007fff23da0434 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #9: 0x00007fff23d9b02e CoreFoundation`__CFRunLoopRun + 974
frame #10: 0x00007fff23d9a944 CoreFoundation`CFRunLoopRunSpecific + 404
frame #11: 0x00007fff25939c71 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 211
frame #12: 0x00007fff25939e85 Foundation`-[NSRunLoop(NSRunLoop) run] + 76
frame #13: 0x000000010bfa550b testww222`+[ViewController networkRequestThreadEntryPoint:](self=ViewController, _cmd="networkRequestThreadEntryPoint:", object=0x0000000000000000) at ViewController.m:84:9
frame #14: 0x00007fff2594f9eb Foundation`__NSThread__start__ + 1047
frame #15: 0x00007fff51c0c109 libsystem_pthread.dylib`_pthread_start + 148
frame #16: 0x00007fff51c07b8b libsystem_pthread.dylib`thread_start + 15
通过对Person对象的dealloc打印输出函数调用栈发现,for循环结束Person对象也不会销毁,而是要等到sleep结束,test方法执行完毕,当前runloop即将休眠时,自动释放池pop后调用release才释放的,推测常驻子线程跟主线程一样,系统也会在runloop上注册一个自动释放池,并在runloop即将休眠时释放。
实现常驻线程时在@1处添加的自动释放池实际上没有必要作用,因为runloop不会退出所以就算该自动释放池捕获了autorelease对象也无法销毁,该处手动添加的自动释放池感觉跟xcode中main方法处的自动释放池一样,要在runloop结束时才销毁。
SDWebImage:
磁盘缓存SDDiskCache: 直接写文件方式缓存图片文件, 清理过期文件时,先遍历所有文件,将最后修改日期距离今天超过1周的文件删除。然后当磁盘有缓存大小限制,则对剩余文件按照时间排序,遍历依次删除较旧文件,直到剩余文件只有限制大小的一半为止。
内存缓存SDMemoryCache: 是NSCache的子类,NSCache是线程安全的,可以设置最大缓存容量,缓存数量等,但是有个问题就是内部的元素销毁不受开发者控制。比如APP退到后台,系统可能会将NSCache清空,再回到前台图片可能会出现闪烁现象,是因为此时图片显示会重新经过查找流程,需要去磁盘中查找耗时导致。 为了避免这种,框架在SDMemoryCache中另外引入了一个NSMapTable,value为弱引用来缓存图片。因为是弱引用所以不会有内存问题。 这样同一张图片会有两份缓存,读取图片的时候会先在NSCache中查找,找不到再到NSMapTable(这里是信号量加锁)中查找。比如当APP退到后台时,系统可能会将NSCache清空,但是由于图片还有UIImageView在强引用所以NSMapTable中缓存的图片不会被置为nil,回到前台时图片查找可以在NSMapTable中找到命中缓存,避免闪烁现象。 内部监听内存告警的通知,收到通知清空NSCache,NSMapTable不会清空。 该缓存方案因为无法自由控制清空哪些缓存的图片,缓存命中率没有LRU算法高。
缓存管理SDImageCache: 串行队列访问SDDiskCache,保证IO安全。 APP进入后台/退出,申请后台延时任务删除SDDiskCache过期文件。
图片的大小cost由CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef)得出
缓存的key,内存缓存一般直接用url.absoluteString,磁盘缓存url不直接作为文件名,一是为加密方面,另外是防止url中含有中文等作为文件路径时可能导致的格式问题,所以磁盘缓存的key会对url进行加密MD5处理,图片后缀保持不变。
3.1 在start方法中回到主线程使用NSURLConnection,将其添加到runloop的NSRunLoopCommonModes中下载图片。 内存缓存直接使用NSCache
3.6 在子线程使用NSURLConnection下载图片,并且启动子线程runloop确保子线程能接收到代理回调,回调完毕关闭runloop。内存缓存直接使用NSCache
4.1 采用NSURLSession下载图片,指定队列回调。 内存缓存直接使用NSCache
5.0 内存缓存采用NSCache的子类SDMemoryCache实现。
YYCache
内存缓存YYMemoryCache: 双向链表和字典配合实现,前者保证顺序,实现快速插入、移动节点,后者保证快速查找。实现了LRU淘汰算法,命中率较高。子线程释放对象,性能优化。
磁盘缓存YYDiskCache:将对象归档成为NSData后,使用sqlite和写文件配合存储。小于20KB用sqlite,大于用文件存储,同时sqlite写入存储文件的相关信息。