Effective Objective-C [下]

Chapter 6: Blocks and Grand Central Dispatch

Item 37: Understand Blocks

《Ry’s Objective-C Tutorial》# Blocksjavascript

Item 38: Create typedefs for Common Block Types

当咱们程序中要使用一些具备共性的Block时(返回值类型、参数个数和类型相同),咱们能够给这种Block定义一个类型:html

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的赶脚。java

Item 39: Use Handler Blocks to Reduce Code Separation

当咱们要执行一个异步操做,好比异步请求时,一般须要在操做(或请求)完成后将结果返回,在Objective-C中通常有两种实现方式:代理和Block回调。ios

代理使用起来比较麻烦,有定义协议,申明代理方法,代理回调、设置代理、实现代理方法等一些列流程,而使用Block回调要简洁得多,咱们一般能够申明一个Block类型的属性,在异步操做执行完后调用一下该Block。git

//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.
    }
};
//...

推荐项目:BlocksKitgithub

Item 40: Avoid Retain Cycles Introduced by Blocks Referencing the Object Owning Them

因为Block会强引用里面出现的对象,若是Block中使用成员变量,则self自己会被Block强引用,因此稍不注意就会出现Retain Cycle。因此一般避免的方法是在Block中引用对象的值而非对象自己,在非ARC下,可使用__block关键字来申明须要在Block中引用的对象,这样该对象就不会被Block retain,而后在Block结束时将引用对象设为nil:objective-c

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];
};

Item 41: Prefer Dispatch Queues to Locks for Synchronization

在多线程环境下,为了保证某些资源操做的可控性,须要给一些方法加锁,保证同时只响应一个对象的调用,一般能够用@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中执行:app

// 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;
    });
}

Item 42: Prefer GCD to performSelector and Friends

不在使用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.
            });
        }
    };
}

Item 43: Know When to Use GCD and When to Use Operation Queues

Item 44: Use Dispatch Groups to Take Advantage of Platform Scaling

不少状况下咱们使用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);
});

Item 45: Use dispatch_once for Thread-Safe Single-Time Code Execution

// `dispatch_once' singleton initialisation
+ (id)sharedInstance {
    static EOCClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

Item 46: Avoid dispatch_get_current_queue

Chapter 7: The System Frameworks

Item 47: Familiarize Yourself with the System Frameworks

《iOS Technology Overview》# Cocoa Touch Frameworks

Item 48: Prefer Block Enumeration to for Loops

// 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;
    }
}];

Item 49: Use Toll-Free Bridging for Collections with Custom Memory-Management Semantics

// 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);
}

Item 50: Use NSCache Instead of NSDictionary for Caches

Item 51: Keep initialize and load Implementations Lean

+ (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).

Item 52: Remember that NSTimer Retains Its Target

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内容进行一遍梳理,因此不要由于我这两篇文章来决定你该不应买本书看看,我不想作推销,更不想黑。

相关文章
相关标签/搜索