說明
注意事項(Ray):文章來自iOS 7 by Tutorials iOS 7Feast的一部分(略)
Objective-C 是最重要的iOS和OSX apps的開發工具。你可以使用其他語言的第三方架構開發apps,例如HTML&Javascript或者C#,但是如果你很快的寫出一個超炫的高效率的原聲apps你就需要使Objective-C。
Foundation 是你開發Objective-C應用時用到的核心架構之一。
作為一名iOS開發人員,非常有必要瞭解最新的Objective-C和Foundation的特性,在iOS7中有了一些重要的改變需要你瞭解。
在這篇文章中,你將快速探索一些在Objective-C和Foundation中新的功能。
1.Modules(模組)
機會是好的,你已經寫了一千遍或更多#import語句:
#import <UIKit/UIKit.h>#import <MapKit/MapKit.h>#import <iAd/iAd.h>
這個文法要追溯到Objective-C的根:vanilla C。#import語句是前置處理器指令和#include有類似的方式工作。唯一的區別是#import不會匯入已經匯入的標頭檔;它是一次性處理。
當預先處理遇到一個#import命令時,就會按字面的意思用被匯入的標頭檔的全部內容替換那一行。先行編譯會遞迴的這麼處理,即使可能是大量的標頭檔。
UIKit的標頭檔,UIKit.h,包含了UIKit架構中包含的所有其他標頭檔。這意味著,您不必手動匯入每個架構的標頭檔,例如UIViewController.h,UIView.h UIButton.h的。
對UIKit架構的大小感到好奇嘛?通過計算所有行的全部UIKit中的頭,你會發現它相當於超過11,000行代碼!
在一個標準的iOS應用,你會在您的大部分檔案中匯入的UIKit,這意味著每一個檔案最終被長11000行。這是不夠理想的,更多的代碼意味著更長的編譯時間。
1.1 原始解決方案:先行編譯標頭檔(Original solution: Pre-compiled Headers)
先行編譯的標頭檔,或PCH檔案,試圖解決這個問題,通過提供在編譯的預先處理階段預先計算和緩衝需要的代碼。你可能看過Xcode產生的stock PCH 檔案,像下面這樣:
#import <Availability.h>#ifndef __IPHONE_5_0#warning "This project uses features only available in iOS SDK 5.0 and later."#endif#ifdef __OBJC__ #import <UIKit/UIKit.h> #import <Foundation/Foundation.h>#endif
如果開發人員開發的app的targets是iOS5之前的一個SDK,#warning將通知他們。UIKit和Foundation umbrella 標頭檔是stockPCH的一部分。因為在您的應用程式裡的每一個檔案將使用Foundation並且大部分會使用UIKit。因此這些都是很好的添加對於PCH檔案以便於在你的APP中預先計算和緩衝這些檔案的編譯檔案。
你可能會問“這有什麼問題嘛?”PCH沒有任何技術性的問題就像是——if it isn’t broke, don’tfix it(沒有壞,就不要修)。然而你可能錯失了很多效能優勢,由於一個易維護的、高度最佳化的PCH檔案導致(你可能會錯過了一台主機上的維護良好的,高度最佳化的PCH檔案的效能優勢)。例如你可能在好幾個地方用到Map Kit架構,你就會看到了通過添加Map Kit umbrella標頭檔或者單獨的你用到的Map Kit類標頭檔到PCH檔案中對編譯時間的提升。
我們都是lazy developers ,沒有人有時間去維護我們工作的項目的PCH檔案。那就是為什麼modules被開發為LLVM的特性。
注意事項:LLVM是一個模組化和可重複使用的編譯器和工具技術與Xcode捆綁的集合。 LLVM有幾個組成部分:對oc開發人員最重要的是clang,原生的C、C++和Objective-C編譯器;和LLDB,原生debugger—開發人員最好的朋友。
1.2 新的解決方案:模組 (Modules)
Modules第一次在Objective-C中公用露面是在2012 LLVM開發人員大會上Apple’s Doug Gregor的一次談話。這是一次迷人的談話,強烈推薦給對編譯感興趣的人。你可以線上看這些視頻http://llvm.org/devmtg/2012-11/#talk6。
Modules封裝架構比以往任何時候更加清潔。不再需要預先處理逐行地用檔案所有內容替換#import指令。相反,一個模組包含了一個架構到自包含的塊中,就像PCH檔案先行編譯的方式一樣提升了編譯速度。並且你不需要在PCH檔案中聲明你要用到哪些架構,使用Modules簡單的獲得了速度上的提升。
但是Modules不只有這些,我相信你會想起這些步驟當你第一次在一個app使用一個新的架構的時候,就像下面這樣:
1. 在使用架構的檔案中添加#import
2. 用用的架構寫代碼
3. 編譯
4. 查看連結錯誤
5. 想起忘記連結的架構
6. 添加忘記的架構到項目中
7. 重新編譯
忘記連結架構式是一件經常的犯的錯誤,但是Modules解決的非常好。
一個Modules不僅告訴編譯器哪些標頭檔組成了Modules,而且還告訴編譯器什麼需要連結。這個就解救了你不用你去手動的連結架構。這雖然是一件小事,但是能讓開發更加簡單就是一件好事。
1.3 怎樣使用Modules
Modules的使用相當簡單。對於存在的工程,第一件事情就是使這個功能生效。你可以在項目的Build Settings通過搜尋Modules找到這個選項,改變Enable Modules 選項為YES,像這樣:
所有的新工程都是預設開啟這個功能的,但是你應該在你所有存在的工程內都開啟這個功能。
Link Frameworks Automatically選項可以用來開啟或者關閉自動連接架構的功能,就像描述的那麼簡單。還是有一點原因的為什麼你會想要關閉這個功能。
一旦Modules功能開啟,你就可以在你的代碼中使用它了。像這樣做,對以前用到的文法有一點小小的改動。用@import代替#import:
@import UIKit;@import MapKit;@import iAd;
只匯入一個架構中你需要的部分也是可能的。例如你只想要匯入UIView,你就這樣寫:
@import UIKit.UIView;
對的-他真的是這麼簡單,技術上,你不需要把所有的#import都換成@import,因為編譯器會隱式的轉換他們。然而儘可能的用新的文法還是好的習慣。
在你興奮的要開始使用Modules之前,不幸的是有一個小警告。Xcode5的Modules還不支援你自己的或者第三方的架構。這是一個不幸的缺點,沒有事情是完美的,即使是Objective-C!
2.新的傳回型別-instancetype
Objective-C添加了一個新的傳回型別,名字叫instancetype。這個僅僅被用作Objective-C方法的傳回型別和對編譯器的一個暗示,暗示方法的傳回型別將是這個方法屬於的類的執行個體。
注意事項: 這個特徵在iOS7和Xcode上沒有嚴格,但是隨著時間的推移會被悄悄的加進最近的Clang。然而Xcode5第一次聲明蘋果已經在他們的架構中使用了這個。你可以再官方的Clang網頁看到更多的內容:
為什麼要使用instancetype呢?看看下面的代碼:
NSDictionary *d = [NSArray arrayWithObjects:@(1), @(2), nil];NSLog(@"%i", d.count);
雖然這個明顯是不正確的,但是編譯器卻不會提醒你任何錯誤。自己嘗試一下在Xcode4.6下編譯。你將看到沒有任何警告,但是這段代碼明顯是錯誤的。這段代碼甚至能夠沒有異常的跑起來,因為NSDictory和NSArray的執行個體都能相應count。
這段代碼正常的原因是由於Objective-C的強大的動態特性。這個類型是對編譯器的一個指導。Count方法在啟動並執行時候被尋找無論什麼類,正好dictionary變數有這個方法。在這種情況下,count方法存在,編譯器相信他是正確的。然而稍後你用到了NSDictionary有而NSArray沒有的方法例如objectAtIndex:就會出現問題。首先他不會明確指出問題出現在哪裡。
但是問什麼編譯器沒有指出 +[NSArray arrayWithObjects:]方法返回的執行個體不是NSDictionary執行個體呢?那是因為這個方法聲明如下:
+ (id)arrayWithObjects:(id)firstObj, ...;
注意到傳回型別是id。id類型是一個意味著任何Objective-C類的umbrella類型。他甚至都不是NSObject的子類。方法沒有傳回型別資訊而不是返回Objective-C類的執行個體。這樣做是有用的,當你隱式的轉換id到一個確切的類型時編譯器不會警告你。例如上面的NSDictionary例子。如果產生警告,id就沒有用啦。
但是這個方法的傳回型別為什麼是id呢?你可以子類化這個方法然後仍然沒有問題的使用它。為了證明為什麼,考慮下面的NSArray的子類:
@interface MyArray : NSArray@end
現在考慮下你的子類在下面的代碼的使用:
MyArray *array = [MyArray arrayWithObjects:@(1), @(2), nil];
現在你應該知道為什麼arrayWithObjects:傳回型別必須是id。如果是NSArray*,這個子類需要轉化成必要的類。這就是新的instancetype傳回型別用到的地方。如果你看iOS7SDK中NSArray的標頭檔,你將注意到這個方法變成了下面的樣子:
+ (instancetype)arrayWithObjects:(id)firstObj, ...;
唯一的不同就是傳回型別。新的傳回型別提示編譯器傳回型別是方法被調用的類的執行個體。所以當arrayWithObjects:被調用的是NSArray時,傳回型別是NSArray*。當調用的是MyArray時,傳回型別是MyArray*。
當維護成功子類化的能力的時候,用id就會出現的問題。如果用Xcode5編譯原始的代碼,你會看到下面的警告:
warning: incompatible pointer types initializing 'NSDictionary *' with an expression of type 'NSArray *' [-Wincompatible-pointer-types]NSDictionary *d = [NSArray arrayWithObjects:@(1), @(2), nil];
那是有協助的,現在你有機會修改這個問題以防止接下來crash。
初始化方法是候選要使用這個新的傳回型別的。現在如果你設定初始化方法返回一個不完整的類型編譯器已經提醒你了。但是他可能隱式的轉化id到instancetype。你應該仍然使用instancetype,因為明確一點還是比較好的。
儘可能多的使用instancetype,他會成為Apple的標準-你不會知道這個將減少你多少你將來的degugging的痛苦時間。
3.新的 Foundations
接下來就是Objective-C核心開發架構Foundation的一些新東西。沒有Foundation很難開發Objective-C應用,所有的iOS Apps都需要使用。在新的iOS SDK中看看這些新添加的內容。
Foundation最主要的提升是網路。(說的應該是NSURLSession)在iOS 7 by Tutorials 有一整章描述。(略)
文章剩下部分展示了Foundation新增加的和改變的東西。
3.1 NSArray
嘗試在NSArray執行個體中訪問一個Object,如果下表越界將爆出異常。當你用數組當做隊列的時候,你可能經常要訪問數組中第一個或者最後一個元素。 在先進先出隊列(FIFO)你可能要從數組的前端POP元素,如果是先進後出隊列(FILO)就要從數組末尾POP元素。
然而,當你訪問數組的第一個或者最後一個元素的時候,你一定要確定沒有超出數組的邊界,如果數組是空得話經常發生這樣的訪問。這就會導致在調用objectAtIndex:不報錯而產生冗餘的代碼,就像下面的這樣:
NSMutableArray *queue = [NSMutableArray new];// ...if (queue.count > 0) { id firstObject = [queue objectAtIndex:0]; // Use firstObject}// ...if (queue.count > 0) { id lastObject = [queue objectAtIndex:(queue.count - 1)]; // Use lastObject}
要訪問最後一個元素,你應該會用到NSArray的這個方法:
- (id)lastObject;
Objective-C開發人員應該可以高興了,現在他們有了一個方法來訪問數組的第一個元素:
- (id)firstObject;
簡單的方法總是被證明是有用的。你不在需要檢查數組是不是空的啦。你可能曾經遇到過由于越界產生的Crash。你可以看看下面的注意事項:
注意事項:如果你仔細的看NSArray標頭檔,其實firstObject在iOS4.0就已經出現啦,直到iOS7才對外開放。因此你可以在iOS7之前擷取這個方法,但是你必須在你自己的標頭檔裡聲明這個方法firstObject來告訴編譯器它確實存在。這不是一個提倡的方法,好歹Apple把這個方法公開了。
先前的代碼可以用這兩個方法重寫,就不用檢查數組長度了,如下:
NSMutableArray *queue = [NSMutableArray new];// ...id firstObject = [queue firstObject];// Use firstObjectid lastObject = [queue lastObject];// Use lastObject
3.2 NSData
Data是你編程處理最多的事情。NSData是Foundation類,封裝了原始位元組並提供方法操縱這些位元組,可以從一個檔案讀或者寫資料。但是一個簡單的任務Base64編碼和解碼還沒有原生的實現。直到iOS7才出現。
Base64是一組二進位到文本轉換的方案,以ASCII格式提供位元據。這些方案用來編碼位元據以儲存或者通過把多媒體檔案轉換成文本資料進行傳輸。這個能保證資料在傳輸過程中的完整性。Base64編碼的最常見的用途是處理電子郵件附件,或者編碼小圖片,這些小圖片是通過基於Web的API返回的JSON相應的一部分。
在iOS7之前,Base64的 編碼和解碼是需要自己實現的或者使用第三方庫。典型的Apple風格,現在是非常容易的使用這個功能。有四個Base64方法如下:
- (id)initWithBase64EncodedString:(NSString *)base64String options:(NSDataBase64DecodingOptions)options;- (NSString *)base64EncodedStringWithOptions: (NSDataBase64EncodingOptions)options; - (id)initWithBase64EncodedData:(NSData *)base64Data options:(NSDataBase64DecodingOptions)options; - (NSData *)base64EncodedDataWithOptions: (NSDataBase64EncodingOptions)options;
頭兩個方法是處理字串的,後兩個方法是處理UTF-8編碼資料的。這兩個成對的方法功能是一樣的,但是有時候用其中一個比另一個效率要高。如果你想要Base64編碼字串然後寫進檔案,你應該使用UTF-8編碼資料的這對方法。另一方面,如果你打算Base64編碼字串然後用做JSON,你應該使用另外一對方法。如果你曾經實現過Base64編碼方法,現在可以刪除了,因為Apple已經幫你實現了。
3.3 NSTimer
NSTimers在apps中經常用來執行循環性任務。NSTimer雖然很有用但是也會產生問題。當有幾個定時器在用的時候,他們可能間斷性的觸發。這就是意味著CPU是間斷性處於活動狀態的。這樣做是更加有效率的,當CPU換起的時候執行一些任務,然後進入睡眠狀態。為瞭解決這個問題,Apple給NSTimer添加了一個容忍屬性來適應這種行為。
容忍提供系統一個指導在timer在計劃之後允許延遲多長時間。為了減少CPU負荷底層系統將要集合這些活動。新屬性的方法是:
- (NSTimeInterval)tolerance;- (void)setTolerance:(NSTimeInterval)tolerance;
你可能永遠都不需要用到這個屬性,但是當你在非常密切相近的觸發了幾個定時器,你可能發現他是有用的,當你在用Instruments檢測CPU使用率的時候。
3.3 NSProgress
不經常見到Foundation會完整的添加一個新類。他是一個穩定的架構。主要是因為不經常用到核心的類。然而iOS7提供了一個完整的新類NSProgress。
本質上,NSProgress是用來通過Objective-C代碼產生進度報告的,分離每一個獨立模組的進度。例如,你可以在一些資料上執行幾個不同的任務,然後每個任務可以管理他自己的進度然後報告給他的父任務。
3.3.1NSProgress結構
NSProgress最簡單的使用方法是報告一些任務集合的進度。例如,你有10個任務執行,當每個任務完成的時候你可以報告進度。當有一個任務完成的時候進度增加%10。然後在NSProgress的執行個體上使用Key Value Observing(KVO),你能夠瞭解到這個執行個體的進度。你可以使用這個通知來更新進度條或者顯示一個指示文字。
NSProgress有更多的用途。Apple通過這個父子類的關係結構使他更加強大。NSProgress的結構更像是網狀樹。每一個NSProgress有一個父類和多個子類。每一個執行個體有一個執行的工作單元的總數,當前任務會處理完成的子任務數的更新來反饋目前狀態。這麼做的話,父類也會被通知進度。
為了減少NSProgress執行個體的傳遞,每個線程有自己的NSProgress執行個體然後子執行個體可以直接從這個執行個體建立。沒有這個功能,每個想要報告進度的任務不得不通過參數的方式來通知。
3.3.2報告進度
NSProgress使用非常簡單。以下面的方法開始:
+(NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount;
這個方法建立了一個NSProgress執行個體作為當前執行個體的子類,以要執行的任務單元總數來初始化。例如,如果任務是迴圈一個數組,然後你可能用數組數來初始化NSProgress執行個體。例如:
NSArray*array = /* ... */; NSProgress*progress = [NSProgressprogressWithTotalUnitCount:array.count]; [arrayenumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { // Perform an expensive operation onobj progress.completedUnitCount = idx; }];
隨著迭代的進行,上面的代碼會更新NSProgress執行個體來反映當前進度。
3.3.3接收進度更新
你可以通過下面的屬性在任何時候擷取任務進度:
@property(readonly) double fractionCompleted;
返回值是0到1,顯示了任務的整體進度。當沒有子執行個體的話,fractionCompleted就是簡單的完成任務數除以總得任務數。
Key Value Observing(KVO)是最好的方法來擷取fractionCompleted值得變化。這麼做非常簡單。你只需要做的是添加一個NSProgress的fractionCompleted屬性的觀察者。像下面這樣:
[_progressaddObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:NULL];
然後覆蓋KVO的這個方法來擷取改變:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{ if (object == _progress) { // Handle new fractionCompleted value return; } // Always call super, incase it uses KVOalso [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];}
在這個方法中你可以擷取fractionCompleted的值的改變。例如你可能改變進度條或者提示文字。
當然,當你處理完的時候記得登出KVO是很重要的。
[_progressremoveObserver:self forKeyPath:@"fractionCompleted" context:NULL];
你必須總是要登出的,如果你沒有登出,當被註冊的Object釋放的時候就會Crash。所以如果必要的話在dealloc中登出作為最後的保障。
4.WhereTo Go From Here(以後該怎麼辦)
(略)
5.原文連結和參考連結
原文連結:http://www.raywenderlich.com/49850/whats-new-in-objective-c-and-foundation-in-ios-7
參考連結:http://www.onevcat.com/2013/06/new-in-xcode5-and-objc/
http://www.longyiqi.com/blog/programming-languages/2012/04/05/at-import-objc/