zoukankan      html  css  js  c++  java
  • Effective Objective-C [下]

    Chapter 6: Blocks and Grand Central Dispatch

    Item 37: Understand Blocks

    《Ry’s Objective-C Tutorial》# Blocks

    Item 38: Create typedefs for Common Block Types

    当我们程序中要使用一些具有共性的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的赶脚。

    Item 39: Use Handler Blocks to Reduce Code Separation

    当我们要执行一个异步操作,比如异步请求时,通常需要在操作(或请求)完成后将结果返回,在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

    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:

    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中执行:

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

  • 相关阅读:
    Linux Shell脚本详细教程
    linux下错误代码E212: Can't open file for writing
    github仓库的基本使用-创建、上传文件、删除
    -bash: ifconfig: command not found解决办法
    Xshell能ping通但连不上CentOS 7
    devtools和vuex mutations
    Google Chrome谷歌浏览器安装devtools
    Vuex状态管理模式
    vue Promise all
    vue Promise链式调用
  • 原文地址:https://www.cnblogs.com/zsw-1993/p/4879487.html
Copyright © 2011-2022 走看看