当前位置: 首页 > news >正文

网络公司除了建网站株洲网站推广优化

网络公司除了建网站,株洲网站推广优化,180天做180个网站,阿里云服务器上如何做网站掌握GCD及操作队列的使用时机 在执行后台任务时#xff0c;GCD 并不一定是最佳方式。还有一种技术叫做 NSOperationQueue#xff0c;它虽然与 GCD 不同#xff0c;但是却与之相关#xff0c;开发者可以把操作以 NSOperation 子类的形式放在队列中#xff0c;而这些操作也…掌握GCD及操作队列的使用时机 在执行后台任务时GCD 并不一定是最佳方式。还有一种技术叫做 NSOperationQueue它虽然与 GCD 不同但是却与之相关开发者可以把操作以 NSOperation 子类的形式放在队列中而这些操作也能够并发执行。 GCD是纯C的API而NSOperationQueue是Objective-C的对象。这意味着使用GCD时任务通过块block来表示而块是一种轻量级的数据结构而使用NSOperationQueue时任务通过NSOperation的子类来表示这是一种更为重量级的Objective-C对象。 虽然GCD提供了一种更轻量级的方式来处理任务但并不总是最佳选择。有时候使用NSOperationQueue所带来的开销微乎其微而使用完整的对象所带来的好处可能会超过其缺点。NSOperationQueue提供了更多的灵活性和控制例如可以对操作进行取消、暂停和恢复等操作。 NSOperationQueue相比于纯GCD的优势 取消操作 使用NSOperationQueue可以轻松取消操作。可以在NSOperation对象上调用cancel方法来设置取消标志这使得取消操作变得更加简单。相比之下如果使用纯GCD任务一旦被提交到队列中就无法取消。指定操作间的依赖关系 NSOperation允许指定操作之间的依赖关系这使得某些操作必须在其他操作执行完毕后才能执行。这种依赖关系对于需要按特定顺序执行任务的情况非常有用。监控操作属性 NSOperation对象的属性可以通过键值观察KVO机制进行监控这使得可以轻松地检测操作的状态变化例如判断操作是否被取消或完成。指定操作的优先级 NSOperation允许指定操作的优先级这使得可以控制操作执行的顺序。与GCD不同NSOperation提供了更为灵活的优先级管理机制。重用NSOperation对象 NSOperation对象是Objective-C的对象可以存储任何信息并且可以多次使用。这使得NSOperation相对于简单的GCD块更为强大因为它们可以包含更多的逻辑和状态信息。 有一个 API 选用了操作队列而非派发队列这就是 NSNotificationCenter 开发者可通过其中的方法来注册监听器以便在发生相关事件时得到通知而这个方法接受的参数是块不是选择子 - (id)addObserverForName:(NSString *)name object:(id)object queue:(NSOperationQueue *)queue usingBlock:(void(^)(NSNotification *))block;在解决多线程与任务管理问题时派发队列并非唯一方案。操作队列提供了一套高层的 Objective-C API能实现纯 GCD 所具备的绝大部份功能而且还能完成一些更为复杂的操作那些操作若改用 GCD 来实现则需另外编写代码。 通过Dispatch Group机制根据系统资源状况来执行任务 dispatch group(意为“派发分组”或“调度组”) 是 GCD 的一项特性能够把任务分组。 其中最重要的用法就是把将要并发执行的多个任务合为一组于是调用者就可以知道这些任务何时才能全部执行完毕。 把压缩一系列文件的任务表示成 dispatch group下面这个函数可以创建 dispatch group: dispatch_group_t dispatch_group_create();想把任务编组有两种办法。第一种是用下面这个函数 void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);它是普通 dispatch_async 函数的变体比原来多一个参数用于表示待执行的块所归属的组。还有种办法能够指定任务所属的 dispatch group那就是使用下面这一对函数 void dispatch_group_enter(dispatch_group_t group); void dispatch_group_leave(dispatch_group_t group);前者能够使分组里正要执行的任务数递增而后者则使之递减。调用了 dispatch_group_enter 以后必须有与之对应的 dispatch_group_leave 才行。这与引用计数相似。 下面这个函数可用于等待 dispatch group 执行完毕 long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);此函数接受两个参数一个是要等待的 group另一个是代表等待时间的 timeout 值。timeout 参数表示函数在等待 dispatch group 执行完毕时应该阻塞多久。 除了可以用上面那个函数等待 dispatch group 执行完毕之外也可以换个办法使用下列函数: void dispatch_group_notiy(dispath_group_t group, dispatch_queue_t queue, dispath_block_t block);不同的是开发者可以向此函数传入块等 dispatch group 执行完毕之后块会在特定的线程上执行。 比方说在 Mac OS X 与 iOS 系统中都不应阻塞主线程因为所有 UI 绘制及事件处理都要在主线程上执行。如果想令数组中的每个对象都执行某项任务并且想等待所有任务执行完毕那么就可以使用这个 GCD 特性来实现。 若当前线程不应阻塞则可用 notify 函数来取代 wait: dispatch_queue_t notifyQueue dispatch_get_main_queue(); dispatch_group_notify(dispatchGroup, notifyQueue, ^{//... });也可以把某些任务放在优先级高的线程上执行同时仍然把所有任务都归入同一个 dispatch group。并在执行完毕时获得通知。 开发者未必总需要使用 dispatch group。有时候采用单个队列搭配标准的异步派发也可以实现相同效果。 为了执行队列中的块GCD 会在适当的时机自动创建新线程或复用旧线程。如果使用并发队列那么其中有可能会有多个线程这也意味着多个块可以并发执行。在并发队列中执行任务所用的并发线程数量取决于各种因素而GCD 只要是根据系统资源状况来判定这些因素的。假如 CPU 有多个核心并且队列中有大量任务等待执行那么GCD 就可能会给该队列配备多个线程。通过 dispatch group 所提供的这种简便方式既可以并发执行一系列给定的任务又能在全部任务结束时得到通知。 一系列任务可归入一个 dispatch group 之中。开发者可以在这组任务执行完毕时获得通知。通过 dispatch group 可以在并发式派发队列里同时执行多项任务。此时 GCD 会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能则需编写大量代码。 使用dispatch_once来执行只需运行一次的线程安全代码 单例模式常见的实现方式为:在类中编写名为 sharedInstance 的方法该方法只会返回全类共用的单例实例而不会在每次调用时都创建新的实例。比如说 implementation EOCClass (instancetype)sharedInstance {static EOCClass *sharedInstance nil;static dispatch_once_t onceToken;dispatch_once(onceToken, ^{sharedInstance [[self alloc] init];});return sharedInstance; }end不过GCD 引入了一项特性能使单例实现起来更为容易。所用的函数是 void dispatch_once(dispatch_once_t *token, dispatch_block_t block);此函数接受类型为 dispatch_once_t 的特殊参数笔者称其为 “标记”token此外还接受块参数。对于给定的标记来说该函数保证相关的块必定会执行且仅执行一次。此操作完全是线程安全的。 刚才实现单例模式所用的 sharedInstance 方法可以用此函数来改写 (instancetype)sharedInstance {static EOCClass *sharedInstance nil;static dispatch_once_t onceToken;dispatch_once(onceToken, ^{sharedInstance [[self alloc] init];});return sharedInstance; }使用 dispatch_once 可以简化代码并且彻底保证线程安全开发者根本无须担心加锁或同步。所有问题都由 GCD 在底层处理。由于每次调用时都必须使用完全相同的标记所以标记要声明成 static。此外dispatch_once 更高效。 经常需要编写 “只需执行一次的线程安全代码”thread-safe single-code execution。通过 GCD 所提供的 dispatch_once 函数很容易就能实现此功能。标记应该声明在 statci 或 global 作用域中这样的话在把只需执行一次的块传给dispatch_once 函数时传进去的标记也是相同的。 不要使用dispatch_get_current_queue 使用 GCD 时经常需要判断当前代码正在哪个队列上执行向多个队列派发任务时更是如此。 dispatch_get_current_queue本来是用于解决由不可重入的代码所引发的死锁但是因为它已经被废弃所以可以选择通过 GCD 所提供的功能来设定“队列特有数据”queue-specific data此功能可以把任意数据以键值对的形式关联到队列里。最重要之处在于假如根据指定的键获取不到关联数据那么系统就会沿着层级体系向上查找直至找到数据或到达根队列为止。比如说这个例子 dispatch_queue_t queueA dispatch_queue_create(com.effectiveobjectivec.queueA, NULL); dispatch_queue_t queueB dispatch_queue_create(com.effectiveobjectivec.queueB, NULL);dispatch_set_target_queue(queueB, queueA);static int kQueueSpecific;CFStringRef queueSpecificValue CFSTR(queueA);dispatch_queue_set_specific(queueA, kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);dispatch_sync(queueB, ^{dispatch_block_t block ^{ NSLog(No deadlock!); };CFStringRef retrievedValue dispatch_get_specific(kQueueSpecific);if (retrievedValue) {block();} else {dispatch_sync(queueA, block);} });在上面这段代码中有两个串行队列 queueA 和 queueB。将 queueB 的目标队列设置为 queueA表示 queueB 依赖于 queueA。定义一个静态整型变量 kQueueSpecific 作为键值用于关联队列特有数据。使用 dispatch_queue_set_specific 函数将特定值在这里是 queueA 的标识符与 queueA 关联起来。在 queueB 上执行同步任务内部判断是否可以访问 queueA 的特定值。如果能够访问到 queueA 的特定值则直接执行任务否则在 queueA 上同步执行任务。 这样即使在 queueB 上同步执行任务也不会产生死锁因为在获取 queueA 的特定值时会自动向上查找到其父级队列 queueA 的特定值从而避免了循环等待。 dispatch_get_current_queue 函数对行为常常与开发者所预期的不同。此函数在iOS开发中已经废弃、只应做调试之用。由于派发队列是按层级来组织的所以无法单用某个队列对象来描述“当前队列”这一概念。dispatch_get_current_queue 函数用于解决由不可重入的代码所引发的死锁然而能用函数解决的问题通常也能改用“队列特定数据”来解决。 熟悉系统框架 编写 Objective-C 应用程序时几乎都会用到系统框架如果直接使用这些框架中的类那么应用程序就可以得益于新版系统库所带来的改进而开发者也就无须手动更新其代码了。 将一系列代码封装为动态库dynamic library并在其中放入描述其接口的头文件这样做出来的东西就叫框架。 各种常用框架 总结 Cocoa 和 Cocoa Touch 框架Cocoa 框架用于 macOS 应用程序开发而 Cocoa Touch 用于 iOS 应用程序开发。它们集成了一系列常用的框架和工具用于创建图形界面应用程序。Foundation 框架Foundation 框架是 Cocoa 和 Cocoa Touch 中的一个核心框架包含了诸如 NSObject、NSArray、NSDictionary 等基础类。它提供了许多基础核心功能例如集合类、字符串处理以及字符串解析等。CoreFoundation 框架CoreFoundation 框架不是 Objective-C 框架而是用 C 语言编写的。然而它与 Foundation 框架密切相关提供了一套与 Foundation 中相对应的 C 语言 API。CoreFoundation 中的数据结构可以与 Foundation 中的 Objective-C 对象进行平滑转换这种技术称为“无缝桥接”。CFNetwork 框架此框架提供了C语言级别的网络通信能力它将“BSD套接字”BSD socket抽象成易于使用的网络接口。而 Foundation 则将该框架里的部分内容封装为 Objective-C 语言的接口以便进行网络通信例如可以用 NSURLConnection 从 URL 中下载数据。CoreAudio 框架该框架所提供的 C语言API 可用来操作设备上的音频硬件。这个框架属于比较难用的那种因为音频处理本身就很复杂。所幸由这套API 可以抽象出另外一套 Objective-C 式API用后者来处理音频问题会更简单些。AVFoundation框架此框架所提供的Objective-C 对像可用来回放并录制音频及视频比如能够在 UI 视图类里播放视频。CoreData 框架此框架所提供的 Objective-C 接口可将对象放入数据库以便持久保存。CoreData 会处理数据的获取及存储事宜而且可以跨越 Mac OS X 及 iOS 平台。CoreText 框架此框架提供的C 语言接口可以高效执行文字排版及渲染操作。AppKit 和 UIKit 框架AppKit 用于 macOS 应用程序开发而 UIKit 用于 iOS 应用程序开发。它们是构建在 Foundation 和 CoreFoundation 之上的核心 UI 框架提供了 UI 元素和粘合机制用于组装应用程序的所有内容。CoreAnimation 框架CoreAnimation 是一个用 Objective-C 编写的框架它提供了渲染图形和播放动画所需的工具。虽然 CoreAnimation 本身不是一个框架但它是 QuartzCore 框架的一部分被广泛用于 UI 框架中。CoreGraphics 框架CoreGraphics 是用 C 语言编写的框架提供了绘制 2D 图形所需的数据结构和函数。UIKit 框架中的 UIView 类在确定视图控件之间的相对位置时会用到 CoreGraphics 中定义的数据结构。其他框架除了上述核心 UI 框架之外还有许多其他框架构建在 UI 框架之上例如 MapKit 框架用于为 iOS 应用程序提供地图功能Social 框架用于为 macOS 和 iOS 应用程序提供社交网络功能。这些框架通常与操作系统平台对应的核心 UI 框架结合使用以丰富应用程序的功能。 可以看出Objective-C的一项重要特点经常需要使用底层的 C 语言级 API。用 C 语言来实现 API 的好处是可以绕过 Objective-C 的运行期系统从而提升执行速度。 许多系统框架都可以直接使用。其中最重要的是 Foundation 与 CoreFoundation这两个框架提供了构建应用程序所需的许多核心功能。很多常见任务都能用框架来做例如音频与视频处理、网络通信、数据管理等。请记住用纯C 写成的框架与用 Objective-C 写成的一样重要若想成为优秀的 Objective-C 开发者应该掌握C 语言的核心概念。 多用块枚举少用for循环 语言中引入“块”这一特性后又多出来几种新的遍历方式采用这几种新方式遍历 collection 时通常会大幅度简化编码过程笔者下面将会详细说明。 for循环 for循环是大家都很熟悉的写法。但是用它遍历字典或者set就会很麻烦。因为字典与 set 都是“无序的”所以无法根据特定的整数下标来直接访问其中的值。 for循环也可以执行反向遍历执行反向遍历时使用 for 循环会比其他方式简单许多。 使用 NSEnumerator 来遍历 NSEnumerator 是个抽象基类其中只定义了两个方法供其具体子类concrete subclass来实现 - (NSArray *)allObjects; - (id)nextObject;其中关键的方法是 nextObject它返回枚举里的下个对象。每次调用该方法时其内部数据结构都会更新使得下次调用方法时能返回下个对象。等到枚举中的全部对象都已返回之后再调用就将返回 nil这表示达到枚举末端了。 例如遍历字典和set // DictionaryNSDictionary *aDictionary /*...*/;NSEnumerator *enumerator [aDictionary keyEnumerator];id key;while ((key [enumerator nextObject]) ! nil) {id value aDictionary[key];// Do something with key and value}// set NSSet *aSet /*...*/;NSEnumerator *enumerator [aSet objectEnumerator];id object;while ((object [enumerator nextObject]) ! nil) {// Do something with object}在第一段代码中使用了 NSDictionary 类的 keyEnumerator 方法来获取字典中所有键的枚举器然后通过枚举器逐个获取键并使用键来访问字典中的值。在注释部分的代码块中可以对每个键值对执行相应的操作。 在第二段代码中使用了 NSSet 类的 objectEnumerator 方法来获取集合中的所有对象的枚举器然后通过枚举器逐个获取集合中的对象。在注释部分的代码块中可以对每个对象执行相应的操作。 快速遍历 快速遍历与使用NSEnumerator 来遍历差不多然而语法更简洁它就是for…in… 这样写简单多了。如果某个类的对象支持快速遍历那么就可以宣称自己遵从名为 NSFastEnumeration 的协议从而令开发者可以采用此语法来迭代该对象。 遍历字典与set 也很简单 // Dictionary NSDictionary *aDictionary /*...*/;for (id key in aDictionary) {id value aDictionary[key];// Do something with key and value}// SetNSSet *aSet /*...*/;for (id object in aSet) {// Do something with object}由于 NSEnumerator 对象也实现了 NSFastEnumeration 协议所以能用来执行反向遍历。若要反向遍历数组可采用下面这种写法 NSArray *anArray /*...*/;for (id object in [anArray reverseObjectEnumerator]) {// Do something with object}基于块的遍历方式 最新引入的一种做法就是基于块来遍历。NSArray 中定义了下面这个方法它可以实现最基本的遍历功能 - (void)enumerateObjectsUsingBlock:(void(^)(id object, NSUInteger idx, BOOL *stop))block;这个方法的方法名是enumerateObjectsUsingBlock:它的参数一个块作为参数块包含三个参数object数组中的元素idx元素在数组中的索引stop一个指向布尔值的指针用于控制遍历过程。如果将 *stop 设置为 YES则遍历会停止。 该方法遍历时既能获取对象也能知道其下标。此方法还提供了一种优雅的机制用于终止遍历操作。 用它遍历字典与 set 也同样简单 // DictonaryNSDictionary *aDictionary /*...*/;[aDictionary enumerateKeyAndObjectsUsingBlock:^(id key, id object, BOOL *stop){// Do something with key and objectif (shouldStop) {*stop YES;}}];// Set NSSet *aSet /*...*/;[aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop) {// Do something with objectif (shouldStop) {*stop YES;}}];此方式大大胜过其他方式的地方在于遍历时可以直接从块里获取更多信息。在遍历数组时可以知道当前所针对的下标。遍历有序set NSOrderedSet时也一样。而在遍历字典时无须额外编码即可同时获取键与值因而省去了根据给定键来获取对应值这一步。 另外一个好处是能够修改块的方法签名以免进行类型转换操作。 若已知字典中的对象必为字符串则用基于块的方式来遍历可以这样编码 NSDictionary *aDictionary /*...*/;[aDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {// Do something with key and obj}];之所以能如此是因为 id 类型相当特殊它可以像本例这样为其他类型所覆写。 用此方式也可以执行反向遍历。数组、字典、set 都实现了前述方法的另一个版本使开发者可向其传入 “选项掩码”option mask: - (void)enumerateObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void(^)(id obj, NSUInteger idx, BOOL *stop))block- (void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void(^)(id key, id obj, BOOL *stop))blockNSEnumerationOption 类型是个 enum,其各种取值可用“按位或”bitwise OR连接用以表明遍历方式。 遍历collection 有四种方法。最基本的办法是 for 循环其次是 NSEnumerator 遍历法及快速遍历法最新、最先进的方式则是“块枚举法”。“块枚举法”本身就能通过GCD 来并发执行遍历操作无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点。若提前知道待遍历的 collection 含有何种对象则应修改块签名指出对象的具体类型。 对自定义其内存管理语义的collection使用无缝桥接 CoreFoundation 框架也定义了一套C 语言API用于操作表示这些collection 及其他各种collection 的数据结构。例如NSArray 是Foundation 框架中表示数组的 Objective-C 类而CFArray 则是 CoreFoundation 框架中的等价物。这两种创建数组的方式也许有区别然而有项强大的功能可在这两个类型之间平滑转换它就是 “无缝桥接”toll-free bridging。 使用“无缝桥接”技术可以在定义于 Foundation 框架中的 Objective-C 类和定义于 CoreFoundation 框架中的C数据结构之间相互转换。 下列代码演示了简单的无缝桥接 NSArray *anNSArray [1, 2, 3, 4, 5];CFArrayRef aCFArray (_bridge CFArrayRef)anNSArray;NSLog(Size of array %li, CFArrayGetCount(aCFArray));// Output: size of array 5这段代码演示了如何将NSArray对象转换为CFArrayRef类型使用__bridge进行类型转换将anNSArray转换为CFArrayRef类型的对象aCFArray。 __bridge 本身的意思是ARC 仍然具备这个 Objective-C 对象的所用权。而 __bridge_retained 则与之相反意味着ARC 将交出对象的所有权。与之相似反向转换可通过 __bridge_transfer 来实现。这三种转换方式称为 “桥式转换”。 在使用 Foundation 框架中的字典对象时会遇到一个大问题那就是其键的内存管理语义为 “拷贝”而值的语义是却是“保留”。除非使用强大的无缝桥接技术否则无法改变其语义。 CoreFoundation 框架中的字典类型叫做 CFDictionary。其可变版本称为 CFMutableDictionary 。创建 CFMutableDictionary 时可以通过下列方法来指定键和值的内存管理语义 CFMutableDictionaryRef CFDictionaryCreateMutable (CFAllocatorRef allocator,CFIndex capacity,const CFDictionaryKeyCallBacks *keyCallBacks,const CFDictionaryValueCallBacks *valueCallBacks}首个参数表示将要使用的内存分配器。CoreFoundation 对象里的数据结构需要占用内存而分配器负责分配及回收这些内存。开发者通常为这个参数传入 NULL表示采用默认的分配器。第二个参数定义了字典的初始大小。它并不会限制字典的最大容量只是向分配器提示了一开始应该分配多少内存。最后两个参数定义了许多回调函数用于指示字典中的键和值在遇到各种事件时应该执行何种操作。 通过无缝桥接技术可以在 Foundation 框架中的 Objective-C 对象与 CoreFoundation 框架中的 C 语言数据结构之间来回转换。在 CoreFoundation 层面创建collection 时可以指定许多回调函数这些函数表示此collection 应如何处理其元素。然后可运用无缝桥接技术将其转换成具备特殊内存管理语义的 Objective-C collection。 构建缓存时选用NSCache而非NSDctionary 实现缓存时使用NSCache 类更好它是 Foundation 框架专为处理这种任务而设计的。。 NSCache 胜过 NSDictionary 之处在于当系统资源将要耗尽时它可以自动删减缓存。 NSCache 并不会“拷贝”键而是会 “保留”它。NSCache 对象不拷贝键的原因在于很多时候键都是由不支持拷贝操作的对象来充当的。 另外NSCache 是线程安全的。而 NSDictionary 则绝对不具备此优势意思就是在开发者自己不编写加锁代码的前提下多个线程便可以同时访问 NSCache。 开发者可以操控缓存删减其内容的时机。有两个与系统资源相关的尺度可供调整其一是缓存中的对象总数其二是所有对象的“总开销”。开发者在将对象加入缓存时可为其指定“开销值”。当对象总数或总开销超过上限时缓存就可能会删减其中的对象了在可用的系统资源趋于紧张时也会这么做。 向缓存中添加对象时需要计算对象的“开销值”。这个“开销值”是一个附加因素通常用于帮助决定何时从缓存中移除对象。 下面这段代码演示了缓存的用法: #import Foundation/Foundation.htypedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);interface EOCNetworkFetcher : NSObject - (id)initWithURL:(NSURL *)url;- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler;endinterface EOCClass : NSObjectendimplementation EOCClass {NSCache *_cache;}- (id)init {if ((self [super init])) {_cache [NSCache new];_cache.countLimit 100;/*** The Size in bytes of data is used as the cost,* so this sets a cost limit of 5MB.*/_cache.totalCostLimit 5 * 1024 * 1024;}return self;}- (void)downloadDataForURL:(NSURL *)url {NSData *cachedData [_cache objectForKey:url];if (cachedData) {// Cache hit[self useData:cacheData];} else {// Cache missEOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];[fetcher startWithCompletionHandler:^(NSData *data) {[_cache setObject:data forKey:url cost:data.length];[self useData:data];}];}}end这段代码实现了一个网络数据下载器 EOCNetworkFetcher 和一个使用了缓存的类 EOCClass。 EOCNetworkFetcher 类负责从指定的 URL 下载数据。它包含了一个初始化方法 initWithURL: 和一个启动下载的方法 startWithCompletionHandler:。 EOCClass 类包含了一个 NSCache 对象 _cache用于缓存下载的数据。它还有一个方法 downloadDataForURL:用于下载指定 URL 的数据首先检查缓存中是否有数据如果有则直接使用缓存的数据如果没有则启动网络下载器进行下载并将下载的数据存入缓存中。 在本例中下载数据所用的 URL就是缓存的键。若缓存中没有访问者所需的数据则下载数据并将其放入缓存。 还有个类叫做 NSPurgeableData此类是NSMutableData 的子类而且实现了 NSDiscardableContent 协议。如果某个对象所占的内存能够根据需要随时丢弃那么就可以实现该协议所定义的接口。 如果将 NSPurgeableData 对象加入NSCache那么当该对象为系统所丢弃时也会自动从缓存中移除。通过 NSCache 的 evictsObjectsWithDiscardedContent 属性可以开启或关闭此功能。 使用 NSPurgeableData 改写的话: - (void)downloadDataForURL:(NSURL *)url {NSPurgeableData *cachedData [_cache objectForKey:url];if (cachedData) {[cacheData beginContentAccess];[self useData:cachedData];[cacheData endContentAccess];} else {EOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];[fetcher startWithCompletionHandler:^(NSData *data) {NSPurgeableData *purgeableData [NSPurgeableData dataWithData:data];[_cache setObject:purgeableData forKey:url cost: purgeableData.length];[self useData:data];[purgeableData endContentAccess];}];}    }这段代码通过 _cache 对象使用给定的 url 从缓存中获取数据。这里使用了 NSPurgeableData 类型的 cachedData 对象来存储获取到的数据。 如果缓存中存在数据即 cachedData 不为 nil则进入缓存命中的分支。在这个分支中首先通过 beginContentAccess 方法将数据标记为开始访问状态然后使用该数据最后通过 endContentAccess 方法将访问结束。 如果缓存中不存在数据则进入缓存未命中的分支。在这个分支中首先创建一个 EOCNetworkFetcher 对象用于从网络下载数据。在下载完成后将下载的数据封装为 NSPurgeableData 对象并存入缓存中。然后使用该数据最后通过 endContentAccess 方法将访问结束。 实现缓存时应选用 NSCache 而非 NSDictionary 对象。因为 NSCache 可以提供优雅的自动删减功能而且是“线程安全的”此外它与字典不同并不会拷贝键。可以给 NSCache 对象设置上限用以限制缓存中的对象总个数及“总成本”而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的“硬限制”hard limit它们仅对NSCache 起指导作用。将 NSPurgeableData 与 NSCache 搭配使用可实现自动清除数据的功能也就是说当 NSPurgeableData 对象所占内存为系统所丢弃时该对象自身也会从缓存中移除。如果缓存使用得当那么应用程序的响应速度就能提高。只有那种 “重新计算起来很费事的”数据才值得放入缓存比如那些需要从网络获取或从磁盘读取的数据。 精简initialize与load的实现代码 有时候类必须先执行某些初始化操作然后才能正常使用。-(void) load 方法它是一个类方法用于在类或分类被加载到运行时系统时执行。每个类和分类都会在程序启动时调用一次 load 方法且仅调用一次。 load 方法的执行顺序是先执行类的 load 方法然后执行分类的 load 方法。这意味着如果一个类有多个分类并且它们都实现了 load 方法那么先执行类的 load 方法然后按照分类的引入顺序依次执行各个分类的 load 方法。 然而load 方法存在一个问题即在执行该方法时运行时系统处于“脆弱状态”。这意味着在 load 方法中使用其他类可能是不安全的因为在执行子类的 load 方法之前必定会先执行所有超类的 load 方法。而在加载依赖的其他库时无法确定其中各个类的加载顺序因此在 load 方法中使用其他类是不可靠的。 比如说 #import EOCClassA.h implementation EOCClassB(void)load {NSLog(Loading EOCClassB);EOCClassA *object [EOCClassA new];}在 EOCClassB 的load 方法里使用 EOCClassA 却不太安全因为无法确定在执行 EOCClassB 的 load 方法之前EOCClassA 是不是已经加载好了。 load 方法不像普通的方法一样遵循继承规则。如果一个类自身没有实现 load 方法那么无论其超类是否实现了 load 方法系统都不会自动调用。这意味着子类的 load 方法不会自动调用其父类的 load 方法除非子类自己实现了 load 方法并在其中显式调用了父类的 load 方法。 而且 load 方法务必实现得精简一些也就是要尽量减少其所执行的操作因为整个应用程序在执行load 方法时都会阻塞。 想执行与类相关的初始化操作还有个办法就是覆写下列方法 (void)initialize;对于每个类来说该方法会在程序首次用该类之前调用且只调用一次。 它是由运行期系统来调用的绝不应该通过代码直接调用。只有当程序用到了相关的类时它才会调用。 此方法与 load 还有个区别就是运行期系统在执行该方法时是处于正常状态的因此从运行期系统完整度上来讲此时可以安全使用并调用任意类中的任意方法。 最后一个区别是: initialize 方法与其他消息一样如果某个类未实现它而其超类实现了那么就会运行超类的实现代码。即它是可以继承的。 当初始化基类 EOCBaseClass 时EOCBaseClass 中定义的 initialize 方法要运行一遍而当初始化 EOCSubClass 时由于该类并未覆写此方法因而还要把父类的实现代码再运行一遍。鉴于此通常都会这么来实现 initialize 方法: (void)initialize {if (self [EOCBaseClass class]) {NSLog(% initialized, self);} }load 与 initialize 方法的实现代码要尽量精简。在里面设置一些状态使本类能够正常运作就可以了不要执行那种耗时太久或需要加锁的任务。对于 load 方法来说其原因已在前面解释过了而 initialize 方法要保持精简的原因也与之相似。 其二开发者无法控制类的初始化时机。类在首次使用之前肯定要初始化但编写程序时不能令代码依赖特定的时间点否则会很危险。 最后一个原因如果某个类的实现代码很复杂那么其中可能会直接或间接用到其他类。若那些类尚未初始化则系统会迫使其初始化。 initialize 方法只应该用来设置内部数据。不应该在其中调用其他方法即便是本类自己的方法也最好别调用。 在加载阶段如果类实现了 load 方法那么系统就会调用它。分类里也可以定义此方法类的 load 方法要比分类中的先调用。与其他方法不同load 方法不参与覆写机制。首次使用某个类之前系统会向其发送 initialize 消息。由于此方法遵从普通的覆写规则所以通常应该在里面判断当前要初始化的是哪个类。load 与 initialize 方法都应该实现的精简一些这有助于保持应用程序的响应能力也能减少引入 “保留环”interdependency cycle的几率。无法在编译期设定的全局常量可以放在 initialize 方法里初始化。 别忘了NSTimer会保留其目标对象 计时器要和 “运行循环”run loop相关联运行循环到时候会触发任务。创建 NSTimer 时可以将其“预先安排”在当前的运行循环中也可以先创建好然后由开发者自己来调度。无论采用哪种方式只有把计时器放在运行循环里它才能正常触发任务。 由于计时器会保留其目标对象所以反复执行任务通常会导致应用程序出问题。也就是说设置成重复执行模式的那种计时器很容易引入 “保留环”。 比如说下列代码 #import Foundation/Foundation.hinterface EOCClass : NSObject - (void)startPolling;- (void)stopPolling;endimplementation EOCClass {NSTimer *_pollTimer;}- (id)init {return [super init];}- (void)dealloc {[_pollTimer invalidate];}- (void)stopPolling {[_pollTimer invalidate];_pollTimer nil;}- (void)startPolling {_pollTimer [NSTimer scheduledTimerWithTimeInterval: 5.0 target: self selector:selector(p_doPoll) userInfo: nil repeats: YES];}- (void)p_doPoll {}end这段代码有个问题当创建 EOCClass 实例并调用其 startPolling 方法时会创建一个 NSTimer 对象并将其赋值给 _pollTimer 实例变量。由于 NSTimer 对象的目标对象是 EOCClass 实例本身因此会对 EOCClass 实例进行强引用导致 EOCClass 实例无法被释放。而 _pollTimer 实例变量也会对 NSTimer 对象进行强引用使得 NSTimer 对象也无法被释放。这样就形成了一个保留环即相互持有对方的强引用导致内存泄漏。 如果想在系统回收本类实例的过程中令计时器无效从而打破保留环那又会陷入死结。因为在计时器对象尚且有效时EOCClass 实例的保留计数绝不会降为 0 因此系统也绝不会将其回收。而现在又没人来调用 invalidate 方法所以计时器将一直处于有效状态。 当指向 EOCClass 实例的最后一个外部引用被移除后该实例仍然存活因为计时器持有对它的强引用。同时计时器对象也无法被系统释放因为它被 EOCClass 实例强引用。这导致了实例和计时器对象互相持有对方的强引用形成了保留环导致内存泄漏。 更糟糕的是除了计时器之外已经没有其他引用指向 EOCClass 实例了因此该实例会永远被保留无法被释放。 这个问题可通过“块”来解决。虽然计时器当前并不直接支持块但是可以用下面这段代码为其添加此功能 #import Foundation/Foundation.hinterface NSTimer (EOCBlockSupport) (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;endimplementation NSTimer (EOCBlocksSupport) (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats {return [self scheduledTimerWithTimeInterval:interval target:self selector:selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats]; } (void)eoc_blockInvoke:(NSTimer *)timer {void (^block)() timer.userInfo;if (block) {block();} }end这段代码为 NSTimer 添加了支持块blocks的功能。通过类别category扩展了 NSTimer 类添加了一个类方法 eoc_scheduledTimerWithTimeInterval:block:repeats:用于创建带有块回调的定时器。 在 eoc_scheduledTimerWithTimeInterval:block:repeats: 方法中调用了 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 方法创建了一个定时器并传入了一个 selector但是这个 selector 实际上是 eoc_blockInvoke: 方法。同时将传入的块对象通过 copy 方法复制并作为 userInfo 参数传递给定时器。 eoc_blockInvoke: 方法是一个类方法用于实际执行定时器触发时的回调操作。它从定时器的 userInfo 中获取了保存的块对象并执行该块对象。 这样通过调用 eoc_scheduledTimerWithTimeInterval:block:repeats: 方法创建的定时器当触发定时器时就会执行传入的块代码块。 我们修改刚才那段有问题的范例代码使用新分类中的 eoc_scheduledTimerWithTimeInterval 方法来创建计时器并改用 weak 引用即可打破保留环 - (void)startPolling {__weak EOCClass *weakSelf self;_pollTimer [NSTimer eoc_scheduledTimerWithTimeInterval: 5.0 block:^{EOCClass *strongSelf weakSelf;[strongSelf p_doPoll];} repeats:YES];}这段代码采用了一种很有效的写法它先定义了一个弱引用令其指向 self然后使块捕获这个引用而不直接去捕获普通的 self 变量也就是说self 不会为计时器所保留。当块开始执行时立刻生成 strong 引用以保证实例在执行期间持续存活。 采用这种写法之后如果外界指向 EOCClass 实例的最后一个引用将其释放则该实例就可为系统所回收了。 NSTimer 对象会保留其目标直到计时器本身失效为止调用 invalidate 方法可令计时器失效另外一次性的计时器在触发完任务之后也会失效。反复执行任务的计时器repeating timer很容易引入保留环如果这种计时器的目标对象又保留了计时器本身那肯定会导致保留环。这种环状保留关系可能是直接发生的也可能是通过对象图里的其他对象间接发生的。可以扩充 NSTimer 的功能用 “块”来打破保留环。不过除非 NSTimer 将来在公共接口里提供此功能否则必须创建分类将相关实现代码加入其中。
http://www.sczhlp.com/news/158464/

相关文章:

  • 网站添加多个关键词某公司网络营销现状分析
  • 网站备案号是什么意思中国进入一级战备有多可怕?
  • 网站建设中html5源码务川网站建设
  • 美妆网站源码asp现在c 做网站用什么软件
  • 淘宝联盟怎样做新增网站推广网站优化关键词排名
  • 无锡网站App微信网站如何做超链接
  • 珠海品牌网站设计小型网站开发
  • 小域名 网站备案vue新增页面
  • 嘉兴外贸网站建设企业网页设计
  • 北京网站建设网站建设wordpress后台无法预览文章
  • DeploySharp开源发布:让C#部署深度学习模型更加简单
  • 论文速读记录 | 2025.10
  • 【Rust GUI开发入门】编写一个本地音乐播放器(15. 记录运行日志) - Jordan
  • 东莞++网站建设赤峰市建设网站
  • 商城网站服务器租用用户图片上传wordpress
  • 河北邯郸做网站的公司哪家好防伪查询网站
  • 公司建网站一般多少钱wordpress幻灯片怎么建
  • 胜利油田局域网主页入口包括搜索引擎排名、网页标签优化、相关链接交换、网络广告投放等
  • 动效h5网站wordpress mysql 搭建
  • 网站建设与管理模拟题1视频二维码生成器
  • 网站开发哪种语言最好热点网站建设
  • 软文网站发布平台erp开发和网站开发
  • 怎么做自己的代刷网站网上投资网站建设
  • 做网站运营有前途么推广下载app
  • 网站上做镜像是什么意思wordpress 获取id
  • 多个网站建站银川品牌网站建设公司
  • 网站地图导出怎么做学历提升大专大概要多少钱
  • 三亚网站建设公司公关
  • 网站模板优势镇江市质监站网址
  • 开设类似于京东商城这类购物网站wordpress开通邮箱