iOS ViewController生命週期

ViewController

ViewController是IOS開發中MVC模式中的C,ViewController是view的controller,ViewController的職責主要包括管理內部各個view的加載顯示和卸載,同時負責與其他ViewController的通信和協調。

在IOS中,有兩類ViewController,一類是顯示內容的,比如UIViewController、UITableViewController等,同時還可以自定義繼承自UIViewController的ViewController;另一類是ViewController容器,UINavigationViewController和UITabBarController等,UINavigationController是以Stack的形式來存儲和管理ViewController,UITabBarController是以Array的形式來管理ViewController。


View的加載

從圖中可以看到,在view加載過程中首先會調用loadView方法,在這個方法中主要完成一些關鍵view的初始化工作,比如UINavigationViewController和UITabBarController等容器類的ViewController;接下來就是加載view,加載成功後,會接着調用viewDidLoad方法,這裏要記住的一點是,在loadView之前,是沒有view的,也就是說,在這之前,view還沒有被初始化。完成viewDidLoad方法後,ViewController裏面就成功的加載view了,如上圖右下角所示。

在Controller中創建view有兩種方式,一種是通過代碼創建、一種是通過Storyboard或Interface Builder來創建,後者可以比較直觀的配置view的外觀和屬性,Storyboard配合IOS6後推出的AutoLayout,應該是Apple之後主推的一種UI定製解決方案,後期我會專門介紹一篇使用AutoLayout進行UI製作的文章。言歸正傳,通過IB或Storyboard創建view,在Controller中創建view後,會在Controller中對view進行一些操作,會出現如下代碼

  1. @interface MyViewController()  
  2. @property (nonatomic) IBOutlet id myButton;  
  3. @property (nonatomic) IBOutlet id myTextField;  
  4.    
  5. - (IBAction)myAction:(id)sender;  
  6. @end  

這裏用IBOutlet標記了一個UIButton和一個UITextField,用IBAction來標記UIButton的響應事件,IBOutlet和IBAction都是一個整形常量,用來標記控件,通過一張圖能比較清晰的看清他們之間的關係:



上圖中,MyViewController是繼承自UIViewController的一個自定義ViewController,它包含兩個View,一個是UIButton,一個是UITextField,從箭頭的指向性上就可以比較好的理解IBOutlet和IBAction了。IBOutlet是告訴Interface Builder,此實例變量被連接到nib文件中的view對象,IBOutlet本身不做任何操作,只是一個標記作用。IBAction同樣是個標記關鍵字,它只能標記方法,它告訴IB用IBAction標記的方法可以被某個控件觸發。

通過編程的方式創建view,如下代碼:

  1. - (void)loadView  
  2. {  
  3.     CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];  
  4.     UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];  
  5.     contentView.backgroundColor = [UIColor blackColor];  
  6.     self.view = contentView;  
  7.    
  8.     levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];  
  9.     [self.view addSubview:levelView];  
  10. }  
上述代碼首先得到屏幕的frame,然後根據該frame生成了一個contentView,並指定當前ViewController的root view爲contentView,然後生成了一個LevelView的自定義View並將它通過addSubview:方法添加到當前ViewController當中,完成view的初始化加載。

 

關於loadView方法的重寫,官方文檔中有一個明顯的註釋,原文如下:

Note: When overriding the loadView method to create your views programmatically, you should not call super. Doing so initiates the default view-loading behavior and usually just wastes CPU cycles. Your own implementation of the loadView method should do all the work that is needed to create a root view and subviews for your view controller.

意思是當通過代碼方式去創建你自己的view時,在loadView方法中不應該調用super,如果調用[super loadView]會影響CPU性能。

代碼創建界面文件

1.創建新的Empty Application Project

2.新建ViewController的類,添加loadView方法,及viewDidLoad等方法

複製代碼
#import "XYZViewController.h"

@interface XYZViewController ()

@end

@implementation XYZViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)loadView
{
    UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
    
    contentView.backgroundColor = [UIColor blueColor];
    
    self.view = contentView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"View Did Load");
}

- (void)viewWillAppear:(BOOL)animated
{
    NSLog(@"View Will Appear");
}

- (void)viewDidAppear:(BOOL)animated
{
    NSLog(@"View Did Appear");
}

- (void)viewWillDisappear:(BOOL)animated
{
    NSLog(@"View Will Disappear");
}

- (void)viewDidDisappear:(BOOL)animated
{
    NSLog(@"View Did Disappear");
}
@end
複製代碼

3.在AppDelegate.m中的application:didFinishLaunchingWithOptions:中註冊ViewController

複製代碼
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    XYZViewController *viewController = [[XYZViewController alloc]initWithNibName:nil bundle:nil];
    self.window.rootViewController = viewController;
    [self.window makeKeyAndVisible];
    return YES;
}
複製代碼

雖然我們可以在AppDelegate.m中的application:didFinishLaunchingWithOptions:中設置window的代碼後面添加view,但是在一般的工程中,我們不會在委託類中管理我們的View。 而是利用委託類中的UIWindow去添加UIViewController,再在ViewController類中去管理View。

附,創建帶NavigationController的代碼

複製代碼
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    /*設置Navigation controller*/
    XYZFirstViewController *viewController = [[XYZFirstViewController alloc] initWithNibName:nil bundle:nil];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:viewController];
    [nav setNavigationBarHidden:YES animated:NO];
    self.window.rootViewController = nav;
    [self.window makeKeyAndVisible];
    [viewController release];
    [nav release];
    return YES;
}
複製代碼

 

ViewController生命週期

alloc -> initWithNibName -> loadView -> viewDidLoad -> viewWillAppear -> viewDidAppear -> viewWillDisappear -> viewDidDisappear -> dealloc

注意viewWillUnload和viewDidUnload已經在ios6被廢棄了,因爲Clearing references to views is no longer necessary。

注意1.沒有viewWillLoad。

注意2.viewDidLoad和viewDidUnload並不是成對的。

 

啓動程序

2014-07-28 17:43:36.124 ViewLifeCycle[4007:a0b] View Did Load
2014-07-28 17:43:36.125 ViewLifeCycle[4007:a0b] View Will Appear
2014-07-28 17:43:36.128 ViewLifeCycle[4007:a0b] View Did Appear

按下Home鍵,並沒有任何記錄

雙擊Home鍵,刪除該程序
2014-07-28 17:43:51.327 ViewLifeCycle[4007:a0b] View Will Disappear
2014-07-28 17:43:51.327 ViewLifeCycle[4007:a0b] View Did Disappear

爲什麼按下Home鍵之後沒有調用viewWillDisappear和viewDidDisappear呢?

因爲在ios4後引入了後臺的概念,當按下Home鍵之後,程序被掛起了,但是該View依然是原來的View,並不是新的。所以只有內存不夠的時候或程序被終止的時候,纔會調用viewWillDisappear和viewDidDisappear。

 

View的卸載

從圖中可以看到,當系統發出內存警告時,會調用didReceiveMemoeryWarning方法,如果當前有能被釋放的view,系統會調用viewWillUnload方法來釋放view,完成後調用viewDidUnload方法,至此,view就被卸載了。此時原本指向view的變量要被置爲nil,具體操作是在viewDidUnload方法中調用self.myButton = nil;


loadView v.s. viewDidLoad

view的nib文件爲nil時,手工創建視圖界面時調用loadView;當view的nib文件存在的時候,初始化工作在viewDidLoad中實現。

loadView時view還沒有生成,viewDidLoad時,view已經生成了,loadView只會被調用一次,而viewDidLoad可能會被調用多次(View可能會被多次加載),當view被添加到其他view中之前,會調用viewWillAppear,之後會調用viewDidAppear。當view從其他view中移除之前,調用viewWillDisAppear,移除之後會調用viewDidDisappear。當view不再使用時,受到內存警告時,ViewController會將view釋放並將其指向爲nil。

ViewController的生命週期中各方法執行流程如下:

alloc—>init—>awakeFromNib—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>dealloc

1. + (id)alloc 分配內存;

2. - (id)init 方法(包括其他-(id)init...方法),只允許調用一次,並且要與 alloc方法 寫在一起,在init方法中申請的內存,要在dealloc方法中釋放(或者其他地方);

3. - (void)awakeFromNib 使用Xib初始化後會調用此方法,一般不會重寫此方法;

4. - (void)loadView 如果使用Xib創建ViewController,就不要重寫該方法。一般不會修改此方法;

5. - (void)viewDidLoad 視圖加載完成之後被調用,這個方法很重要,可以在此增加一些自己定義的控件,注意此時view的frame不一定是顯示時候的frame,真實的frame會在 - (void)viewDidAppear: 後。在iOS6.0+版本中在對象的整個生命週期中只會被調用一次,這裏要注意在iOS3.0~iOS5.X版本中可能會被重複調用,當ViewController收到內存警告後,會釋放View,並調用viewDidUnload,之後會重新調用viewDidLoad,所以要支持iOS6.0以前版本的童鞋要注意這裏的內存管理。
6. - (void)viewWillAppear:(BOOL)animated view 將要顯示的時候,可以在此加載一些圖片,和一些其他佔內存的資源;
7. - (void)viewDidAppear:(BOOL)animated view 已經顯示的時候;

8. - (void)viewWillDisappear:(BOOL)animated view 將要隱藏的時候,可以在此將一些佔用內存比較大的資源先釋放掉,在 viewWillAppear: 中重新加載。如:圖片/聲音/視頻。如果View已經隱藏而又在內存中保留這些在顯示前不會被調用的資源,那麼App閃退的機率會增加,尤其是ViewController比較多的時候;

9. - (void)viewDidAppear:(BOOL)animated view 已經隱藏的時候;

10. - (void)dealloc,不要手動調用此方法,當引用計數值爲0的時候,系統會自動調用此方法。

當受到內存警告時,那麼此時系統默認操作會檢查當前視圖控制器的view是否還在使用,如果沒在使用且控制器實現了loadView方法,ViewController會將view release並將其指向爲nil。

注意,不要在loadView中調用父類方法[super loadView],因爲這會影響CPU性能。

注意2,切換前後臺不會調用viewWillAppear

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章