世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
Python语法基础
Python生态健全,在量化数据分析,机器学习等领域使用广泛
HTTP协议无状态
http是无状态(HTTP/1.0)的协议,HTTP/1.0之后呢?
【读书笔记】Swift教程
随着第三代语言规范的崛起,Apple推出了Swift,以后也是大力支持的官方语言,作为iOS开发者,学习Swift是大势所趋,Swift相比于Obj-C,更简洁,语言自带支持的面相协议编程也是解耦的最佳实现。
iOS开发中的 Self-Manager 模式
赋予一个 Widget 更大的权利,让其自己负责自己的事件
关于KVC的一些总结
iOS组件化之Target-Action方案
【读书笔记】《Effective Objective-C 2.0》
NSSet、NSDictionary底层实现
Foundation框架很多都是和Core Foundation对应,例如NSSet和_CFSet相对应,NSDictionary和_CFDictionary相对应。
GCD之 Barrier,Apply,Source
Dispatch Barrier栅栏函数
简介
在并发调度队列中执行的任务的同步点
使用栅栏函数来同步调度队列中一个或多个任务的执行。在向并发调度队列添加栅栏时,该队列会延迟栅栏任务(以及栅栏之后提交的所有任务)的执行,直到所有先前提交的任务都执行完成为止。在完成先前的任务后,队列将自己执行栅栏任务。栅栏任务执行完毕之后,队列将恢复其正常执行行为。
栅栏函数
同步栅栏函数
dispatch_barrier_sync
:提交一个栅栏 block 以同步执行,并等待该 block 执行完;dispatch_barrier_sync_f
:提交一个栅栏 function 以同步执行,并等待该 function 执行完。异步栅栏函数
dispatch_barrier_async
:提交一个栅栏 block 以异步执行,并直接返回;dispatch_barrier_async_f
:提交一个栅栏 function 以异步执行,并直接返回。
同步栅栏函数 dispatch_barrier_sync
1 | void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); |
dispatch_barrier_sync
同步栅栏函数会等待该函数传入的队列中的任务都执行完毕,再执行dispatch_barrier_sync
函数中的任务以及后面的任务,会阻塞当前线程。
- 使用场景:保证顺序执行,会阻塞当前线程
需求: 现有任务1、2、3、4,前两个任务执行完毕,再执行后两个任务以及主线程的代码。
解决方法:
- 使用 GCD 队列组;
- 使用
dispatch_barrier_sync
函数。
1 | dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT); |
异步栅栏函数 dispatch_barrier_async
1 | void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); |
先来看一下上面的代码改为异步栅栏函数的执行效果:
1 | dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT); |
从打印日志可以看到,改为异步栅栏函数,任务3、4仍然可以等到任务1、2以及栅栏任务都执行完毕再执行,但不会阻塞主线程,并且栅栏任务是在子线程执行。
dispatch_barrier_async
异步栅栏函数会等待该函数传入的队列中的任务都执行完毕,再执行dispatch_barrier_async
函数中的任务以及后面的任务,执行该函数会直接返回,继续往下执行代码,不会阻塞当前线程。
- 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新;
- 适合于大规模的 I/O 操作;
- 当访问数据库或者文件的时候,更新数据的时候不能和其他更新或者读取的操作在同一时间执行,可以使用队列组不过有点复杂,可以使用dispatch_barrier_async解决;
- 保证线程安全、读写安全。
使用场景1:保证线程安全
例如: 我们要从网络上异步获取很多图片,然后将它们添加到非线程安全的对象——数组中去:异步并发。
同一时间点,可能有多个线程执行给数组添加对象的方法,所以可能会丢掉 1 到多次,我们执行 1000 次,可能数组就保存了 990 多个,还有程序出现奔溃的可能。
解决办法:
① 加锁:比较耗时,而且下载完什么时候添加进数组也不一定。我们希望所有图片都下载完,再往数组里面添加;
② 使用 GCD 队列组;
③ 使用dispatch_barrier_async函数,栅栏中的任务会等待队列中的所有任务执行完成,才会执行栅栏中的任务,保证了线程安全。
1 | dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT); |
使用场景2:实现读写安全
dispatch_barrier_async
可以用来实现“读写安全”。我们将“写”操作放在dispatch_barrier_async
中,这样能确保在“写”操作的时候会等待前面的“读”操作完成,而后续的“读”操作也会等到“写”操作完成后才能继续执行,提高文件读写的执行效率。
1 | // 创建一个并发队列 |
1 | - (void)viewDidLoad { |
dispatch_barrier_sync
与 dispatch_barrier_async
的区别及注意点
相同点: 都会等待队列中在它们(栅栏)之前提交的任务都执行完毕,再执行它们的任务,接着执行它们后面的任务。
不同点:
dispatch_barrier_sync
:提交一个栅栏 block 以同步执行,并等待该 block 执行完;由于是同步,不会开启新的子线程,会阻塞当前线程。dispatch_barrier_async
:提交一个栅栏 block 以异步执行,并直接返回,会继续往下执行代码,不会阻塞当前线程。
注意点:
dispatch_barrier_(a)sync
函数传入的的队列必须是自己手动创建的并发队列,如果传入的是全局并发队列或者串行队列,那么这个函数是没有栅栏的效果的,效果等同于dispatch_(a)sync函数。- 只能栅栏
dispatch_barrier_(a)sync
函数中传入的queue。
Dispatch Apply多次执行
dispatch_apply
类似一个 for 循环。提交一个 block 到指定队列,并使该 block 执行指定的次数 n,等待 block 执行完 n 次才会返回。
1 | /*! |
注意点:
- 该函数会等待 block 都执行完毕才会返回,所以是同步的,会阻塞;
- 如果指定的队列是全局并发队列
dispatch_get_global_queue
,则这些 block 可以并发执行,这里需要注意可重入性; - 如果指定的队列是手动创建的并发队列,在有些情况下不会并发执行,所以建议使用全局并发队列
dispatch_get_global_queue
; - 当使用串行队列时,不会开启子线程,block 在主线程按串行执行;
- 当使用并发队列时,不一定会开启子线程,block 不一定都在子线程执行,也可能都在主线程执行,取决于任务的耗时程度。
串行队列
1 | - (void)test |
并发队列
1 | - (void)test |
- 使用场景
在某些场景下使用dispatch_apply
会对性能有很大的提升,比如你的代码需要以每个像素为基准来处理计算 image 图片。同时dispatch_apply能够避免一些线程爆炸的情况发生(创建很多线程)
1 | //危险,可能导致线程爆炸以及死锁 |
在异步中实现同步,并将任务并发执行
1 | dispatch_queue_t queue = dispatch_get_global_queue(0, 0); |
将上面代码的队列改为手动创建的并发队列,任务就不会并发执行:
1 | dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT); |
Dispatch Source
dispatch
框架提供一套接口用于监听系统底层对象(如文件描述符、Mach 端口、信号量等),当这些对象有事件产生时会自动把事件的处理 block
函数提交到 dispatch
队列中执行,这套接口就是 Dispatch Source API
,Dispatch Source
其实就是对 kqueue
功能的封装,可以去查看 dispatch_source
的 c 源码实现(什么是 kqueue
?Google,什么是 Mach 端口? Google Again),Dispatch Source 主要处理以下几种事件:
1 | DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加 |
当有事件发生时,dispatch source
自动将一个 block
放入一个 dispatch queue
执行。
GCD定时器
NSTimer
和 CADisplayLink
定时器不准时的问题,解决办法就是使用 GCD
定时器。GCD
的定时器是直接跟系统内核挂钩的,而且它不依赖于RunLoop,所以它非常的准时。
1 | dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL); |
dispatch_queue_set_specific
& dispatch_get_specific
这两个 API 类似于objc_setAssociatedObject
跟objc_getAssociatedObject
,FMDB 里就用到这个来防止死锁,来看看 FMDB 的部分源码:
1 | static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey; |
当要执行数据库操作时,如果在 queue 里面的 block 执行过程中,又调用了 indatabase 方法,需要检查是不是同一个 queue,因为同一个 queue 的话会产生死锁情况
1 | - (void)inDatabase:(void (^)(FMDatabase *db))block { |
dispatch_once_t
dispatch_once_t
必须是全局或static
变量
这一条算是“老生常谈”了,但我认为还是有必要强调一次,毕竟非全局或非static
的dispatch_once_t
变量在使用时会导致非常不好排查的bug,正确的如下:
1 | //静态变量,保证只有一份实例,才能确保只执行一次 |
注意
onceToken实现单例时释放单粒 onceToken的作用域和赋值
dispatch_after
dispatch_after
是延迟提交,不是延迟运行
先看看官方文档的说明:
Enqueue a block for execution at the specified time.
Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!。
看看如下代面试题:
1 | //创建串行队列 |
结果如下:
1 | 2015-03-31 20:57:27.122 GCDTest[45633:1812016] Begin add block... |