iOS开发iOS移动开发社区iOS学习开发

iOS 开发 -《Effective Objective-C 2

2018-01-31  本文已影响137人  Q以梦为马
文章共分为三篇:

第一篇:iOS 开发 -《Effective Objective-C 2.0:编写高质量 iOS 与 OS X 代码的 52 个有效方法》读书笔记(1)
第二篇:iOS 开发 -《Effective Objective-C 2.0:编写高质量 iOS 与 OS X 代码的 52 个有效方法》读书笔记(2)
第三篇:iOS 开发 -《Effective Objective-C 2.0:编写高质量 iOS 与 OS X 代码的 52 个有效方法》读书笔记(3)

接上篇:iOS 开发 -《Effective Objective-C 2.0:编写高质量 iOS 与 OS X 代码的 52 个有效方法》读书笔记(2)

第 6 章 块与大中枢派发

第 35 条:理解 “块”这一概念

blcok和函数类似,它是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围的东西。用^来表示,后面接一对大括号,括号里是blcok的实现代码。

    // 无返回值无参数
    void(^testBlcok)(void) = ^{
        NSLog(@"testBlcok");
    };
    testBlcok(); // print:"testBlcok"

    // 无返回值有参数
    void(^testBlcok)(int a) = ^(int a) {
        NSLog(@"%d", a);
    };
    testBlcok(5); // print:"5"

    // 有返回值有参数
    int (^addBlock)(int a, int b) = ^(int a, int b) {
        return a + b;
    };
    NSLog(@"%d", addBlock(13, 6)); // print:"19"
    __block NSInteger count = 0;
    int additional = 5;
    int (^addBlock)(int a, int b) = ^(int a, int b) {
        count = count + 10;
        return a + b + additional;
    };
    NSLog(@"addBlock = %d", addBlock(13, 6)); // print:"24"
    NSLog(@"count = %ld", count); // print:"10"

第 36 条:为常用的块类型创建 typedef

为常用的块类型创建 typedef,主要是为了代码的易读性,用的时候也较为方便。请看下面代码对比:

// 第一种写法
- (void)testWithBlockString:(NSString *)string withBlock:(void(^)(id dataSource))block;
/* 这种写法非常难记,也很难懂,用的时候不方便 */

// 第二种写法
typedef void (^testBlock)(id dataSource);
- (void)testWithBlockString:(NSString *)string withBlockName:(testBlock)block;
/* 用 typedef 关键字,为常用的块类型起个别名,方便易懂 */

第 37 条:用 handler 块降低代码分散程度

iOS开发中,我们经常会异步处理一些任务,然后等任务执行结束后通知相关方法。实现此需求的方法有很多,比如可以选择代理委托,也可以选择blockblock更轻型,使用更简单,能够直接访问上下文,这样类中不需要存储临时数据,使用block的代码通常会在同一个地方,这样使代码更连贯,可读性好。

typedef void (^testBlock)(id dataSource);
- (void)testWithBlockString:(NSString *)string withBlockName:(testBlock)block;

第 38 条:用块引用其所属对象时不要出现保留环

这条讲的比较基础,是iOSblock的循环引用问题。所谓循环引用,就是两个对象相互持有,这样就会造成循环引用。

typedef void (^testBlock)(id dataSource);
@property (copy, nonatomic) testBlock block;
@property (nonatomic, copy) NSString *blockString;

- (void)testBlock {
    self.block = ^(id dataSource) {
        NSString *blockString = self.blockString;
        NSLog(@"blockString = %@", blockString);
    };
}
block 循环引用
- (void)testBlock {
    __weak typeof(self) weakSelf = self;
    self.block = ^(id dataSource) {
        NSString *blockString = weakSelf.blockString;
        NSLog(@"blockString = %@", blockString);
    };
}

但并非所有 block 都会造成循环引用,在开发中,一些同学只要有block的地方就会用__weak来修饰对象,其实没有必要,以下几种block是不会造成循环引用的:

    dispatch_async(dispatch_get_main_queue(), ^{
        NSString *blockString = self.blockString;
        NSLog(@"blockString = %@", blockString);
    });

代码解读:因为self并没有对GCD中的block进行持有,所以不会形成循环引用。

    [NNHomeViewController testWithBlockName:^(id dataSource) {
        NSString *blockString = self.blockString;
        NSLog(@"blockString = %@", blockString);
    }];

代码解读:同上,block不是被self所持有的。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self testWithBlock:^{
        NSString *blockString = self.blockString;
        NSLog(@"blockString = %@", blockString);
    }];
}

- (void)testWithBlock:(void(^)(void))block {
    block();
}

第 39 条:多用派发队列,少用同步锁

在 OC 中,如果有多个线程要执行同一份代码,有时可能会出问题。这种情况下,通常要使用锁来实现某种同步机制。

第一种:内置的“同步块”

- (void)synchronizedMethod {
    @synchronized(self) {
        // safe
    }
}

这种写法会根据给定的对象,自动创建一个锁,并等待块中代码执行完毕。执行到这段代码结尾处,锁就释放了。但滥用@synchronized(self)会很大程度上降低代码效率,因此不推荐使用。

第二种:直接使用 NSLock 对象(也可以使用 NSRecursiveLock 递归锁)

_lock = [[NSLock alloc] init];

- (void)synchronizedMethod {
    [_lock lock];
    // safe
    [_lock unlock];
}

这种写法也有缺陷,在极端情况下,同步块会导致死锁,另外与 GCD 相比效率也很低。

- (NSString *)testString {
    __block NSString *localTestString;
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        localTestString = self.testString;
    });
    return localTestString;
}

- (void)setTestString:(NSString *)testString {
    dispatch_barrier_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        self.testString = testString;
    });
}

使用同步队列及栅栏块,可以令同步行为更加高效。

第 40 条:多用 GCD,少用 performSelector 系列方法

GCD 出现之前,开发者延迟调用一些方法,或者指定运行方法的线程会用 performSelector,但是在 GCD 出来之后就不需要再使用performSelector了。

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
  1. 延迟调用方法:
// GCD
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];
});

// performSelector
[self performSelector:@selector(doSomething) withObject:nil  afterDelay:5.0];
  1. 指定运行方法的线程:
// GCD
dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
});

// performSelector
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];

第 41 条:掌握 GCD 及操作队列的使用时机

这条讲的是什么时候该用 GCD,什么时候不该用GCDGCD技术确实很棒,但GCD并不总是最佳解决方案。比如当我们想取消队列中的某个操作时,或者需要后台执行任务时,这时我们可以用NSOperationQueue,其实NSOperationQueueGCD有很多相像之处。NSOperationQueueGCD之前就已经有了,GCD就是在其某些原理上构建的。GCDC层次的API,而NSOperation是重量级的OC对象。

第 42 条:通过 Dispatch Group 机制,根据系统资源状况来执行任务

dispatch groupGCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知,开发者可以拿到结果然后继续下一步操作。

请看以下代码:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        // 添加操作...
        NSLog(@"1%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        // 添加操作...
        NSLog(@"2%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        // 添加操作...
        NSLog(@"3%@", [NSThread currentThread]);
    });
    // 收到通知,回到主线程刷新UI
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"回到主线程刷新UI");
    });

多个任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知。

第 43 条:使用 dispatch_once 来执行只需运行一次的线程安全代码

这条讲的单例模式,即常用的dispatch_once。使用 dispatch_once 可以简化代码并且彻底保证线程安全,我们根本无须担心加锁或同步,另外它没有使用重量级的同步机制,所以也更高效。

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

第 44 条:不要使用 dispatch_get_current_queue

第 7 章 系统框架

第 45 条:熟悉系统框架

开发者会碰到的主要框架就是Fundation。另外还有个与Fundation相伴的框架,叫做CoreFoundation。除了FundationCoreFoundation,还有很多系统库,其中包括但不限于下面列出的这些:

第 46 条:多用块枚举,少用 for 循环

遍历collection有四种方式。最基本的办法是for循环,其次是NSEnumerator遍历法快速遍历法,最新最先进的方式则是“块枚举法”

    NSArray *testArray = @[@1, @2, @3, @4, @5];
    // for 循环遍历
    for (int i = 0; i < testArray.count; i ++) {
        NSLog(@"testArray[%d] = %@", i, testArray[i]);
    }
    
    // NSEnumerator遍历法
    NSEnumerator *enumerator = [testArray objectEnumerator];
    id object;
    while ((object = [enumerator nextObject]) != nil) {
        NSLog(@"object = %@", object);
    }
    
    // 快速遍历
    for (NSObject *obj in testArray) {
        NSLog(@"obj = %@", obj);
    }
    
    // 块枚举遍历数组
    [testArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"idx = %zd, obj = %@", idx, obj);
    }];

    // 块枚举遍历字典
    NSDictionary *testDic = @{@"name":@"liu zhong ning",@"age":@"25"};
    [testDic enumerateKeysAndObjectsUsingBlock:^(NSString * key,id object,BOOL * stop){
        NSLog(@"testDic[%@] = %@", key, object);
    }];

块枚举法拥有其他遍历方式都具备的优势,而且还能带来更多好处,在遍历字典的时候,还可以同时提供键和值,而且还有选项可以开启并发迭代功能,所以多写点代码还是值的。

第 47 条:对自定义其内存管理语义的collection使用无缝桥接

通过无缝桥接技术,可以在Foundation框架中的OC对象与CoreFoundation框架中的C语言数据结构之间来回转换。

    NSArray *testNSArray = @[@1, @2, @3, @4, @5];
    CFArrayRef testCFArray = (__bridge CFArrayRef)testNSArray;
    NSLog(@"Size of array = %li", CFArrayGetCount(testCFArray));
    // Output:Size of array = 5

想深入了解无缝桥接技术的童鞋可以点击这里:iOS无缝桥接官方文档

第 48 条:构建缓存时选用 NSCache 而非 NSDictionary

第 49 条:精简 initialize 与 load 的实现代码

第 50 条:别忘了 NSTimer 会保留其目标对象

开发中经常会用到NSTimer,由于定时器NSTimer会保留其目标对象,所以反复执行任务通常会导致应用程序出问题,也就是说很容易造成循环引用。请看以下代码:

#import <Foundation/Foundation.h>

@interface NNTimer: NSObject

- (void)startPolling;
- (void)stopPolling;

@end

@implementation NNTimer {
    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 {
    // Poll the resource
}

@end

上面这段代码是存在问题的。如果创建了本类的实例,并调用其startPolling方法,那么会如何呢?创建计时器的时候,由于目标对象是self,所以要保留此实例。然而,因为计时器是用实例变量存放的,所以实例也保留了计时器,于是就产生了保留环。

单从计时器本身入手,你会发现很难解决这个问题,那么如何解决这个问题呢?我们可以通过“块”来解决。虽然计时器当前不直接支持块,但是可以用下面这段代码为其添加此功能:
.h文件:

@interface NSTimer (NNBlocksSupport)
+ (NSTimer *)nn_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(void(^)(void))block
                                       repeats:(BOOL)repeats;
@end

.m文件:

#import "NSTimer+NNBlocksSupport.h"

@implementation NSTimer (NNBlocksSupport)

+ (NSTimer *)nn_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)(void))block
                                       repeats:(BOOL)repeats {
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(nn_blockInvoke:)
                                       userInfo:[block copy]
                                        repeats:repeats];
    
}

+ (void)nn_blockInvoke:(NSTimer *)timer {
    void (^block)(void) = timer.userInfo;
    if (block) {
        block();
    }
}

@end

结束语:由于个人能力有限,这三篇读书笔记难免有错误或不足之处,还望各位道友能不吝赐教,谢谢。

最后安利一下这本书:PDF版

上一篇 下一篇

猜你喜欢

热点阅读