性能监控-①卡顿监控

卡顿监控

一、如何监控卡顿

以下内容摘自:iOS-Monitor-Platform 项目中的 如何监控卡顿

那怎么监控应用的卡顿情况?通常有以下两种方案

  • FPS 监控:这是最容易想到的一种方案,如果帧率越高意味着界面越流畅,上文也给出了计算 FPS 的实现方式,通过一段连续的 FPS 计算丢帧率来衡量当前页面绘制的质量。

  • 主线程卡顿监控:这是业内常用的一种检测卡顿的方法,通过开辟一个子线程来监控主线程的 RunLoop,当两个状态区域之间的耗时大于阈值时,就记为发生一次卡顿。美团的移动端性能监控方案 Hertz 采用的就是这种方式

    主线程卡顿监控的实现思路:开辟一个子线程,然后实时计算 kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting 两个状态区域之间的耗时是否超过某个阀值,来断定主线程的卡顿情况

    其他参考文章:iOS 性能监控(二)—— 主线程卡顿监控

更详细内容推荐查看项目中md文档。

二、FPS

FPS”通常指的是”Frames Per Second”,即每秒传输帧数,用于衡量动画或视频渲染的性能。

一般情况下,我们的屏幕刷新率是 1/60s 一次。CADisplayLink 实际上跟平常用的 NSTimer 的用法基本相似,NSTimer 的时间间隔是以秒为单位,而 CADisplayLink 则是使用帧率来作为时间间隔的单位。

计算核心:CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。

实践代码:CQDemoKit 的 CQTSFPSView.m

附:其他定时器

iOS:三种常见计时器(NSTimer、CADisplayLink、dispatch_source_t)的使用

1、NSTimer:进入后台时停止

iOS开发:解决App进入后台,倒计时(定时器)不能正常计时的问题

方法一(主流方法):根据记录开始的时间和获取当前时间进行时间差操作进行处理。监听进入前台、进入后台的消息,在进入后台的时候存一下时间戳,停掉定时器(系统会强制停止定时器);在再进入前台时,计算时间差。若剩余的时间大于时间差,就减去时间差,否则赋值剩余时间为0。

方法二:苹果只允许三种情况下的App在后台可以一直执行:音视频、定位更新、下载,若是直播、视频播放、地图类、有下载的应用可以这样使用,但是有些小需求就不需这样做。

方法三:通过向苹果的系统申请,在后台完成一个Task任务。

2、NSTimer:滑动时停止

2内存-②循环引用Timer.md》 中的 【3、NSTimer和NSRunLoop的关系?】

寻找卡顿的切入点

监控卡顿,最直接就是找到主线程都在干些啥玩意儿。

我们知道一个线程的消息事件处理都是依赖于NSRunLoop来驱动,所以要知道线程正在调用什么方法,就需要从NSRunLoop来入手

参考文章

iOS 性能监控(一)—— CPU功耗监控

iOS 性能监控(三)—— 方法耗时监控

iOS-卡顿监测-FPS监测

iOS实时卡顿检测-RunLoop(附实例)

必备知识架构-线程与网络-③网络

必备知识架构-线程与网络-③网络

[toc]

知识架构

iOS知识库

Android知识库

目录

九、在一个https连接的网站里,输入账号密码点击登录后,到服务器返回这个请求前,中间经历了什么

< 返回目录

回答这个问题,我们必须要先了解,一次完整的HTTP请求过程,大概是什么样的。

点击登录和服务器返回这个请求前,中间发生的起始就是一个域名解析和一次完整的http请求过程。

域名解析:即请求DNS服务器,进行域名解析。DNS服务器负责将你的网络地址解析成IP地址,这个IP地址对应网上一台机器;

而一次完整的HTTP请求过程,下面介绍之。

1、一次完整的HTTP请求过程

一次完整的HTTP请求过程如下图所示:

一次完整的HTTP请求过程
详情可参考:一次完整的HTTP请求过程

HTTP与HTTPS的区别

http和https使用的是完全不同的连接方式。

区别统计:

①HTTP是不安全的(它的信息使用明文传输),而HTTPS是安全的(会进行加密);

②使用的端口也不同,http使用的是80端口,https使用的是443端口。

③HTTP无需证书,而HTTPS需要认证证书。

在网络模型中,HTTP工作于应用层,而HTTPS工作在传输层;

2、TCP三次握手

一个故事让大家明白为什么会有tcp三次握手

1
2
3
4
5
> 敌人封锁江面, 我方间谍和联络员只能通过电报机隔着江面交流. 但是那时的电报机质量不稳定, 有可能会出现失灵的情况. 所以就出现了如下对话:
间谍: 联络员, 你能收到我发的话么? 你要是能收到, 就说明我这个电报机可以发电报.
联络员: 间谍, 我收到你发的话了, 这说明我的电报机可以收. 但是我不确定我的电报机能不能发, 你能收到我发的这句话么? 你要是能收到, 就说明我的电报机是可以发电报的.
间谍: 联络员, 我收到你的话了. 我的电报机也是能发能收, 我们可以正式交流情报了.
>

TCP三次握手过程

第一次握手: 客户端给服务器发送一个含有同步序列号SYN 报文。

主机A通过向主机B 发送一个含有同步序列号SYN的标志位的数据段给主机B ,向主机B 请求建立连接,通过这个数据段,主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我.

第二次握手: 服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。

主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用哪佧序列号作为起始数据段来回应我

第三次握手: 客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。

主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:”我已收到回复,我现在要开始传输实际数据了,这样三次握手就完成了,主机A和主机B 就可以传输数据了.

服务器收到 ACK 报文之后,三次握手建立完成。

附:

TCP三次握手的作用是为了确认双方的接收与发送能力是否正常。

握手过程中传送的包里正常不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据

但其实第三次握手的时候,是可以携带数据的。也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。

3、TCP四次挥手

1
2
3
4
5
6
> 举个栗子:把客户端比作男孩,服务器比作女孩。通过他们的分手来说明“四次挥手”过程。
> "第一次挥手":日久见人心,男孩发现女孩变成了自己讨厌的样子,忍无可忍,于是决定分手,随即写了一封信告诉女孩。
> “第二次挥手”:女孩收到信之后,知道了男孩要和自己分手,立马给男孩写了一封回信:分手就分手,给我点时间,我要把你的东西整理好,全部还给你!男孩收到女孩的第一封信之后,明白了女孩知道自己要和她分手。随后等待女孩把自己的东西收拾好。
> “第三次挥手”:过了几天,女孩把男孩送的东西都整理好了,于是再次写信给男孩:你的东西我整理好了,快把它们拿走,从此你我恩断义绝!
> “第四次挥手”:男孩收到女孩第二封信之后,知道了女孩收拾好东西了,可以正式分手了,于是再次写信告诉女孩:我知道了,这就去拿回来!
>

4、为什么TCP建立连接要进行3次握手,而断开连接要进行4次?

35 张图解:被问千百遍的 TCP 三次握手和四次挥手面试题

详解 TCP 连接的“ 三次握手 ”与“ 四次挥手 ”/为什么“握手”是三次,“挥手”却要四次?

建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。

释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。

所以是“三次握手”,“四次挥手”。

tcp_三次握手tcp_四次挥手

四次握手与三次握手

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。

为什么需要四次挥手。

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

5、TCP两次握手会出现什么问题

《计算机网络》(谢希仁 译)中讲了原因:
1.采用两次握手,那么若Client向Server发起的包A1如果在传输链路上遇到的故障,导致传输到Server的时间相当滞后,在这个时间段由于Client没有收到Server的对于包A1的确认(如果有确认,即Server有收到的话,会发送回一个B1包),那么就会重传一个包A2(应该是等超时了),假设服务器正常收到了A2的包,然后返回确认B2包。由于没有第三次握手,这个时候Client和Server就被认为是已经建立连接,可以传输数据了。

接着假设Client发送的第一个A1包随后在链路中传到了Server,对于Server来说这是一个新连接请求,然后Server又为这个连接申请资源,返回B1包,但是由于Client现在用的是A2包,A1包是无效的,Client对于返回的B1包也不会去理会,即后面建立的这个连接其实是没用的(相当于“僵尸”的连接),Server一直为这个连接维持着资源,造成资源的浪费。

所以采用两次握手,有可能会浪费Server的网络资源。

TCP的三次握手最主要是防止已过期/失效的连接再次传到被连接的主机。

为什么要进行第三次握手?

为了防止服务器端开启一些无用的连接增加服务器开销以及防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

十、TCP与UDP

< 返回目录

1、TCP与UDP的区别

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!
UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。

TCP与UDP的区别:

1
2
3
4
5
1. 基于连接与无连接;
2. 对系统资源的要求(TCP较多,UDP少);
3. UDP程序结构较简单;
4. 流模式与数据报模式 ;
5. TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

2、为什么说UDP是不可靠的?

答:UDP不是面向连接的,UDP传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说UDP是无连接的、不可靠的一种数据传输协议
也正由于上面的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。

也正由于上面的特点,使得UDP的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。

所以采用TCP传输协议的MSN比采用UDP的QQ传输文件慢,
但并不能说QQ的通信是不安全的,因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,即使是这样,UDP因为在底层协议的封装上没有采用类似 TCP的“三次握手”而实现了TCP所无法达到的传输效率。

3、移动端IM系统的协议选型:UDP还是TCP?

移动端IM系统的协议选型:UDP还是TCP?

TCP还是UDP?长连接如何实现?如何实现心跳机制?心跳的间隔如何确定?这些问题都是讨论移动端IM消息推送等类似话题时,几乎一定被问到的问题。

个人认为,更恰当的方式应该是:两种通信协议同时使用,各有侧重。UDP用于保持大量终端的在线与控制,应用与业务则通过TCP去实现。这个和FTP服务控制与数据分离,采取不同的连接,有异曲同工之处。
事实上,这个也是即时通讯巨头QQ所采用的方式。早期的时候,QQ还是主要使用TCP协议,而后来就转向了采用UDP的方式来保持在线,TCP的方式来上传和下载数据。现在,UDP是QQ的默认工作方式,表现良好。相信这个也被沿用到了微信上。
简单的考证:登录PC版QQ,关闭多余的QQ窗口只留下主窗口,并将其最小化。几分钟过后,查看系统网络连接,会发现QQ进程已不保有任何TCP连接,但有UDP网络活动。这时在发送聊天信息,或者打开其他窗口和功能,将发现QQ进程会启用TCP连接。

十一、网络数据缓存

< 返回目录

iOS开发网络篇—数据缓存

RTMP VS RTSP

RTMP(Real-Time Messaging Protocol)和RTSP(Real-Time Streaming Protocol)都是用于流媒体数据传输的协议,但它们在设计和应用上有一些关键的区别。

  • RTMP是基于TCP的协议,RTSP是一种基于TCP或UDP的网络控制协议。
  1. 延迟
    • RTMP的延迟通常在3-30秒,适合于对实时性要求不是极高的应用。
    • RTSP的延迟通常在2-5秒,适合于对实时性要求较高的应用。
  2. 编解码器支持
    • RTMP通常支持H.264视频编码和AAC音频编码。
    • RTSP支持多种视频编码,包括H.265、H.264、VP9等。

在选择使用RTMP还是RTSP时,需要根据具体应用场景、对实时性的要求、客户端兼容性以及安全性需求来决定。例如,如果需要与HTML5兼容且对实时性要求不高,可能会选择基于HTTP的流媒体协议如HLS或DASH。如果需要低延迟和复杂的交互控制,则可能会选择RTSP。而RTMP可能更适合那些已经建立在Flash基础上的旧系统。

1、RTSP

RTSP(Real-Time Streaming Protocol)和TCP、UDP都是网络协议,它们在数据传输和流媒体领域中扮演着重要的角色。下面我将分别介绍它们的特点和用途:

  1. RTSP(Real-Time Streaming Protocol)
    • 定义:RTSP是一种网络控制协议,用于控制流媒体服务器上的媒体流。它允许客户端发出播放、暂停、停止等控制命令。
    • 用途:RTSP常用于视频监控系统中,通过它可以实现对视频流的控制,如请求视频流的开始、停止、快进、倒退等。
    • 特点:
      • 基于TCP或UDP,通常使用TCP来保证控制消息的可靠传输
      • 它不传输媒体本身,而是控制媒体流的传输,媒体数据通常通过RTP(Real-time Transport Protocol)传输。
      • 支持多种媒体类型,包括音频和视频。
  2. TCP(Transmission Control Protocol)
    • 定义:TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。
    • 用途:TCP广泛应用于需要可靠传输的应用中,如网页浏览(HTTP)、文件传输(FTP)、邮件传输(SMTP)等。
    • 特点:
      • 确保数据包按顺序到达,如果数据包丢失,会重新发送。
      • 建立连接需要三次握手,断开连接需要四次挥手,这增加了一定的延迟。
  3. UDP(User Datagram Protocol)
    • 定义:UDP是一种无连接的传输层协议,它在IP协议的基础上提供一种简单的数据传输服务。
    • 用途:UDP适用于对实时性要求高的应用,如视频会议、在线游戏、实时监控等。
    • 特点:
      • 不保证数据包的顺序、完整性或可靠性,如果数据包丢失,不会重发。
      • 没有建立连接的过程,发送数据前不需要握手,因此延迟较低。
      • 适用于那些可以容忍一定丢包率的应用,或者应用层自己实现数据的重传和顺序控制。

在实际应用中,RTSP通常与RTP(Real-time Transport Protocol)配合使用,RTSP用于控制媒体流,而RTP用于实际传输媒体数据。RTP可以选择在TCP或UDP上运行,但在流媒体传输中,UDP更常见,因为它的低延迟特性更适合实时媒体流的传输。而TCP则因其可靠性,适用于需要确保数据完整性的场景。

2、RTP

实时传输协议(RTP)是一种网络协议,专门设计用于在互联网上传输音频和视频数据。它通常与实时传输控制协议(RTCP)一起使用,后者用于监控数据传输的质量和提供反馈。RTP 被设计为一个基于数据包的协议,将媒体流分成数据包进行传输,每个数据包都有一个序列号,使得接收方能够以正确的顺序重新组合数据包。此外,RTP 还包括一个时间戳,允许接收器同步音频和视频流。

RTP 通常用于各种实时音频和视频传输的应用,如IP语音(VoIP)、视频会议、流媒体和广播电视等。它被广泛支持,并经常与其他协议如RTSP和SIP结合使用,以在互联网上传输音频和视频内容。

RTP 数据包的结构包括一个固定头部和可选的扩展头部,以及实际的媒体数据。

网络 | http常见错误码

  • HTTP状态码介绍,以及HTTP错误代码的故障修复?

    HTTP 状态码是一个客户发出请求时候,WEB服务器返回给客户的一个状态回应,常见HTTP状态有以下五类:

    1.信息代码:1xx,

    2.成功代码:2xx,

    3.重定向:3xx,

    4.客户端错误:4xx,

    5.服务器错误:5xx

    其中,我们需要关注的是客户端和服务器端错误代码,即4xx和5xx代码以及这些代码的常见解决方案。

    客户端错误,从400到499编码的客户端错误是由客户端引起的某些错误导致的,该客户端是Web浏览器、curl命令或postman等其他服务器测试软件发出的请求等。

    服务器错误,当服务器发生错误或服务器识别出无法处理请求时,将发送代码为500到599的服务器错误。

常见状态码:

一些常见的状态码为:

  • 200 – 服务器成功返回网页

  • 302

  • 304(未修改)

  • 404 – 请求的网页不存在

  • 503 – 服务器超时

1xx(临时响应)

表示临时响应并需要请求者继续执行操作的状态代码。

2xx (成功)

表示成功处理了请求的状态代码。

3xx (重定向)

表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。

4xx(请求错误)

这些状态代码表示请求可能出错,妨碍了服务器的处理。

5xx(服务器错误)

这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

AFNetworking 302 重定向

所有的AF请求都会在post get等请求前执行下面这个方法,所以只需重写这个方法就能拦截请求

1
2
3
4
5
6
@interface AFURLSessionManager ()
// 省略一堆代码
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
// 省略一堆代码

@end

使用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
manager = [AFHTTPSessionManager manager];
[manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionTask * _Nonnull task, NSURLResponse * _Nonnull response, NSURLRequest * _Nonnull request) {
NSHTTPURLResponse *resPonse = (NSHTTPURLResponse *)response;
//1、如果要忽略重定向
/*
if (resPonse.statusCode == 302){
return nil;
}else {//正常请求
return request;
}
*/
// 2、通过重定向url去获取数据
if (resPonse.statusCode == 302){//如果响应code==302 就是重定向
NSMutableURLRequest *res = [NSMutableURLRequest requestWithURL:request.URL];
//通过抓包发现请求头不见了 所以在这里添加请求头,如果没有这个需求的可以忽略
NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:request.allHTTPHeaderFields];
NSString *valueStr = [NSString stringWithFormat:@" %@",localToken];
[headers setObject:valueStr forKey:@"Authorization"];
res.allHTTPHeaderFields = headers;

return res;
}else {//正常请求
return request;
}
}];

常见笔试/面试题

< 返回目录

1、简要说下Http通信协议的原理,与Socket协议的区别有哪些?

答:HTTP协议:简单对象访问协议,对应于应用层,HTTP协议是基于TCP连接的

tcp协议:对应于传输层

ip协议:对应于网络层

TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,才能使用TCP/IP协议。

http连接:http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断掉;

socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。

2、cookie 和session 的区别:

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、安全性:
cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗。
考虑到安全应当使用session。

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

5、所以个人建议:
将登陆信息等重要信息存放为SESSION
其他信息如果需要保留,可以放在COOKIE中

END

< 返回目录

第三方库SDWebImage①缓存-①NSCache

必备知识架构-第三方库SDWebImage①缓存-①NSCache

[toc]

一、NSCache的认识

1、为什么内存缓存要基于 NSCache?

NSCache和NSMutableDictionary,它们是iOS中常用的两个缓存类,基本上相同,都是健-值形式的内存缓存方式

1、NSCache的使用很方便,提供了类似可变字典的使用方式,但它比可变字典更适用于实现缓存,最重要的原因为NSCache是线程安全的,使用NSMutableDictionary自定义实现缓存时需要考虑加锁和释放锁NSCache已经帮我们做好了这一步,即在开发者自己不编写加锁代码的前提下,多个线程便可以同时访问NSCache。

2、其次,在内存不足时NSCache会自动释放存储的对象,不需要手动干预,如果是自定义实现需要监听内存状态然后做进一步的删除对象的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@interface NSCache <KeyType, ObjectType> : NSObject {
@private
id _delegate;
void *_private[5];
void *_reserved;
}

@property (copy) NSString *name;

@property (nullable, assign) id<NSCacheDelegate> delegate;

- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;

- (void)removeAllObjects;

// NSCache可以指定缓存的限额,当缓存超出限额自动释放内存
// ①对象缓存可持有最大的数量 ,默认是0 没有限制),一旦超出限额,会自动删除之前添加的缓存数据
@property NSUInteger countLimit; // limits are imprecise/not strict
// ②缓存中可持有的最大空间 默认是0(没有限制)
@property NSUInteger totalCostLimit; // limits are imprecise/not strict

// 管理丢弃内容
// 是否可以自动缓存清除可丢弃的内容,默认是YES
@property BOOL evictsObjectsWithDiscardedContent;

@end

3、还有一点就是NSCachekey不需要实现NSCopying协议,因为NSCache的键key不会被复制/拷贝。在键key不支持拷贝操作的情况下,该类用起来比字典更方便。

2、NSCache什么时候会删除缓存中的对象

NSCache删除缓存中的对象会在以下情形中发生:

  • NSCache缓存对象自身被释放
  • 手动调用removeObjectForKey:removeAllObjects方法
  • 缓存中对象的个数大于countLimit,或缓存中对象的总cost值大于totalCostLimit
  • 程序进入后台后
  • 收到系统的内存警告

二、SDMemoryCache的认识

以上已说明内存缓存要基于 NSCache,所以SDMemoryCache要继承于NSCache,源码如下:

1
2
3
4
5
6
7
8
9
10
// SDMemoryCache.h

/**
A memory cache which auto purge the cache on memory warning and support weak cache.
*/
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache> // 请注意这里除继承 NSCache <KeyType, ObjectType> 外,还有一个 与 SDMemoryCache 类名同名的 <SDMemoryCache> 协议

@property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config;

@end

可以看出类@interface SDMemoryCache还要遵守<SDMemoryCache>协议。虽然类@interface SDMemoryCache没提供什么方法,但与 SDMemoryCache 类名同名的<SDMemoryCache>协议提供了。

<SDMemoryCache>协议源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
A protocol to allow custom memory cache used in SDImageCache.
*/
@protocol SDMemoryCache <NSObject>

@required

- (nonnull instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;

- (nullable id)objectForKey:(nonnull id)key;

- (void)setObject:(nullable id)object forKey:(nonnull id)key;

- (void)setObject:(nullable id)object forKey:(nonnull id)key cost:(NSUInteger)cost;

- (void)removeObjectForKey:(nonnull id)key;
- (void)removeAllObjects;

@end

1、为什么另外建了个与类名SDMemoryCache同名的<SDMemoryCache>协议,并把方法提到了<SDMemoryCache>协议中?

答:为了当你想要要使用自定义的缓存类的时候,可以不用继承@interface SDMemoryCache,而只需遵循<SDMemoryCache>协议的方便。

设计模式-①概览.md

// 开闭原则:对扩展开放,对修改封闭.
// 里氏替换原则:应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。因为这里父类是抽象类,所以肯定遵守里氏替换原则。

2、内存缓存SDMemoryCache、磁盘缓存SDDiskCache的基类各是什么?

答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 内存类
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache>

@end


// 磁盘缓存类
@interface SDDiskCache : NSObject <SDDiskCache>

@property (nonatomic, strong, readonly, nonnull) SDImageCacheConfig *config;

- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath;

@end

三、内存缓存的设计

1、SDImageCache

id<SDMemoryCache> memoryCache;id<SDDiskCache> diskCache;

一个SDImageCache对象,由内存和磁盘共同控制缓存。控制的策略由SDImageCacheConfig类来定制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@interface SDImageCache : NSObject

#pragma mark - Properties
@property (nonatomic, copy, nonnull, readonly) SDImageCacheConfig *config;

@property (nonatomic, strong, readonly, nonnull) id<SDMemoryCache> memoryCache;
@property (nonatomic, strong, readonly, nonnull) id<SDDiskCache> diskCache;

@property (nonatomic, copy, nonnull, readonly) NSString *diskCachePath;

@property (nonatomic, copy, nullable) SDImageCacheAdditionalCachePathBlock additionalCachePathBlock;

#pragma mark - Singleton and initialization

/**
* Returns global shared cache instance
*/
@property (nonatomic, class, readonly, nonnull) SDImageCache *sharedImageCache;

/**
* Init a new cache store with a specific namespace
*
* @param ns The namespace to use for this cache store
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;

同时SDImageCache还有个类目

1
2
3
4
5
6
7
8
9
// SDImageCache.h
@interface SDImageCache (SDImageCache) <SDImageCache>

@end

// SDImageCacheDefine.h
@protocol SDImageCache <NSObject>

@end

内存和磁盘共同控制缓存策略定制类SDImageCacheConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// SDImageCacheConfig.h
@interface SDImageCacheConfig : NSObject <NSCopying>

// 是否使用内存做缓存,默认为YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground;

// 缓存图片的最长时间,单位是秒,默认是缓存一周
@property (assign, nonatomic) NSTimeInterval maxDiskAge;
// 缓存占用最大的空间,单位是字节
@property (assign, nonatomic) NSUInteger maxDiskSize;


@property (assign, nonatomic) NSUInteger maxMemoryCost;
@property (assign, nonatomic) NSUInteger maxMemoryCount;

/*
* The attribute which the clear cache will be checked against when clearing the disk cache
* Default is Modified Date
*/
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;



@end

2、SDImageCachesManager

管理NSArray<id> *caches;

一张图片就是一份SDImageCache元素对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
A caches manager to manage multiple caches.
*/
@interface SDImageCachesManager : NSObject <SDImageCache>

@property (nonatomic, class, readonly, nonnull) SDImageCachesManager *sharedManager;

@property (nonatomic, assign) SDImageCachesManagerOperationPolicy queryOperationPolicy;
@property (nonatomic, assign) SDImageCachesManagerOperationPolicy storeOperationPolicy;
@property (nonatomic, assign) SDImageCachesManagerOperationPolicy removeOperationPolicy;
@property (nonatomic, assign) SDImageCachesManagerOperationPolicy containsOperationPolicy;
@property (nonatomic, assign) SDImageCachesManagerOperationPolicy clearOperationPolicy;

@property (nonatomic, copy, nullable) NSArray<id<SDImageCache>> *caches; // 一张图片就是一份SDImageCache元素对象。

- (void)addCache:(nonnull id<SDImageCache>)cache;
- (void)removeCache:(nonnull id<SDImageCache>)cache;

@end

###

END

第三方库SDWebImage②请求-①简介

必备知识架构-第三方库SDWebImage②请求-①简介

[toc]

2、SDWebImageDownloader 异步的图片下载器

SDWebImageDownloader是一个异步的图片下载器,它是一个单例类,主要负责图片的下载操作的管理。图片的下载是放在一个NSOperationQueue操作队列中来完成的,默认情况下,队列最大并发数是6。如果需要的话,我们可以通过SDWebImageDownloader类的maxConcurrentDownloads属性来修改。其声明如下:

3、SDWebImageDownloaderOperation 下载操作

下面我们来说一说SDWebImage的下载操作SDWebImageDownloaderOperation。该类继承自NSOperation,并且采用了 SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate 四个协议方法。

①、SDWebImageDownloaderOperation的下载请求

先通过URL等生成NSURLRequest,并设置给operation属性。

1
2
3
4
5
6
7
声明:
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;

定义:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];

有时候下我们希望它支持后台下载。所以在operation的start方法中,如果支持后台shouldContinueWhenAppEntersBackground,则将当前请求添加到后台任务中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;

if (sself) {
[sself cancel];

[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}

对于图片的下载,SDWebImageDownloaderOperation的下载使用NSURLSession类。

1
self.dataTask = [session dataTaskWithRequest:self.request];
1
2
3
4
5
6
7
8
9
10
11
12
if (self.dataTask) {
[self.dataTask resume];

......
// 任务开始后,会在主线程抛出下载开始通知
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
......
}

END

SDWebImage①缓存-②缓存原理

[toc]

七、多线程–第三方库SDWebImage

参考资料:

这个类库提供一个UIImageView类别以支持加载来自网络的远程图片。具有缓存管理、异步下载、同一个URL下载次数控制和优化等特征。

基本思路

SDWebImage 基本思路如下:

SDWebImage基本思路

1 扩展(category) UIImageView,这样写出的代码更整洁

2 GCD 异步下载

3 重用 UITableViewCell 加异步下载会出现图片错位,所以每次 cell 渲染时都要预设一个图片 (placeholder),

以覆盖先前由于 cell 重用可能存在的图片, 同时要给 UIImageView 设置 tag 以防止错位。

4 内存 + 文件 二级缓存, 内存缓存基于 NSCache

暂时没有考虑 cell 划出屏幕的情况,一是没看明白 SDWebImage 是怎么判断滑出屏幕并 cancel 掉队列中对应的请求的

二是我觉得用户很多情况下滑下去一般还会滑回来,预加载一下也挺好。坏处是对当前页图片加载性能上有点小影响。

1、SDWebImage在图片下载及缓存的处理方法

SDWebImage加载网络图片的原理图分析如下:
SDWebImage加载网络图片的原理图分析

其中图片的获取与存储过程大概如下:
SDWebImage theory

大概描述为:
注:SDWebImage中的SDWebImageDownloader有使用到GCD相关dispatch_barrier_sync
1)当我门需要获取网络图片的时候,我们首先需要的便是URl没有URl什么都没有,获得URL后我们SDWebImage实现的并不是直接去请求网路,而是检查图片缓存中有没有和URl相关的图片,如果有则直接返回image,如果没有则进行下一步。

2)当图片缓存中没有图片时,SDWebImage依旧不会直从网络上获取,而是检查沙盒中是否存在图片,如果存在,则把沙盒中对应的图片存进image缓存中,然后按着第一步的判断进行。

3)如果沙盒中也不存在,则显示占位图,然后根据图片的下载队列缓存判断是否正在下载,如果下载则等待,避免二次下载。如果不存则创建下载队列,下载完毕后将下载操作从队列中清除,并且将image存入图片缓存中。

4)刷新UI(当然根据实际情况操作)将image存入沙盒缓存。

缓存策略的源码解析:SDWebImage 源码解析—缓存策略

END

第三方库SDWebImag②请求-①避免重复请求问题

必备知识架构-第三方库SDWebImag②请求-①避免重复请求问题

[toc]

iOS 如何避免在短时间内频繁发出相同的网络请求?

SDWebImage框架特征

  • 类别UIImageView,UIButton,MKAnnotationView- - 添加Web图像和高速缓存管理
  • 异步图像下载器
  • 具有自动缓存到期处理的异步内存+磁盘映像缓存
  • 背景图像解压缩
  • 保证相同的URL不会被下载多次
  • 保证虚假网址不会重复重试
  • 保证主线程永远不会被阻止
  • 使用GCD和ARC

一、SDWebImage是如何避免在短时间内频繁发出相同的网络请求?

SDWebImage是如何避免在短时间内频繁发出相同的网络请求?

设想:一个列表所有的图片请求都是同一个url。

答:

1、如何确保不生成重复的网络请求

图片下载的回调信息存储在SDWebImageDownloader类的URLOperations属性中,该属性是一个字典,key是图片的URL地址,value则是一个SDWebImageDownloaderOperation对象,包含每个图片的多组回调信息。由于我们允许多个图片同时下载,因此可能会有多个线程同时操作URLOperations属性。需要保证URLOperations操作(添加、删除)的线程安全性。

为了避免同一个URL在被多次任务请求的时候,进行多次的重复网络下载。

1、将下载地址URL与其对应的网络下载请求,通过下载管理器SDWebImageDownloader的URLOperations属性管理起来。(该属性是一个字典,key是图片的URL地址,value是operation)

2、我们并不会对每次操作一进来就进行创建请求任务,而是先通过之前缓存的下载任务URLOperations,通过URL寻找是否有该操作了,①如果有则不创建,而是直接取出来使用;②如果没有才创建operation来使用,并添加到URLOperations中。以备后续有同样URL请求的时候,能会从URLOperations中得到operation,就不会导致重复创建和添加到队列中了。(通过URLOperations,我们以此保证一个URL同时在被请求多次的情况下,生成/取到的是同一个,也只有一个SDWebImageDownloaderOperation,从而也就只会被下载一次。)。

在SDWebImage版本5.8.1中的相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
SD_LOCK(self.operationsLock);	// 6、加锁,防止添加过程中,又有数据需要添加,保证线程安全
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url]; // 1、从进行中的 URLOperations 获取
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context]; // 2、如果之前没有下载操作,则创建,并加入到 URLOperations 中
// ....省略一堆代码
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
if (!self) {
return;
}
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url]; // 3、下载完成后,记得删除操作
SD_UNLOCK(self.operationsLock);
};
self.URLOperations[url] = operation; // 4、保存到 URLOperations 中,用于判断
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
[self.downloadQueue addOperation:operation]; // 4、添加操作

} else {
// ....省略一堆代码
@synchronized (operation) {
// 5、如果之前有下载操作,则不用创建操作,但是仍然需要保存回调,不然其他位置的图片的请求就丢失了(虽然说它们是同一张图片)
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
}
SD_UNLOCK(self.operationsLock); // 6、解锁

3、问:重复的url请求,没有创建operation了,那operation的回调怎么办?

答:虽然重复的URL只有一个SDWebImageDownloaderOperation。但是这个opeartion的callbackBlocks是个数组,所有的回调都用这个数组保存起来的,所以不会丢失的(这里callbackBlocks数组里存放字典,对应进行时候的ProgressCallback回调和结束时候的CompletedCallback回调)。

4、问:对同一个operation的callbackBlocks数组操作,有什么要注意的?

答:因为不管同个url不管有没有新的operation生成,我们都会有一个当前任务对应downloadOperationCancelToken生成,来给这个operation添加回调。即同一个url所对应opeartion里的callbackBlocks数组,在有多次图片任务时候,就会有多次操作callbackBlocks的情况。所以对callbackBlocks的操作,也要保证线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// SDWebImageDownloader.m

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// ....省略一堆代码

SD_LOCK(self.operationsLock);
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished || operation.isCancelled) {
// ....省略一堆代码
} else {
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
// ....省略一堆代码
}
SD_UNLOCK(self.operationsLock);

// 虽然重复的URL只有一个SDWebImageDownloaderOperation。但是SDWebImageDownloadToken是每个URL都会有一个的,只是他们的SDWebImageDownloaderOperation是同一个。
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;

return token;
}

对callbackBlocks的操作,也要保证线程安全,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SDWebImageDownloaderOperation.m

@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
@synchronized (self) {
[self.callbackBlocks addObject:callbacks];
}
return callbacks;
}

2、它是如何保证请求管理的线程安全

说明:由于我们允许多个任务同时进行,也就造成了会有多个线程同时操作URLOperations属性。为了保证URLOperations操作(添加、删除)的线程安全性,我们添加了一个锁,且考虑到各种具备锁功能的性能问题,这里我们使用信号量semaphore。所以,我们控制线程安全的锁是使用信号量实现的operationsLock,且其初始值为1,用它来确保同一时间只有一个线程操作URLOperations属性

在SDWebImage版本5.8.1中的相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SDWebImageDownloader.m

- (nonnull instancetype)init {
return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig];
}

- (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config {
self = [super init];
if (self) {
// ....省略一堆代码
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
// ....省略一堆代码
_operationsLock = dispatch_semaphore_create(1);
// ....省略一堆代码
}
return self;
}

3、其它知识说明

3.1、SDWebImageDownloader 是什么?(异步的图片下载器)

SDWebImageDownloader是一个异步的图片下载器,它是一个单例类,主要负责图片的下载操作的管理。

图片的下载是放在一个NSOperationQueue操作队列中来完成的,默认情况下,队列最大并发数是6。

1
2
3
4
5
6
7
8
9
10
11
12
13
// SDWebImageDownloader.m
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;


_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;

// SDWebImageDownloaderConfig.h
/**
* The maximum number of concurrent downloads.
* Defaults to 6.
*/
@property (nonatomic, assign) NSInteger maxConcurrentDownloads;

如果需要的话,我们可以通过修改maxConcurrentDownloads属性来修改并发下载数。

二、其他版本的SDWebImage的代码解析

1、SDWebImageDownloader 是什么?(异步的图片下载器)

其声明及定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
//SDWebImageDownloader.m

@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;

- (id)init {
if ((self = [super init])) {
...
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); // 并发队列
...
}
return self;
}

每有一个图片下载请求,SDWebImageDownloader图片下载管理器就会生成一个继承自NSOperation的下载操作SDWebImageDownloaderOperation,并添加到下载队列downloadQueue中。

[sself.downloadQueue addOperation:operation];

该队列允许修改最大并发数。

1、由于我们允许多个任务同时进行,也就造成了会有多个线程同时操作URLOperations属性。为了保证URLOperations操作(添加、删除)的线程安全性,我们添加了线程操作的信号量operationsLock,其初始值为1,来确保同一时间只有一个线程操作URLOperations属性,我们以添加操作为例,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (!operation) {
operation = createCallback();
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
}
UNLOCK(self.operationsLock);

2、同样由于为了避免同一个URL的图片被下载多次,所以我们并不会对每次操作一进来就进行创建请求任务,而是先通过之前缓存的下载任务URLOperations中通过URL寻找是否有该操作了,如果有则不创建,如果没有才创建。因此,这里就会有一个URL同时在被请求多次的情况下,生成/取到的是同一个SDWebImageDownloaderOperation。

三、其他

对于下载任务的执行,SDWebImage还允许我们设置是默认的先进先出还是后进先出。该功能的实现,其自然是通过队列操作的依赖来完成的(其中lastAddedOperation指的是上一次的操作)。源代码如下:

1
2
3
4
5
6
[sself.downloadQueue addOperation:operation];
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}

其他参考文章

END

第三方库SDWebImage①缓存-①缓存不更新问题

必备知识架构-第三方库SDWebImage①缓存-①缓存不更新问题

[Toc]

一、背景

后台图片内容换了,但是url还是老的,手机就用了缓存,没有从后台更新图片。

主要问题表现在哪里呢?
很多app都有用户的概念,用户一般都会有头像,基本上都上传到服务器上,而服务器往往也支持在pc端更新头像(比如微博、QQ等)。
如果你的头像使用SDWebImage设置的,那么你会发现,pc端更新头像后,客户端可能(往往)不会自动更新!!!

二、解决

问:使用SDWebImage如何加载url不变,但图片已经变化的图片。

答:解决方法可以有如下几种:

  1. 让服务器更新url,也就是说服务器端如果更新了头像,那么就生成新的url(推荐)

    后台给的url中增加字段,表示图片是否更新,比如增加一个timestamp字段.图片更新了,就更新下这个字段;
    对客户端来说,只要这个timestamp字段变了,整个url就不一样了,就会从网络取图片。比如http://xxx/xx? timestamp=xxx
    也可以添加图片文件的md5来表示文件是否更新,比如http://xxx/xx? md5=xxx。并且md5比时间戳要好,这是强校验。时间戳在服务器回滚或者服务器重启的时候会有特殊的逻辑。不过大多数时候时间戳也够用了。
    ====这个方案客户端不用改,后台改动也不会太大。====强烈推荐

  2. 客户端只使用内存缓存,不使用磁盘缓存,那么下次启动时候就会重新下载,从而得到最新的了。(缺点:本次使用过程中没能看到最新图片。且即使之后下载到最新图片了,由于只使用内存缓存,不使用磁盘缓存。导致程序关闭又打开之后,缓存就没了,需要访问网络,重新加载图片)

    SDWebImageCacheMemoryOnly这个参数对解决这个问题有帮助,只用内存缓存,不用磁盘缓存,App关了再开,肯定会重新下载,不会出现服务器和手机缓存图片不一致的情况。

  3. 客户端使用SDWebImageRefreshCached,同时让服务器端支持cache-control。

    SDWebImageRefreshCached,这个参数就是为了解决url没变但是服务器图片改变的问题,很适合当前的场景。方案就是磁盘缓存不自己实现了,直接使用NSURLCache。记得AFNetworking的大神Matt就曾经嘲笑过SDWebImage的缓存是多此一举,还不如系统的NSURLCache好用。

    SDWebImageRefreshCached参数设置之后,会怎么样?

    • 不使用SDWebImage提供的内存缓存和硬盘缓存

    • 采用NSURLCache提供的缓存,默认情况下有效时间只有5秒

    • 图片不一致的问题是解决了,不过效果跟不使用缓存差别不大。个人建议这个参数还是不要用为好,为了一个小特性,丢掉了SDWebImage最核心的特色。

      1
      2
      3
      4
      >   [imageview sd_setImageWithURL:[NSURL URLWithString:url]	
      > placeholderImage:nil
      > options:SDWebImageRefreshCached];
      >
  4. 不使用SDWebImage,自己控制缓存,用系统API实现(NSURLCache)实现。(缺点还是和3一样,得服务端配合更改。)

    主要也是和使用SDWebImageRefreshCached时候一样,会涉及到Cache-Control(设定缓存有效时间,默认是5s)Last-Modified/If-Modified-Since(时间戳)Etag/If-None-Match(标签,一般用MD5值)

附:其他options

SDWebImageRetryFailed表示就算下载失败也会再次尝试(不把下载失败的的url加入黑名单)

参考文章:

数据库-①FMDB

必备知识架构-数据库-①FMDB

[toc]

一、FMDB的多线程问题

你项目用FMDB吗?你写的数据库类是线程安全的吗?如果不是,希望这篇小文章会对你有点帮助

1、FMDB如何保证多线程处理的数据安全

对于数据操作,最重要的一点就是数据安全的问题,在多线程中,线程安全是数据安全的首要前提,下面谈谈FMDB 是如何对多线程进行处理的。

详情参考:FMDB 中的多线程处理

FMDatabaseQueue为何会线程安全的一点心得

2、FMDatabaseQueue为何能保证线程安全

在做iOS项目的时候,会用到许多第三方的框架,比如用FMDB来处理数据库的东西。但是FMDatabase并不是线程安全的,当你在不同的线程里同时操作一个Database的时候,就容易产生崩溃。但是好在FMDB提供了一个解决的方法,就是FMDatabaseQueue。通过它来操作数据库就是线程安全的了。
即:FMDB是使用databaseQueue实现数据库操作线程安全

1
2
3
4
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:_dbPath];
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdateWithFormat:@"INSERT INTO TABLENAME(id, name) VALUES (%zd, %@);", @1, @"idage"];
}];

queue inDatabase:的本质如下:

FMDB数据安全1

_queue的初始化为:

1
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

FMDB是怎么处理线程安全的?

综上所述:FMDB的线程处理就是强行就所有的多线程逻辑在一个串行队列中同步处理。

二、FMDB的数据库事务(Database Transaction)

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。简单的来说就是可以同时处理多个数据库操作,并且速度很快。

FMDB是支持数据库事务的。

1
2
3
4
5
6
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];

三、为什么要从FMDB迁移到WCDB?

为什么要从FMDB迁移到WCDB?

表结构

WCDB 提供了 ORM 的功能,将类的属性绑定到数据库表的字段。在日常实践中,类的属性名和表的字段名通常不一致。因此,WCDB 提供了 WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName) 宏,用于映射属性名。

END

第三方库SDWebImage①缓存-①缓存不更新问题

必备知识架构-第三方库SDWebImage③其他

[toc]

iOS - 主线程调度在应用中的小技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma mark - 宏定义
#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
block();\
} else {\
dispatch_async(queue, block);\
}
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif

dispatch_queue_get_label:返回创建队列时为队列指定的标签。

END