<<Effective Objective C 2.0編寫高質量iOS與OS X代碼的52個有效方法>>讀書筆記

<<Effective Objective-C 2.0編寫高質量iOS與OS X代碼的52個有效方法>>這本書相信很多人都讀過,最近又重溫了一遍兵罢,這本書很薄孤里,只有209頁,幾個小時就可以看完。雖然很薄茁彭,但是寫的很好陡厘,這本書的內容結構是這樣的抽米,先是擺出結論,然后再花幾倍的筆墨來解釋為什么要這樣糙置。如果作者去除解釋部分云茸,估計本書可以壓縮到80頁。這本書我看了好幾遍了谤饭,但是還是感覺意猶未盡标捺,于是自己就根據(jù)它的每個章節(jié)提了一些問題懊纳。(有的章節(jié)其實沒必要提問題的,就是了解一下就夠了亡容,但是由于慣例嗤疯,所以這些不太適合提問題的章節(jié)還是提問了。) 其中的少數(shù)問題其實”超綱”了,也就是僅憑本書是答不出或者答不完整的闺兢,還需要查很多的資料才能完全答出來茂缚。有興趣的同學可以看看自己能答出來多少。由于本人水平有限屋谭,有的問題出的不是很好脚囊。

可以點擊這里下載PDF版

第一章:熟悉Objective-C

第一條:了解Objective-C語言的起源

  1. 如何理解OC的動態(tài)綁定?
  2. 說說[xxObj doSomething]從開始執(zhí)行到結束的過程桐磁。
  3. 從運行程序到main方法的調用悔耘,這之間發(fā)生了什么?
    建議大家好好看看runtime component的實現(xiàn)過程我擂,runtime源碼鏈接,重點看看objc-runtime-new.mm類衬以。

第二條: 在類的文件中盡量少引用其他頭文件

  1. 你有用過@class嗎?請說一下它的常用情景校摩。
  2. #import和#include的區(qū)別看峻。

第三條:多用字面量語法,少用與之等價的方法

  1. 字面量語法相比傳統(tǒng)創(chuàng)建方法的優(yōu)勢秧耗?

第四條:多用類型常量备籽,少用#define預處理指令

  1. 類型常量相對于#define預處理指令的優(yōu)點舶治?
  2. 請手寫一個通知名稱的聲明和實現(xiàn)分井。

第五條:多用枚舉表示狀態(tài)、選項霉猛、狀態(tài)碼

  1. NS_OPTIONS為什么能實現(xiàn)組合多個選項的功能?
  2. c語言的enum不是夠用了嗎尺锚?Foundation框架為啥要再弄一個NS_ENUM的宏?

第二章:對象惜浅、消息瘫辩、運行期

第六條:理解“屬性”這一概念

  1. 為什么說atomic不能保證屬性在多線程中是安全的?請舉個例子說明。你如何保證iOS中多線程中訪問可變數(shù)組,可以得到正確的數(shù)據(jù)?
  2. 請分別說明iOS開發(fā)中@property的屬性特征中的原子性坛悉,讀寫權限和內存管理語義的默認情況?(如什么都不寫的話伐厌,是nonatomic還是atomic?)
  3. weak是如何實現(xiàn)變量銷毀時指向nil的?
  4. @synthesize和@dynamic有什么區(qū)別?你有使用過它們嗎?什么情況下會需要使用它們裸影?請舉例
  5. 你有在.h文件中使用readonly嗎挣轨?為啥要這么設計?一般還需要在.m中做些什么來配合readonly使用?

第七條:在對象內部盡量直接訪問實例變量

  1. 什么時候使用屬性轩猩,什么時候使用實例變量卷扮?分別說說使用它們的優(yōu)缺點荡澎。
  2. 如果在初始化方法和dealloc方法中使用了屬性,可能會出現(xiàn)什么問題晤锹?說一說出現(xiàn)這種問題的原因摩幔。

第八條:理解“對象等同性”這一概念

  1. 自定義對象,如何實現(xiàn)isEqual鞭铆?
  2. hash方法存在的意義或衡?hash方法如何實現(xiàn)?為什么衔彻?為什么說相同的對象一定具有相同的哈希碼薇宠,而兩個哈希碼相同的對象卻未必相同?

第九條:以“類族模式”隱藏實現(xiàn)細節(jié)

  1. Foundation框架和UIKit框架中都有類應用了“類族模式”設計艰额,請至少說出2個采用了這種模式的類澄港。
  2. “類族模式”有什么好處?請你設計一個采用了“類族模式”的類柄沮。

第十條:在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)

  1. 分類中的@property屬性跟普通類中使用@property聲明屬性有什么不同回梧?如何在分類中實現(xiàn)跟普通類中使用@property一樣的效果?
  2. 你在項目中或者看到別人使用過關聯(lián)對象技術嗎祖搓?

第十一條:理解“objc_msgSend”的作用

  1. SEL的本質是什么?
  2. 給對象發(fā)送消息是如何”動態(tài)消息派發(fā)系統(tǒng)”派發(fā)的狱意?說一說這個過程(消息傳遞機制)。
  3. 蘋果為動態(tài)消息派發(fā)系統(tǒng)做了哪些優(yōu)化來提高查找方法列表的速度?

第十二條:理解消息轉發(fā)機制

  1. 請說說消息轉發(fā)機制拯欧?你在項目中有使用過消息轉發(fā)嗎(在開發(fā)中的具體應用場景)详囤?

第十三條:用“方法調配技術”調試“黑盒方法”

  1. 如何動態(tài)添加方法和替換方法實現(xiàn)?

第十四條:理解“類對象”的用意

  1. 說說你對類對象的理解镐作?清楚它的內存布局嗎藏姐?

第三章:接口與API設計

第十五條:用前綴避免命名空間沖突

  1. 你在項目中創(chuàng)建類和設計分類方法時,如何避免類的重復定義及分類方法的相互覆蓋?

第十六條:提供“全能初始化方法”

  1. 如果讓你設計一個類该贾,提供多個初始化方法羔杨,你的.h文件怎么設計接口?
  2. 為什么在init方法中要調用super init杨蛋,不調用行不行兜材?請說明理由。

第十七條:實現(xiàn)description方法

  1. 有使用過LLDB嗎逞力?
  2. 在Xcode的打印含有中文字符串的字典和數(shù)組時曙寡,會出現(xiàn)編碼和不是正常的文字,你是怎么解決的?

第十八條:盡量使用不可變對象

  1. 在某個類的頭文件中寇荧,有個屬性@property (nonatomic, strong) NSMutableArray *friends;,這么寫有什么問題嗎举庶?如果有問題,請說明問題砚亭,如何修改?

第十九條:使用清晰而協(xié)調的命名方式

  1. 你平時是怎么樣為方法和類與協(xié)議命名的?

第二十條:為私有方法名加前綴

  1. 為什么要給私有方法加前綴?

第二十一條:理解Objective-C錯誤模型

  1. 第三方崩潰日志收集平臺的設計原理是什么灯变?如果讓你自己設計一個殴玛,你怎么設計出類似的功能。

第二十二條:理解NSCopying協(xié)議

  1. 什么是深拷貝添祸,什么是淺拷貝滚粟,有什么區(qū)別?
  2. 如何實現(xiàn)自定義對象支持拷貝功能?

第四章:協(xié)議與分類

第二十三條:通過委托與數(shù)據(jù)源協(xié)議進行對象間通信

  1. 什么是委托(代理)模式刃泌?這個模式的作用是什么凡壤?
  2. UITableView為什么有了delegate還要增加dataSource屬性,這2個屬性為什么都是weak?
  3. UITableView的某些代理方法調用的非常頻繁,頻繁判斷某個委托對象是否響應代理方法比較耗費性能耙替,請問如何優(yōu)化?

第二十四條:將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中

  1. 某個類的.m過于臃腫,雜糅了太多的方法亚侠,如何重構它?
  2. 分類里實現(xiàn)了與本類相同的方法,為什么會調用分類里面的方法而不調用本類的原來方法俗扇?

第二十五條:總是為第三方類的分類名稱加前綴

  1. 增加分類和分類方法時要注意什么?(主要是命名上)

第二十六條:勿在分類中聲明屬性

  1. 為什么分類中的@property不能生成實例變量硝烂?

第二十七條:使用“class-continuation”分類隱藏實現(xiàn)細節(jié)

  1. 匿名分類跟分類有什么區(qū)別?

第二十八條:通過協(xié)議提供匿名對象

  1. 協(xié)議除了用于代理模式,還有什么作用?

第五章:內存管理

第二十九條:理解引用計數(shù)

  1. 說說你對引用計數(shù)的理解?蘋果設計引用計數(shù)的用意是什么?

第三十條:以ARC簡化引用計數(shù)

  1. ARC和MRC有什么區(qū)別?ARC是如何實現(xiàn)的铜幽?
  2. 使用了ARC還會出現(xiàn)內存泄露嗎滞谢?請舉例說明。
  3. weak是如何做到自動置為nil的除抛?

第三十一條:在delloc方法中只釋放引用并解除監(jiān)聽

  1. ARC下我們在dealloc方法中應該做什么事情?

第三十二條:編寫“異常安全代碼”時留意內存管理問題

  1. Java等語言中經常大量用到異常狮杨,為什么OC中比較少出現(xiàn)異常機制的代碼?

第三十三條:以弱引用避免保留環(huán)

  1. weak和unsafe_unretained有什么區(qū)別?哪些情況下會用到weak,請舉例到忽。

第三十四條:以 “自動釋放池塊” 降低內存峰值

  1. 項目中有用過自動釋放池嗎橄教?有什么作用?

第三十五條:用“僵尸對象”調試內存管理問題

  1. 如何監(jiān)測項目中出現(xiàn)的“僵尸對象”?

第三十六條:不要使用retainCount

  1. 為什么不要用retainCount? retainCount不準的原因在哪里?

第六章:塊與大中樞派發(fā)

第三十七條:理解”塊”這一概念

  1. block有哪幾種?它的內部結構是什么樣的喘漏?

第三十八條:為常用的塊類型創(chuàng)建typedef

  1. 請手寫一個block屬性护蝶,并分別使用typedef重新定義它。

第三十九條:用handler塊降低代碼分散程度

  1. 使用block代替代理有啥有點和缺點?分別在何種情況使用比較合適?
  2. NSNotificationCenter在發(fā)送通知的時候陷遮,是同步還是異步?

第四十條:用塊引用其所屬對象時不要出現(xiàn)保留環(huán)

  1. 什么時候容易出現(xiàn)block的循環(huán)引用?出現(xiàn)這種情況除了使用weak滓走,還有其他的解決方法嗎垦江?

第四十一條:多用派發(fā)隊列帽馋,少用同步鎖

  1. atomic的實現(xiàn)原理是咋樣的?
  2. 如何用GCD解決訪問可變數(shù)組過程中的多線程安全問題?

第四十二條:多用GCD,少用performSelector系列方法

  1. performSelector方法有什么局限性和缺點? 如何解決比吭。

第四十三條:掌握GCD以及操作隊列的使用時機

  1. GCD和NSOperation各有哪些特點绽族?你平時喜歡用哪個?

第四十四條:通過Dispatch Group機制衩藤,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務

  1. 使用過dispatch group嗎?它的作用是什么?
  2. GCD大概有哪些比較常用的函數(shù)?

第四十五條:使用dispatch_once來執(zhí)行只需要運行一次的線程安全代碼

  1. 請分別用2種方式來實現(xiàn)單例吧慢。
  2. dispatch_once為什么說是線程安全的?

第四十六條:不要使用dispatch_get_current_queue

  1. 什么情況會出現(xiàn)死鎖?請用GCD模擬一種情況。

第七章:系統(tǒng)框架

第四十七條:熟悉系統(tǒng)框架

  1. 除了Foundation和UIKit框架赏表,你還知道哪些系統(tǒng)框架?

第四十八條:多用塊枚舉检诗,少用for循環(huán)

  1. 你知道哪些遍歷的方法?各有什么特點?

第四十九條:對自定義其內存管理語義的collecion使用無縫橋接

  1. 你了解橋接技術嗎?為什么需要橋接?

第五十條:構建緩存時選用NSCache而非NSDictionary

  1. 你了解NSCache嗎匈仗?它相比于NSDictionary有哪些優(yōu)點?

第五十一條:精簡initialize與load的實現(xiàn)代碼

  1. 有聽過load和initialize方法嗎?說說它們的特點逢慌。

第五十二條:別忘了NSTimer會保留其目標對象

  1. [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeatDoSomething) userInfo:nil repeats:YES];這句代碼有什么問題?如何解決?

本書精簡版

1. 了解Object-C 語言的起源

消息結構和函數(shù)調用的區(qū)別:

使用消息結構的語言悠轩,其運行時所應執(zhí)行的代碼由運行環(huán)境來決定;而使用函數(shù)調用的語言攻泼,則由編譯器決定火架。另外,CGRect結構體忙菠,儲存在棧區(qū)

2. 在類的頭文件中盡量少引用其他頭文件

如果不是非用不可何鸡,盡量用@class(向前聲明)代替#import,加快編譯速度牛欢。

3. 多用字面量語法骡男,少用與之等價的方法
id object1 = @"1";
id object2 = nil;
id object3 = @"3";
    
NSArray *arrayA = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSArray *arrayB = @[object1, object2, object3];

上面的代碼我們發(fā)現(xiàn),arrayA雖然能創(chuàng)建出來傍睹,但是其中卻只有object1一個對象洞翩,原因在于“arrayWithObjects:”方法會依次處理各個參數(shù),直到發(fā)現(xiàn)nil為止焰望,由于object2是nil骚亿,所以該方法會提前結束,按字面量語法創(chuàng)建arrayB時會拋出異常熊赖。
所以:

使用字面量語法創(chuàng)建數(shù)組来屠、字典...更安全,語法更簡潔震鹉。

4. 多用類型常量俱笛,少用#define 預處理指令

僅在“編譯單元(.m文件內)”內使用

define ANIMATION_DURATION 0.3 // 不建議
static NSTimeInterval const kANIMATION_DURATION = 0.3; // 建議

需要外露使用

// EOCAnimatedView.h
extern const NSTimeInterval EOCAnimateViewAnimationDuration;
// EOCAnimatedView.m
const NSTimeInterval EOCAnimateViewAnimationDuration = 0.3;

上述宏定義,預處理指令會把源代碼中的ANIMATION_DURATION字符串替換成0.3传趾。假設此指令聲明在某個頭文件中迎膜,那么所有引入了這個頭文件的代碼,其ANIMATION_DURATION都會被替換浆兰。
涉及知識點:

  • 常量命名:若常量局限于某“編譯單元”(也就是實現(xiàn)文件.m)之內磕仅,則在前面加上字母k;若常量在類之外可見簸呈,則通常以類名為前綴榕订。
  • 宏:
    1.作用:在預編譯階段替換酥郭;
    2.其他:在預編譯階段執(zhí)行祠乃,不做編譯檢查叉寂,不會報編譯錯誤尊剔;
  • const:
    1.作用:僅僅用來修飾右邊的變量条舔,被const修飾的變量是只讀的向瓷;
    2.其他:在編譯階段執(zhí)行歹嘹,會做編譯檢查届慈,會報編譯錯誤;
  • static的作用:
    1.修飾局部變量:(1). 延長局部變量的聲明周期憔辫,程序結束才會銷毀鸯檬;(2).局部變量只會生成一份內存,只會初始化一次螺垢;(3). 改變局部變量的作用域喧务;
    2.修飾全局變量:(1). 只能在本文件中訪問,修改全局變量的作用域枉圃,生命周期不會改變功茴;(2). 避免重復定義全局變量;
  • extern:
    1.作用:只是用來獲取全局變量(包括全局靜態(tài)變量)的值孽亲,不能用于定義變量坎穿;
    2.原理:先在擋圈文件中查找有沒有全局變量,沒有找到返劲,才會去其他文件查找玲昧;
  • static與const聯(lián)合使用:
    1.作用:聲明一個只讀的靜態(tài)變量;(在每個文件都需要定義一份靜態(tài)全局變量)
    2.使用場景:在一個文件中經常使用的字符串常量篮绿;
  • extern與const聯(lián)合使用:
    1.作用:只需要定義一份全局變量孵延,多個文件共享;
    2.使用場景:在多個文件中經常使用同一個字符串常量亲配;
5. 用枚舉表示狀態(tài)尘应、選項、狀態(tài)碼

在處理枚舉類型的switch語句中不要實現(xiàn)default分支吼虎。這樣的話犬钢,加入新枚舉之后,編譯器就會提示開發(fā)者:switch語句并未處理所有枚舉思灰。

6. 理解“屬性”這一概念
@interface EOCPersion: NSObject {
    @public
    NSString *_firstName;
    NSString *_lastName;
    @private
    NSString *_someInternalData;
}
@end

編寫時使用實例變量有個問題:就是對象布局在編譯期就已經固定了玷犹,只要碰到訪問_firstName變量的代碼,編譯器就把其替換為“偏移量”(offset),這個偏移量是“硬編碼”(hardcode)洒疚,表示該變量距離存放對象的內存區(qū)域的起始地址有多遠歹颓,這樣做目前沒問題,但是如果又加了一個實例變量拳亿,如:

@interface EOCPersion: NSObject {
    @public
    NSString *_dateOfBirth;
    NSString *_firstName;
    NSString *_lastName;
    @private
    NSString *_someInternalData;
}
@end

那么原來_firstName的偏移量現(xiàn)在卻指向_dateOfBirth了晴股,把偏移量硬編碼于其中的那些代碼都會讀取到錯誤的值愿伴。所以肺魁,修改之后必須重新編譯,否則就會出錯隔节。Obejcet-C的做法是鹅经,把實例變量當做一種存儲偏移變量所用的“特殊變量”寂呛,交由“類對象”保管,偏移量會在運行期查找瘾晃,如果類的定義變了贷痪,那么存儲的偏移量也就變了,這樣的話蹦误,無論何時訪問實例變量劫拢,總能使用正確的偏移量。另外還有一種解決辦法就是强胰,盡量不要直接訪問實例變量舱沧,而應該通過存取方法來做。

atomic一定安全嗎偶洋?

具備atomic特質的獲取方法會通過鎖定機制來確保其操作的原子性熟吏,但這無法保證絕對的“線程安全”,例如:一個線程在連續(xù)多次讀取某個屬性值的過程中有別的線程在同時改寫該值玄窝,那么即便將屬性聲明為atomic牵寺,也還是會讀到不同的屬性值。

7. 在對象內部盡量直接訪問實例變量

在對象之外訪問實例變量時恩脂,總是應該通過屬性來做帽氓,然后在對象內部訪問實例變量是該如何?作者建議

在讀取實例變量的時候采用直接訪問的形式俩块,而在設置實例變量的時候通過屬性來做杏节。

8. 理解“對象等同性”這一概念

重寫hash的一種思路,相對高效

- (NSUInteger)hash
{
    NSInteger firstNameHash = [_firstName hash];
    NSInteger lastNameHash = [_lastName hash];
    NSInteger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}

一般重寫“isEqual:”方法

- (BOOL)isEqualToPerson:(EOCPerson *)otherPerson
{
    if (self == otherPerson) return YES;
    if (![_firstName isEqualToString:otherPerson.firstName]) return NO;
    if (![_lastName isEqualToString:otherPerson.lastName]) return NO;
    if (_age != otherPerson.age) return NO;
    
    return YES;
}

-(BOOL)isEqual:(id)object
{
    if ([self class] == [object class]) {
        return [self isEqualToPerson:(EOCPerson *)object];
    } else {
        return [super isEqual:object];
    }
}
9. 以“類簇模式”隱藏實現(xiàn)細節(jié)
10. 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)

可以把關聯(lián)對象想象成NSDictionary典阵,只不過設置關聯(lián)對象時用到的鍵(key)是個“不透明的指針”奋渔。

11. 理解objc_msgSend的作用

如果某函數(shù)的最后一項操作是調用另外一個函數(shù),那么久可以運用“尾調用優(yōu)化”技術壮啊。編譯器會生成調轉至另一函數(shù)所需要的指令碼嫉鲸,而且不會向調用堆棧中推入新的“棧幀”。只有當某函數(shù)的最后一個操作僅僅是調用其他函數(shù)而不會將其返回值另做他用時歹啼,才能執(zhí)行“尾調用優(yōu)化”玄渗。這項優(yōu)化對objc_msgSend非常關鍵,如果不這么做的話狸眼,那么每次調用Object-C方法之前藤树,都需要為調用objc_msgSend函數(shù)準備“棧幀”,大家在“棧蹤跡”中可以看到這種“棧幀”拓萌。此外岁钓,若是不優(yōu)化,還會過早的發(fā)生“棧溢出”現(xiàn)象。

12. 理解消息轉發(fā)機制
image

基本就是消息轉發(fā)的大體流程屡限,需要注意的一點就是步驟越往后品嚣,處理消息的代價就越大,最好能在第一步就處理完钧大,這樣的話翰撑,運行期系統(tǒng)就看可以將此方法緩存起來,如果這個類的實例稍后還受到同樣的選擇子(方法選擇器)啊央,那么根本無須啟動消息轉發(fā)流程眶诈。

13. 用“方法調配技術”調試“黑盒方法”

利用動態(tài)特性,運行時動態(tài)添加瓜饥、交換方法

14. 理解“類對象”的用意

盡量使用類型信息查詢方法來確定對象類型册养,而不要直接比較類對象,因為某些對象可能實現(xiàn)了消息轉發(fā)功能压固。11~14 全是老成長談的runtime機制球拦,這個一時半會且三言兩語是無法講解清楚的(單拿出一整篇文章來講都不見得能面面俱到

15. 用前綴避免命名空間沖突
16. 提供“全能初始化方法”
17. 實現(xiàn)description方法
  • 實現(xiàn)description方法返回一個有意義的字符串,用以描述該實例帐我;
  • 若想在調試時打印出更詳盡的對象描述信息坎炼,則應實現(xiàn)debugDescription方法;
18. 盡量使用不可變對象

這個意思是說:

盡量把對外公布出來的屬性設為只讀拦键,而且只有在確有必要的時候才將屬性對外公布谣光。

盡管把屬性設置成readOnly之后,可以防止外部亂改屬性值芬为,但并不是完全不能改萄金,外界仍然可以通過“鍵值編碼”(KVC)技術設置這個屬性值。

19. 使用清晰而協(xié)調的命名方式

別怕麻煩媚朦,要見名知意氧敢,方法命名可參考系統(tǒng)方法名。

20. 為私有方法名加前綴

作者建議:

編寫類的實現(xiàn)代碼時询张,經常要寫一些只在內部使用的方法孙乖。應該為私有方法的名稱前加上某些前綴,這有助于調試份氧,因為據(jù)此很容易就能把公共方法和私有方法區(qū)別開唯袄。可以用“p_”打頭蜗帜。(不要單單只用一個下劃線做私有方法的前綴恋拷,因為這種做法是預留給蘋果大大用的)。

21. 理解Object-C錯誤模型

當出現(xiàn)“不那么嚴重的錯誤”時厅缺,Object-C語言所用的編程范式為:令方法返回nil/0蔬顾,或者使用NSError宴偿。
NSError對象里封裝了三條信息:

  • Error domain(錯誤范圍,其類型為字符串)阎抒;
  • Error code(錯誤碼酪我,其類型為整數(shù));
  • User info(用戶信息消痛,其類型為字典)
22. 理解NSCoding協(xié)議

想要實現(xiàn)復制功能且叁,需要遵從NSCoding協(xié)議,實現(xiàn)copyWithZone:方法秩伞;
深拷貝:

深拷貝:在拷貝對象自身時逞带,將其底層數(shù)據(jù)也一并復制過去。Foundation框架中所有collection類在默認情況下都執(zhí)行淺拷貝纱新。

23. 通過委托與數(shù)據(jù)源協(xié)議進行對象間通信

這里主要是講了使用delegate的場景以及相關的注意事項展氓,比如使用weak防止循環(huán)引用等。這里有一個優(yōu)化點:(不過筆者很少這么干脸爱,不是很少遇汞,是壓根沒有??)

我們通常把委托對象能否響應某個協(xié)議方法這一信息緩存起來,以優(yōu)化程序效率簿废。 將方法響應能力緩存起來的最佳途徑是實用“位段”數(shù)據(jù)類型空入。這是一項乏人問津的C語言特性,但在此處用起來卻正合適族檬。

// 協(xié)議聲明部分
@class EOCNetworkFetcher;

@protocol EOCNetworkFetcherDelegate
@optional
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didUpdateProgressTo:(float )progress;

@end

在.m文件中 定義一個結構體_delegateFlags用來緩存方法響應能力

@interface EOCNetworkFetcher ()
{
    struct {
        unsigned int didReceiveData : 1;
        unsigned int didFailWithError : 1;
        unsigned int didUpdateProgressTo : 1;
    } _delegateFlags;
}
@end

@implementation EOCNetworkFetcher

- (void)setDelegate:(id<EOCNetworkFetcherDelegate>)delegate
{
    _delegate = delegate;
    _delegateFlags.didReceiveData = [_delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
    _delegateFlags.didFailWithError = [_delegate respondsToSelector:@selector(networkFetcher:didFailWithError:)];
    _delegateFlags.didUpdateProgressTo = [_delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}

// 在使用的時候 直接判斷
if (_delegateFlags.didUpdateProgressTo) {
        [_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
    }

@end
24. 將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中

使用分類機制可以把類的實現(xiàn)代碼劃分成易于管理的小塊歪赢;

25. 總是為第三方類的分類名稱增加前綴
  • 向第三方類中添加分類時,總應該給其名稱加上你專用的前綴单料;
  • 向第三方類中添加分類時埋凯,總應該給其中的方法名加上你專用的前綴;
26. 勿在分類中聲明屬性

把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里扫尖。不建議在分類中添加屬性白对,雖然通過關聯(lián)對象能夠解決在分類中不能合成實例變量的問題。但不是最理想的换怖,且在內管理上容易出錯躏结,分類只是一種手段,目的是在于擴展類的功能狰域,而非封裝數(shù)據(jù)媳拴。

27. 使用“class-continuation分類” 隱藏實現(xiàn)細節(jié)

其實就是類擴展

  • 通過“class-continuation分類”向類中新增實例變量;
  • 如果某屬性在主接口中聲明為“只讀”兆览,而類的內部又要用設置方法修改此屬性屈溉,那么就在“class-continuation分類”中將其擴展為“可讀寫”。
  • 把私有方法的原型聲明在“class-continuation分類”里面抬探;
  • 若想使類所遵循的協(xié)議不為人所知子巾,則可于“class-continuation分類”中聲明帆赢。
28. 通過協(xié)議提供匿名對象
29. 理解引用計數(shù)
- (void)setFoo:(id)foo
{
    [foo retain];
    [_foo release];
    _foo = foo;
}

此方法將保留新值釋放舊值,然后更新實例變量线梗,令其指向新值椰于。順序很重要。假如還未保留新值就先把舊值釋放了仪搔,而且這兩個值又指向同一個對象瘾婿,那么,限執(zhí)行的release操作就有可能導致系統(tǒng)將此對象永久回收烤咧。而后續(xù)的retain操作則無法令這個已經徹底回收的對象復生偏陪,于是實例變量就成了懸掛指針。

30. 以ARC簡化引用計數(shù)
  1. ARC在調用這些方法時煮嫌,并不通過普通的Object-C消息派發(fā)機制笛谦,而是直接調用其底層的C語言版本,這樣性能更好昌阿,因為保留及釋放操作需要頻繁執(zhí)行饥脑,所以直接調用底層函數(shù)能節(jié)省很多CPU周期,比如ARC會調用與retain等價的底層函數(shù)objc_retain懦冰。
  2. 在編譯期灶轰,ARC會把能夠相互抵消的retain、release儿奶、autorelease操作簡約框往。如果發(fā)現(xiàn)在同一個對象上執(zhí)行多次的“保留”與“釋放”操作,那么ARC有時可以成對的移除這兩個操作闯捎;
  3. ARC也包含運行期組件椰弊。用于檢測當前方法返回之后即將執(zhí)行的那段代碼。如果發(fā)現(xiàn)那段代碼要在返回的對象上執(zhí)行retain操作瓤鼻,則設置全局數(shù)據(jù)結構中的一個標志位秉版,而不執(zhí)行autorelease操作。
  4. ARC最在dealloc方法中自動生成.cxx_destrucet方法茬祷,釋放對象
31. 在dealloc方法中只釋放引用并解除監(jiān)聽

簡單說就是在dealloc方法里清焕,只應該釋放引用,或者移除監(jiān)聽者(KVO或NSNotificationCenter)祭犯,不要做其他事秸妥。

32. 編寫“異常安全代碼”時留意內存管理問題
  • 捕獲異常時,一定要注意將try塊內所創(chuàng)建的對象清理干凈沃粗;
  • 在默認情況下粥惧,ARC不生成安全處理異常所需的清理代碼,開啟編譯器標志后(-fobjc-arc-exceptions)最盅,可生成這種代碼突雪,不過會導致應用程序變大起惕,而且會降低運行效率。
33. 以弱引用避免保留環(huán)

使用weak咏删。weak所指向的實例回收后惹想,weak屬性指向nil,而unsafe_unreatined屬性仍然指向那個已經回收的實例督函。

34. 以“自動釋放池塊”降低內存峰值
for (int i = 0; i < 100000; i++) {
        [self doSomethingWithInt:i];
    }

如果“doSomethingWithInt:”方法要創(chuàng)建臨時對象嘀粱,那么這些對象很可能放在自動釋放池里。但是侨核,這些對象在調用完方法之后就不再使用了草穆,他們也依然處于存活狀態(tài)灌灾,因為目前還在自動釋放池里搓译,等待線程執(zhí)行下一次事件循環(huán)時才會清空。這就意味著在執(zhí)行for循環(huán)時锋喜,會持續(xù)有新對象創(chuàng)建出來些己,加入到自動釋放池中。所有這些對象要等到for循環(huán)執(zhí)行完之后才會釋放嘿般,所以會造成內存高峰段标,即:內存用量持續(xù)上漲,然后突然下降炉奴。

NSArray *databaseRecords = /* ... */;
    NSMutableArray *people = [NSMutableArray array];
    for (NSDictionary *record in databaseRecords) {
        @autoreleasepool {
            EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
            [people addObject:person];
        }
    }

35. 用“僵尸對象”調試內存管理問題
  1. 僵尸對象:已經被銷毀的對象(不能再使用的對象);
  2. 野指針:指向僵尸對象(不可用內存)的指針
  3. 給野指針發(fā)消息會報EXC_BAD_ACCESS錯誤
  • 系統(tǒng)在回收對象時逼庞,可以不將其真的回收,而是把它轉成僵尸對象瞻赶;
  • 系統(tǒng)會修改對象的isa指針赛糟,令其指向特殊的僵尸類,從而使該對象變?yōu)榻┦瑢ο笤已贰=┦瑢ο竽軌蝽憫械倪x擇器璧南,響應方式:打印一條包含消息內容及其接受者的消息,然后終止應用程序师逸。

開啟“僵尸對象”的檢測

在Xcode中設置Edit Scheme -> Diagnostics -> Zombie Objects
從匯編的調用順序可以阿蓋總結僵尸對象生成過程:

//1司倚、獲取到即將deallocted對象所屬類(Class)
Class cls = object_getClass(self);

//2、獲取類名
const char *clsName = class_getName(cls)

//3篓像、生成僵尸對象類名
const char *zombieClsName = "_NSZombie_" + clsName;

//4动知、查看是否存在相同的僵尸對象類名,不存在則創(chuàng)建
Class zombieCls = objc_lookUpClass(zombieClsName);
if (!zombieCls) {
     //5员辩、獲取僵尸對象類 _NSZombie_
 Class baseZombieCls = objc_lookUpClass(“_NSZombie_");

     //6盒粮、創(chuàng)建 zombieClsName 類
 zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0);
}
//7、在對象內存未被釋放的情況下銷毀對象的成員變量及關聯(lián)引用屈暗。
   objc_destructInstance(self);

//8拆讯、修改對象的 isa 指針脂男,令其指向特殊的僵尸類
objc_setClass(self, zombieCls);

那么Zombie Object是如何被觸發(fā)的?

//1种呐、獲取對象class
Class cls = object_getClass(self);

//2宰翅、獲取對象類名
const char *clsName = class_getName(cls);

//3、檢測是否帶有前綴_NSZombie_
if (string_has_prefix(clsName, "_NSZombie_")) {
//4爽室、獲取被野指針對象類名
const char *originalClsName = substring_from(clsName, 10);
 
//5汁讼、獲取當前調用方法名
const char *selectorName = sel_getName(_cmd);
&emsp;&emsp;
//6、輸出日志
NSLog(''*** - [%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self);

&emsp;//7阔墩、結束進程
&emsp;abort();
36. 不要使用retainCount
  • 對象的保留計數(shù)看似有用,實則不然啸箫,因為任何給定時間點上的“絕對保留計數(shù)”都無法反映對象生命期的全貌耸彪;
  • ARC下調用該方法會編譯報錯。
37. 理解“塊”這一概念

塊和函數(shù)類似忘苛,只不過是直接定義在另一個函數(shù)里蝉娜,和定義它的那個函數(shù)共享同一范圍內的東西。
block的內存管理:

  • 默認情況下block的內存是在棧中(不需要手動去管理block內存),它不會對所引用的對象進行任何操作扎唾;
  • 如果對block進行了copy操作, block的內存會搬到堆里面,它會對所引用的對象做一次retain操作召川。

為什么加上__block就可以修改外部的變量了?

真正的原因是這樣的:我們都知道:Block不允許修改外部變量的值胸遇,這里所說的外部變量的值荧呐,指的是棧中指針的內存地址。__block所起到的作用就是只要觀察到該變量被 block 所持有纸镊,就將“外部變量”在棧中的內存地址放到了堆中倍阐。進而在block內部也可以修改外部變量的值。

38. 為常用的塊型創(chuàng)建typedef

以typedef重新定義塊類型薄腻,可令變量用起來更加簡單

39. 用handler塊降低代碼的分散程度
40. 用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
41. 多用派發(fā)隊列收捣,少用同步鎖

為啥@synchronized(self)效率低?

同步行為針對的對象是self庵楷。然而共用同一個鎖的那些同步塊罢艾,都必須按順序執(zhí)行,若是在self對象頻繁加鎖尽纽,那么程序可能要等另一段與此無關的代碼執(zhí)行完畢咐蚯,才能繼續(xù)執(zhí)行當前代碼。

手動實現(xiàn)atomic特性弄贿,通常會這么寫:

- (NSString *)someString
{
    @synchronized(self) {
        return _someString;
    }
}

- (void)setSomeString:(NSString *)someString
{
    @synchronized(self) {
        _someString = someString;
    }
}

這么寫雖然能提供某種程度的“線程安全”春锋,但卻無法保證訪問該對象時絕對的線程安全,在同一個線程上多次調用獲取方法(getter)差凹,每次獲取到的結果卻未必相同期奔。在兩次訪問操作之間侧馅,其他線程可能會寫入新的屬性值。
優(yōu)化后:

- (NSString *)someString
{
    __block NSString *localSomeString;
    dispatch_sync(__syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

- (void)setSomeString:(NSString *)someString
{
    dispatch_barrier_async(__syncQueue, ^{
        _someString = someString;
    });
}
image
42. 多用GCD呐萌,少用performSelector

performSelector系列方法有局限性馁痴。如:具備延后執(zhí)行功能的那些方法(performSelector: withObject: afterDelay:)都無法處理帶有兩個參數(shù)的選擇器。而能夠指定執(zhí)行線程的那些方法(performSelector: onThread: withObject: waitUntilDone:)也不是特別通用肺孤,如果要用這些方法罗晕,就得把許多參數(shù)都打包到字典里,然后在受調用的方法里將其提取出來赠堵,這樣會增加開銷且可能出bug小渊。

43. 掌握GCD及操作隊列的使用時機

使用NSOperation及NSOperationQueue的好處如下:

  • 取消某個操作。在運行任務之前茫叭,可以在NSOperation對象上調用cancel方法酬屉,該方法會設置對象內的標志位,用以表名此任務不需要執(zhí)行杂靶。
  • 指定操作間的依賴關系梆惯;
  • 通過鍵值觀察機制(KVO)監(jiān)控NSOperation對象的屬性酱鸭。比如isCancelled(是否已經取消)吗垮、isFinished(是否已完成);
  • 指定操作的優(yōu)先級凹髓。GCD的隊列確實有優(yōu)先級烁登,不過那是針對整個隊列來說的,而不是針對每個塊來說的蔚舀;
  • 重用NSOperation對象饵沧。
44. 通過Dispatch Group機制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務
  • 使用場景:這個比較適合比如下載多張圖片然后再合成一張圖片赌躺,或者請求C需要依賴請求A和請求B的結果的情況狼牺;
  • 注意事項:調用dispatch_group_enterdispatch_group_leave須成對出現(xiàn);
  • dispathc_apply所用的隊列可以是并發(fā)隊列礼患,然而dispathc_apply會持續(xù)阻塞是钥,知道所有任務都執(zhí)行完畢。
45. 使用dispathc_once來執(zhí)行只需要運行一次的線程安全代碼

單例

46. 不要使用dispathc_get_current_queue
  1. dispathc_get_current_queue函數(shù)行為常常與開發(fā)者所預期的不同缅叠。
  2. dispathc_get_current_queue函數(shù)用于解決由不可重入的代碼所引發(fā)的死鎖悄泥,然而能用此函數(shù)解決的問題,通常也能改用“隊列特定數(shù)據(jù)”來解決肤粱。
47. 熟悉系統(tǒng)框架
48. 多用塊枚舉弹囚,少用for循環(huán)

“塊枚舉法”本身就能通過GCD來并發(fā)執(zhí)行遍歷操作。

49. 對自定義其內存管理語義的collection使用無縫橋接

主要講了下CF的一些底層函數(shù)领曼,比如CFDictionary

50. 構建緩存時選用NSCache而非NSDictionary

相比NSDictionary鸥鹉,優(yōu)勢在于:自動刪減功能蛮穿,而且是“線程安全的”,它與字典不同毁渗,并不會拷貝鍵绪撵。

51. 精簡initialize與load方法

load是在編譯階段執(zhí)行,是方法地址執(zhí)行祝蝠,不走消息發(fā)送機制音诈。首次使用某個類之前,系統(tǒng)會向其發(fā)送initialize消息绎狭; load方法:

執(zhí)行順序:父類 -> 子類 -> 分類细溅。都執(zhí)行

initialize方法:(執(zhí)行順序)

  1. 分類會覆蓋本類。
  2. 父類 -> 子類
  3. 如果子類沒有執(zhí)行initialize 那么父類的initialize可能會執(zhí)行多次儡嘶。
52. 別忘了NSTimer會保留其目標對象

書中是用分類擴展喇聊,用“塊”來實現(xiàn)的。也可以通過關聯(lián)對象來實現(xiàn)蹦狂。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末誓篱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子凯楔,更是在濱河造成了極大的恐慌窜骄,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摆屯,死亡現(xiàn)場離奇詭異邻遏,居然都是意外死亡,警方通過查閱死者的電腦和手機虐骑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門准验,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人廷没,你說我怎么就攤上這事糊饱。” “怎么了颠黎?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵另锋,是天一觀的道長。 經常有香客問我盏缤,道長砰蠢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任唉铜,我火速辦了婚禮台舱,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己竞惋,他們只是感情好柜去,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拆宛,像睡著了一般嗓奢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浑厚,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天股耽,我揣著相機與錄音,去河邊找鬼钳幅。 笑死物蝙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的敢艰。 我是一名探鬼主播诬乞,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钠导!你這毒婦竟也來了震嫉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牡属,失蹤者是張志新(化名)和其女友劉穎票堵,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湃望,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡换衬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了证芭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡担映,死狀恐怖废士,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情蝇完,我是刑警寧澤官硝,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站短蜕,受9級特大地震影響氢架,放射性物質發(fā)生泄漏。R本人自食惡果不足惜朋魔,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一岖研、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦孙援、人聲如沸害淤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窥摄。三九已至,卻和暖如春础淤,著一層夾襖步出監(jiān)牢的瞬間崭放,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工鸽凶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留莹菱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓吱瘩,卻偏偏與公主長得像道伟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子使碾,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359