iOS7中Objective-C和Foundation的新特性

 
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語句:
  1. #import <UIKit/UIKit.h>   
  2. #import <MapKit/MapKit.h>   
  3. #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 文件,像下面這樣:
  1. #import <Availability.h>   
  2. #ifndef __IPHONE_5_0   
  3. #warning "This project uses features only available in iOS SDK 5.0 and later."   
  4. #endif   
  5.    
  6. #ifdef __OBJC__   
  7.     #import <UIKit/UIKit.h>   
  8.     #import <Foundation/Foundation.h>   
  9. #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:
  1. @import UIKit;   
  2. @import MapKit;   
  3. @import iAd;   
只導入一個框架中你需要的部分也是可能的。例如你只想要導入UIView,你就這樣寫:
  1. @import UIKit.UIView;   
對的-他真的是這麼簡單,技術上,你不需要把所有的#import都換成@import,因爲編譯器會隱式的轉換他們。然而儘可能的用新的語法還是好的習慣。
    
在你興奮的要開始使用Modules之前,不幸的是有一個小警告。Xcode5的Modules還不支持你自己的或者第三方的框架。這是一個不幸的缺點,沒有事情是完美的,即使是Objective-C!
 
2.新的返回類型-instancetype   
Objective-C添加了一個新的返回類型,名字叫instancetype。這個僅僅被用作Objective-C方法的返回類型和對編譯器的一個暗示,暗示方法的返回類型將是這個方法屬於的類的實例。
 
注意事項: 這個特徵在iOS7和Xcode上沒有嚴格,但是隨着時間的推移會被悄悄的加進最近的Clang。然而Xcode5第一次聲明蘋果已經在他們的框架中使用了這個。你可以再官方的Clang網頁看到更多的內容:http://clang.llvm.org/docs/LanguageExtensions.html#objective-c-features
    
爲什麼要使用instancetype呢?看看下面的代碼:
  1. NSDictionary *d = [NSArray arrayWithObjects:@(1), @(2), nil];   
  2. NSLog(@"%i", d.count);   
雖然這個明顯是不正確的,但是編譯器卻不會提醒你任何錯誤。自己嘗試一下在Xcode4.6下編譯。你將看到沒有任何警告,但是這段代碼明顯是錯誤的。這段代碼甚至能夠沒有異常的跑起來,因爲NSDictory和NSArray的實例都能相應count。
     
這段代碼正常的原因是由於Objective-C的強大的動態特性。這個類型是對編譯器的一個指導。Count方法在運行的時候被查找無論什麼類,正好dictionary變量有這個方法。在這種情況下,count方法存在,編譯器相信他是正確的。然而稍後你用到了NSDictionary有而NSArray沒有的方法例如objectAtIndex:就會出現問題。首先他不會明確指出問題出現在哪裏。
     
但是問什麼編譯器沒有指出 +[NSArray arrayWithObjects:]方法返回的實例不是NSDictionary實例呢?那是因爲這個方法聲明如下:
  1. + (id)arrayWithObjects:(id)firstObj, ...;   
注意到返回類型是id。id類型是一個意味着任何Objective-C類的umbrella類型。他甚至都不是NSObject的子類。方法沒有返回類型信息而不是返回Objective-C類的實例。這樣做是有用的,當你隱式的轉換id到一個確切的類型時編譯器不會警告你。例如上面的NSDictionary例子。如果產生警告,id就沒有用啦。
    
但是這個方法的返回類型爲什麼是id呢?你可以子類化這個方法然後仍然沒有問題的使用它。爲了證明爲什麼,考慮下面的NSArray的子類:
  1. @interface MyArray : NSArray   
  2. @end   
現在考慮下你的子類在下面的代碼的使用:
  1. MyArray *array = [MyArray arrayWithObjects:@(1), @(2), nil];   
現在你應該知道爲什麼arrayWithObjects:返回類型必須是id。如果是NSArray*,這個子類需要轉化成必要的類。這就是新的instancetype返回類型用到的地方。如果你看iOS7SDK中NSArray的頭文件,你將注意到這個方法變成了下面的樣子:
  1. + (instancetype)arrayWithObjects:(id)firstObj, ...;   
唯一的不同就是返回類型。新的返回類型提示編譯器返回類型是方法被調用的類的實例。所以當arrayWithObjects:被調用的是NSArray時,返回類型是NSArray*。當調用的是MyArray時,返回類型是MyArray*。
    
當維護成功子類化的能力的時候,用id就會出現的問題。如果用Xcode5編譯原始的代碼,你會看到下面的警告:
  1. warning: incompatible pointer types initializing 'NSDictionary *' with an expression of type 'NSArray *' [-Wincompatible-pointer-types]   
  2. 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:不報錯而產生冗餘的代碼,就像下面的這樣:
  1. NSMutableArray *queue = [NSMutableArray new];   
  2. // ...   
  3. if (queue.count > 0) {   
  4.     id firstObject = [queue objectAtIndex:0];   
  5.     // Use firstObject   
  6. }   
  7. // ...   
  8. if (queue.count > 0) {   
  9.     id lastObject = [queue objectAtIndex:(queue.count - 1)];   
  10.     // Use lastObject   
  11. }   
 
要訪問最後一個元素,你應該會用到NSArray的這個方法:
  1. - (id)lastObject;   
Objective-C開發者應該可以高興了,現在他們有了一個方法來訪問數組的第一個元素:
  1. - (id)firstObject;   
簡單的方法總是被證明是有用的。你不在需要檢查數組是不是空的啦。你可能曾經遇到過由於越界產生的Crash。你可以看看下面的注意事項:
 
注意事項:如果你仔細的看NSArray頭文件,其實firstObject在iOS4.0就已經出現啦,直到iOS7纔對外開放。因此你可以在iOS7之前獲取這個方法,但是你必須在你自己的頭文件裏聲明這個方法firstObject來告訴編譯器它確實存在。這不是一個提倡的方法,好歹Apple把這個方法公開了。
    
先前的代碼可以用這兩個方法重寫,就不用檢查數組長度了,如下:
  1. NSMutableArray *queue = [NSMutableArray new];   
  2. // ...   
  3. id firstObject = [queue firstObject];   
  4. // Use firstObject   
  5. id lastObject = [queue lastObject];   
  6. // Use lastObject   
3.2 NSData  
Data是你編程處理最多的事情。NSData是Foundation類,封裝了原始字節並提供方法操縱這些字節,可以從一個文件讀或者寫數據。但是一個簡單的任務Base64編碼和解碼還沒有原生的實現。直到iOS7纔出現。
    
Base64是一組二進制到文本轉換的方案,以ASCII格式提供二進制數據。這些方案用來編碼二進制數據以存儲或者通過把多媒體文件轉換成文本數據進行傳輸。這個能保證數據在傳輸過程中的完整性。Base64編碼的最常見的用途是處理電子郵件附件,或者編碼小圖片,這些小圖片是通過基於Web的API返回的JSON相應的一部分。
    
在iOS7之前,Base64的 編碼和解碼是需要自己實現的或者使用第三方庫。典型的Apple風格,現在是非常容易的使用這個功能。有四個Base64方法如下:
  1. - (id)initWithBase64EncodedString:(NSString *)base64String    
  2.       options:(NSDataBase64DecodingOptions)options;   
  3.    
  4. - (NSString *)base64EncodedStringWithOptions:   
  5.       (NSDataBase64EncodingOptions)options;   
  6.     
  7. - (id)initWithBase64EncodedData:(NSData *)base64Data    
  8.       options:(NSDataBase64DecodingOptions)options;   
  9.     
  10. - (NSData *)base64EncodedDataWithOptions:   
  11.       (NSDataBase64EncodingOptions)options;  
頭兩個方法是處理字符串的,後兩個方法是處理UTF-8編碼數據的。這兩個成對的方法功能是一樣的,但是有時候用其中一個比另一個效率要高。如果你想要Base64編碼字符串然後寫進文件,你應該使用UTF-8編碼數據的這對方法。另一方面,如果你打算Base64編碼字符串然後用做JSON,你應該使用另外一對方法。如果你曾經實現過Base64編碼方法,現在可以刪除了,因爲Apple已經幫你實現了。
 
3.3 NSTimer
NSTimers在apps中經常用來執行週期性任務。NSTimer雖然很有用但是也會產生問題。當有幾個定時器在用的時候,他們可能間斷性的觸發。這就是意味着CPU是間斷性處於活動狀態的。這樣做是更加有效率的,當CPU換起的時候執行一些任務,然後進入睡眠狀態。爲了解決這個問題,Apple給NSTimer添加了一個容忍屬性來適應這種行爲。
     
容忍提供系統一個指導在timer在計劃之後允許延遲多長時間。爲了減少CPU負荷底層系統將要集合這些活動。新屬性的方法是:
  1. - (NSTimeInterval)tolerance;   
  2. - (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使用非常簡單。以下面的方法開始:
  1. +(NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount;  
這個方法創建了一個NSProgress實例作爲當前實例的子類,以要執行的任務單元總數來初始化。例如,如果任務是循環一個數組,然後你可能用數組數來初始化NSProgress實例。例如:
  1. NSArray*array = /* ... */;   
  2.     
  3. NSProgress*progress =   
  4.     [NSProgressprogressWithTotalUnitCount:array.count];   
  5.     
  6. [arrayenumerateObjectsUsingBlock:   
  7.     ^(id obj, NSUInteger idx, BOOL *stop) {   
  8.         // Perform an expensive operation onobj   
  9.         progress.completedUnitCount = idx;   
  10.     }];   
 
隨着迭代的進行,上面的代碼會更新NSProgress實例來反映當前進度。
 
3.3.3接收進度更新 
你可以通過下面的屬性在任何時候獲取任務進度:
  1. @property(readonly) double fractionCompleted;   
返回值是0到1,顯示了任務的整體進度。當沒有子實例的話,fractionCompleted就是簡單的完成任務數除以總得任務數。
    
Key Value Observing(KVO)是最好的方法來獲取fractionCompleted值得變化。這麼做非常簡單。你只需要做的是添加一個NSProgress的fractionCompleted屬性的觀察者。像下面這樣:
  1. [_progressaddObserver:self   
  2.            forKeyPath:@"fractionCompleted"   
  3.               options:NSKeyValueObservingOptionNew   
  4.                context:NULL];   
 然後覆蓋KVO的這個方法來獲取改變:
  1. -(void)observeValueForKeyPath:(NSString *)keyPath   
  2.                       ofObject:(id)object   
  3.                         change:(NSDictionary*)change   
  4.                        context:(void *)context   
  5. {   
  6.     if (object == _progress) {   
  7.         // Handle new fractionCompleted value   
  8.         return;   
  9.     }   
  10.     
  11.     // Always call super, incase it uses KVOalso   
  12.     [super observeValueForKeyPath:keyPath   
  13.                          ofObject:object   
  14.                            change:change   
  15.                           context:context];   
  16. }   
在這個方法中你可以獲取fractionCompleted的值的改變。例如你可能改變進度條或者提示文字。
    
當然,當你處理完的時候記得註銷KVO是很重要的。
  1. [_progressremoveObserver:self   
  2.               forKeyPath:@"fractionCompleted"   
  3.                   context:NULL]; 
你必須總是要註銷的,如果你沒有註銷,當被註冊的Object釋放的時候就會Crash。所以如果必要的話在dealloc中註銷作爲最後的保障。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章