http://esoftmobile.com/2013/08/17/effective-objective-c-2/
《Ry’s Objective-C Tutorial》# Blocks
当我们程序中要使用一些具有共性的Block时(返回值类型、参数个数和类型相同),我们可以给这种Block定义一个类型:
typedef NSComparisonResult (^NSComparator)(id obj1, id obj2); //... - (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr; - (void)sortUsingComparator:(NSComparator)cmptr; //...
// Simplified with typedef typedef void(^EOCCompletionHandler)(NSData *data, NSError *error); - (void)startWithCompletionHandler:(EOCCompletionHandler)completion;
国内比较有名的开源框架BeeFramework中就大量应用到Block,并通过类型定义的Block作为属性,实现类似于很多脚本语言方法调用:self.HTTP_GET(URL).PARAM(postDict);
, 笔者之前在TouchXML基础上封装了一层W3C标准DOM API时也尝试过这种实现,最后在Objective-C中可以直接这样调用:document.getElementById(@"xxx").setAttribute(@"class", @"xxx");
是不是有点写JS的赶脚。
当我们要执行一个异步操作,比如异步请求时,通常需要在操作(或请求)完成后将结果返回,在Objective-C中一般有两种实现方式:代理和Block回调。
代理使用起来比较麻烦,有定义协议,申明代理方法,代理回调、设置代理、实现代理方法等一些列流程,而使用Block回调要简洁得多,我们通常可以申明一个Block类型的属性,在异步操作执行完后调用一下该Block。
//CXMLHttpRequest.h typedef void (^CXMLHttpRequestCallbackBlock) (CXMLHttpRequest *request); @interface CXMLHttpRequest : NSObject //... @property (nonatomic, copy) CXMLHttpRequestCallbackBlock onreadystatechange; //... @end //CXMLHttpRequest.m //call when request state changed. _onreadystatechange(self); //User CXMLHttpRequest CXMLHttpRequest *request = [CXMLHttpRequest new]; request.onreadystatechange = ^(CXMLHttpRequest *req) { if (req.state == 4 && req.statusCode == 200) { //get req.responseText. } }; //...
推荐项目:BlocksKit。
由于Block会强引用里面出现的对象,如果Block中使用成员变量,则self本身会被Block强引用,所以稍不注意就会出现Retain Cycle。所以通常避免的方法是在Block中引用对象的值而非对象本身,在非ARC下,可以使用__block
关键字来申明需要在Block中引用的对象,这样该对象就不会被Block retain,然后在Block结束时将引用对象设为nil:
MyViewController * __block myController = [[MyViewController alloc] init…]; // ... myController.completionHandler = ^(NSInteger result) { [myController dismissViewControllerAnimated:YES completion:nil]; myController = nil; };
在ARC模式下,则也可以用__weak
(iOS5.0一下版本用__unsafe_unretained
)关键字申明一个弱引用对象:
MyViewController *__weak weakSelf = self; self.completionHandler = ^(NSData *data) { //... [weakSelf clearUp]; };
在多线程环境下,为了保证某些资源操作的可控性,需要给一些方法加锁,保证同时只响应一个对象的调用,通常可以用@synchronized()
和NSLock
:
// @synchronized block - (void)synchronisedMethod { @synchronized(self) { // Safe } }
// NSLock _lock = [[NSLock alloc] init]; - (void)synchronisedMethod { [_lock lock]; // Safe [_lock unlock]; }
我们还可以使用dispatch queue来保证同步操作,首先创建一个dispatch queue,然后将同步操作在该queue中执行:
// Using GCD queue for synchronisation _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; }); }
不在使用GCD时,如果一项任务需要分别在主线程和非主线程中执行,我们需要通过performSelector
方法来改变执行的线程,我们还不得不把任务分解成不同的方法,某些方法内的代码在主线程执行,某些在非主线执行:
- (void)pulldown { _indicator.hidden = NO; [_indicator startAnimating]; [self performSelectorInBackground:@selector(download) withObject:nil]; } - (void)download { NSURL *URL = [NSURL URLWithString:@"http://xxx."]; NSString *data = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:nil]; if (data) { [self performSelectorOnMainThread:@selector(reloadData:) withObject:data waitUntilDone:NO]; } } - (void)reloadData { [_indicator stopAnimating]; _indicator.hidden = YES; //refresh view with data. }
而如果使用GCD,所有的操作就要简洁很多:
- (void)pulldown { _indicator.hidden = NO; [_indicator startAnimating]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *URL = [NSURL URLWithString:@"http://xxx"]; NSString *data = [NSString stringWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:nil]; if (data) { dispatch_async(dispatch_get_main_queue(), ^{ [_indicator stopAnimating]; _indicator.hidden = YES; //refresh view with data. }); } }; }
很多情况下我们使用GCD来执行一些异步操作,但是异步操作就存在一个返回顺序问题,如我们需要异步下载3个数据,只有当3个数据都下载完成后才刷新视图,而3个异步下载返回顺序是未知的,这是我们可以使用dispatch group来管理这三个任务:
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ //下载数据1 }); dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ //下载数据2 }); dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ //下载数据3 }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //刷新视图 });
其实熟悉JS或者说熟悉Node.js的人都了解,异步编程下的协同问题一直是比较受关注的话题,其中 Node大牛 @朴灵的EventProxy,个人感觉和dispatch group有异曲同工之妙:
var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) { _.template(template, data, l10n); }); $.get("template", function (template) { // something ep.emit("template", template); }); $.get("data", function (data) { // something ep.emit("data", data); }); $.get("l10n", function (l10n) { // something ep.emit("l10n", l10n); });
// `dispatch_once' singleton initialisation + (id)sharedInstance { static EOCClass *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
《iOS Technology Overview》# Cocoa Touch Frameworks
// Block enumeration NSArray *anArray = /* … */; [anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){ // Do something with `object’ if (shouldStop) { *stop = YES; } }]; NSDictionary *aDictionary = /* … */; [aDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, NSUInteger idx, BOOL *stop){ // Do something with `key’ and `object’ if (shouldStop) { *stop = YES; } }]; NSSet *aSet = /* … */; [aSet enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){ // Do something with `object’ if (shouldStop) { *stop = YES; } }];
// No-ops for non-retaining objects. static const void* EOCRetainNoOp(CFAllocatorRef allocator, const void *value) { return value; } static void EOCReleaseNoOp(CFAllocatorRef allocator, const void *value) { } NSMutableArray* EOCNonRetainArray(){ CFArrayCallBacks callbacks = kCFTypeArrayCallBacks; callbacks.retain = EOCRetainNoOp; callbacks.release = EOCReleaseNoOp; return (NSMutableArray *)CFArrayCreateMutable(nil, 0, &callbacks); } NSMutableDictionary* EOCNonRetainDictionary(){ CFDictionaryKeyCallBacks keyCallbacks = kCFTypeDictionaryKeyCallBacks; CFDictionaryValueCallBacks callbacks = kCFTypeDictionaryValueCallBacks; callbacks.retain = EOCRetainNoOp; callbacks.release = EOCReleaseNoOp; return (NSMutableDictionary *)CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &callbacks); }
+ (void)load;
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
+ (void)initialize;
Initializes the receiver before it’s used (before it receives its first message).
NSTimer会对retain它的Target,所以不要在Target的dealloc中销毁(invalidate)NSTimer对象,因 为Timer和Target之间已经形成了Retain cycle,需要在dealloc前就破坏这个Retain cycle。
我们可以对NSTimer拓展,让它支持调用Block方法:
// Block support for NSTimer #import <Foundation/Foundation.h> @interface NSTimer (EOCBlocksSupport) + (void)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats; @end @implementation NSTimer (EOCBlocksSupport) + (void)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
到这里,全部的代码都过了一遍了,网友@Alfred_Kwong说原书很多内容没有在代码中体现,建议还是读一读原书。其实也是,即使原书所有的 内容在代码中都有体现,我也不可能两篇博文就把所有东西总结出来。我更多的是通过该书的52个主题,结合代码,自己对Objective-C内容进行一遍 梳理,所以不要因为我这两篇文章来决定你该不该买本书看看,我不想做推销,更不想黑。