本文介紹了一種名為依賴注入(Dependency Injection)的設(shè)計模式,并使用這種模式釋放不必一直持有的對象,用來達到釋放內(nèi)存的效果。
什么是依賴注入
舉個栗子:
“When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn’t want you to have. You might even be looking for something we don’t even have or which has expired.
What you should be doing is stating a need, “I need something to drink with lunch,” and then we will make sure you have something when you sit down to eat.”
以上答案來自Stack Overflow,大概意思是,對于一個5歲大的小朋友來說,從冰箱里取東西出來是一件危險的事情,你可能忘記關(guān)冰箱門,或者取出爸爸的伏特加,也可能尋找根本不存在的食物或者用舌頭舔冷凍室的鐵欄桿……
因此,這件事的解決辦法就是,對你的父母說:“我想在吃午飯時喝點什么”,然后父母就會拿出一些喝的給你。
換成程序員能聽懂的話就是:高層類(5歲小孩)應(yīng)該依賴底層基礎(chǔ)設(shè)施(家長)來提供必要的服務(wù)。
依賴注入是一個將行為從依賴中分離的技術(shù),簡單地說,它允許開發(fā)者定義一個方法函數(shù)依賴于外部其他各種交互,而不需要編碼如何獲得這些外部交互的實例。 這樣就在各種組件之間解耦,從而獲得干凈的代碼,相比依賴的硬編碼, 一個組件只有在運行時才調(diào)用其所需要的其他組件,因此在代碼運行時,通過特定的框架或容器,將其所需要的其他依賴組件進行注入,主動推入。
為什么需要依賴注入
依賴注入框架的運用可以幫我們將APP的設(shè)計分割成好幾個模塊,分給不同的開人員,當(dāng)完成開發(fā)之后再進行合并充分解決了團隊之間模塊化分工的不足。
借用objccn.io的話說:
我最初決定鉆研DI是因為在執(zhí)行測試驅(qū)動開發(fā) (TDD),而在 TDD 的過程中有一個很糾結(jié)的問題會時常跳出來:“對于這個實現(xiàn),如何編寫單元測試?”。后來我發(fā)現(xiàn)其實 DI 本身是在彰顯一個更高層面的概念:代碼組成了模塊,模塊拼接構(gòu)建成了應(yīng)用本身。
如何實現(xiàn)依賴注入
以下例子來自objeccn.io。
構(gòu)造器注入
構(gòu)造器注入,即將某個依賴對象傳入到構(gòu)造器中 (在 Objective-C中指 designated 初始化方法) 并存儲起來,以便在后續(xù)過程中使用:
- (NSNumber *)nextReminderId
{
NSNumber *currentReminderId = [self.userDefaults objectForKey:@"currentReminderId"];
if (currentReminderId) {
currentReminderId = @([currentReminderId intValue] + 1);
} else {
currentReminderId = @0;
}
[self.userDefaults setObject:currentReminderId forKey:@"currentReminderId"];
return currentReminderId;
}
屬性注入
對于屬性注入,nextReminderId 的代碼看起來和 self.userDefaults 的做法是一致的。只是這次不是將依賴對象傳遞給初始化方法,而是采用屬性賦值方式:
@interface Example
@property (nonatomic, strong) NSUserDefaults *userDefaults;
- (NSNumber *)nextReminderId;
@end
現(xiàn)在可以在單元測試中創(chuàng)建一個對象,然后將需要的東西通過對 userDefaults 屬性進行賦值。但是要是這個屬性沒有被預(yù)先設(shè)定的話要怎么辦呢?這時,我們可以使用 lazy 加載的方法為其設(shè)置一個適當(dāng)?shù)哪J值,這能保證始終可以通過 getter 拿到一個確切的值:
- (NSUserDefaults *)userDefaults
{
if (!_userDefaults) {
_userDefaults = [NSUserDefaults standardUserDefaults];
}
return _userDefaults;
}
這樣的話,對 userDefaults 來說,如果在使用者取值之前做過賦值操作,那么從 self.userDefaults 得到的就是通過 setter 賦的值。如果這個屬性在使用前未被賦值,從 self.userDefaults 得到的就是[NSUserDefaults standardUserDefaults]。
方法注入
如果依賴對象只在某一個方法中被使用,則可以利用方法參數(shù)做注入:
- (NSNumber *)nextReminderIdWithUserDefaults:(NSUserDefaults *)userDefaults
{
NSNumber *currentReminderId = [userDefaults objectForKey:@"currentReminderId"];
if (currentReminderId) {
currentReminderId = @([currentReminderId intValue] + 1);
} else {
currentReminderId = @0;
}
[userDefaults setObject:currentReminderId forKey:@"currentReminderId"];
return currentReminderId;
}
再一次說明,這樣看起來可能會很奇怪,并不是所有的例子中 NSUserDefaults作為依賴都顯得恰如其分。比如說這個例子中,如果使用 NSDate 做注入?yún)?shù)傳入可能更會彰顯其特點。
如何實現(xiàn)內(nèi)存釋放
上面提到的幾種實現(xiàn)依賴注入的方法中,可以用來釋放不必一直持有的對象的方法是屬性注入。如果在一個對象的生命周期中,有什么屬性是可以隨時從文件系統(tǒng)中(或是其他方法)恢復(fù)的,那就讓我們注入它吧!
舉個例子,設(shè)備橫屏?xí)r顯示紅色按鈕redButton,設(shè)備豎屏?xí)r顯示綠色按鈕greenButton,同時兩個button的行為完全一致,對外暴露的接口可以統(tǒng)一為一個button:
// 這是對外接口,屬性注入
- (UIButton *)button
{
if(UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
return self.redButton;
}
else {
return self.greenButton;
}
}
// 繼續(xù)注入兩個button
- (UIButton *)redButton
{
if(!_redButton) {
_redButton = [UIButton new];
// do sth.
}
return _redButton;
}
- (UIButton *)greenButton
{
if(!_greenButton) {
_greenButton = [UIButton new];
// do sth.
}
return _greenButton;
}
設(shè)備處于某個方向時,同時只有一個button被顯示,因此可以釋放掉不被顯示的一個以節(jié)約內(nèi)存
- (UIButton *)removeHiddenButton
{
if(UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
[_greenButton removeFromSuperView];
_greenButton = nil;
}
else {
[_redButton removeFromSuperView];
_redButton = nil;
}
}
- (void)layoutSubviews
{
[self button];
[self removeHiddenButton];
}
屬性注入保證了調(diào)用者(高級類)不必關(guān)心被調(diào)用對象的初始化情況,所以即使被調(diào)用對象已經(jīng)釋放,通過調(diào)用注入接口,仍然可以在需要時生成。利用這一特性,我們可以在收到內(nèi)存警告時釋放掉暫時不用的對象,同時也必須注意,被釋放的對象必須是可恢復(fù)的。