37、理解“块”这一概念
typedef void(^VoidBlock)(void);
typedef void(^StringBlock)(NSString *value);
1》块是C/c++/OC 的此法闭包
2》块可接受参数,也可返回值
3》块可以分配在栈或堆上,也可以是全局的。 分配在栈上的块可拷贝到堆里,这样的话,就和标准的OC对象一样,具备引用计数了。
块的基础知识
枚举方法、常用的方法末尾,都使用“内联块” 。
块的内部结构:
块本身也是对象
《1》invoke: 函数指针,指向块的实现代码。
《2》函数原型至少要接受一个void*型参数,此参数代表块。
《3》块其实就是一种代替函数指针的语法结构,原来使用函数指针时,需要用“不透明的void指针”来传递状态。 而改用块之后,则可以吧原来用标准C语言特性所编写的代码封装成简明且易用的接口。
《4》descriptor变量是指向结构体的指针,每个块都包含此结构体,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针。
《5》块还会把它所捕获的所有变量拷贝一份。 这些拷贝放在descriptor变量后面, 捕获了多少个变量,就要占据多少内存空间。 Note:拷贝的并不是对象本身,而是指向这些对象的指针变量。 invoke函数为何需要把块对象作为参数传递进来呢?原因就在于: 执行块时,要从内存中把这些捕获到的变量读出来。
全局块、栈块以及堆块
栈:系统自动回收的。
堆: 相当于一个对象,有对应的引用计数。
全局:编译的时候就确定了,所以在程序结束的时候才回收。
38、为常用的块类型创建typedef
1》 以typedef重新定义块类型, 可令块变量用起来更加简单
2》定义新类型应遵从现有额命名习惯,勿使用其名称与别的类型相冲突
3》不妨为同一个块签名定义多个类型别名。 如果要重构的diamante使用了块类型的某个别名,那么值需要修改相应的typedef中的块签名即可,无需改动其他typedef。
因为块的语法非常难记, 所以,我们可以取别名简化定义。
39、用handler块降低代码分散程度
1》 在创建对象时, 可以使用内联的handler块将相关业务逻辑一并声明
2》在有多个实例需监控时,如果采用委托模式,那么经常需要传入的对象来切换,而若修改handler块来实现,则可直接间更快与相关的对象放在一起
3》设计API时如果用到了handler块,那么可以增加一个参数,使调用者可以通过此参数来决定应该把块安排在哪个队列上执行。
常常用到的异步线程,防止在网络请求的时候,阻塞主线程。
异步方法在执行任务完成之后,需要以某种手段通知相关代码。 试下此工鞥呢有很多办法,常用的技巧是涉及一个委托协议
。
委托协议,在tableView上有经典的使用。 并不会错误,但是存在的问题是代码比较分散。 一般是调用次数比较多的,为了提高性能,就会使用了delegate。
委托模式有个缺点:如果类要分别使用多个获取器下载不同数据,那么就得delegate回调方法里根据传入的获取 其参数来切换。
不仅令代码变长,而且还要吧挽留过数据获取器对象保存为实例变量,以便于判断语句中使用。
改为块: 无需在回调方法里切换。 每个handler的业务逻辑,都是和相关的获取器对象一起来定义的:
block的好处: 集中业务代码,代码更加简洁。
block 可以把成功的数据和失败的数据合成block返回,也可分开处理。
—— 一般推荐集合返回处理。
写成为一个:
缺点:代码比较长,且比较复杂。
好处:更为灵活。 eg:数据下载到一半,网络出现故障。 (2)还有一个好处是如果返回数据太短,这个和网络获取数据失败可以作为同一种情况处理。
(3)某些代码必须云心在特定线程上。 eg:UI必须在主线程上。 最好由调用API的人来决定handler应该运行在哪个线程上。
image.png
40、用块引用其所属对象时不要出现循环引用
1》如果块所捕获的对象直接或简洁的保留了块本省,就得当心循环问题
2》一定要找个适当的时机解除循环应用,而不能包责任推给API的调用者。
可以使用weak的方式声明、或者在执行block完之后,将block=nil.
41、 多用派发队列, 少用同步锁
1》派发队列可以用来表述同步语义(synchronization semantic),这种做法要比使用@synchronized 块或NSLock对象更简单
2》将同步与异步派发结合起来,可以实现与普通枷锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程
3》使用不同队列以及栅栏块,可以令同步行为更加高效。
[有关内容需要补充]
42、多用GCD,少用performSelector系列方法
1》performSelector 系列方法在内存管理方面容易疏失。 它无法确定将要执行的选择子具体是什么, 因为ARC编译器也就无法插入适当的内存管理方法
2》performSelector 系列方法所能处理的选择子太过于局限了,选择子的返回值类型以及发送给方法的参数个数都受到限制
3》如果想把任务放在另一个线程上执行,那么最好不要用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。
【内容需要补充】
43、掌握GCD以及操作队列的使用时机
1》要解决多线程与任务管理问题时, 派发队列并非唯一方案
2》操作队列提供了一套高层的OC API, 能实现纯GCD所具备的绝大部分功能, 而且还能完成一些更为复杂的操作,哪些操作若改用GCD来实现,则需要另外编写代码。
[内容需要补充]
44、通过dispatch group 机制,根据系统资源状况来执行任务。
1》一系列任务可归入一个dispatch group 之中。开发者可以在这组任务之心完毕时获得通知。
2》通过dispatch group , 可以在并发式派发队列里同时执行多项任务。此时,GCD会根据系统资源装快来调度这些并发的任务。 开发者若自己来实现此功能,则需要编写大量代码。
45、使用dispatch_once 来执行只需要运行一次的线程安全代码。
单例模式
上面是两种方式实现的单例, 发现后者比前者速度更加快。
1》经常需要编写“只需要执行一次的线程安全代码”。通过GCD所提供的dispatch_once 函数,很容易就能够实现此功能
2》标记应该声明在static或global作用于中, 这样的话,在吧只需要之心一次的块传给dispatch_once函数时,穿进去的标记也是相同的
46、不要使用dispatch_get_current_queue
网友评论