文章共分為三篇:
第一篇:iOS 開發(fā) -《Effective Objective-C 2.0:編寫高質量 iOS 與 OS X 代碼的 52 個有效方法》讀書筆記(1)
第二篇:iOS 開發(fā) -《Effective Objective-C 2.0:編寫高質量 iOS 與 OS X 代碼的 52 個有效方法》讀書筆記(2)
第三篇:iOS 開發(fā) -《Effective Objective-C 2.0:編寫高質量 iOS 與 OS X 代碼的 52 個有效方法》讀書筆記(3)
接上篇:iOS 開發(fā) -《Effective Objective-C 2.0:編寫高質量 iOS 與 OS X 代碼的 52 個有效方法》讀書筆記(2)
第 6 章 塊與大中樞派發(fā)
第 35 條:理解 “塊”這一概念
blcok和函數類似,它是直接定義在另一個函數里的,和定義它的那個函數共享同一個范圍的東西。用^來表示,后面接一對大括號,括號里是blcok的實現(xiàn)代碼。
- 格式: 返回類型 (^blockName)(參數){實現(xiàn)代碼};
// 無返回值無參數
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可以訪問局部變量,但是不能修改,如果修改局部變量,需要加__block
__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"
- 另外關于
block的修飾符應該注意:
1.如果用copy修飾Block,該Block就會存儲在堆空間。則會對Block的內部對象進行強引用,導致循環(huán)引用。內存無法釋放。
解決方法:新建一個指針(__weak typeof(Target) weakTarget = Target )指向Block代碼塊里的對象,然后用weakTarget進行操作。就可以解決循環(huán)引用問題。
2.如果用weak修飾Block,該Block就會存放在??臻g。不會出現(xiàn)循環(huán)引用問題。
第 36 條:為常用的塊類型創(chuàng)建 typedef
為常用的塊類型創(chuàng)建 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開發(fā)中,我們經常會異步處理一些任務,然后等任務執(zhí)行結束后通知相關方法。實現(xiàn)此需求的方法有很多,比如可以選擇代理委托,也可以選擇block。block更輕型,使用更簡單,能夠直接訪問上下文,這樣類中不需要存儲臨時數據,使用block的代碼通常會在同一個地方,這樣使代碼更連貫,可讀性好。
typedef void (^testBlock)(id dataSource);
- (void)testWithBlockString:(NSString *)string withBlockName:(testBlock)block;
第 38 條:用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
這條講的比較基礎,是iOS中block的循環(huán)引用問題。所謂循環(huán)引用,就是兩個對象相互持有,這樣就會造成循環(huán)引用。
- 請看下面代碼:
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);
};
}
- 代碼截圖:

- 解決方法:
- (void)testBlock {
__weak typeof(self) weakSelf = self;
self.block = ^(id dataSource) {
NSString *blockString = weakSelf.blockString;
NSLog(@"blockString = %@", blockString);
};
}
但并非所有 block 都會造成循環(huán)引用,在開發(fā)中,一些同學只要有block的地方就會用__weak來修飾對象,其實沒有必要,以下幾種block是不會造成循環(huán)引用的:
- 大部分
GCD方法
dispatch_async(dispatch_get_main_queue(), ^{
NSString *blockString = self.blockString;
NSLog(@"blockString = %@", blockString);
});
代碼解讀:因為self并沒有對GCD中的block進行持有,所以不會形成循環(huán)引用。
-
block屬于另外一個類
[NNHomeViewController testWithBlockName:^(id dataSource) {
NSString *blockString = self.blockString;
NSLog(@"blockString = %@", blockString);
}];
代碼解讀:同上,block不是被self所持有的。
-
block并不是屬性值,而是臨時變量
- (void)viewDidLoad {
[super viewDidLoad];
[self testWithBlock:^{
NSString *blockString = self.blockString;
NSLog(@"blockString = %@", blockString);
}];
}
- (void)testWithBlock:(void(^)(void))block {
block();
}
第 39 條:多用派發(fā)隊列,少用同步鎖
在 OC 中,如果有多個線程要執(zhí)行同一份代碼,有時可能會出問題。這種情況下,通常要使用鎖來實現(xiàn)某種同步機制。
- GCD 出現(xiàn)之前通常使用兩種方法:
第一種:內置的“同步塊”
- (void)synchronizedMethod {
@synchronized(self) {
// safe
}
}
這種寫法會根據給定的對象,自動創(chuàng)建一個鎖,并等待塊中代碼執(zhí)行完畢。執(zhí)行到這段代碼結尾處,鎖就釋放了。但濫用@synchronized(self)會很大程度上降低代碼效率,因此不推薦使用。
第二種:直接使用 NSLock 對象(也可以使用 NSRecursiveLock 遞歸鎖)
_lock = [[NSLock alloc] init];
- (void)synchronizedMethod {
[_lock lock];
// safe
[_lock unlock];
}
這種寫法也有缺陷,在極端情況下,同步塊會導致死鎖,另外與 GCD 相比效率也很低。
- 推薦: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 出現(xiàn)之前,開發(fā)者延遲調用一些方法,或者指定運行方法的線程會用 performSelector,但是在 GCD 出來之后就不需要再使用performSelector了。
- 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;
-
performSelector 方法存在的缺點
- 內存管理問題:在
ARC下使用performSelector編譯器經常出現(xiàn)警告,因為它無法確定將要執(zhí)行的選擇子具體是什么,因而ARC編譯器也就無法插入適當的內存管理方法。 -
performSelector系列方法所能處理的選擇子太過于局限,performSelector的返回值只能是void或對象類型;而且它無法處理帶有多個參數的選擇子,最多只能處理兩個參數。
- 內存管理問題:在
用 GCD 代替 performSelector 系列方法
- 延遲調用方法:
// 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];
- 指定運行方法的線程:
// GCD
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
// performSelector
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
第 41 條:掌握 GCD 及操作隊列的使用時機
這條講的是什么時候該用 GCD,什么時候不該用GCD。GCD技術確實很棒,但GCD并不總是最佳解決方案。比如當我們想取消隊列中的某個操作時,或者需要后臺執(zhí)行任務時,這時我們可以用NSOperationQueue,其實NSOperationQueue跟GCD有很多相像之處。NSOperationQueue在GCD之前就已經有了,GCD就是在其某些原理上構建的。GCD是C層次的API,而NSOperation是重量級的OC對象。
- 使用
NSOperation及NSOperationQueue的好處如下:- 取消某個操作。
- 指定操作間的依賴關系。
- 通過鍵值觀察機制監(jiān)控NSOperation對象的屬性。
- 指定操作的優(yōu)先級。
- 重用NSOperation對象。
第 42 條:通過 Dispatch Group 機制,根據系統(tǒng)資源狀況來執(zhí)行任務
dispatch group是GCD的一項特性,能夠把任務分組。調用者可以等待這組任務執(zhí)行完畢,也可以在提供回調函數之后繼續(xù)往下執(zhí)行,這組任務完成時,調用者會得到通知,開發(fā)者可以拿到結果然后繼續(xù)下一步操作。
請看以下代碼:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 創(chuàng)建一個隊列組
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之中。開發(fā)者可以在這組任務執(zhí)行完畢時獲得通知。
第 43 條:使用 dispatch_once 來執(zhí)行只需運行一次的線程安全代碼
這條講的單例模式,即常用的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
-
dispatch_get_current_queue函數的行為常常與開發(fā)者所預期的不同,此函數已經廢棄,只應做調試之用。 - 由于
GCD是按層級來組織的,所以無法單用某個隊列對象來描述"當前隊列"這一概念。 -
dispatch_get_current_queue函數用于解決由不可以重入的代碼所引發(fā)的死鎖,然后能用此函數解決的問題,通常也可以用"隊列特定數據"來解決。
第 7 章 系統(tǒng)框架
第 45 條:熟悉系統(tǒng)框架
開發(fā)者會碰到的主要框架就是Fundation。另外還有個與Fundation相伴的框架,叫做CoreFoundation。除了Fundation與CoreFoundation,還有很多系統(tǒng)庫,其中包括但不限于下面列出的這些:
CFNetwork:此框架提供了 C 語言級別的網絡通信能力,它將 BSD 套接字
(BSD socket)抽象成易于使用的網絡接口。而Foundation則將該框架里的部分內容封裝為OC語言的接口,以便進行網絡通信。CoreAudio:此框架所提供的
C語言API可以用來操作設備上的音頻硬件。AVFoundation:此框架所提供的
OC對象可用來回訪并錄制音頻及視頻,比如能夠在UI視圖類里播放視頻。CoreData:此框架所提供的
OC接口可以將對象放入數據庫,將數持久保存。CoreText:此框架提供的C語言接口可以高效執(zhí)行文字排版以及渲染操作。
-
要點:
- 許多系統(tǒng)框架都可以直接使用。其中最重要的是
Fundation與CoreFoundation,這兩個框架提供了構架應用程序所需的許多核心功能。 - 很多常見任務都能用框架來做。例如音頻與視頻處理、網絡通信、數據管理等。
- 用純
C寫成的框架與用OC寫成的一樣重要,若想成為優(yōu)秀的OC開發(fā)者,應該掌握C語言的核心概念。
- 許多系統(tǒng)框架都可以直接使用。其中最重要的是
第 46 條:多用塊枚舉,少用 for 循環(huán)
遍歷collection有四種方式。最基本的辦法是for循環(huán),其次是NSEnumerator遍歷法及快速遍歷法,最新最先進的方式則是“塊枚舉法”。
- 四種遍歷方法:
NSArray *testArray = @[@1, @2, @3, @4, @5];
// for 循環(huán)遍歷
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);
}];
塊枚舉法擁有其他遍歷方式都具備的優(yōu)勢,而且還能帶來更多好處,在遍歷字典的時候,還可以同時提供鍵和值,而且還有選項可以開啟并發(fā)迭代功能,所以多寫點代碼還是值的。
第 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
- __bridge:ARC仍然具備這個Objective對象的所有權。
- __bridge_retained:ARC將交出對象的所有權。
- __bridge_transfer:C轉化為OC
想深入了解無縫橋接技術的童鞋可以點擊這里:iOS無縫橋接官方文檔
第 48 條:構建緩存時選用 NSCache 而非 NSDictionary
- 實現(xiàn)緩存時應選用
NSCache而非NSDictionary對象。因為NSCache可以提供優(yōu)雅的自動刪減功能,而且是“線程安全的”,此外,它與字典不同,并不會拷貝鍵。 - 可以給
NSCache對象設置上限,用以限制緩存中的對象總個數及“總成本”,而這些尺度則定義了緩存刪減其中對象的時機。但是絕對不要把這些尺度當成可靠的“硬限制”,他們僅對NSCache起指導作用。 - 將
NSPurgeableData與NSCache搭配使用,可實現(xiàn)自動清除數據的功能,也就是說,當NSPurgeableData對象所占內存為系統(tǒng)所丟棄時,該對象自身也會從緩存中移除。 - 如果緩存使用得當,那么應用程序的相應速度就能提高。只有那種“重新計算起來很費事的”數據,才值得放入緩存,比如那些需要從網絡獲取或從磁盤讀取的數據。
第 49 條:精簡 initialize 與 load 的實現(xiàn)代碼
- 當程序啟動的時候,類和分類,必定會調動且僅調用一次
load方法。先調用類的load方法,再調用分類的load方法。先調用超類的load方法,再調用子類的load方法。load方法需要實現(xiàn)得精簡一些,因為整個應用程序會在執(zhí)行load方法時都會阻塞。 -
initialize方法會在程序首次用該類之前調用,且只調用一次。initialize是“懶加載”的,如果某個類一直都沒有使用,就不會執(zhí)行該類的initialize方法。initialize方法可以安全使用并調用任意類中的任意方法。initialize方法只應該用來設置內部數據,不應該在其中調用其他方法。 - 在加載階段,如果類實現(xiàn)了
load方法,那么系統(tǒng)就會調用它。分類里也可以定義此方法,類的load方法要比分類中的先調用。與其他方法不同,load方法不參與覆寫機制。 - 首次使用某個類之前,系統(tǒng)會向其發(fā)送
initialize消息。由于此方法遵從普通的覆寫規(guī)則,所以通常應該在里面判斷當前要初始化的是哪個類。 -
load與initialize方法都應該實現(xiàn)的精簡一點,這樣有助于保持應用程序的響應能力,也可以減少引入依賴環(huán)的幾率。 - 無法在編譯器設定的全局變量,可以放在
initialize方法里初始化。
第 50 條:別忘了 NSTimer 會保留其目標對象
開發(fā)中經常會用到NSTimer,由于定時器NSTimer會保留其目標對象,所以反復執(zhí)行任務通常會導致應用程序出問題,也就是說很容易造成循環(huán)引用。請看以下代碼:
#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
上面這段代碼是存在問題的。如果創(chuàng)建了本類的實例,并調用其startPolling方法,那么會如何呢?創(chuàng)建計時器的時候,由于目標對象是self,所以要保留此實例。然而,因為計時器是用實例變量存放的,所以實例也保留了計時器,于是就產生了保留環(huán)。
單從計時器本身入手,你會發(fā)現(xiàn)很難解決這個問題,那么如何解決這個問題呢?我們可以通過“塊”來解決。雖然計時器當前不直接支持塊,但是可以用下面這段代碼為其添加此功能:
.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版