2012-08-15 66 views
9

我已經看到了堆棧溢出的很多帖子,指出控制器的viewDidLoad方法僅在第一次訪問控制器時調用,並且不一定每次都是,但始終在至少一次。viewDidLoad實際上每次都有一個segue轉換被調用

這不是我所看到的!我把一個簡單的測試放在一起,以突出顯示: https://github.com/imuz/ViewDidLoadTest

它似乎是導航控制器塞格和模態視圖總是調用viewDidLoad。唯一不被調用的時間是在製表符之間切換。

viewDidLoad中的每一個解釋,我可以找到相矛盾的:

和蘋果自己的文檔表明,一個視圖只卸載時內存不足。

我目前正在viewDidLoad中進行初始化,並假定它被稱爲每個segue過渡。

我在這裏錯過了什麼嗎?

回答

11

我相信蘋果文檔正在描述視圖控制器未被釋放的情況。如果使用segue,那麼您正在引發新目標控制器的實例化,並且作爲新對象,它需要加載視圖。

在基於xib的應用程序中,我有時會緩存控制器對象,我知道我可能會經常重複使用。在這些情況下,他們的行爲與視圖必須加載時的文檔保持一致。

編輯: 在閱讀您包含的鏈接後,我沒有看到它們之間的矛盾。他們也在談論在視圖控制器對象的生命週期中發生的事情。

+0

它在那裏他們談談看法位被卸載的「內存不足」言下之意,他們在默認情況下它是這種情況我還沒有看到四周留有。所以真的取決於父控制器的實現。如果您使用導航控制器,則每次都會創建並加載新的實例。與tabcontrollers不同。 – Imran 2012-08-15 12:58:42

+1

測試低內存行爲(在模擬器上)的一種方法是放置一個視圖控制器,用模態視圖控制器覆蓋它,並使用硬件 - >模擬內存警告選項。隱藏的控制器的視圖應該卸載,然後在模態被取消時重新加載。 – 2012-08-15 14:05:25

+0

oic有趣,生病嘗試。我想任何目前不活躍的視圖都是卸載的候選人。 – Imran 2012-08-15 14:20:36

0

每次從頭開始加載控制器的視圖(即請求但尚未提供)時調用它。如果您取消分配控制器並且視圖與其一致,那麼在您下次實例化控制器時(例如,當您創建控制器以通過模式或通過輪播推送控制器時),它將再次被調用。選項卡中的視圖控制器不會因爲選項卡控制器將它們放在附近而被取消分配。

12

Phillip Mills的回答是正確的。這只是它的一個增強。

該系統按記錄工作。

您所看到的viewDidLoad因爲視圖控制器推送到導航控制器是實例。它必須調用viewDidLoad。

如果你進一步研究,你會發現每個視圖控制器在被彈出時都會被釋放(只需在dealloc中放置一個斷點或NSLog)。這種釋放與視圖控制器容器無關......它不控制它使用的控制器的壽命......它只是對它有強烈的參考。

當控制器從導航控制器堆棧彈出時,導航控制器釋放其引用,並且由於沒有其他引用,視圖控制器將釋放。

導航控制器只保存處於活動堆棧中的視圖控制器的強引用。

如果你想重複使用相同的控制器,負責重新使用它。當你使用故事板時,你放棄了這個控制(很大程度上)。

比方說,您有一個push segue來查看控制器Foo作爲點擊某個按鈕的結果。當點擊該按鈕時,「系統」將創建Foo(目標視圖控制器)的實例,然後執行搜尋。控制器容器現在擁有對該視圖控制器的唯一強大參考。一旦完成它,VC將會釋放。

由於每次都會創建一個新的控制器,因此每次顯示控制器時都會調用viewDidLoad

現在,如果要更改此行爲並緩存視圖控制器供以後重複使用,則必須具體執行此操作。如果你不使用storyboard segues,那很容易,因爲你實際上是在推送/彈出VC到導航控制器。

但是,如果您使用storyboard segues,則會有點麻煩。

有很多方法可以做到,但都需要某種形式的黑客行爲。故事板本身負責實例化新的視圖控制器。一種方法是覆蓋instantiateViewControllerWithIdentifier。這是當segue需要創建視圖控制器時調用的方法。即使你沒有給出標識符的控制器也會被調用(如果你沒有指定一個標識符,系統會提供一個構成的唯一標識符)。

請注意,我希望這主要是用於教育目的。當然,我並不認爲這是解決問題的最好方法,不管它們是什麼。

喜歡的東西...

@interface MyStoryboard : UIStoryboard 
@property BOOL shouldUseCache; 
- (void)evict:(NSString*)identifier; 
- (void)purge; 
@end 
@implementation MyStoryboard 
- (NSMutableDictionary*)cache { 
    static char const kCacheKey[1]; 
    NSMutableDictionary *cache = objc_getAssociatedObject(self, kCacheKey); 
    if (nil == cache) { 
     cache = [NSMutableDictionary dictionary]; 
     objc_setAssociatedObject(self, kCacheKey, cache, OBJC_ASSOCIATION_RETAIN); 
    } 
    return cache; 
} 
- (void)evict:(NSString *)identifier { 
    [[self cache] removeObjectForKey:identifier]; 
} 
- (void)purge { 
    [[self cache] removeAllObjects]; 
} 
- (id)instantiateViewControllerWithIdentifier:(NSString *)identifier { 
    if (!self.shouldUseCache) { 
     return [super instantiateViewControllerWithIdentifier:identifier]; 
    } 
    NSMutableDictionary *cache = [self cache]; 
    id result = [cache objectForKey:identifier]; 
    if (result) return result; 
    result = [super instantiateViewControllerWithIdentifier:identifier]; 
    [cache setObject:result forKey:identifier]; 
    return result; 
} 
@end 

現在,你必須使用這個故事板。不幸的是,儘管UIApplication在主要故事板上保留了,但它並沒有公開API來獲取它。但是,每個視圖控制器都有一個方法storyboard來獲取它創建的故事板。

如果您正在加載自己的故事板,那麼只需實例化MyStoryboard即可。如果您正在使用默認故事板,那麼您需要強制系統使用您的特殊故事板。再次,有很多方法可以做到這一點。一種簡單的方法是覆蓋視圖控制器中的故事板訪問器方法。

您可以讓MyStoryboard成爲將所有內容轉發到Uistoryboard的代理類,或者您可以將主要故事板轉換爲isa-swizzle,或者您可以讓本地控制器從其故事板方法返回一個。

現在,請記住,這裏有一個問題。如果你不止一次地推動堆棧上的同一個視圖控制器會怎麼樣?通過緩存,完全相同的視圖控制器對象將被多次使用。那真的是你想要的嗎?

如果沒有,那麼你現在需要管理與控制容器本身的互動,使他們能夠檢查是否該控制器已經被他們知道,在這種情況下,一個新的實例是必要的。

因此,有一種方式來獲得緩存控制器,而使用默認的故事板塞格斯(實際上有相當多的方式)...但是,這並不一定是好事,當然不是你在默認情況下會得到什麼。

+0

感謝您的詳細解答。沒問題,沒有緩存控制器。我只是想了解加載卸載循環多一點,所以我可以明白我能做什麼,不能放在viewDidLoad中。現在更清楚了。 – Imran 2012-08-15 15:43:09

+1

其實,viewWillUnload和viewDidUnload已經被棄用了,所以你不應該把任何代碼放在那裏。在內存壓力下處理didReceiveMemoryWarning釋放資源。 – 2012-08-15 15:53:44

+0

啊,在IOS6的權利。聽起來好像沒有什麼真正意義的viewDidLoad了,它可能是一個構造函數。我認爲這意味着內存警告將不再導致viewDidUnload被調用?這種改變的事情,因爲它意味着viewDidLoad只曾被稱爲一次?我目前沒有IOS6進行測試。 – Imran 2012-08-15 16:24:17

相關問題