讀后感先放在前邊
現(xiàn)在詳細來看這本書應(yīng)該也不晚吧,iOS 開發(fā)之類的書籍其實網(wǎng)上的總結(jié)還是蠻多的 有很多文章寫得都是挺不錯的, 但是終歸是別人的的讀后感總結(jié),看著別人的總結(jié)終歸不能完全吸收為自己的,所以近期抽空把 iOS 相關(guān)書籍看一遍 來對自己的知識體系做一個校驗
書中舉得很多例子都是挺好的 此文章也總結(jié)了此書的大綱,只有一些本人比較生疏的知識點才會展開詳細描述,書中有很多細節(jié)并不是我們?nèi)粘i_發(fā)中能注意到的但是很重要的一些知識點, 此篇文章寫得耗費時間還是挺久的
第一章 熟悉 Objective-C
1 了解 Objective-C 語言的起源
OC 語言使用了"消息結(jié)構(gòu)" 而非是"函數(shù)調(diào)用"
消息結(jié)構(gòu)與函數(shù)調(diào)用區(qū)別關(guān)鍵在于:
一 使用消息結(jié)構(gòu)的語言,其運行時所應(yīng)執(zhí)行的代碼有運行環(huán)境來決定
二 使用函數(shù)調(diào)用的語言,則有編譯器決定的
OC 語言使用動態(tài)綁定的消息結(jié)構(gòu),也就是說在在運行時才會檢查對象類型,接受一條消息之后,究竟應(yīng)執(zhí)行何種代碼, 有運行期環(huán)境而非編譯器來決定
下圖來看一下 OC 對象的內(nèi)存分配
此圖布局演示了一個分配在對堆中的 NSString 實例, 有兩個分配在棧上的指針指向改實例
OC 系統(tǒng)框架中也有很多使用結(jié)構(gòu)體的, 比如 CGRect, 因為如果改用 OC 對象來做的話, 性能就會受影響
2 在類的頭文件中盡量少引用其他頭文件
- 我們?nèi)绶潜匾? 就不要引入頭文件, 一般來說, 應(yīng)在某個類的頭文件中使用向前聲明(向前聲明的意思就是用
@Class Person
來表明 Person 是一個類)來提及別的類, 并在實現(xiàn)文件中引入那些類的 頭文件, 這樣做盡量降低類之間 的耦合 - 有時無法使用向前聲明,比如要聲明某個類遵循一項協(xié)議,這種情況下,盡量吧"該類遵循某協(xié)議"的這條聲明一直"Class-Continuation 分類"中,如果不行的話, 就把協(xié)議單獨放在一個頭文件中.然后將其引入
3 多用字面量語法 少用與之等價的方法
推薦使用字面量語法:
NSString * someString = @"奧卡姆剃須刀";
NSNumber *number = @18;
NSArray *arr = @[@"123",@"456];
NSDictionary *dict = @{
@"key":@"value"
};
對應(yīng)的非字面量語法
NSString *str = [NSString stringWithString:@"奧卡姆"];
NSNumber *number = [NSNumber numberWithInt:18];
NSArray *arr = [NSArray arrayWithObject:@"123",@"456"];
4 多用類型常量,少用 #define 預(yù)處理指令
不要使用預(yù)處理指令定義常量, 這樣定義出來的常量不含類型,編譯器只會在編譯前據(jù)此執(zhí)行查找與替換操作, 即使有人重新定義了常量值, 編譯器也不會產(chǎn)生警告信息, 這將導(dǎo)致應(yīng)用程序中的常量值不一致
在實現(xiàn)文件中使用 static const 來定義"只在編譯單元內(nèi)可見的常量",由于此類常量不在全局符號表中, 所以無須為其名稱加前綴
舉例說明
不合適的寫法
//動畫時間
#define ANIMATION_DUATION 0.3
正確的寫法
視圖修改 const修飾的變量則會報錯
static const NSTimeInterval KAnimationDuration = 0.3
- 在頭文件中使用 extern 來聲明全局變量,并在相關(guān)實現(xiàn)文件中定義其值.這種常量要出現(xiàn)在全局符號表中, 所以其名稱應(yīng)該加以區(qū)隔,通常用與之相關(guān)的類名做前綴.
// EOCAnimatedView.h
extern const NSTiemInterval EOCAnimatedViewANmationDuration
// EOCAnimatedView.m
const NSTiemInterval EOCAnimatedViewANmationDuration = 0.3
這樣定義的常量要優(yōu)于# Define 預(yù)處理指令, 因為編譯器會確保常量不變, 而且外部也可以使用
5 用枚舉表示狀態(tài),選項, 狀態(tài)碼
- 如果把傳遞給某個方法的選項表示為枚舉類型,而對個選項又可以同事使用, 那么就將各選項值定義為2的冪, 以便通過按位或操作器組合起來
- 在處理枚舉類型的 switch 語句中不要實現(xiàn) default 分支, 這樣的話, 加入新枚舉之后,編譯器就會提示開發(fā)者, switch 語句并未處理所有枚舉
按位或操作符枚舉
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
第二章 對象,消息,運行期
6 理解"屬性"這一概念
- 可以用@ property 語法來定義對象中所封裝的數(shù)據(jù)
- 通過"特性"來指定存儲數(shù)據(jù)所需的正確語義
- 在設(shè)置屬性所對應(yīng)的實例變量時, 一定要遵守該遵守該屬性所聲明的語義
- 開發(fā) IOS 程序是應(yīng)該使用 nonatomic 屬性,因為 atomic 屬性會嚴重影響性能
7 在對象內(nèi)部盡量直接訪問實例變量
- 在對象內(nèi)部讀取數(shù)據(jù)是, 應(yīng)該直接通過實例變量來讀,而寫入數(shù)據(jù)是,則應(yīng)該通過屬性來寫
- 在初始化方法及 dealloc 中,總是應(yīng)該直接通過實例變量來讀寫數(shù)據(jù)
- 有時會使用惰性初始化技術(shù)(高大上的說法,其實就是懶加載)配置某份數(shù)據(jù),這種情況下,需要通過屬性來讀取數(shù)據(jù)
8 理解"對象等同性"這一概念
- 若想檢測對象的等同性. 請?zhí)峁?code>isEqual 與
hash
方法 - 相同的對象必須具有相同的哈希碼,但是兩個哈希碼相同的對象卻未必相同
- 不要盲目的逐個檢測每條屬性,而是應(yīng)該依照具體需求來制定檢測方案
- 編寫hash 方法是,應(yīng)該使用計算速度快而且哈希碼碰撞幾率低的算法
9 以類族模式隱藏實現(xiàn)細節(jié)
- 類族模式可以吧實現(xiàn)細節(jié)隱藏在一套簡單的公共接口后面,
- 系統(tǒng)框架中經(jīng)常使用類族
- 從類族的公共抽象基類中繼承自雷是要當(dāng)心,若有開發(fā)文檔,則應(yīng)實現(xiàn)閱讀
此小節(jié)比較抽象,用文中的規(guī)則來總結(jié)一下 大概如下
- 1 子類應(yīng)該繼承自類族中的抽象基類
若想編寫 NSArray 類族的子類,則需令其繼承自不可變數(shù)組的基類或可變數(shù)組的基類 - 2 子類應(yīng)該定義自己的數(shù)據(jù)存儲方式
開發(fā)者編寫 NSArray 子類時, 經(jīng)常在這個問題上受阻, 子類必須用一個實例變量來存放數(shù)組中的對象, 這似乎與大家預(yù)想的不同, 我們以為 NSArray 自己肯定會保存那些對象,所以子類中就無需在存一份了, 但是大家要記住, NSArray 本身只不過是包在其他隱藏對象外面的殼, 他僅僅定義了所有數(shù)組都需具備的一些接口,對于這個自定義的數(shù)組子類來說, 可以用 NSArray 來保存其實例 - 3 子類應(yīng)該復(fù)寫超類文檔中指明需要復(fù)寫的方法
在每個抽象基類中, 都有一些子類必須腹瀉的方法, 比如說,想要編寫 NSArray 的子類, 就需要實現(xiàn) count 及 objectAtIndex 方法,像 lastObject 這種方法則無需事先,因為基類可以根據(jù)前兩個方法實現(xiàn)出這個方法
10 在既有類中使用關(guān)聯(lián)對象存放自定義數(shù)據(jù)
- 可以通過"關(guān)聯(lián)對象" 機制來吧兩個對象連起來
- 定義關(guān)聯(lián)對象時,可指定內(nèi)存管理語義,用以模仿定義屬性時所采用的"擁有關(guān)系"和"非擁有關(guān)系"
- 只有在其他做法不可行是,才應(yīng)選用關(guān)聯(lián)對象,因為這種做法通常會引入難于查找的 bug
這種方法我在分類中經(jīng)常使用,而且屢試不爽 以下是本人在項目中的用法
static void *callBackKey = "callBackKey";
@implementation UIView (category)
- (void)addTapWithBlock:(callBack)callback{
objc_setAssociatedObject(self, callBackKey, callback, OBJC_ASSOCIATION_COPY);
self.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapClick)];
[self addGestureRecognizer:tap];
}
- (void)tapClick{
callBack block = objc_getAssociatedObject(self, callBackKey);
if (block) {
block();
}
}
11 理解 objc_msgSend 的作用
objc_msgSend 函數(shù)會依據(jù)接受者與選擇子的類型來調(diào)用適當(dāng)?shù)姆椒?為了完成此操作, 該方法需要在接受者所屬的類中搜尋其"方法列表" ,如果能找到與選擇名稱相符的方法,就跳至其實現(xiàn)代碼, 若是找不到 就沿著繼承體系繼續(xù)向上查找, 等找到合適的方法在挑戰(zhàn), 如果還是找不到相符的方法,那就執(zhí)行"消息轉(zhuǎn)發(fā)"操作 這個會在12條來講
- 消息有接受者,選擇子及參數(shù)構(gòu)成, 給某對象"發(fā)送消息"也就相當(dāng)于在該對象上"調(diào)用方法"
- 發(fā)給某對象的全部消息都要有"動態(tài)消息派發(fā)系統(tǒng)"來處理, 該系統(tǒng)會查出對應(yīng)的方法,并執(zhí)行其代碼
12 理解 消息轉(zhuǎn)發(fā)機制 重點再次復(fù)習(xí)一遍
消息轉(zhuǎn)發(fā)分為兩大階段,第一階段先征詢接受者,所屬的類, 看其是否能動態(tài)添加方法,以處理當(dāng)前這個"未知的的選擇子" 這叫做"動態(tài)方法解析",第二階段涉及完整的消息轉(zhuǎn)發(fā)機制. 如果運行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了, 那么接受者自己就無法再以動態(tài)新增方法的手段來響應(yīng)包含蓋選擇子的消息了, 此時,運行期系統(tǒng)會請求接受者以其他手段來處理與消息相關(guān)的方法調(diào)用, 這又細分兩小步. 首先請接受者看看有沒有其他對象能處理這條消息,若有 則運行期系統(tǒng)會吧消息轉(zhuǎn)給那個對象,于是消息轉(zhuǎn)發(fā)過程結(jié)束,一切如常, 若沒有背援的接受者,則啟動完整的消息轉(zhuǎn)發(fā)機制,運行期系統(tǒng)會吧消息有關(guān)的全部細節(jié)都封裝在 NSInvocation 對象中, 在給接受者最后一次機會, 令其設(shè)法解決當(dāng)前還沒處理的這條消息
動態(tài)方法解析
+ (Bool) resolveInstanceMethod:(SEL)selector
該方法的參數(shù)就是那個未知的選擇子,其返回值為 Boolean 類型,表示這個類是否能新增一個實例方法用以處理此選擇子.在繼續(xù)往下執(zhí)行轉(zhuǎn)發(fā)機制之前, 本類有機會新增一個處理此選擇子的方法,假如尚未實現(xiàn)的方法不是實例方法而是類方法, 那么運行期系統(tǒng)就會調(diào)用另外一個方法 和當(dāng)前方法類似 resolveClassMethod
備援接受者
當(dāng)前接受者還有第二次機會能處理未知的選擇子,在這一步,運行期系統(tǒng)會問它: 能不能把這條消息轉(zhuǎn)給其他接受者來處理. 與該步驟對應(yīng)的處理方法如下:
- (id)forwardingTargetForSelestor:(SEL)selector
方法參數(shù)代表未知的選擇子, 若當(dāng)前接受者能找到備援對象,則將其返回,若找不到,就返回 nil
完整的消息轉(zhuǎn)發(fā)
如果轉(zhuǎn)發(fā)算法已經(jīng)到這一步的話,俺那么唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了.首先創(chuàng)建 NSInvocation 對象, 把與尚未處理的那條消息有關(guān)的全部細節(jié), 都封裝于其中,此對象包含選擇子、目標,及參數(shù), 在觸發(fā) NSInvocation 對象時, "消息派發(fā)系統(tǒng)"將親自出馬,把消息指派給目標對象 此步驟會調(diào)用下列方法來轉(zhuǎn)發(fā)消息
- (void)forwardInvocation:(NSInvocation * )invocation
再觸發(fā)消息前, 先以某種方式改變消息內(nèi)容,比如追加另外一個參數(shù),或是改換選擇子等等
實現(xiàn)此方法是,若發(fā)現(xiàn)某調(diào)用不應(yīng)有本類處理,擇婿調(diào)用超類的同名方法, 這樣的話,繼承體系中的每個類都有機會處理此調(diào)用請求,直至 NSObject, 如果最后調(diào)用了 NSOBject 方法,那么該方法還會繼而調(diào)用doesNotRecognizeSelector
以拋出異常,此異常表明選擇子最終未能得到處理
消息轉(zhuǎn)發(fā)全流程
- 若對象無法響應(yīng)某個選擇子,則進入消息轉(zhuǎn)發(fā)流程
- 通過運城期的動態(tài)方法解析功能,我們可以在需要用到某個方法時再將其加入類中
- 對象可以把其中無法解讀的某些選擇子轉(zhuǎn)交給其他對象來處理
- 經(jīng)過上述兩步之后, 如果還是沒辦法處理選擇子,那就啟動完整的消息轉(zhuǎn)發(fā)機制
13 用"方法調(diào)配技術(shù)"調(diào)試"黑盒方法"
通俗講 其實就是利用 runtime 實現(xiàn)方法交換 這個就不再詳細解說了
- 在運行器,可以向類中新增或替換選擇子所對應(yīng)的方法實現(xiàn)
- 使用另一份實現(xiàn)來替換原有的方法實現(xiàn), 這道工序叫做"方法調(diào)配", 開發(fā)者常用此技術(shù)向原有實現(xiàn)中添加功能
- 一般來說, 只有調(diào)試程序的時候,才需要在運行期修改方法實現(xiàn), 這種做法不易濫用
14 理解"類對象"的用意
每個 Objective-C 對象實例都是指向某塊內(nèi)存數(shù)據(jù)的指針,描述 Objective-C對象所用的數(shù)據(jù)結(jié)構(gòu)定義在運行期程序庫的頭文件里, id 類型本身也是定義在這里
typedef struct objc_object {
Class isa;
} * id
由此可見,每個對象結(jié)構(gòu)體的首個成員是 Class 類的變量. 該變量定義了對象所屬的類,通常稱為 isa 指針
Class 對象也定義在運行期程序庫的頭文件中中:
typedef struct objc_class *Class;
struct objc_class{
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
}
此結(jié)構(gòu)體存放類的元數(shù)據(jù),此結(jié)構(gòu)體的首個變量也是 isa 指針, 這說明, Class 本身也是 Objective-C 對象,結(jié)構(gòu)體中的 super_class 它定義了本類的超類, 類對象所屬的類型(也就是 isa 指針所指向的類型)是另外一個類, 叫做元類,用來表述類對象本身所具備的元數(shù)據(jù).每個類僅有一個類對象,而每個類對象僅有一個與之相關(guān)的元類
假設(shè)有一個 someClass 的子類從 NSObject 中繼承而來,則它的繼承體系可由下圖表示
在類繼承體系中查詢類型信息
可以用類型信息查詢方法來檢視類繼承體系,isMemberOfClass
能夠判斷出對象是否是特定類的實例 而isKindOfClass
則能夠判斷出對象是否為某類或某派生派類的實例
- 每個實例都一個指向 Class 對象的指針, 用以表明其類型,而這些 Class 對象則構(gòu)成了類的繼承體系
- 如果對象類型無法在編譯期確定,那么應(yīng)該使用類型信息查詢方法來探知
- 盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現(xiàn)了消息轉(zhuǎn)發(fā)功能
第三章 接口與 API 設(shè)計
15 用前綴避免命名空間沖突
- 選擇與你的公司,應(yīng)用程序或二者皆有關(guān)聯(lián)之名稱作為類名的前綴,并在所有代碼中均使用這一前綴.
- 若自己所開發(fā)的程序庫中使用到第三方庫, 則應(yīng)為其中的名稱加上前綴
16 提供"全能初始化方法"
UITableViewCell 初始化該類對象的時候,需要指明其樣式及標識符, 標識符能夠區(qū)分不同類型的單元格, 由于這種對象的創(chuàng)建成本比較高, 所以繪制表格時 可依照標識符來服用,提升程序執(zhí)行效率,這種可以為對象提供必要信息以便其能完成工作的初始化方法叫做"全能初始化方法"
- 在類中提供一個全能初始化方法,并于文檔中指明, 其他初始化方法均應(yīng)調(diào)用此方法
- 若全能初始化方法與超類不同, 則需覆蓋超類中的對應(yīng)方法
- 如果超類的初始化方法不適用于子類, 那么應(yīng)該復(fù)寫這個超類方法,并在其中排除異常
這一點寫開源框架的時候十分的受用
17 實現(xiàn) description 方法
這個就不多說了 實際開發(fā)中經(jīng)常用
- 實現(xiàn) description 方法 返回一個有意義的字符串,用以描述該實例
- 若想在調(diào)試時打印出更詳盡的對象描述信息,則應(yīng)實現(xiàn) debugDescription
18 盡量使用不可變對象
- 盡量創(chuàng)建不可變對象
- 若某屬性僅可在對象內(nèi)部修改,則在
class-continuation分類
中將其有 readonly 屬相擴展為 readwrite 屬性 - 不要把可變的 collection 作為屬性公開,而應(yīng)提供相關(guān)方法, 以此修改對象中的可變 collection
LLPerson.h
@interface LLPerson : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
LLPerson.m
@interface LLPerson()
@property (nonatomic, copy, readwrite) NSString *name;
@property (nonatomic, assign, readwrite) NSInteger age;
@end
@implementation LLPerson
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age{
if (self = [super init]) {
self.name = name;
self.age = age;
}
return self;
}
19 使用清晰而協(xié)調(diào)的命名方式
方法命名的幾條規(guī)則
- 如果方法的返回值是新創(chuàng)建的, 那么方法名的首個詞應(yīng)是返回值得類型,除非前面還有修飾語,例如 localizedString 屬性的存取方法不遵循這種命名方式,因為一般以為這些方法不會創(chuàng)建對象,即便有時返回內(nèi)部對象的一份拷貝, 我們也認為那相當(dāng)于原有的對象,這些存取方法應(yīng)該按照其所對應(yīng)的屬性來命名
- 應(yīng)該把表示參數(shù)類型的名詞放在參數(shù)前面
- 如果方法要在當(dāng)前對象執(zhí)行操作,那么久應(yīng)該包含動詞;若執(zhí)行操作時還需要參數(shù),則應(yīng)該在動詞后面加上一個或多個名詞
- 不要使用 str 這種簡稱,應(yīng)該用 string 這樣的全稱
- Boolean 屬性應(yīng)加上 is 前綴,如果方法返回非屬性的 Boolean 值, 那么應(yīng)該根據(jù)其功能 選用 has 或 is 當(dāng)前綴
- 將 get 這個前綴留給那些借由"輸出參數(shù)"來保存返回值的方法, 比如說,把返回值填充到"C語言數(shù)組"里的那張方法就可以使用這個詞做前綴
類與協(xié)議的命名 - 起名時應(yīng)遵從標準的 objective-C 命名規(guī)范,這樣創(chuàng)建出來的接口更容易為開發(fā)者所理解
- 方法名要言簡意賅,從左至右讀起來要像個日常用于中的句子才好
- 方法名不要使用縮略后的類型名稱
- 給方法起名時的第一要務(wù) 就是確保其風(fēng)格與你自己的代碼或所要集成的框架相符
20 為私有方法名加前綴
- 給私有方法的名稱加上前綴, 這樣可以很容易的將其同公共方法區(qū)分
- 不要單用一個下劃線做私有方法的前綴, 因為這種做法是預(yù)留給蘋果公司用的
21 理解 OBjective -C 錯誤類型
- 只有發(fā)生了可使整個應(yīng)用程序崩潰的嚴重錯誤時, 才應(yīng)使用異常
- 在錯誤不那么嚴重的情況下, 可以指派"委托方法"來處理錯誤,也可以把錯誤信息放在 NSError 對象里, 經(jīng)由"輸出參數(shù)"返回給調(diào)用者
// 比如 有一個抽象基類, 他的正確用法是先從中繼承一個類,然后使用這個子類, 在這種情況下,如果有人直接使用了一個抽象基類,那么久拋出異常
- (void)mustOverrideMethod{
NSString *reason = [NSString stringWithFormat:@"%@m must be overridden",
NSStringFromSelector(_cmd)];
@throw [NSException
exceptionWithName:NSInternalInconsistencyException
reason:reason
userInfo:nil];
}
22 理解 NSCopying 協(xié)議
- 若想另自己所寫的對象具有拷貝功能, 則需實現(xiàn) NSCopying 協(xié)議
- 如果自定義的對象分為可變版本與不可變版本, 那么就要同時實現(xiàn) NSCoping與 NSMutableCopying 協(xié)議
- 賦值對象是需決定采用淺拷貝還是深拷貝,一般情況下應(yīng)該盡量執(zhí)行淺拷貝
- 如果你所寫的對象需要深拷貝,那么可考慮新增一個專門執(zhí)行深拷貝的方法
第四章 協(xié)議與分類
23 通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
這個就是常規(guī)我們使用的代理了 但是書中講了一個新的知識點 我倒是從前從沒有見過的 可以一起來看一下
- 如果有必要,可實現(xiàn)含有位段的結(jié)構(gòu)體, 將委托對象是否能相應(yīng)相關(guān)協(xié)議方法這一信息緩存至其中
這個知識點比較有價值
// 定義一個結(jié)構(gòu)體
@interface LLNetWorkFetcher(){
struct {
unsigned int didReceiveData : 1;
unsigned int didDailWIthError : 1;
unsigned int didUpdateProgressTo : 1;
} _delegateFlags;
// 在外界設(shè)置代理的時候 重寫 delegate 的 set 方法 對此結(jié)構(gòu)體進行賦值
- (void)setDelegate:(id<LLNetworkFetcherDelegate>)delegate{
_delegate = delegate;
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didDailWIthError = [delegate respondsToSelector:@selector(networkFetcher:didDailWIthError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}
// 這樣在調(diào)用的時候只需判斷 結(jié)構(gòu)體里邊的標志就可以了 不需要一直調(diào)用 respondsToSelector 這個方法
if (_delegateFlags.didUpdateProgressTo) {
[_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
}
}
24 將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
- 使用分類機制把類的實現(xiàn)代碼劃分成易于管理的小塊
- 將應(yīng)該視為私有的方法歸入名叫 Private 的分類中, 以隱藏實現(xiàn)細節(jié)
25 總是為第三方類的分類名稱加前綴
分類的方法加入到類中這一操作是在運行期系統(tǒng)加載分類是完成的.運行期系統(tǒng)會把分類中所實現(xiàn)的每個方法都加入到類的方法列表中,如果類中本來就有此方法,而分類又實現(xiàn)了一次, 那么分類中的方法會覆蓋原來那一份實現(xiàn)代碼, 實際上可能會發(fā)生多次覆蓋, 多次覆蓋的結(jié)果一最后一個分類為準
- 向第三方類中添加分類時, 總應(yīng)給其名稱加上你專用的前綴
- 向第三方類中添加分類是,總應(yīng)給其中的方法加上你專用的前綴
26 勿在分類中聲明屬性
這個老生常談了
- 把封裝數(shù)據(jù) 所用的全部屬性都定義在主接口里
- 在"Class-continuation分類"之外的其他分類中,可以定義存取方法,但盡量不要定義屬性
27 使用"class - continuation分類" 隱藏實現(xiàn)細節(jié)
class - continuation分類 通俗點來講其實就是我們平時所說的延展
- 通過"class - continuation分類"向類中新增實例變量
- 如果某屬性在主接口中聲明為"只讀" 而在類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在"class - continuation分類" 將其擴展為"可讀寫"
- 把私有方法的原型聲明在"class - continuation分類里面
- 若想是類所遵循的協(xié)議不為人所知, 則可于"class - continuation分類中聲明
28 通過協(xié)議提供匿名對象
- 協(xié)議可在某種程度上提供匿名類型, 具體的對象類型可以淡化成遵從某協(xié)議的 id 類型,協(xié)議里規(guī)定了對象所應(yīng)實現(xiàn)的方法
- 使用匿名對象來隱藏類型名稱(或類名)
- 如果具體類型不重要,重要的是對象能夠響應(yīng)(定義在協(xié)議里)特定方法,那么可以使用匿名對象來表示
第五章 內(nèi)存管理
29 理解引用計數(shù)器
這一點也不多說了 不過有一個概念確實是之前沒想過的
UIApplication 對象是 跟對象
- 引用計數(shù)機制通過可以遞增遞減的計數(shù)器來管理內(nèi)存, 對象創(chuàng)建好之后, 其保留計數(shù)至少為1 , 若保留計數(shù)為正,則對象繼續(xù)存活, 當(dāng)保留計數(shù)降為0時,對象就被銷毀了
- 在對象生命期中, 其余對象通過引用來保留或釋放此對象, 保留與釋放操作分別會遞增及遞減保留計數(shù)
30 以 ARC 簡化引用計數(shù)
- 有了 ARC 之后, 程序員就無需擔(dān)心內(nèi)存管理問題了, 使用 ARC 來編程,可省去類中許多"樣板代碼"
- ARC 管理對象生命期的辦法基本就是:在合適的地方插入"保留"及釋放操作, 在 ARC 環(huán)境下, 變量的內(nèi)存管理語義可以通過修飾符指明,而原來則需要手工執(zhí)行"保留"及"釋放" 操作
- 由方法所返回的對象,其內(nèi)存管理語義總是通過方法名來體現(xiàn), ARC 將此確定為開發(fā)者必須遵守的規(guī)則
- ARC 只負責(zé)管理 OBjectice-C 對象的內(nèi)存, 尤其注意: CoreFounfation 對象不歸 ARC 管理,開發(fā)者必須適時使用 CFRetain/CFRelease
31 在 dealloc 方法中只釋放引用并解除監(jiān)聽
- 在 dealloc 方法里, 應(yīng)該做的事情就是釋放指向其他對象的引用, 并取消原來訂閱的"鍵值觀測"(KVO) 或 NSNotificationCenter 等通知, 不要做其他事情
- 如果對象持有文件描述符等系統(tǒng)資源, 那么應(yīng)該專門編寫一個方法來釋放此種資源. 這樣的類要和其使用者約定,用完資源后必須調(diào)用 close
- 執(zhí)行異步任務(wù)的方法不應(yīng)該放在 dealloc 里調(diào)用;只能在正常狀態(tài)下,執(zhí)行的那些方法也不應(yīng)在 dealloc 里調(diào)用,因此此時對象已處于正在回收的狀態(tài)了
32 編寫"異常安全代碼"時留意內(nèi)存管理問題
- 在捕獲異常時, 一定要注意將 Try 塊內(nèi)所創(chuàng)立的對象清理干凈
- 在默認情況下, ARC 不生成安全處理代異常所需的清理代碼,開啟編譯器標志后, 可以生成這種代碼,不過會導(dǎo)致應(yīng)用程序變大, 而且會降低運行效率
如下邊代碼
若在 ARC 且必須捕獲異常時, 則需要打開-fobjc-arc-exceptions
標志
NSObject *object;
@try {
object = [NSObject new];
[object doSomeThingThatMayThrow];
}
@catch(...){
}
@finally{
}
33 以弱引用避免保留環(huán)
- 將某些引用設(shè)為 weak 可避免出現(xiàn)"保留環(huán)"
- weak 引用可以自動清空,也可以不自動清空.自動清空(autonilling)是隨著 ARC 而引入的新特性,由運行期系統(tǒng)來實現(xiàn).在具備自動清空功能的弱引用上,可以隨意讀取其數(shù)據(jù),因為這種引用不會指向已經(jīng)回收的對象
34 以"自動釋放池塊"降低內(nèi)存峰值
- 自動釋放池排布在棧中, 對象收到 autorelease 消息后, 系統(tǒng)將其放入最頂端的池里
- 要合理運用自動釋放池, 可降低應(yīng)用程序的內(nèi)存封值
- @autoreleasepool 這種新式寫法能創(chuàng)建出更為輕便的自動釋放池
常見的例子就是 下邊的 加上@autoreleasepool
應(yīng)用程序在執(zhí)行循環(huán)的時候內(nèi)存峰值就會降低
NSArray *dataArr = [NSArray array];
NSMutableArray *personArrM = [NSMutableArray array];
for (NSDictionary *recode in dataArr) {
@autoreleasepool{
LLPerson *person = [[LLPerson alloc]initWithRecode:recode];
[personArrM addObject:person];
}
}
35 用"僵尸對象"調(diào)試內(nèi)存管理問題
- 系統(tǒng)在回收對象時,可以不將其真的回收, 而是把它轉(zhuǎn)化為僵尸對象,通過環(huán)境變量 NSZombieEnabled 可開啟此功能
- 系統(tǒng)會修改對象的 isa 指針,令其指向特殊的僵尸類, 從而使改對象變?yōu)榻┦瑢ο?僵尸類能夠相應(yīng)所有的選擇子, 相應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息,然后終止應(yīng)用程序
36 不要使用retainCount
- 對象的保留計數(shù)看似有用, 實則不然,因為任何給定時間點上的"絕對保留計數(shù)"都無法反應(yīng)對象生命期的全貌
- 引入 ARC 之后, retainCount 方式就正式廢止了,在 ARC 下調(diào)用該方法會導(dǎo)致編譯器報錯
第六章 塊與大中樞派發(fā)
37 塊的內(nèi)部結(jié)構(gòu)
塊本身也是對象,在存放塊對象內(nèi)存區(qū)域中, 首個變量是指向 Class 對象的指針,該指針叫做 isa, 其余內(nèi)存里含有塊對象正常運轉(zhuǎn)所需的各種信息, 在內(nèi)存布局中,最重要的就是 invoke 變量,這就是函數(shù)指針,指向塊的實現(xiàn)代碼, 函數(shù)原型只要要接受一個 void* 型的參數(shù), 此參數(shù)代表塊.剛才說過,塊其實就是一種代替函數(shù)指針的語法結(jié)構(gòu), 原來使用函數(shù)指針是,需要用不透明的 void 指針來傳遞狀態(tài) 而改用塊之后, 則可以把原來用標準 C 語言特性所編寫的代碼封裝成簡明且易用的接口.
descriptor 變量是指向結(jié)構(gòu)體的指針, 每個塊里都包含此結(jié)構(gòu)體,其中聲明了塊對象的總體大小,還聲明了 copy 和 dispose 這兩個輔助函數(shù)所對象的函數(shù)指針, 輔助函數(shù)在拷貝及丟棄塊對象時運行, 其中會執(zhí)行一些操作, 比方說 前者要保留捕獲的對象, 而后者則將之釋放
塊還會把它所捕獲的所有變量都拷貝一份, 這些拷貝放在 descriptor 變量后邊,捕獲了多少變量,就要占據(jù)多少內(nèi)存空間, 請注意, 拷貝的并不是對象變量,而是指向這些對象的指針變量, invoke 函數(shù)為何需要把塊對象作為參數(shù)傳進來呢? 原因就在于,執(zhí)行塊的時候 要從內(nèi)存中把這些捕獲到的變量讀出來
38 為常用的塊類型創(chuàng)建 typedef
- 以 typedef 重新定義塊類型, 可令塊變量用起來更加簡單
- 定義新類型時應(yīng)遵從現(xiàn)有的命名習(xí)慣,無使其名稱與別的類型相沖突
- 不妨為同一個塊簽名定義多個類型別名, 如果要重構(gòu)的代碼 使用了塊類型的某個別名, 那么只需要就該相應(yīng)的 typedef 中的塊簽名即可, 無序改動氣的 typedef
39 用 Handel 塊降低代碼分散程度 其實也就是我們所說的 block 回調(diào)
- 在創(chuàng)建對象時, 可以使用內(nèi)聯(lián)的 handle 塊將相關(guān)業(yè)務(wù)邏輯一并聲明
- 在有多個實例需要監(jiān)控時, 如果采用委托模式, 那么經(jīng)常需要根據(jù)傳入的對象來切換, 而若改用 handle 塊來實現(xiàn), 則可直接將塊與相關(guān)對象放在一起
- 設(shè)計 API 是如果用到了 handle 塊 那么可以增加一個參數(shù), 使調(diào)用者可通過參數(shù)來決定把塊安排在哪個隊列上執(zhí)行
40 用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
- 如果塊所捕獲的對象直接或間接的保留了塊本身, 那么就得當(dāng)心保留環(huán)問題了
- 一定要找個適當(dāng)?shù)臅r機解除保留環(huán), 而不能把責(zé)任推給 API 的調(diào)用者
41 多用派發(fā)隊列,少用同步鎖
這一點就詳細說說吧
在 OC 中多線程要執(zhí)行同一份代碼,那么有時可能會出問題, 這種情況下,通常要使用鎖來實現(xiàn)某種同步機制.
在 GCD 出現(xiàn)之前, 有兩種方法:
- 1 第一種采用內(nèi)置的"同步塊"
- (void)synchronizedMethod{
@synchronized(self){
// safe
}
}
- 2 直接使用 NSLock 對象
_lock = [[NSLock alloc]init];
- (void)synchronizedMethod{
[_lock lock];
// safe
[_lock unlock];
}
這兩種方法都很好不過也都有缺陷 比如說,在極端情況下,同步塊會導(dǎo)致死鎖, 另外 其效率也不見得高, 而如果直接使用鎖對象的話,一旦遇到死鎖, 就會非常麻煩
GCD 的到來它能以更簡單更高效的形式為代碼加鎖
我們都知道屬性就是開發(fā)者經(jīng)常需要同步的地方,這種屬性需要做成"原子的", 用 atomic 即可實現(xiàn)這一點, 但如果我們自己實現(xiàn)的話就可以用 GCD 來實現(xiàn)
- 優(yōu)化1
使用"串行同步隊列, 將讀取操作及寫入操作都安排在同一個隊列里,即可保證數(shù)據(jù)同步" 如一下代碼
_syncQueue = dispatch_queue_create("aokamu.syncQueue", NULL);
- (NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_sync(_syncQueue, ^{
_someString = someString;
})
}
上述代碼: 把設(shè)置操作與獲取操作都安排在序列化的隊列里執(zhí)行了, 這樣的話, 所有針對屬性的訪問操作就都同步了, 全部加鎖任務(wù)都在 GCD 中處理, 而 GCD 是相當(dāng)深的底層來實現(xiàn)的,于是能夠做許多優(yōu)化
- 優(yōu)化2 設(shè)置方法不一定非得是同步的
- (void)setSomeString:(NSString *)someString{
dispatch_async(_syncQueue, ^{
_someString = someString;
})
}
這個吧同步派發(fā)改成異步派發(fā),可以提升設(shè)置方法的執(zhí)行速度, 而讀取操作與寫入操作依然會按順序執(zhí)行, 不過這樣寫昂寫還是有一個弊端. :如果你測一下程序性能,那么可能會發(fā)現(xiàn)這種寫法比原來慢, 因為執(zhí)行異步派發(fā)時需要拷貝塊.
- 優(yōu)化3 終極優(yōu)化 不用串行隊列, 而改用并發(fā)隊列 并且使用 柵欄(barrier)
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
})
}
在隊列中 柵欄塊必須單獨執(zhí)行, 不能與其他塊并行, 這只對并發(fā)隊列有意義, 因為串行隊列中的塊總是按順序逐個來執(zhí)行的, 并發(fā)隊列如果發(fā)現(xiàn)接下來要處理的塊是個柵欄塊,那么久一直要等當(dāng)前所有并發(fā)塊都執(zhí)行完畢,才會單獨執(zhí)行這個柵欄塊 待柵欄塊執(zhí)行過后 再按正常方式向下處理 如下圖
- 派發(fā)隊列可用來表述同步語義,這種做法要比使用
@synchronized
塊或者NSLock
對象更簡單 - 將同步與異步派發(fā)結(jié)合起來,可以實現(xiàn)與普通加鎖機制一樣的同步下行為,而這么做卻不會阻塞執(zhí)行異步派發(fā)的線程
- 使用同步隊列及柵欄塊.可以令同步行為更加高效
42 多用 GCD 少用 performSelector 系列方法
這個現(xiàn)在已經(jīng)沒有人去用performSelector 系列方法了
- performSelector 系列方法在內(nèi)存管理方面容易有疏失,他無法確定將要執(zhí)行的選擇子具體是什么, 因而 ARC 編譯器也就無法插入適當(dāng)?shù)膬?nèi)存管理方法
- performSelector 系列方法所能處理的選擇子太過于局限了,選擇子的返回值類型及發(fā)送給方法的參數(shù)個數(shù)都受到限制
- 如果想把任務(wù)放在另一個線程上執(zhí)行,那么最好不要用performSelector系列方法,而是應(yīng)該把任務(wù)封裝到塊里, 然后調(diào)用大中樞派發(fā)機制的相關(guān)方法來實現(xiàn)
43 掌握 GCD 及操作隊列的使用時機
在來簡單總結(jié)一下操作隊列(NSOPeration)的幾種使用方法
① 取消某個操作
運行任務(wù)前可以調(diào)用 cancel 方法 ,該方法會設(shè)置對象內(nèi)的標志位,用以表明此任務(wù)不需要執(zhí)行, 不過已經(jīng)啟動的任務(wù)無法取消了,
②指定操作間的依賴關(guān)系
一個操作可以依賴其他多個操作
③ 通過鍵值觀測機制監(jiān)控 NSOperation 對象的屬性.
NSOPeration 對象有許多屬性都適合通過鍵值觀測機制來監(jiān)聽
④指定操作的優(yōu)先級
NSOperation 對象也有"線程優(yōu)先級",這決定了運行此操作的線程處在何種優(yōu)先級上
- 在解決多線程與任務(wù)管理問題時,派發(fā)隊列并非唯一方案
- 操作隊列提供了一套高層的 Objective-CAPI, 能實現(xiàn)純 GCD 所具備的絕大部分功能,而且還能完成一些更為復(fù)雜的操作, 那些操作弱改用 GCD 來實現(xiàn), 則需另外編寫代碼
44 通過 Dispatch Group 機制, 根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)
這個也簡單記錄一下把
Dispatch Group 俗稱 GCD 任務(wù)組,我們 用偽代碼來看一下 Dispatch Group的用法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
for (id object in collectin) {
dispatch_group_async(dispatchGroup,
queue,
^{
[object performTask];
})
}
dispatch_group_notify(dispatchGroup,
dispatch_get_main_queue(),
^{
[self updateUI];
})
notify回調(diào)的隊列完全可以自己來定 可以用自定義的串行隊列或全局并發(fā)隊列
這里還有 GCD 的另一個函數(shù)平時比較少用的 那就是dispatch_apply
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(array.count,
queue,
^(size_t i) {
id object = array[i];
[object performTask];
})
dispatch_apply所使用的隊列可以使并發(fā)隊列, 也可以是串行隊列, 加入把塊派給了當(dāng)前隊列(或體系中高于當(dāng)前隊列的某個串行隊列),這將會導(dǎo)致死鎖,
- 一系列任務(wù)可以歸入一個 dispatch group 之中,開發(fā)者可以在這組任務(wù)執(zhí)行完畢是獲得通知
- 通過 dispatch Group, 可以在并發(fā)式派發(fā)隊列里同時執(zhí)行多項任務(wù), 此時 GCD 會根據(jù)系統(tǒng)資源狀況來調(diào)度這些并發(fā)執(zhí)行的任務(wù), 開發(fā)者若自己來實現(xiàn)此功能,則需編寫大量代碼
45 使用 dispatch_once 來執(zhí)行只需要運行一次的線程安全代碼
這個就是老生常談的單例了 也就不多說了
46 不要使用 dispatch_get_current_queue
這個函數(shù)已經(jīng)廢棄了 此處就不多說了
第七章 系統(tǒng)框架
47 熟悉系統(tǒng)框架
我們開發(fā)者經(jīng)常碰到的就是 Foundation 框架 像NSobject,NSArray,NSDictionary 等類 都在其中,
還有一個與Foundation相伴的框架是 CoreFoundation,CoreFoundation 不是 OC 框架,但是確定編寫 OC 應(yīng)用程序時所應(yīng)熟悉的重要框架,Foundation框架中的許多功能都可以在此框架中找到對應(yīng)的 C 語言 API
除了 Foundation和CoreFoundation還有以下系統(tǒng)庫:
CFNetwork 此框架提供了 C 語言級別的網(wǎng)絡(luò)通信, 它將"BSD 套接字"抽象成易于使用的網(wǎng)絡(luò)接口
CoreAudio 該框架所提供的 C語言 API 可用來操作設(shè)備上的音頻硬件, 這個框架屬于比較難用的那種, 因為音頻處理本身就很復(fù)雜,所幸由這套 API 可以抽象出另外一個 OC 的 API, 用后者來處理音頻問題會簡單些
AVFoundation 此框架所提供的 OC 對象可用來回放并錄制音頻及視頻,比如 能夠在 UI 視圖類播放視頻
CoreData 此框架中所提供的 OC 接口可將對象放入到數(shù)據(jù)庫中,便于持久保存
CoreText 此框架提供的 C語言接口可以高效執(zhí)行文字排版及渲染操作
請記住 用純 C 語言寫的框架與用 OC 寫成的一樣重要, 若想成為優(yōu)秀的 OC 開發(fā)者, 應(yīng)該掌握 C 語言的核心概念
48 多用塊枚舉 少用 for 循環(huán)
- 塊枚舉法 本身就能通過 GCD 來并發(fā)執(zhí)行遍歷操作,無須另行編寫代碼,而采用其他遍歷方式則無法輕易實現(xiàn)這一點
- 若提前知道待遍歷的 collection 含有何種對象,則應(yīng)修改塊簽名, 指出對象的具體內(nèi)容
NSArray<LLPerson *> *dataArr = [NSArray array];
[dataArr enumerateObjectsUsingBlock:^(LLPerson * _Nonnull obj,
NSUInteger idx,
BOOL * _Nonnull stop) {
}];
49 對自定義其內(nèi)存管理語義的 collection 使用無縫橋接
- 通過無縫橋接技術(shù), 可以在 Foundation 框架中的 OC 對象與 CoreFoundation 框架中的 C語言數(shù)據(jù)結(jié)構(gòu)之間來回轉(zhuǎn)換
- 在CoreFoundation 層面創(chuàng)建collection 時,可以執(zhí)行許多回調(diào)函數(shù), 這些函數(shù)表示此 collection 應(yīng)如何處理其元素, 然后可運用無縫橋接技術(shù), 將其轉(zhuǎn)換成具備特殊內(nèi)存管理語義的 OC collection
NSArray *anNSArray = @[@1,@2,@3,@4,@5];
CFArrayRef aCFArray = (__bridge CFArrayRef)(anNSArray);
NSLog(@"count = %li",CFArrayGetCount(aCFArray));
// Output: count = 5 ;
50 構(gòu)建緩存時選用 NSCache 而非 NSDIctionary
- 實現(xiàn)緩存時選用 NSCache 而非 NSDictionary 對象,因為 NSCache 可以提供優(yōu)雅的自動刪減功能,而且是線程安全的, 此外 他與字典不同,并不會拷貝鍵
- 可以給 NSCache 對象設(shè)置上限, 用以限制緩存中的對象總個數(shù)及"總成本".而這些初度則定義了緩存刪減其中對象的時機, 但是絕對不要把這些尺度當(dāng)成可靠地"硬限制"他們僅僅對 NSCache 起指導(dǎo)作用
- 將 NSPurgeableData 與 NSCache 搭配使用.可實現(xiàn)自動清除數(shù)據(jù)的功能,也就是說,當(dāng)NSPurgeableData對象所占內(nèi)存為系統(tǒng)所丟棄時,該對象自身也會從緩存中移除
- 如果緩存使用得當(dāng), 那么應(yīng)用程序的響應(yīng)速度就能提高,只有那種"重新計算起來很費事的"數(shù)據(jù)才值得放入緩存,比如那些需要從網(wǎng)絡(luò)獲取或者從磁盤讀取的數(shù)據(jù)
來看下邊偽代碼
typedef void(^LLNetWorkFetcherCompleteHandler)(NSData *data);
@interface LLNetWorkFetcher : NSObject
- (instancetype)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(LLNetWorkFetcherCompleteHandler)handler;
@end
#import "LLClass.h"
#import "LLNetWorkFetcher.h"
@implementation LLClass{
NSCache *_cache;
}
- (instancetype)init{
if (self = [super init]) {
_cache = [NSCache new];
_cache.countLimit = 100;
_cache.totalCostLimit = 5 * 1024 * 1024;
}
return self;
}
- (void)downLoadDataForURL:(NSURL *)url{
NSData *cacheData = [_cache objectForKey:url];
if (cacheData) {
[self useData:cacheData];
}else{
LLNetWorkFetcher *fetcher = [[LLNetWorkFetcher alloc]initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
[_cache setObject:data forKey:url cost:data.length];
[self useData:cacheData];
}];
}
}
51 精簡 initialize 與 load 的實現(xiàn)代碼
+ (void) load
對于加入運行期系統(tǒng)的每個類及分類來說,必定會調(diào)用此方法而且僅調(diào)用一次,當(dāng)包含類或者分類的程序庫載入系統(tǒng)時, 就會執(zhí)行此方法
如果分類和其所屬的類都定義了 load 方法, 則先調(diào)用類里邊的 在調(diào)用分類里邊的
load 方法的問題在于執(zhí)行該方法時,運行期系統(tǒng)是"脆弱狀態(tài)",在執(zhí)行子類的 load 方法之前,必定會先執(zhí)行所有超類的 load 方法, 如果代碼還依賴了其他程序庫,那么程序庫里相關(guān)類的 load 方法也必定會先執(zhí)行, 根據(jù)某個給定的程序庫,卻無法判斷出其中各個類的載入順序, 因此 在 load 方法中使用其他類是不安全的.
load 方法不像其他普通方法一樣, 他不遵從那套繼承規(guī)則, 如果某個類本身沒有實現(xiàn) load 方法,那么不管其各級超類是否實現(xiàn)此方法, 系統(tǒng)都不會調(diào)用.+ (void)initialize
對于每個類來說 該方法會在程序首次使用該類之前調(diào)用, 且只調(diào)用一次,他是有運行期系統(tǒng)來調(diào)用的,絕不應(yīng)該通過代碼直接調(diào)用 他與 load 方法有一定的區(qū)別的
首先 他是惰性調(diào)用的, 也就是說只有當(dāng)程序用到了相關(guān)的類是,才會調(diào)用 如果某個類一直都沒有使用, 那么其 initialize 方法就一直不會運行
其次, 運行期系統(tǒng)在執(zhí)行該方法時,是處于正常狀態(tài)的, 因此 從運行期系統(tǒng)完整度來講, 此時可以安全使用并調(diào)用任意類中的任意方法 而且運行期系統(tǒng)也能確保initialize 方法一定會在"線程安全的環(huán)境"中執(zhí)行,也就是說 只有執(zhí)行initialize的那個線程 可以操作類與類實例,
最后, initialize 方法與其他消息一樣,如果某個類未實現(xiàn)它, 而其超類實現(xiàn)了,俺那么就會運行超類的實現(xiàn)代碼
- 在加載階段 如果類實現(xiàn)了 load 方法,那么系統(tǒng)就會調(diào)用它.分類里也可以定義此方法,類的 load 方法要比分類中先調(diào)用,其他方法不同, load 方法不參與復(fù)寫機制
- 首次使用某個類之前,系統(tǒng)會向其發(fā)送initialize 消息,由于此方法遵從普通的復(fù)寫規(guī)則,所以通常應(yīng)該在里邊判斷當(dāng)前要初始化的是哪一個類
- load 和initialize 方法都應(yīng)該實現(xiàn)的精簡一些, 這有助于保持應(yīng)用程序的相應(yīng)能力 也能減少引入"依賴環(huán)"的幾率
- 無法在編譯期設(shè)定的全局變量,可以放在initialize 方法里初始化
52 別忘了 NSTimer 會保留其目標對象
計時器是一種很方便也很有用的對象,但是 由于計時器會保留其目標對象, 所以反復(fù)執(zhí)行任務(wù)通常會導(dǎo)致應(yīng)用程序出問題,也就是很容易引入"保留環(huán)"
來看下列代碼
@interface LLClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation LLClass{
NSTimer *_pollTimer;
}
- (void)startPolling{
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(p_doPoll)
userInfo:nil
repeats:YES];
}
- (void)stopPolling{
[_pollTimer invalidate];
_pollTimer = nil;
}
- (void)p_doPoll{
}
- (void)dealloc{
[_pollTimer invalidate];
}
計時器的目標對象是 self, 然后計時器使用實例變量來存放的, 所以實例變量也保存李計時器, 于是就產(chǎn)生了保留環(huán)
本書中提供了一個用"塊"來解決的方案 雖然計時器當(dāng)前并不直接支持塊,但是可以用下面這段代碼添加功能
@implementation NSTimer (LLBlocksSupport)
+ (NSTimer *)ll_schedeledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(ll_blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)ll_blockInvoke:(NSTimer *)timer{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
上邊的代碼是在 NSTimer 分類中添加的代碼 來看一下具體的使用
- (void)startPolling{
__weak LLClass *weakSelf = self;
_pollTimer = [NSTimer ll_schedeledTimerWithTimeInterval:5.0
block:^{
LLClass *strongSelf = weakSelf;
[strongSelf p_doPoll];
}
repeats:YES];
先定義弱引用,然后用block捕獲這個引用,但是在用之前在立刻生成 strong 引用.保證實例在執(zhí)行期間持續(xù)存活
- NSTimer 對象會保留其目標, 直到計時器本身失效為止,調(diào)用 invalidate 方法可令計時器失效, 另外 一次性的計時器, 在觸發(fā)任務(wù)之后,也會失效,
- 反復(fù)執(zhí)行任務(wù)的計時器,很容易引入保留環(huán), 如果這種計時器的目標對象有保留了計時器本事,那么肯定會導(dǎo)致保留環(huán),這種環(huán)保留,可能直接發(fā)生,也可能是通過對象圖里的其他對象間接發(fā)生
- 可以擴充 NSTimer 的功能,用"塊"來打破保留環(huán),不過 除非 NSTimer 將來在公共接口里提供此功能, 否則必須創(chuàng)建分類,將相關(guān)實現(xiàn)代碼加入其中