注意事項(Ray):文章來自iOS 7 by Tutorials iOS 7Feast的一部分(略)html
Objective-C 是最重要的iOS和OSX apps的開發工具。你可使用其餘語言的第三方框架開發apps,例如HTML&Javascript或者C#,可是若是你很快的寫出一個超炫的高效率的原聲apps你就須要使Objective-C。前端
Foundation 是你開發Objective-C應用時用到的核心框架之一。objective-c
做爲一名iOS開發者,很是有必要了解最新的Objective-C和Foundation的特性,在iOS7中有了一些重要的改變須要你瞭解。express
在這篇文章中,你將快速瀏覽一些在Objective-C和Foundation中新的功能。編程
機會是好的,你已經寫了一千遍或更多#import語句:數組
[cpp] view plaincopy緩存
#import <UIKit/UIKit.h> 網絡
#import <MapKit/MapKit.h> app
#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行。這是不夠理想的,更多的代碼意味着更長的編譯時間。
預編譯的頭文件,或PCH文件,試圖解決這個問題,經過提供在編譯的預處理階段預先計算和緩存須要的代碼。你可能看過Xcode生成的stock PCH 文件,像下面這樣:
[cpp] view plaincopy
#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—開發者最好的朋友。
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,並且還告訴編譯器什麼須要連接。這個就解救了你不用你去手動的連接框架。這雖然是一件小事,可是能讓開發更加簡單就是一件好事。
Modules的使用至關簡單。對於存在的工程,第一件事情就是使這個功能生效。你能夠在項目的Build Settings經過搜索Modules找到這個選項,改變Enable Modules 選項爲YES,像這樣:
全部的新工程都是默認開啓這個功能的,可是你應該在你全部存在的工程內都開啓這個功能。
Link Frameworks Automatically選項能夠用來開啓或者關閉自動鏈接框架的功能,就像描述的那麼簡單。仍是有一點緣由的爲何你會想要關閉這個功能。
一旦Modules功能開啓,你就能夠在你的代碼中使用它了。像這樣作,對之前用到的語法有一點小小的改動。用@import代替#import:
[cpp] view plaincopy
@import UIKit;
@import MapKit;
@import iAd;
只導入一個框架中你須要的部分也是可能的。例如你只想要導入UIView,你就這樣寫:
[cpp] view plaincopy
@import UIKit.UIView;
對的-他真的是這麼簡單,技術上,你不須要把全部的#import都換成@import ,由於編譯器會隱式的轉換他們。然而儘量的用新的語法仍是好的習慣。
在你興奮的要開始使用Modules以前,不幸的是有一個小警告。Xcode5的Modules還不支持你本身的或者第三方的框架。這是一個不幸的缺點,沒有事情是完美的,即便是Objective-C!
Objective-C添加了一個新的返回類型,名字叫instancetype。這個僅僅被用做Objective-C方法的返回類型和對編譯器的一個暗示,暗示方法的返回類型將是這個方法屬於的類的實例。
注意事項: 這個特徵在iOS7和Xcode上沒有嚴格,可是隨着時間的推移會被悄悄的加進最近的Clang。然而Xcode5第一次聲明蘋果已經在他們的框架中使用了這個。你能夠再官方的Clang網頁看到更多的內容:http://clang.llvm.org/docs/LanguageExtensions.html#objective-c-features
爲何要使用instancetype呢?看看下面的代碼:
[cpp] view plaincopy
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實例呢?那是由於這個方法聲明以下:
[cpp] view plaincopy
+ (id)arrayWithObjects:(id)firstObj, ...;
注意到返回類型是id。id類型是一個意味着任何Objective-C類的umbrella類型。他甚至都不是NSObject的子類。方法沒有返回類 型信息而不是返回Objective-C類的實例。這樣作是有用的,當你隱式的轉換id到一個確切的類型時編譯器不會警告你。例如上面的 NSDictionary例子。若是產生警告,id就沒有用啦。
可是這個方法的返回類型爲何是id呢?你能夠子類化這個方法而後仍然沒有問題的使用它。爲了證實爲何,考慮下面的NSArray的子類:
[cpp] view plaincopy
@interface MyArray : NSArray
@end
如今考慮下你的子類在下面的代碼的使用:
[html] view plaincopy
MyArray *array = [MyArray arrayWithObjects:@(1), @(2), nil];
如今你應該知道爲何arrayWithObjects:返回類型必須是id。若是是NSArray*,這個子類須要轉化成必要的類。這就是新的 instancetype返回類型用到的地方。若是你看iOS7SDK中NSArray的頭文件,你將注意到這個方法變成了下面的樣子:
[cpp] view plaincopy
+ (instancetype)arrayWithObjects:(id)firstObj, ...;
惟一的不一樣就是返回類型。新的返回類型提示編譯器返回類型是方法被調用的類的實例。因此當arrayWithObjects:被調用的是NSArray時,返回類型是NSArray*。當調用的是MyArray時,返回類型是MyArray*。
當維護成功子類化的能力的時候,用id就會出現的問題。若是用Xcode5編譯原始的代碼,你會看到下面的警告:
[cpp] view plaincopy
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的痛苦時間。
接下來就是Objective-C核心開發框架Foundation的一些新東西。沒有Foundation很難開發Objective-C應用,全部的iOS Apps都須要使用。在新的iOS SDK中看看這些新添加的內容。
Foundation最主要的提高是網絡。(說的應該是NSURLSession)在iOS 7 by Tutorials 有一整章描述。(略)
文章剩下部分展現了Foundation新增長的和改變的東西。
嘗試在NSArray實例中訪問一個Object,若是下表越界將爆出異常。當你用數組當作隊列的時候,你可能常常要訪問數組中第一個或者最後一個元素。 在先進先出隊列(FIFO)你可能要從數組的前端POP元素,若是是先進後出隊列(FILO)就要從數組末尾POP元素。
然而,當你訪問數組的第一個或者最後一個元素的時候,你必定要肯定沒有超出數組的邊界,若是數組是空得話常常發生這樣的訪問。這就會致使在調用objectAtIndex:不報錯而產生冗餘的代碼,就像下面的這樣:
[cpp] view plaincopy
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的這個方法:
[cpp] view plaincopy
- (id)lastObject;
Objective-C開發者應該能夠高興了,如今他們有了一個方法來訪問數組的第一個元素:
[cpp] view plaincopy
- (id)firstObject;
簡單的方法老是被證實是有用的。你不在須要檢查數組是否是空的啦。你可能曾經遇到過因爲越界產生的Crash。你能夠看看下面的注意事項:
注意事項:若是你仔細的看NSArray頭文件,其 實firstObject在iOS4.0就已經出現啦,直到iOS7纔對外開放。所以你能夠在iOS7以前獲取這個方法,可是你必須在你本身的頭文件裏聲 明這個方法firstObject來告訴編譯器它確實存在。這不是一個提倡的方法,好歹Apple把這個方法公開了。
先前的代碼能夠用這兩個方法重寫,就不用檢查數組長度了,以下:
[cpp] view plaincopy
NSMutableArray *queue = [NSMutableArray new];
// ...
id firstObject = [queue firstObject];
// Use firstObject
id lastObject = [queue lastObject];
// Use lastObject
Data是你編程處理最多的事情。NSData是Foundation類,封裝了原始字節並提供方法操縱這些字節,能夠從一個文件讀或者寫數據。可是一個簡單的任務Base64編碼和解碼尚未原生的實現。直到iOS7纔出現。
Base64是一組二進制到文本轉換的方案,以ASCII格式提供二進制數據。這些方案用來編碼二進制數據以存儲或者經過把多媒體文件轉換成文本數據進行 傳輸。這個能保證數據在傳輸過程當中的完整性。Base64編碼的最多見的用途是處理電子郵件附件,或者編碼小圖片,這些小圖片是經過基於Web的API返 回的JSON相應的一部分。
在iOS7以前,Base64的 編碼和解碼是須要本身實現的或者使用第三方庫。典型的Apple風格,如今是很是容易的使用這個功能。有四個Base64方法以下:
[cpp] view plaincopy
- (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已經幫你實現了。
NSTimers在apps中常常用來執行週期性任務。NSTimer雖然頗有用可是也會產生問題。當有幾個定時器在用的時候,他們可能間斷性的觸發。 這就是意味着CPU是間斷性處於活動狀態的。這樣作是更加有效率的,當CPU換起的時候執行一些任務,而後進入睡眠狀態。爲了解決這個問題,Apple給 NSTimer添加了一個容忍屬性來適應這種行爲。
容忍提供系統一個指導在timer在計劃以後容許延遲多長時間。爲了減小CPU負荷底層系統將要集合這些活動。新屬性的方法是:
[cpp] view plaincopy
- (NSTimeInterval)tolerance;
- (void)setTolerance:(NSTimeInterval)tolerance;
你可能永遠都不須要用到這個屬性,可是當你在很是密切相近的觸發了幾個定時器,你可能發現他是有用的,當你在用Instruments檢測CPU使用率的時候。
不常常見到Foundation會完整的添加一個新類。他是一個穩定的框架。主要是由於不常常用到核心的類。然而iOS7提供了一個完整的新類NSProgress。
本質上,NSProgress是用來經過Objective-C代碼產生進度報告的,分離每個獨立模塊的進度。例如,你能夠在一些數據上執行幾個不一樣的任務,而後每一個任務能夠管理他本身的進度而後報告給他的父任務。
NSProgress最簡單的使用方法是報告一些任務集合的進度。例如,你有10個任務執行,當每一個任務完成的時候你能夠報告進度。當有一個任務完成的時 候進度增長%10。而後在NSProgress的實例上使用Key Value Observing(KVO),你可以瞭解到這個實例的進度。你可使用這個通知來更新進度條或者顯示一個指示文字。
NSProgress有更多的用途。Apple經過這個父子類的關係結構使他更增強大。NSProgress的結構更像是網狀樹。每個 NSProgress有一個父類和多個子類。每個實例有一個執行的工做單元的總數,當前任務會處理完成的子任務數的更新來反饋當前狀態。這麼作的話,父 類也會被通知進度。
爲了減小NSProgress實例的傳遞,每一個線程有本身的NSProgress實例而後子實例能夠直接從這個實例建立。沒有這個功能,每一個想要報告進度的任務不得不經過參數的方式來通知。
NSProgress使用很是簡單。如下面的方法開始:
[cpp] view plaincopy
+(NSProgress *)progressWithTotalUnitCount:(int64_t)unitCount;
這個方法建立了一個NSProgress實例做爲當前實例的子類,以要執行的任務單元總數來初始化。例如,若是任務是循環一個數組,而後你可能用數組數來初始化NSProgress實例。例如:
[cpp] view plaincopy
NSArray*array = /* ... */;
NSProgress*progress =
[NSProgressprogressWithTotalUnitCount:array.count];
[arrayenumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop) {
// Perform an expensive operation onobj
progress.completedUnitCount = idx;
}];
隨着迭代的進行,上面的代碼會更新NSProgress實例來反映當前進度。
你能夠經過下面的屬性在任什麼時候候獲取任務進度:
[cpp] view plaincopy
@property(readonly) double fractionCompleted;
返回值是0到1,顯示了任務的總體進度。當沒有子實例的話,fractionCompleted就是簡單的完成任務數除以總得任務數。
Key Value Observing(KVO)是最好的方法來獲取fractionCompleted值得變化。這麼作很是簡單。你只須要作的是添加一個NSProgress的fractionCompleted屬性的觀察者。像下面這樣:
[cpp] view plaincopy
[_progressaddObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
而後覆蓋KVO的這個方法來獲取改變:
[cpp] view plaincopy
-(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是很重要的。
[cpp] view plaincopy
[_progressremoveObserver:self
forKeyPath:@"fractionCompleted"
context:NULL];
你必須老是要註銷的,若是你沒有註銷,當被註冊的Object釋放的時候就會Crash。因此若是必要的話在dealloc中註銷做爲最後的保障。