<<Effective Objective-C 2.0編寫高質量iOS與OS X代碼的52個有效方法>>這本書相信很多人都讀過,最近又重溫了一遍兵罢,這本書很薄孤里,只有209頁,幾個小時就可以看完。雖然很薄茁彭,但是寫的很好陡厘,這本書的內容結構是這樣的抽米,先是擺出結論,然后再花幾倍的筆墨來解釋為什么要這樣糙置。如果作者去除解釋部分云茸,估計本書可以壓縮到80頁。這本書我看了好幾遍了谤饭,但是還是感覺意猶未盡标捺,于是自己就根據(jù)它的每個章節(jié)提了一些問題懊纳。(有的章節(jié)其實沒必要提問題的,就是了解一下就夠了亡容,但是由于慣例嗤疯,所以這些不太適合提問題的章節(jié)還是提問了。) 其中的少數(shù)問題其實”超綱”了,也就是僅憑本書是答不出或者答不完整的闺兢,還需要查很多的資料才能完全答出來茂缚。有興趣的同學可以看看自己能答出來多少。由于本人水平有限屋谭,有的問題出的不是很好脚囊。
可以點擊這里下載PDF版
第一章:熟悉Objective-C
第一條:了解Objective-C語言的起源
- 如何理解OC的動態(tài)綁定?
- 說說[xxObj doSomething]從開始執(zhí)行到結束的過程桐磁。
- 從運行程序到main方法的調用悔耘,這之間發(fā)生了什么?
建議大家好好看看runtime component的實現(xiàn)過程我擂,runtime源碼鏈接,重點看看objc-runtime-new.mm類衬以。
第二條: 在類的文件中盡量少引用其他頭文件
- 你有用過@class嗎?請說一下它的常用情景校摩。
- #import和#include的區(qū)別看峻。
第三條:多用字面量語法,少用與之等價的方法
- 字面量語法相比傳統(tǒng)創(chuàng)建方法的優(yōu)勢秧耗?
第四條:多用類型常量备籽,少用#define預處理指令
- 類型常量相對于#define預處理指令的優(yōu)點舶治?
- 請手寫一個通知名稱的聲明和實現(xiàn)分井。
第五條:多用枚舉表示狀態(tài)、選項霉猛、狀態(tài)碼
- NS_OPTIONS為什么能實現(xiàn)組合多個選項的功能?
- c語言的enum不是夠用了嗎尺锚?Foundation框架為啥要再弄一個NS_ENUM的宏?
第二章:對象惜浅、消息瘫辩、運行期
第六條:理解“屬性”這一概念
- 為什么說atomic不能保證屬性在多線程中是安全的?請舉個例子說明。你如何保證iOS中多線程中訪問可變數(shù)組,可以得到正確的數(shù)據(jù)?
- 請分別說明iOS開發(fā)中@property的屬性特征中的原子性坛悉,讀寫權限和內存管理語義的默認情況?(如什么都不寫的話伐厌,是nonatomic還是atomic?)
- weak是如何實現(xiàn)變量銷毀時指向nil的?
- @synthesize和@dynamic有什么區(qū)別?你有使用過它們嗎?什么情況下會需要使用它們裸影?請舉例
- 你有在.h文件中使用readonly嗎挣轨?為啥要這么設計?一般還需要在.m中做些什么來配合readonly使用?
第七條:在對象內部盡量直接訪問實例變量
- 什么時候使用屬性轩猩,什么時候使用實例變量卷扮?分別說說使用它們的優(yōu)缺點荡澎。
- 如果在初始化方法和dealloc方法中使用了屬性,可能會出現(xiàn)什么問題晤锹?說一說出現(xiàn)這種問題的原因摩幔。
第八條:理解“對象等同性”這一概念
- 自定義對象,如何實現(xiàn)isEqual鞭铆?
- hash方法存在的意義或衡?hash方法如何實現(xiàn)?為什么衔彻?為什么說相同的對象一定具有相同的哈希碼薇宠,而兩個哈希碼相同的對象卻未必相同?
第九條:以“類族模式”隱藏實現(xiàn)細節(jié)
- Foundation框架和UIKit框架中都有類應用了“類族模式”設計艰额,請至少說出2個采用了這種模式的類澄港。
- “類族模式”有什么好處?請你設計一個采用了“類族模式”的類柄沮。
第十條:在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)
- 分類中的@property屬性跟普通類中使用@property聲明屬性有什么不同回梧?如何在分類中實現(xiàn)跟普通類中使用@property一樣的效果?
- 你在項目中或者看到別人使用過關聯(lián)對象技術嗎祖搓?
第十一條:理解“objc_msgSend”的作用
- SEL的本質是什么?
- 給對象發(fā)送消息是如何”動態(tài)消息派發(fā)系統(tǒng)”派發(fā)的狱意?說一說這個過程(消息傳遞機制)。
- 蘋果為動態(tài)消息派發(fā)系統(tǒng)做了哪些優(yōu)化來提高查找方法列表的速度?
第十二條:理解消息轉發(fā)機制
- 請說說消息轉發(fā)機制拯欧?你在項目中有使用過消息轉發(fā)嗎(在開發(fā)中的具體應用場景)详囤?
第十三條:用“方法調配技術”調試“黑盒方法”
- 如何動態(tài)添加方法和替換方法實現(xiàn)?
第十四條:理解“類對象”的用意
- 說說你對類對象的理解镐作?清楚它的內存布局嗎藏姐?
第三章:接口與API設計
第十五條:用前綴避免命名空間沖突
- 你在項目中創(chuàng)建類和設計分類方法時,如何避免類的重復定義及分類方法的相互覆蓋?
第十六條:提供“全能初始化方法”
- 如果讓你設計一個類该贾,提供多個初始化方法羔杨,你的.h文件怎么設計接口?
- 為什么在init方法中要調用super init杨蛋,不調用行不行兜材?請說明理由。
第十七條:實現(xiàn)description方法
- 有使用過LLDB嗎逞力?
- 在Xcode的打印含有中文字符串的字典和數(shù)組時曙寡,會出現(xiàn)編碼和不是正常的文字,你是怎么解決的?
第十八條:盡量使用不可變對象
- 在某個類的頭文件中寇荧,有個屬性
@property (nonatomic, strong) NSMutableArray *friends;
,這么寫有什么問題嗎举庶?如果有問題,請說明問題砚亭,如何修改?
第十九條:使用清晰而協(xié)調的命名方式
- 你平時是怎么樣為方法和類與協(xié)議命名的?
第二十條:為私有方法名加前綴
- 為什么要給私有方法加前綴?
第二十一條:理解Objective-C錯誤模型
- 第三方崩潰日志收集平臺的設計原理是什么灯变?如果讓你自己設計一個殴玛,你怎么設計出類似的功能。
第二十二條:理解NSCopying協(xié)議
- 什么是深拷貝添祸,什么是淺拷貝滚粟,有什么區(qū)別?
- 如何實現(xiàn)自定義對象支持拷貝功能?
第四章:協(xié)議與分類
第二十三條:通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
- 什么是委托(代理)模式刃泌?這個模式的作用是什么凡壤?
- UITableView為什么有了delegate還要增加dataSource屬性,這2個屬性為什么都是weak?
- UITableView的某些代理方法調用的非常頻繁,頻繁判斷某個委托對象是否響應代理方法比較耗費性能耙替,請問如何優(yōu)化?
第二十四條:將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
- 某個類的.m過于臃腫,雜糅了太多的方法亚侠,如何重構它?
- 分類里實現(xiàn)了與本類相同的方法,為什么會調用分類里面的方法而不調用本類的原來方法俗扇?
第二十五條:總是為第三方類的分類名稱加前綴
- 增加分類和分類方法時要注意什么?(主要是命名上)
第二十六條:勿在分類中聲明屬性
- 為什么分類中的@property不能生成實例變量硝烂?
第二十七條:使用“class-continuation”分類隱藏實現(xiàn)細節(jié)
- 匿名分類跟分類有什么區(qū)別?
第二十八條:通過協(xié)議提供匿名對象
- 協(xié)議除了用于代理模式,還有什么作用?
第五章:內存管理
第二十九條:理解引用計數(shù)
- 說說你對引用計數(shù)的理解?蘋果設計引用計數(shù)的用意是什么?
第三十條:以ARC簡化引用計數(shù)
- ARC和MRC有什么區(qū)別?ARC是如何實現(xiàn)的铜幽?
- 使用了ARC還會出現(xiàn)內存泄露嗎滞谢?請舉例說明。
- weak是如何做到自動置為nil的除抛?
第三十一條:在delloc方法中只釋放引用并解除監(jiān)聽
- ARC下我們在dealloc方法中應該做什么事情?
第三十二條:編寫“異常安全代碼”時留意內存管理問題
- Java等語言中經常大量用到異常狮杨,為什么OC中比較少出現(xiàn)異常機制的代碼?
第三十三條:以弱引用避免保留環(huán)
- weak和unsafe_unretained有什么區(qū)別?哪些情況下會用到weak,請舉例到忽。
第三十四條:以 “自動釋放池塊” 降低內存峰值
- 項目中有用過自動釋放池嗎橄教?有什么作用?
第三十五條:用“僵尸對象”調試內存管理問題
- 如何監(jiān)測項目中出現(xiàn)的“僵尸對象”?
第三十六條:不要使用retainCount
- 為什么不要用retainCount? retainCount不準的原因在哪里?
第六章:塊與大中樞派發(fā)
第三十七條:理解”塊”這一概念
- block有哪幾種?它的內部結構是什么樣的喘漏?
第三十八條:為常用的塊類型創(chuàng)建typedef
- 請手寫一個block屬性护蝶,并分別使用typedef重新定義它。
第三十九條:用handler塊降低代碼分散程度
- 使用block代替代理有啥有點和缺點?分別在何種情況使用比較合適?
- NSNotificationCenter在發(fā)送通知的時候陷遮,是同步還是異步?
第四十條:用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
- 什么時候容易出現(xiàn)block的循環(huán)引用?出現(xiàn)這種情況除了使用weak滓走,還有其他的解決方法嗎垦江?
第四十一條:多用派發(fā)隊列帽馋,少用同步鎖
- atomic的實現(xiàn)原理是咋樣的?
- 如何用GCD解決訪問可變數(shù)組過程中的多線程安全問題?
第四十二條:多用GCD,少用performSelector系列方法
- performSelector方法有什么局限性和缺點? 如何解決比吭。
第四十三條:掌握GCD以及操作隊列的使用時機
- GCD和NSOperation各有哪些特點绽族?你平時喜歡用哪個?
第四十四條:通過Dispatch Group機制衩藤,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務
- 使用過dispatch group嗎?它的作用是什么?
- GCD大概有哪些比較常用的函數(shù)?
第四十五條:使用dispatch_once來執(zhí)行只需要運行一次的線程安全代碼
- 請分別用2種方式來實現(xiàn)單例吧慢。
- dispatch_once為什么說是線程安全的?
第四十六條:不要使用dispatch_get_current_queue
- 什么情況會出現(xiàn)死鎖?請用GCD模擬一種情況。
第七章:系統(tǒng)框架
第四十七條:熟悉系統(tǒng)框架
- 除了Foundation和UIKit框架赏表,你還知道哪些系統(tǒng)框架?
第四十八條:多用塊枚舉检诗,少用for循環(huán)
- 你知道哪些遍歷的方法?各有什么特點?
第四十九條:對自定義其內存管理語義的collecion使用無縫橋接
- 你了解橋接技術嗎?為什么需要橋接?
第五十條:構建緩存時選用NSCache而非NSDictionary
- 你了解NSCache嗎匈仗?它相比于NSDictionary有哪些優(yōu)點?
第五十一條:精簡initialize與load的實現(xiàn)代碼
- 有聽過load和initialize方法嗎?說說它們的特點逢慌。
第五十二條:別忘了NSTimer會保留其目標對象
-
[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ā)機制
基本就是消息轉發(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ù)
- ARC在調用這些方法時煮嫌,并不通過普通的Object-C消息派發(fā)機制笛谦,而是直接調用其底層的C語言版本,這樣性能更好昌阿,因為保留及釋放操作需要頻繁執(zhí)行饥脑,所以直接調用底層函數(shù)能節(jié)省很多CPU周期,比如ARC會調用與retain等價的底層函數(shù)objc_retain懦冰。
- 在編譯期灶轰,ARC會把能夠相互抵消的retain、release儿奶、autorelease操作簡約框往。如果發(fā)現(xiàn)在同一個對象上執(zhí)行多次的“保留”與“釋放”操作,那么ARC有時可以成對的移除這兩個操作闯捎;
- ARC也包含運行期組件椰弊。用于檢測當前方法返回之后即將執(zhí)行的那段代碼。如果發(fā)現(xiàn)那段代碼要在返回的對象上執(zhí)行retain操作瓤鼻,則設置全局數(shù)據(jù)結構中的一個標志位秉版,而不執(zhí)行autorelease操作。
- 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. 用“僵尸對象”調試內存管理問題
- 僵尸對象:已經被銷毀的對象(不能再使用的對象);
- 野指針:指向僵尸對象(不可用內存)的指針
- 給野指針發(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);
  
//6、輸出日志
NSLog(''*** - [%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self);
 //7阔墩、結束進程
 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;
});
}
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_enter與dispatch_group_leave須成對出現(xiàn);
- dispathc_apply所用的隊列可以是并發(fā)隊列礼患,然而dispathc_apply會持續(xù)阻塞是钥,知道所有任務都執(zhí)行完畢。
45. 使用dispathc_once來執(zhí)行只需要運行一次的線程安全代碼
單例
46. 不要使用dispathc_get_current_queue
- dispathc_get_current_queue函數(shù)行為常常與開發(fā)者所預期的不同缅叠。
- 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í)行順序)
- 分類會覆蓋本類。
- 父類 -> 子類
- 如果子類沒有執(zhí)行initialize 那么父類的initialize可能會執(zhí)行多次儡嘶。
52. 別忘了NSTimer會保留其目標對象
書中是用分類擴展喇聊,用“塊”來實現(xiàn)的。也可以通過關聯(lián)對象來實現(xiàn)蹦狂。