广东外贸网站建设企业,吉林票务通app,钓鱼网站制作教程视频,做pvc卡片的交流网站文章目录第37条#xff1a;理解block这一概念第38条#xff1a;为常用的块类型创建typedef第39条#xff1a;用handler块降低代码分散程度第41条#xff1a;多用派发队列#xff0c;少用同步锁方案一#xff1a;使用串行同步队列来将读写操作都安排到同一个队列里#x…
文章目录第37条理解block这一概念第38条为常用的块类型创建typedef第39条用handler块降低代码分散程度第41条多用派发队列少用同步锁方案一使用串行同步队列来将读写操作都安排到同一个队列里方案二将写操作放入栅栏快中让他们单独执行将读取操作并发执行。第42条多用GCD少用performSelector系列方法第43条掌握GCD及操作队列的使用时机第44条通过Dispath Group机制根据系统资源状况来执行任务第45条使用dispatch_once来执行只需运行一次的线程安全代码第46条不要使用dispatch_get_current_queue第37条理解block这一概念
对于“块”的基础知识就不再赘述了这里强调一下块的种类。
块(Block)分为三类
栈块堆块全局块
栈block
定义块的时候其所占内存区域是分配在栈中的而且只在定义它的那个范围内有效
void (^block)();if ( /* some condition */ ) {block ^{NSLog(Block A);};} else {block ^{NSLog(Block B);};
}block();上面定义的两个块只在if else语句范围内有效一旦离开了最后一个右括号如果编译器覆写了分配给块的内存那么就会造成程序崩溃。
堆block
为了解决这个问题我们可以给对象发送copy消息复制一份到堆里并自带引用计数
void (^block)();if ( /* some condition */ ) {block [^{NSLog(Block A);} copy];
} else {block [^{NSLog(Block B);} copy];
}block();全局block
全局块声明在全局内存里而不需要在每次用到的时候于栈中创建。
void (^block)() ^{NSLog(This is a block);
};第38条为常用的块类型创建typedef
如果我们需要重复创建某种块相同参数返回值的变量我们就可以通过typedef来给某一种块定义属于它自己的新类型
例如
int (^variableName)(BOOL flag, int value) ^(BOOL flag, int value){// Implementationreturn someInt;
}这个块有一个bool参数和一个int参数并返回int类型。我们可以给它定义类型
typedef int(^EOCSomeBlock)(BOOL flag, int value);再次定义的时候就可以通过简单的赋值来实现
EOCSomeBlock block ^(BOOL flag, int value){// Implementation
};定义作为参数的块
- (void)startWithCompletionHandler: (void(^)(NSData *data, NSError *error))completion;这里的块有一个NSData参数一个NSError参数并没有返回值
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;”通过typedef定义块签名的好处是:如果要某种块增加参数那么只修改定义签名的那行代码即可。
第39条用handler块降低代码分散程度
下载网络数据时如果使用代理方法会使得代码分布不紧凑而且如果有多个下载任务的话还要在回调的代理中判断当前请求的类型。但是如果使用block的话就可以让网络下载的代码和回调处理的代码写在一起这样就可以同时解决上面的两个问题
用代理下载
- (void)fetchFooData {NSURL *url [[NSURL alloc] initWithString:http://www.example.com/foo.dat];_fooFetcher [[EOCNetworkFetcher alloc] initWithURL:url];_fooFetcher.delegate self;[_fooFetcher start];}- (void)fetchBarData {NSURL *url [[NSURL alloc] initWithString: http://www.example.com/bar.dat];_barFetcher [[EOCNetworkFetcher alloc] initWithURL:url];_barFetcher.delegate self;[_barFetcher start];}- (void)networkFetcher:(EOCNetworkFetcher*)networkFetcher didFinishWithData:(NSData*)data
{ //判断下载器类型if (networkFetcher _fooFetcher) {_fetchedFooData data;_fooFetcher nil;} else if (networkFetcher _barFetcher) {_fetchedBarData data;_barFetcher nil;}
}用block下载
- (void)fetchFooData {NSURL *url [[NSURL alloc] initWithString:http://www.example.com/foo.dat];EOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];[fetcher startWithCompletionHandler:^(NSData *data){_fetchedFooData data;}];}- (void)fetchBarData {NSURL *url [[NSURL alloc] initWithString: http://www.example.com/bar.dat];EOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];[fetcher startWithCompletionHandler:^(NSData *data){_fetchedBarData data;}];}还可以将处理成功的代码放在一个块里处理失败的代码放在另一个块中
“#import Foundation/Foundation.hclass EOCNetworkFetcher;
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data);
typedef void(^EOCNetworkFetcherErrorHandler)(NSError *error);interface EOCNetworkFetcher : NSObject- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler: (EOCNetworkFetcherCompletionHandler)completion failureHandler: (EOCNetworkFetcherErrorHandler)failure;endEOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHander:^(NSData *data){// Handle success
}failureHandler:^(NSError *error){// Handle failure
}];这样写的好处是我们可以将处理成功和失败的代码分开来写看上去更加清晰。 我们还可以将 成功和失败的代码都放在同一个块里
“#import Foundation/Foundation.hclass EOCNetworkFetcher;
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData *data, NSError *error);interface EOCNetworkFetcher : NSObject- (id)initWithURL:(NSURL*)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;endEOCNetworkFetcher *fetcher [[EOCNetworkFetcher alloc] initWithURL:url];[fetcher startWithCompletionHander:^(NSData *data, NSError *error){if (error) {// Handle failure} else {// Handle success}
}];这样做的好处是如果及时下载失败或中断了我们仍然可以取到当前所下载的data。而且如果在需求上指出下载成功后得到的数据很少也视为失败那么单一块的写法就很适用因为它可以取得数据后成功再判断其是否是下载成功的。 第40条用块引用其所属对象时不要出现保留环
如果块捕获的对象直接或间接地保留了块本身那么就需要小心保留环问题:
implementation EOCClass {EOCNetworkFetcher *_networkFetcher;NSData *_fetchedData;}- (void)downloadData {NSURL *url [[NSURL alloc] initWithString:http://www.example.com/something.dat];_networkFetcher [[EOCNetworkFetcher alloc] initWithURL:url];[_networkFetcher startWithCompletionHandler:^(NSData *data){NSLog(Request URL % finished, _networkFetcher.url);_fetchedData data;}];}在这里出现了保留环块要设置_fetchedData变量就需要捕获self变量。而selfEOCClass实例通过实例变量保留了获取器_networkFetcher而_networkFetcher又保留了块。
解决方案是在块中取得了data后将_networkFetcher设为nil。 - (void)downloadData {NSURL *url [[NSURL alloc] initWithString:http://www.example.com/something.dat];_networkFetcher [[EOCNetworkFetcher alloc] initWithURL:url];[_networkFetcher startWithCompletionHandler:^(NSData *data){NSLog(Request URL % finished, _networkFetcher.url);_fetchedData data;_networkFetcher nil;}];}第41条多用派发队列少用同步锁
多个线程执行同一份代码时很可能会造成数据不同步。作者建议使用GCD来为代码加锁的方式解决这个问题。
方案一使用串行同步队列来将读写操作都安排到同一个队列里
_syncQueue dispatch_queue_create(com.effectiveobjectivec.syncQueue, NULL);//读取字符串
- (NSString*)someString {__block NSString *localSomeString;dispatch_sync(_syncQueue, ^{localSomeString _someString;});return localSomeString;}//设置字符串
- (void)setSomeString:(NSString*)someString {dispatch_sync(_syncQueue, ^{_someString someString;});
}这样一来读写操作都在串行队列进行就不容易出错。
但是还有一种方法可以让性能更高
方案二将写操作放入栅栏快中让他们单独执行将读取操作并发执行。
_syncQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//读取字符串
- (NSString*)someString {__block NSString *localSomeString;dispatch_sync(_syncQueue, ^{localSomeString _someString;});return localSomeString;
}
//设置字符串
- (void)setSomeString:(NSString*)someString {dispatch_barrier_async(_syncQueue, ^{_someString someString;});}显然数据的正确性主要取决于写入操作那么只要保证写入时线程是安全的那么即便读取操作是并发的也可以保证数据是同步的。
这里的dispatch_barrier_async方法使得操作放在了同步队列里“有序进行”保证了写入操作的任务是在串行队列里。
第42条多用GCD少用performSelector系列方法
在iOS开发中有时会使用performSelector来执行某个方法但是performSelector系列的方法能处理的选择子很局限
它无法处理带有多个参数的选择子。 返回值只能是void或者对象类型。 但是如果将方法放在块中通过GCD来操作就能很好地解决这些问题。尤其是我们如果想要让一个任务在另一个线程上执行最好应该将任务放到块里交给GCD来实现而不是通过performSelector方法。
举几个 来比较这两种方案
延后执行某个任务的方法
// 使用 performSelector:withObject:afterDelay:
[self performSelector:selector(doSomething) withObject:nil afterDelay:5.0];// 使用 dispatch_after
dispatch_time_t time dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){[self doSomething];
});将任务放在主线程执行
// 使用 performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:selector(doSomething) withObject:nil waitUntilDone:NO];// 使用 dispatch_async
// (or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{[self doSomething];
});注意 如果waitUntilDone的参数是Yes那么就对应GCD的dispatch_sync方法。 我们可以看到使用GCD的方式可以将线程操作代码和方法调用代码写在同一处一目了然而且完全不受调用方法的选择子和方法参数个数的限制。
第43条掌握GCD及操作队列的使用时机
除了GCD操作队列NSOperationQueue也是解决多线程任务管理问题的一个方案。对于不同的环境我们要采取不同的策略来解决问题有时候使用GCD好些有时则是使用操作队列更加合理。
使用NSOperation和NSOperationQueue的优点
可以取消操作在运行任务前可以在NSOperation对象调用cancel方法标明此任务不需要执行。但是GCD队列是无法取消的因为它遵循“安排好之后就不管了fire and forget”的原则。 可以指定操作间的依赖关系例如从服务器下载并处理文件的动作可以用操作来表示。而在处理其他文件之前必须先下载“清单文件”。而后续的下载工作都要依赖于先下载的清单文件这一操作。 监控NSOperation对象的属性可以通过KVO来监听NSOperation的属性可以通过isCancelled属性来判断任务是否已取消通过isFinished属性来判断任务是否已经完成。 可以指定操作的优先级操作的优先级表示此操作与队列中其他操作之间的优先关系我们可以指定它。
第44条通过Dispath Group机制根据系统资源状况来执行任务
有时需要等待多个并行任务结束的那一刻执行某个任务这个时候就可以使用dispath group函数来实现这个需求
通过dispath group函数可以把并发执行的多个任务合为一组于是调用者就可以知道这些任务何时才能全部执行完毕。
//一个优先级低的并发队列
dispatch_queue_t lowPriorityQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);//一个优先级高的并发队列
dispatch_queue_t highPriorityQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);//创建dispatch_group
dispatch_group_t dispatchGroup dispatch_group_create();//将优先级低的队列放入dispatch_group
for (id object in lowPriorityObjects) {dispatch_group_async(dispatchGroup,lowPriorityQueue,^{ [object performTask]; });
}//将优先级高的队列放入dispatch_group
for (id object in highPriorityObjects) {dispatch_group_async(dispatchGroup,highPriorityQueue,^{ [object performTask]; });
}//dispatch_group里的任务都结束后调用块中的代码
dispatch_queue_t notifyQueue dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,notifyQueue,^{// Continue processing after completing tasks
});第45条使用dispatch_once来执行只需运行一次的线程安全代码
有时我们可能只需要将某段代码执行一次这时可以通过dispatch_once函数来解决。
dispatch_once函数比较重要的使用例子是单例模式 我们在创建单例模式的实例时可以使用dispatch_once函数来令初始化代码只执行一次并且内部是线程安全的。
而且对于执行一次的block来说每次调用函数时传入的标记都必须完全相同通常标记变量声明在static或global作用域里。 (id)sharedInstance { static EOCClass *sharedInstance nil; static dispatch_once_t onceToken; dispatch_once(onceToken, ^{ sharedInstance [[self alloc] init]; }); return sharedInstance; } 我们可以这么理解在dispatch_once块中的代码在程序启动到终止的过程里只要运行了一次后就给自己加上了注释符号不再存在了。
第46条不要使用dispatch_get_current_queue
我们无法用某个队列来描述“当前队列”这一属性因为派发队列是按照层级来组织的。
那么什么是队列的层级呢?
安排在某条队列中的快会在其上层队列中执行而层级地位最高的那个队列总是全局并发队列。
在这里BC中的块会在A里执行。但是D中的块可能与A里的块并行因为A和D的目标队列是并发队列。
正因为有了这种层级关系所以检查当前队列是并发的还是非并发的就不会总是很准确。