本隨筆系列主要介紹從一個Windows平台從事C#開發到Mac平台蘋果開發的一系列感想和體驗歷程,本系列文章是在起步階段逐步積累的,希望帶給大家更好,更真實的轉換歷程體驗。本文繼續上一篇隨筆《從C#到Objective-C,循序漸進學習蘋果開發(3)--分類(category)和協議Protocal的理解》,繼續對比介紹它們兩者之間的差異,以便我們從C#陣營過來的人員加深印象,深入了解Objective-C語言的特性。本篇隨筆主要針對Objective-C里面的代碼塊(block)和異常處理概念的理解進行介紹。
1、Object C的代碼塊(block)
Objective-C的代碼塊從剛剛學習的時候,感覺有點奇怪,慢慢感覺它在C#里面也有點熟悉,它在Objective-C里面的引入,好像是主要用來解決代碼回調和同步調用的問題的,說到這里,如果熟悉C#的特性的,可能會聯想到了C#里的Action<T>和Func<T>的概念了吧,沒錯,他們就是一丘之貉,哈哈。
代碼塊本質上是和其他變量類似。不同的是,代碼塊存儲的數據是一個函數體。使用代碼塊是,你可以像調用其他標准函數一樣,傳入參數數,並得到返回值,字符(^)是代碼塊的語法標記。
如下面的例子就是一個代碼塊的定義
void (^simpleBlock)(void) = ^{ NSLog(@"This is a block"); };
定義后,你就可以通過類似函數的方式進行使用了,看了下面的代碼是不是感覺很熟悉的樣子呢。
simpleBlock();
當然,對於這樣的東西,它也是可以接受參數的,即使是多個參數也沒問題,這個如果是帶參數的,應該就是和C#的Func<T>很相似了,下面是一個兩個參數的代碼塊例子。
double (^multiplyTwoValues)(double, double) = ^(double firstValue, double secondValue) { return firstValue * secondValue; }; double result = multiplyTwoValues(2,4);
這樣的代碼塊,它還可以獲取類里面定義的局部變量,但是由於它的特殊性,好像如果不加特殊處理,它獲取到的變量或者屬性的值,是在它出現的那瞬間的快照。
下面一個例子,很好介紹代碼塊里面獲取內容是快照的現實。
int anInteger = 42; void (^testBlock)(void) = ^{ NSLog(@"Integer is: %i", anInteger); }; anInteger = 84; testBlock();
上面代碼塊里面,打印出來的值,是42,而非84,因為它在代碼塊出現的那瞬間,就拿到了局部變量,之后就沒有跟隨大部隊變化了。
那這種方式有無變通的方法,讓它可以根據變量的變化而自動變化呢?當然有了,需要特殊處理即可,答案就是使用__block進行標識,它就可以跟隨大部隊的步伐了。
如果上面的代碼塊里面變量的定義使用了這個關鍵字,那么值就似乎84了,如下代碼塊所示。
__block int anInteger = 42; void (^testBlock)(void) = ^{ NSLog(@"Integer is: %i", anInteger); }; anInteger = 84; testBlock();
這個__block功能很強大,告訴編譯器,它可以獲取變量的最新值,也可以在代碼塊里面對值進行修改(危險嗎?反正你知道就好)
前面說道了,Objective-C里面的代碼塊類似c#里面lambda的Action 和 Func 那么舉個例子來大致介紹下把。
對比一下下面兩組代碼,其一是Objective-C的代碼塊
typedef void (^MethodBlock)(int); - (void) fooWithBlock:(MethodBlock)block { int a = 5; block(a); } - (void) regularFoo { [self fooWithBlock:^(int val) { NSLog(@"%d", val); }]; }
接着是C#里面的代碼例子,感覺它們很接近吧。在這里,你可能會感嘆,編程語言這個世界里,很小,世界都趨向於大同了。
void Foo(Action<int> m) { int a = 5; m(a); } void RegularFoo() { Foo(val => // Or: Foo(delegate(int val) { Console.WriteLine(val); }); }
不過代碼塊的使用,你會慢慢感覺它雖然很強大,但是很多地方也不是很容易理解,畢竟對於我們這些入門沒有很深根基的人來說,要慢慢消化。
再來看看下面這個例子代碼,這個方法里面的代碼塊定義,很有意思。
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock { ... callbackBlock(); }
再來看看下面這個代碼塊,你可能會更暈,沒事,暈了就對了,說明你是一個正常的人。
void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) { ... return ^{ ... }; };
最后記得,如果是一個方法有多個參數,記得把代碼塊的參數放到最后來定義。
- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;
至於代碼塊如何簡化同步調用的問題,讓給讀者自己去了解研究了,我感覺也有點頭暈了。哈哈。
2、Object C的錯誤及異常處理
我們知道,在開發各種應用程序或者系統的時候,錯誤肯定難以避免,有效處理錯誤異常就是你一個很有必要的內容。在C#里面,我們如果需要拋出異常,我們使用throw方法進行,所有的錯誤都以異常對象Exception作為基類進行擴展,包括各種各樣的異常對象,而對錯誤異常的捕捉是通過try {} catch(Exception ex) finally {}這樣的代碼或者類似處理進行的,對於Objective-C來說,它又是如何處理錯誤異常的呢?
其實Objective-C對錯誤處理的機制也差不多,它對異常的支持包括四個編譯器指令: @try
, @catch
, @throw
以及 @finally。是不是又一次感覺到語言的大同了,這個東西和C#的處理幾乎沒有什么差別。
另外Objective-C還引入了一個NSError的東西,這個東西和NSException有什么關系呢?這個東西有點類似於我們在C#開發的時候,增加一個out的輸入參數,用來把程序內部的錯誤信息傳遞出去,然后交給調用者,讓它們愛怎么用就怎么用,反正我處理完成了,有無錯誤我都告訴你了。由於NSError可以傳遞的信息比較豐富,一般來說這樣對程序的處理也很方便。
如網絡連接的異常,你可以通過下面的代碼把它傳遞出來。
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
下面我們來看看一個寫文件的錯誤如何處理的,首先定義一個函數,包含了NSError的參數的,注意一般這個參數是放到最后的,這點好像和我們有些這樣處理的C#約定也是一樣的。
- (BOOL)writeToURL:(NSURL *)aURL
options:(NSDataWritingOptions)mask
error:(NSError **)errorPtr;
那我們調用這個writeToURL的函數的時候,有錯誤發生就應該處理,錯誤發生的時候,它執行完畢了,並且返回一個NO的值
NSError *anyError; BOOL success = [receivedData writeToURL:someLocalFileURL options:0 error:&anyError]; if (!success) { NSLog(@"Write failed with error: %@", anyError); // present error to user }
為了表示錯誤的了來源,NSError有一個domain的屬性,約定一般以公司的名稱(或特別的名稱)來進行區分。
com.iqidi.appOrFrameworkName.ErrorDomain
如構造一個NSError的代碼大概如下所示。
NSString *domain = @"com.iqidi.MyApplication.ErrorDomain"; NSString *desc = NSLocalizedString(@"Unable to…", @""); NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc }; NSError *error = [NSError errorWithDomain:domain code:-101 userInfo:userInfo];
而常規的異常,我們一般還是通過NSException進行處理,異常就是發生問題的時候,停下來第一時間請示如何處理,如果有處理的線路就按照處理的線路進行,否則就一級級往上推了。
它的處理和C#差不多,我們都很熟悉了,代碼結構如下所示。
@try { // code that throws an exception ... } @catch (CustomException *ce) { // most specific type // handle exception ce ... } @catch (NSException *ne) { // less specific type // do whatever recovery is necessary at his level ... // rethrow the exception so it's handled at a higher level @throw; } @catch (id ue) { // least specific type // code that handles this exception ... } @finally { // perform tasks necessary whether exception occurred or not ... }
異常的構造和拋出代碼和C#的也很類似
NSException* myException = [NSException exceptionWithName:@"FileNotFoundException" reason:@"File Not Found on System" userInfo:nil]; @throw myException;
如果在處理異常的時候,需要處理一些對象的內存釋放,那么一般是把它放到@finally包含的代碼塊里面。
這個和C#類似,雖然C#不會需要處理內存的釋放問題,但是對於一些耗時的操作對象,如Connection,一般最好也放到finally里面確保關閉,處理類似。
- (void)doSomething { NSMutableArray *anArray = nil; array = [[NSMutableArray alloc] initWithCapacity:0]; @try { [self doSomethingElse:anArray]; } @finally { [anArray release]; } }
如果要拋出原汁原味的異常,這點也和C#相似,通過@throw;方法即可。
@try { NSException *e = [NSException exceptionWithName:@"FileNotFoundException" reason:@"File Not Found on System" userInfo:nil]; @throw e; } @catch(NSException *e) { @throw; // rethrows e implicitly }
看到這里,發現大多數的處理機制和語法使用,和C#並無太多的不同,我們了解就好,具體碰到什么問題,在查看下幫助文檔,水來土掩,洗洗睡去吧。