iOS push頁面出現的卡頓假象,引申出的viewcontroller的和view的

  • 問題:從一個控制器push到另一個控制器時,有時會出現卡頓的一種現象,如下
push卡頓假象.gif

測試代碼里什么數據都沒有加載,所以排除是卡頓原因,view沒有漸變效果,而是瞬間變換的,loadView里創建新的UIView后沒有設置背景色(默認是透明),所以切換時會看到上一頁的內容。
解決:push的下一頁面的self.view設置一個顏色就可以解決

  • 說到這里不免提下viewcontroller的生命周期問題

一、ViewController結構

按結構可以對所有ViewController分成兩類:1、主要用于展示內容的ViewController,這種ViewController主要用于為用戶展示內容,并與用戶交互,如UITableViewController,UIViewController。2、用于控制和顯示其他ViewController的ViewController。這種ViewController一般都是一個ViewController的容器。如UINavigationController,UITabbarController。它們都有一個屬性:viewControllers。其中UINavigationController表示一種Stack式結構,push一個ViewController或pop一次,因此后一個ViewController一般會依賴前一個ViewController。而UITabbarController表示一個Array結構,各個ViewController是并列的。第一種ViewController會經常被繼承,用來顯示不同的數據給用戶。而第二種很少被繼承,除非你真的需要自定義它。注:細心的同學應該能發現,在Xcode中新建一個ViewController時,只可以選擇繼承自UIViewController和UITableViewController,而它們都是第一種。

二、Controller和View的生命周期

這里指的View是指Controller的View。它作為Controler的屬性,生命周期在Controller的生命周期內。就是說你的Controller不能在view釋放前就釋放了。
圖2 ViewController生命周期

當你alloc并init了一個ViewController時,這個ViewController應該是還沒有創建view的。ViewController的view是使用了lazyInit方式創建,就是說你調用的view屬性的getter:[self view]。在getter里會先判斷view是否創建,如果沒有創建,那么會調用loadView來創建view。loadView完成時會繼續調用viewDidLoad。loadView和viewDidLoad的一個區別就是:loadView時還沒有view。而viewDidLoad時view以及創建好了。當view被添加其他view中之前時,會調用viewWillAppear,而之后會調用viewDidAppear。當view從其他view中移出之前時,會調用viewWillDisAppear,而之后會調用viewDidDisappear。當view不在使用,而且是disappeared,受到內存警告時,那么viewController會將view釋放并將其指向nil。

三、代碼組織(如何設計良好的viewcontroller)

ViewController生命周期中有那么多函數,一個重要問題就是什么代碼該寫在什么地方。

  • 1、init里不要出現創建view的代碼。良好的設計,在init里應該只有相關數據的初始化,而且這些數據都是比較關鍵的數據。init里不要掉self.view,否則會導致viewcontroller創建view。(因為view是lazyinit的)。
  • 2、loadView中只初始化view,一般用于創建比較關鍵的view如tableViewController的tabView,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前),最好也不要初始化一些非關鍵的view。如果你是從nib文件中創建的viewController在這里一定要首先調用super的loadView方法,但建議不要重載這個方法。
  • 3、viewDidLoad 這時候view已經有了,最適合創建一些附加的view和控件了。有一點需要注意的是,viewDidLoad會調用多次(viewcontroller可能多次載入view,參見圖2)。
  • 4、viewWillAppear 這個一般在view被添加到superview之前,切換動畫之前調用。在這里可以進行一些顯示前的處理。比如鍵盤彈出,一些特殊的過程動畫(比如狀態條和navigationbar顏色)。
  • 5、viewDidAppear 一般用于顯示后,在切換動畫后,如果有需要的操作,可以在這里加入相關代碼。
  • 6、viewDidUnload iOS6中,viewDidUnload回調方法被Deprecated掉了

出現的問題

1、 (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 可以查找XIB中有沒有視圖view。如果有,則不會再走loadView。如果這個時候你的VC是沒有xib的,哪么顯然走這個方法后,是找不到任何view的,即self.view 仍為nil.然后,就跑loadview,這個時候會被觸發,如果在loadView中,什么也不做,也不實例化一個View。哪么程序繼續跑到viewDidLoad里,如果這里還是沒有實例化VIEW。哪么這個VC就沒有視窗。在這里很多時侯會出現一個誤區(死循環)。

  • 死循環的原因
    1、沒有XIB。
    2、ViewController中的loadView方法中沒有做任何實例化self.view的操作。如:
    -(void)loadView
    {
    寫了一大堆代碼,但最好并沒有執行以下兩種方式中的其中一種。
    //方式一:實例化時使用[supper loadView];
    //方式二 : self.view = [UIView alloc]....
    }
    3、在viewDidLoad中調用了self.view。
    只要這三個條件同時滿足,必定死循環。方式一時,調用了[Supper LoadView] 這個時候由父類產生了一個(0,20,Width,height )。這里的寬高根據是IPAD,還是IPHONE不同而不同,但原點坐標一定是(0,20)即去除狀態條。方式二,沒有對self.view作任可賦值,所以使得self.View = nil;
    在條件二滿足的情況下,程序運行到步驟三,這個時候,如果在這里調用了self.View。因為self.View在步驟二中為空,所以又回調到了loadView來,但因loadView中沒有對self.View作實例化,于是在跑完loadView后,又繼續跑viewDidLoad,但因ViewDidLoad中又沒有實例化的情況下,使用了self.View.因此就出會現來回調用的現象。

2、initWithCoder不調用
initWithCoder 是一個類在IB中創建但在xocde中被實例化時被調用的.比如,通過IB創建一個controller的nib文件,然后在xcode中通過 initWithNibName來實例化這個controller,那么這個controller的initWithCoder會被調用.或者是一個view的nib文件,類似方法創建時調用initWithCoder

UIView的機制

  1. initWithFrame方法是什么?
    initWithFrame方法用來初始化并返回一個新的視圖對象,根據指定的CGRect(尺寸)。
    當然,其他UI對象,也有initWithFrame方法,但是,我們以UIView為例,來搞清楚initWithFrame方法。

  2. 什么時候用initWithFrame方法?
    簡單的說,我們用編程方式申明,創建UIView對象時,使用initWithFrame方法。
    在此,我們必須搞清楚,兩種方式來進行初始化UIView。
    1.使用 Interface Builder 方式。
    這種方式,就是使用nib文件。通常我們說的“拖控件” 的方式。

實際編程中,我們如果用Interface Builder 方式創建了UIView對象。(也就是,用拖控件的方式)
那么,initWithFrame方法方法是不會被調用的。因為nib文件已經知道如何初始化該View。(因為,我們在拖該view的時候,就定義好了長、寬、背景等屬性)。
這時候,會調用initWithCoder方法,我們可以用initWithCoder方法來重新定義我們在nib中已經設置的各項屬性。

這就是為什么使用initWithCoder:的原因,因為BIDViewController.xib的view是BIDQuartzFunView類型,而不是UIView類型了,所以其實是從nib中加載對象實例。

  • 使用編程方式。
    就是我們聲明一個UIView的子類,進行“手工”編寫代碼的方式。

實際編程中,我們使用編程方式下,來創建一個UIView或者創建UIView的子類。這時候,將調用initWithFrame方法,來實例化UIView。
特別注意,如果在子類中重載initWithFrame方法,必須先調用父類的initWithFrame方法。在對自定義的UIView子類進行初始化操作。
比如:

- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];// 先調用父類的initWithFrame方法
    if (self) {
        
        // 再自定義該類(UIView子類)的初始化操作。
        _scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
        [_scrollView setFrame:CGRectMake(0, 0, 320, 480)];
        _scrollView.contentSize = CGSizeMake(320*3, 480);
        
        [self addSubview:_scrollView];
    }
    return self;
}

在這里,我想,應該對initWithFrame方法略知一二了。

當我們所寫的程序里沒用用Nib文件(XIB)時,用代碼控制視圖內容,需要調用initWithFrame去初始化

- (id)initWithFrame:(CGRect)frame
{
    if (self =[superinitWithFrame:frame]) {
        // 初始化代碼
    }
    return self;
}

用于視圖加載nib文件,從nib中加載對象實例時,使用 initWithCoder初始化這些實例對象

- (id)initWithCoder:(NSCoder*)coder
{
    if (self =[superinitWithcoder:coder]) {
        // 初始化代碼
    }
    return self;
}

1.initWithCoder: 對于.xib,當你嵌入一個視圖對象到xib,視圖加載時默認調用的是該方法;例如:假如創建的view來自nib,那么將會調用initWithCoder,由系統來調用,自己不能調用。
2.initWithFrame: 非.xib的手動編碼,視圖加載時默認調用的是該方法。是由自己調用,來初始化對象的

問題:

1、初始化view時不管用什么方式,

//添加lifeview,檢測view生命周期
    self.lifeView = [[LifeView alloc] initWithFrame:CGRectMake(10, 500, 200, 20)];
    self.lifeView = [[LifeView alloc] init];
    [self.view addSubview:self.lifeView];

都會先調用

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor yellowColor];
    }
    return self;
}

init只有view用init初始化時才會調用

- (instancetype)init {
    self = [super init];
    if (self) {
        self.backgroundColor = [UIColor redColor];
    }
    return self;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容