《ios程式設計第四版》隨感,隨想

陳振發表於2017-12-13

《ios程式設計第四版》隨感,隨想

oc的物件可以看作一個指揮中心,控制著記憶體的各個部分。如同一張網一樣。採用的是連結串列的結構。

物件會直接儲存非指標型別的例項變數。

向super傳送訊息,其實是向self傳送訊息,但是要求系統在查詢方法時跳過當前物件的類,從父類開始查詢。

在初始化方法中,應直接訪問例項變數。而在其他方法中,要使用存取方法。

任何一個類,無論有多少初始化方法,,都必須選定其中一個作為指定初始化方法。指定初始化方法要確保物件的每一個例項變數都處在一個有效的狀態。

串聯使用初始化方法的機制可以減少錯誤,也更容易維護程式碼。在建立類時,需要先確定指定初始化方法,然後旨在指定初始化方法中編寫初始化的核心程式碼,其他初始化方法只需要呼叫指定初始化方法(直接或間接)並傳入預設值即可。

初始化方法總結

  • 類會繼承父類所有的初始化方法,也可以為類加入任意數量的初始化方法。
  • 每個類都要選定一個指定初始化方法。
  • 在執行其他初始化工作之前,必須先用指定初始化方法呼叫父類的指定初始化方法。(直接或間接)
  • 其他初始化方法要呼叫指定初始化方法(直接或間接)。
  • 如果某個類的指定初始化方法與其父類的不同,就必須覆蓋父類的指定初始化方法並呼叫新的指定初始化方法(直接或間接)。

標頭檔案的宣告順序 例項變數宣告應該寫在最前面,然後是類方法,接下來是初始化方法,最後是其他方法。

如果某個類方法的返回型別是這個類的物件,就可以將這種類方法稱為便捷方法(convenience method)。

任何屬性都有三個特性:

  • 多執行緒特性(nonatomic,atomic),預設是atomic;
  • 讀/寫特性(readwrite, readonly),預設是readwrite;
  • 記憶體管理特性(strong,weak,copy,unsafe_unretained),預設是strong,非物件屬性預設值是unsafe_unretained;

通常情況下,當某個屬性是指向其他物件的指標,而且該物件的類有可修改的子類(如NSString/NSMutableString)時,應該將屬性的記憶體管理特性設定為copy。原因如下:

  • 如果屬性指向的物件的類有可修改的子類,那麼該屬性可能會指向該子類。該子類物件有可能在擁有者不知情的情況下被修改。因此,最好先複製該物件。
  • 向不可變物件傳送copy時,會返回一個指向自己的指標。不會造成記憶體空間浪費。

當為宣告的屬性重寫了存方法或取方法時,編譯器便不會自動生成該方法和例項變數。

檢視控制器

檢視控制器通過兩種方式建立檢視層次結構:

  • 程式碼方式:覆蓋UIViewController中的loadView
  • Nib檔案方式:使用Interface Builder建立一個Xib檔案,然後加入所需的檢視層次結構,最後檢視控制器會在執行時載入由該xib檔案編譯而成的nib檔案

將插座變數宣告為弱引用是一種程式設計約定。當系統的可用記憶體偏少時,檢視控制器會自動釋放其檢視並在之後需要顯示時在建立。以便在釋放view時同時釋放view的所有子檢視,從而避免記憶體洩漏。

指向XIB檔案中的頂層物件的插座變數必須宣告為強引用,相反,當插座變數指向頂層物件所擁有的物件(例如頂層物件的子檢視)時,應該使用弱引用。

檢視的延遲載入 為了實現檢視延遲載入,在initWithNibName:bundle:中不應該訪問view貨view的任何子檢視。凡是和view或view的子檢視相關的初始化程式碼,都應該在viewDidLoad方法中實現,避免載入不需要在螢幕上顯示的檢視。

訪問檢視 應該在以下兩個方法中訪問xib檔案中建立的檢視:

viewDidLoad viewWillAppear

區別是: 如果你只需在應用啟動後設定一次檢視物件,就選擇viewDidLoad。 而如果使用者每次看到的檢視控制器的view時都需要對其進行設定,則選擇viewWillAppear

與控制器及其檢視進行互動

  • application:didFinishLaunchingWithOptions:只在應用啟動完畢後呼叫一次。
  • initWithNibName:bundle:該方法是UIViewController的指定初始化方法,建立檢視控制器時,就會呼叫該方法。
  • loadView:可以覆蓋該方法,使用程式碼方式設定檢視控制器的view屬性。
  • viewDidLoad可以覆蓋該方法,設定使用NIB檔案建立的檢視物件,該方法會在檢視控制器載入完畢後被呼叫。
  • viewWillAppear: viewDidAppear: viewWillDisappear: 和viewDidDisappear:這些方法會被呼叫很多次。

UITableViewController

MVC

  • Model(模型):負責儲存資料,與使用者介面無關。
  • View (檢視):負責顯示介面,與模型物件無關。
  • Controller(控制器):負責確保檢視物件和模型物件的資料保持一致。

@class

當某個檔案只需要使用某個類的宣告,無需知道具體實現細節時,可以使用該@class 指令。 使用該指令的好處: 當某個類的標頭檔案發生變化時,對那些通過@class指令宣告該類的其他檔案,編譯器可以不用重新編譯,這樣就可以節省編譯時間。

而在另一些檔案中,程式會向宣告的類或物件傳送訊息時,就必須匯入該類的標頭檔案,是編譯器知道所有的實現細節。

重用UITableViewCell

如果針對每條記錄建立相應的UITableViewCell物件,就會很快耗盡ios裝置的記憶體資源。 為了解決該問題,需要重用UITableViewCell物件。該機制是: 滾動時,部分UITableViewCell物件會移出視窗,UITableView將移出視窗的cell物件放入UITableViewCell物件池,等待重用。

按照約定,應該將UITableViewCell或UITableViewCell子類的類名用作reuseIdentifier

檢視的責任是將模型物件中的資料呈現給使用者,只更新檢視而不更新模型物件就會發生資料不一致的錯誤。

相機

NSDictionary

NSDictionary非常有用,其中最常見的用法是可變資料結構和查詢表。

可變資料結構: 為了在程式碼中描述一個模型物件,常見的做法是建立一個NSObject的子類,然後新增模型物件的相關屬性。**使用NSDictionary與NSObject子類的區別是,**NSObject的子類要求事先明確定義好該類的各項屬性,並且之後無法修改,新增和刪除。而使用NSDictionary就可以。

查詢表: 當需要編寫包含大連if-else或switch語句的程式碼時,通常應該考慮替換為NSDictionary。例如:

- (void)changeCharacterClass:(id)sender {
    NSString *enteredText = textField.text;
    CharaterClass *cc = nil;
    
    if ([enteredText isEqualToString:@"Warrior"]) {
        cc = knignt;
    } else if ([enteredText isEqualToString:@"Mage"]) {
        cc = wizard;
    }
    
    character.characterClass = cc;
}
複製程式碼

以上程式碼應替換為:


NSMutableDictionary *lookup = [[NSMutableDictionary alloc] init];
[lookup setObject:knight forKey:@"Warrior"];
[lookup setObject:wizard forKey:@"Mage"];

- (void)changeCharacterClass:(id)sender {
    
    
    character.characterClass = [lookup objectForKey:textField.text];
}
複製程式碼

處理觸控事件並建立線條物件

使用valueWithNonretainedObject:方法將UITouch物件的記憶體地址封裝為NSValue物件。使用記憶體地址分辨UITouch物件的原因是,在觸控事件開始,移動,結束的整個過程中,其記憶體地址是不會改變的,記憶體地址相同的UITouch物件一定是同一個物件。

為什麼UITouch物件自身不能用做NSMutableDictionary的鍵?這是由於NSDictionary及其子類NSMutableDictionary的鍵必須遵守NSCopying協議--鍵必須可以複製(可以響應copy訊息)。UITouch物件並不遵守NSCopying協議,因為每個觸控事件都是唯一的,不應該被複制。

響應物件鏈:

[圖片上傳失敗...(image-4919fc-1509616086734)]

如果沒有為某個UIResponder物件覆蓋特定的事件處理方法,那麼該物件的nextResponder會嘗試處理相應的觸控事件。最終傳遞給UIApplication,如果UIApplication也無法對其進行處理,系統就會丟棄該事件。

自動佈局入門

  • 如果檢視都是檢視控制器的一級子檢視,那麼遍歷一次就可以找出全部檢視。

- (void)viewDidLayoutSubviews {
    for (UIView *subView in self.view.subviews) {
        if (subView hasAmbiguousLayout) {
            NSLog(@"%@", subView);
        }
    }
}
複製程式碼
  • 如果這些檢視中又包含複雜的檢視層次結構,就應該使用另一種方法:

viewWillAppear中加斷點,然後在控制檯輸入:

po [[UIWindow keyWindow] _autolayoutTrace]

如果應用介面佈局跟期望不一致,同時也無法確定原因,可以使用該方法。

檢視控制器需要針對iPhone和iPad顯示完全不同的介面,通過建立兩個獨立的XIB檔案來實現

針對iPhone和iPad的XIB檔案需要在類名後加上對應的字尾:

CZViewController~iphone.xib
CZViewController~ipad.xib
複製程式碼

[圖片上傳失敗...(image-c2d487-1509616086734)]

[圖片上傳失敗...(image-d4d689-1509616086734)]

#在程式碼中使用自動佈局

通常,若要為UIViewController建立檢視,如果是建立整個檢視的層次結構及所有檢視約束,就覆蓋loadView方法;如果只是通過NIB檔案建立的檢視層次結構中新增一個檢視或約束,就覆蓋viewDidLoad

Visual Format Language (視覺化格式語言)

根據Apple的命名規範,應該使用屬性的名稱或例項變數的名稱作為檢視物件的鍵。視覺化格式字串將使用字典中的鍵表示相應的檢視物件。

XIB檔案中建立並新增約束只需一步就可以完成,但是在程式碼中,建立和新增約束需要分為兩個不同的步驟。

NSDictionary *nameMap = @{@"imageView" : self.imageView,
                              @"valueField" : self.valueField,
                              @"toolBar" : self.toolBar
                              };
    NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[imageView]-0-|"
                                            options:0
                                            metrics:nil
                                              views:nameMap];
    NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[valueField]-[imageView]-[toolBar]"
                                                                           options:0
                                                                           metrics:nil
                                                                             views:nameMap];
    
    [self.view addConstraints:horizontalConstraints];
    [self.view addConstraints:verticalConstraints];
複製程式碼

###如何判斷約束應該新增到哪個檢視中,以下是判定法則:

  • 如果約束 同時對 多個父檢視相同的 檢視起作用,那麼約束應該新增到它們的父檢視中。(例如下圖中的約束A)
  • 如果約束 同時對 多個父檢視不同的 檢視起作用,但是這些檢視在層次結構中有共同的祖先檢視,那麼約束應該新增到它們最近以及的祖先檢視中。(例如下圖中的約束C)
  • 如果約束只對某個檢視自身起作用,那麼約束應該新增到該檢視中。(例如下圖中的約束B)
  • 如果約束同時對某個檢視及其父檢視起作用,那麼約束應該新增到其父檢視中。

[圖片上傳失敗...(image-8181a6-1509616086734)]

(Intrinsic Content Size)固有內容大小

固有內容大小的含義:檢視要顯示的實際內容區域大小。

自動佈局系統會根據固有內容大小為檢視新增相應的約束,與其他約束不同,這類約束有兩個優先順序屬性,分別是內容放大優先順序(content hugging priority)和內容縮小優先順序(content compression resistance priority)。

content hugging priority: 表示檢視固有內容大小的放大優先順序,如果該優先順序為1000,表示不允許自動佈局系統基於固有內容放大檢視尺寸,相反,如果該優先順序小於1000,自動佈局系統會在必要時放大檢視的尺寸。 content compression resistance priority:表示檢視固有內容大小的縮小優先順序,如果該優先順序為1000,表示不允許自動佈局系統基於固有內容大小縮小檢視尺寸;相反,如果該優先順序小於1000,自動佈局系統會在必要時縮小檢視的尺寸。

檢視與控制器之間的關係

父子關係

當使用檢視控制器容器時,就會產生擁有父子關係的檢視控制器。通過viewControllers來儲存一組檢視控制器。

檢視控制器容器的特性:

  • 容器物件會將viewControllers中的檢視作為子檢視加入自己的檢視。
  • 容器檢視物件通常都會有自己的預設外觀。

處在同一個父子關係下的檢視控制器形成一個族系:

  • 任何容器物件都可以通過viewControllers訪問其子物件。

  • 子物件可以通過UIViewController物件的四個特定屬性來訪問其容器物件:

    1. 先介紹四個特定屬性的前三個:navigationController,tabBarController,splitViewController。當某個檢視控制器收到這三個訊息中的某一個,就會沿著族系向上查詢,直到找到型別匹配的檢視控制器。如果沒找到,返回nil。
    2. 第四個屬性:parentViewController,該屬性會指向族系中“最近的”那個容器物件。

顯示與被顯示關係(presenting-presenter relationship)

當某個檢視控制器以模態(modal)形式顯示另一個檢視控制器時,就會產生擁有這種關係的檢視控制器。當某個檢視控制器A以模態形式顯示另一個檢視控制器B時, B的檢視會覆蓋A的檢視。這和之前介紹的父子關係不同,父子關係的子檢視只會在容器物件的檢視內顯示。 當某個檢視控制器A以模態形式顯示另外一個檢視控制器B時,A的presentedViewController會指向B,而B的presentingViewController屬性會指向A。

[圖片上傳失敗...(image-80af6a-1509616086734)]

在顯示與被顯示關係中,位於關係的兩頭的檢視控制器不會處於同一個族系中。被顯示的檢視控制器會有自己的族系。

如上圖所示,凡是針對父子關係的屬性,其指向的物件都會在當前族系的範圍內。因此,向族系2中的檢視控制器傳送UITabBarController物件,會返回nil。

不同族系中的檢視控制器關係: 當應用以模態形式顯示某個檢視控制器時,負責顯示該檢視控制器的將是相關族系中的頂部檢視控制器(以上圖為例,族系2中的檢視控制器的presentingViewController的屬性指向的都是UITabBarController物件)。

通過編寫程式碼,可以改變這種“頂部物件負責以模態形式顯示其他檢視控制器”的行為(僅限iPad)。這樣就可以限定檢視的顯示位置。

為此,UIViewController提供了definesPresentationContext屬性,其預設值是NO,當為NO時,會將“顯示權”傳遞給父檢視控制器,並沿族系依次向上傳遞,直到最頂層檢視控制器。相反,如果在傳遞過程中,如果某個檢視控制器的definesPresentationContext是YES,該檢視控制器就不會再將“顯示權”傳遞給父檢視控制器,而是由自己負責新的檢視控制器。此外,對於這種情況,必須將需要顯示的檢視控制器的modalPresentationStyle屬性設定為UIModalPresentationCurrentContext

儲存,讀取與應用狀態

應用沙盒

應用沙盒會包含以下多個目錄:

  • 應用程式包(application bundle):包含應用可執行檔案,例如NIB檔案。
  • Documents/ :存放應用執行時生成的並且需要保留的資料。同步裝置時會備份該目錄。
  • Library/Caches/ :存放應用執行時生成的需要保留的資料。不會同步。
  • Library/Caches/ :存放所有的偏好設定。使用NSUserDefaults類,可以通過Library/Perferences目錄中的某個特定檔案以鍵值對的形式儲存資料。同步裝置時會備份該目錄。
  • tmp/ :存放應用執行時所需的臨時資料。不會同步。可通過NSTemporaryDirectory函式得到tmp/目錄的全路徑。

應用狀態與切換

[圖片上傳失敗...(image-314295-1509616086734)]

處於未執行狀態的應用,不會執行任何程式碼,也不會佔用RAM。 當應用處於啟用狀態時:

  • 被某個系統事件打斷,臨時進入未啟用狀態。這類事件包括收到簡訊,收到推送,來電等。
  • 按下頂部鎖定按鈕,切換至未啟用狀態,並且保留未啟用狀態直到裝置解鎖。
  • 按下home鍵,或進入多工介面,或通過某種途徑切換至另一個應用時,狀態切換至未啟用狀態,停留極短的時間,然後進入後臺執行狀態。預設情況下,進入後臺狀態的應用大約有10秒的時間,然後進入掛起狀態。

應用的各種狀態:

狀態 介面是否可見 是否能接收事件 是否能執行程式碼
未執行狀態
啟用狀態
未啟用狀態 大部分
後臺執行狀態
掛起狀態

模型

標準的模型-檢視-控制器設計模式要求控制物件負責模型物件的儲存和讀取。但這樣做的效果並不是很好。控制物件主要的任務是處理模型物件和檢視物件之間的互動,如果還要負責實現所有的存取細節,則可能會不堪重負。為此,將模型物件的存取邏輯移入另一類物件:儲存物件

儲存和讀取模型物件的實現細節全部由儲存物件負責。儲存物件通過以下方式來建立和儲存模型物件:

  • 通過指定資料夾來建立和儲存。
  • 通過資料庫
  • 通過Web服務
  • 其他

這種設計模式為:模型-檢視-控制器-儲存(Model-View-Controller-Store)。

這種設計模式的好處:

  1. 簡化控制器類
  2. 不用修改控制器物件或應用的其他部分,就能修改儲存物件的工作方式。因此,無論應用有多少個需要存取資料的控制物件,都只需要修改相應的儲存物件即可。

NSException和NSError

NSException和NSError的使用場景不同。

NSException 如果需要指出程式設計師的編碼錯誤,則應該使用NSException。例如:一個方法只能接受奇數作為引數,但是程式設計師在呼叫該方法時傳入了偶數,這時應該丟擲異常,以方便程式設計師解決程式碼錯誤。

NSError 對於預期錯誤,如使用者錯誤和裝置環境錯誤,應該使用NSError。例如:一個方法需要讀取使用者照片,但是沒有訪問使用者相簿的許可權,這時應該向方法呼叫者返回一個NSError物件,指出不能執行本次操作的原因。

文件系統的讀取和寫入

NSString類似,NSDictionaryNSArray也有writeToFile:initWithContentsOfFile:。只有當容器物件包含可序列化(property list serializable)物件時,才能通過writeToFile:這類方法將資料寫入。

可序列化物件包括: NSString, NSNumber, NSData, NSArray, NSDictionaryNSArray和NSDictionary這類物件的檔案寫入方法生成的都是XML格式的property list檔案。

建立UITableViewCell子類

block物件應被宣告為copy

將block物件宣告為copy的原因是: 系統對block物件和其他物件的記憶體管理方式不同,block物件是在棧中建立的,而其他物件是在堆中建立的。這意味著,即使應用針對新建立的block物件保留了強引用型別的指標,一旦建立該物件的方法返回,新建立的block物件也會被立即釋放。為了在宣告block物件的方法返回後仍然保留該物件,必須向其傳送copy訊息。拷貝某個block物件時,應用會在堆中建立該物件的備份。

Web 服務

NSURL NSURLRequest NSURLSessionTask NSURLSession

  • NSURL物件負責以URL的格式儲存Web應用的位置。對大多數Web服務,URL將包含基地址,Web應用名和需要傳送的引數。
  • NSURLRequest物件負責儲存需要傳送給Web伺服器的全部資料。這些資料包括:一個NSURL物件,快取方案,等待Web伺服器響應的最長時間和需要通過http協議傳送的額外資訊(NSMutableURLRequest是NSURLRequest的子類)。
  • 每一個NSURLSessionTask都表示一個NSURLRequest的生命週期,NSURLSessionTask可以跟蹤NSURLRequest的狀態,還可以對NSURLRequest執行取消,暫停,繼續操作。NSURLSessionTask有多種不同功能的子類,包括NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask
  • NSURLSession物件可以看作是一個生產NSURLSessionTask物件的工廠。可以設定其生產出的NSURLSessionTask物件的通用屬性。例如請求頭的內容,是否允許在蜂窩網下傳送請求等。NSURLSession還有一個功能強大的委託,可以跟蹤NSURLSessionTask物件的狀態,處理伺服器的認證要求等。

伺服器端根據功能將Web服務分配到不同的路徑,然後要求客戶端以路徑作為引數請求不同的Web服務。

URL字串必須是URL安全的(URL-safe)。例如,在URL中不允許出現空格字元和雙引號,必須使用轉義序列(escape-sequence)來替換這些字元: NSString *search = @"Play some \"Abba\""; NSString *escaped = [search stirngByAddingPercentEscapsUsingEncoding:NSUTF8StringEncoding]; // 轉義後的字串是“Play%20some%20%22Abba%22”

解除轉義:stringByRemovingPercentEncoding

##解析JSON資料 Apple提供了專門用於解析JSON資料的類:NSJSONSerialization.可以講JSON資料中的物件轉換為對應的OC物件。

預設情況下,NSURLSessionDataTask是在後臺執行緒中執行completionHandler的。 [圖片上傳失敗...(image-b5deb4-1509616086734)]

HTTP請求的主體是傳送給伺服器的資料,這些資料通常是XML格式,JSON格式或Base-64編碼後的資料。如果某個請求包含主體,就必須包含Content-Length頭。NSURLRequest會計算主體的大小並自動新增Content-Length頭。

Core Data

固化機制: 使用固化機制的最大缺點是資料必須“整存整取”,要訪問固化檔案中的任何資料,必須先解固整個檔案;要儲存資料的任何改動,必須重寫整個檔案。

Core Data: Core Data沒有這樣的缺點。Core Data可以只讀取已存物件中的一小部分。如果取出的物件發生了變化,也只要更新相應的部件。如果某個應用要在檔案系統和RAM之間傳送大量模型物件,那麼Core Data的這種增量讀取,更新,刪除和插入的特性可以大幅提高效能。

Core Data可以儲存transformable型別,CoreData會在儲存或恢復transformable型別的實體屬性時首先將其轉換為NSData,然後再存入檔案系統或恢復為OC物件。為了向Core Data描述轉換過程,需要建立NSValueTransformer的子類。

Core Data 框架中的NSManagedObjectContext負責應用和資料庫之間的互動工作。通過NSManagedObjectContext物件所使用的NSPersistentStoreCoordinator物件,可以指定檔案路徑並開啟相應的SQLite資料庫。NSPersistentStoreCoordinator物件需要配合某個模型檔案才能工作(NSManagedObjectModel物件可以代表模型檔案)。

啟用狀態恢復

如果應用啟用了狀態恢復,系統在終止應用之前會遍歷應用樹中的每一個節點,記錄每個節點的狀態資訊,例如,物件的唯一標識,類和需要儲存的狀態資料。在終止應用之後,系統會將這些資訊儲存到檔案系統中。

其中物件的唯一標識又稱為物件的恢復標識(restoration identifier)。通常與物件的類名相同;類稱為恢復類(restoration class),通常與該物件的isa指標指向的類相同;狀態資料則儲存了物件的狀態資訊,例如,UITabBarController的狀態資料包括當前選中的是哪一個標籤項。

當應用重新啟動後,系統會讀取之前儲存的狀態資訊,重新建立應用樹,依次恢復樹中的每一個節點:

  • 系統通過節點的恢復類為該節點建立一個新的檢視控制器。
  • 將一組恢復標識賦給新節點:包括該節點的恢復標識及其所有祖先節點的恢復標識。陣列中第一個標識是根節點的恢復標識,最後一個標識是新節點的恢復標識。
  • 將對應的狀態資料賦給新節點。狀態資料儲存在NSCoder物件中。

相關文章