1. 程式人生 > >深入Objective-C的動態特性

深入Objective-C的動態特性

目錄(?)[-]

  1. 動態語言基礎
  2. 深入執行時

 Objective-C有相當多的動態特性,基本上也是最常用的有動態型別(Dynamic typing)、動態繫結(Dynamic binding)和動態載入(Dynamic loading),這些都是在Cocoa程式開發中非常常用的語言特性,在此之後OC底層也提供了相當豐富的執行時特性,比如列舉類屬性方法、獲取方法實現等等。雖然在平常的Cocoa開發中這些底層的執行特性基本用不著,但是在某些情況下如果你知道這些特性併合理加以運用的話,往往能事半功倍。

  動態語言基礎:

  1.動態型別

  也就是執行時再決定物件的型別。這類動態特性在日常應用中非常常見,簡單的說就是id型別。id型別即通用的物件類,任何物件都可以被id指標指向,而在實際應用中往往使用introspection(內省)來確定該物件實際所屬類:

id obj = someInstance;//id指標指向了obj物件
if ([obj isKindOfClass:someClass])//採用內省來判斷這個obj物件的確切型別也可以使用isMemberOfClass方法(NSObject方法)
{
    someClass *classSpecifiedInstance  = (someClass *)obj;//對obj進行安全的強制型別轉換,並且用一個確定型別的指標來指向它         
}

  2.動態繫結

  基於動態型別,在某個例項物件被確定後,其型別便被確定了。該物件對應的屬性和相應訊息也被完全確定了,這就是動態繫結的實質。在繼續之前,需要明確Objective-C中的訊息機制。由於OC的動態特性,OC中很少提及"函式"的概念,傳統的函式一般在編譯時就已經把引數資訊和函式實現打包到編譯後的原始碼中了。而在OC中最常使用的是訊息機制。呼叫一個例項的方法,所做的是向該例項的指標傳送訊息,例項在接收到訊息之後,從自身的實現中尋找響應這條訊息的方法。

  動態繫結所做的,即是在例項所屬的類確定後,將某些屬性和相應的方法繫結到例項上。這裡所指的屬性和方法當然包括了原來沒有在類中實現的,而是在執行時才需要的新加入的實現。在Cocoa層次上,我們一般會向一個NSObject物件傳送-respondToSelector:或者-instancesRespondToSelector:來確定物件是否可以對某個SEL做出相應,而在OC轉發訊息機制被觸發之前,對應的類的+resolveClassMethod:和+resolveInstanceMethod:將會被呼叫,在此時有機會動態地向類或者例項新增新的方法,也就是類的實現是可以動態繫結的。

  3.動態載入

  根據需求載入所需的資源,對於iOS開發來說基本就是根據不同的機型來適配。最經典的例子就是在Retina裝置上載入@2x的圖片,而在老一些的普通屏裝置上載入原圖。

  深入執行時

  基本的動態特性在常規的Cocoa開發中非常常用,特別是動態型別和動態繫結。由於Cocoa程式大量地使用Protocol-Delegate的設計模式,因此大部分的delegate指標型別必須是id,以滿足執行時delegate的動態替換。而OC中還有一些高階或者說更加底層的執行時特性,在一般的Cocoa開發中較為少見,基本被運用在OC和其他語言的介面上。但是如果有所瞭解並且使用得當的話,在Cocoa中往往可以輕易解決棘手的問題。

  這類執行時特性大多由/usr/lib/libobjc.A.dylib這個動態庫提供,裡面包括對類、例項成員、成員方法和訊息傳送的很多的API,包括獲取類例項變數列表,替換類中方法,為類成員新增變數,動態改變方法實現等等,十分強大。雖然文件開頭表明是對於Mac OS X Objective-C 2.0適用,但是由於這些是OC的底層方法,因此對於iOS開發來說也是完全相同的。

  舉一個簡單的例子。比如在進行Universal應用或者遊戲時,如果使用IB構建大量的自定義的UI,那麼iPhone版本轉向iPad版本的過程中面臨的一個重要問題就是如何從不同的nib中載入介面。在iOS5以前,所有的UIViewController在使用預設的頁面載入時(init 或者initWithNibName:),都會走-loadNibNamed:owner:options:。而因為我們無法拿到-loadNibNamed:owner:options:的實現,因此對其過載是比較困難而且存在風險的。因此在做iPad版本的nib時,一個簡單的辦法是將所有的nib的命名方式統一,然後使用自己實現的新的類似-loadNibNamed:owner:options:的方法將原方法替換掉,同時保證非iPad的裝置還走原來的loadNibNamed:owner:options:方法。使用OC執行時特性可以較簡單地完成這一任務。

  程式碼如下,在程式執行時呼叫+swizze,交換自己實現的loadNibNamed:owner:options:和系統的loadNibNamed:owner:options:,之後所有的loadNibNamed:owner:options:訊息都將會發為loadNibNamed:owner:options:,由自己的程式碼進行處理。

  

1 2 3 4 5 6 +( BOOL )swizze {      Method oldMethod = class_getInstanceMethod( self , @selector (loadNibNamed:owner:options:)); //<span style="font-family: 仿宋; font-size: 15px;">取出系統的實現方法,儲存為oldMethod</span>      if  (!oldMethod) { //<span style="font-family: 仿宋;">如果沒有這個方法直接返回NO</span>          return  NO ;      }      Method newMethod = class_getInstanceMethod( self , @selector (loadPadNibNamed:owner:options:));<span style= "font-family: 仿宋;" > //取出自定義的實現方法,儲存為newMethod</span>
1 if  (!newMethod) { return  NO ; } method_exchangeImplementations(oldMethod, newMethod); return  YES ; } //<span style="font-family: 仿宋; font-size: 15px;">用自定義的方法實現來取代系統的方法實現</span>

 

  loadNibNamed:owner:options的實現如下,注意在其中的loadNibNamed:owner:options由於之前已經進行了交換,因此實際會發送為系統的loadNibNamed:own

1 2 3 4 5 6 7 8 9 10 11 12 13 -( NSArray  *)loadPadNibNamed:( NSString  *)name owner:( id )owner options:( NSDictionary  *)options {      NSString  *newName = [name stringByReplacingOccurrencesOfString:@ "@pad"  withString:@ "" ];      newName = [newName stringByAppendingFormat:@ "@pad" ];      //判斷是否存在      NSFileManager  *fm = [ NSFileManager  defaultManager];      NSString * filepath = [[ NSBundle  mainBundle] pathForResource:newName ofType:@”nib”];      //這裡呼叫的loadPadNibNamed:owner:options:實際為為交換後的方法,即loadNibNamed:owner:options:      if  ([fm fileExistsAtPath:filepath]) {          return  [ self  loadPadNibNamed:newName owner:owner options:options];      } else  {          return  [ self  loadPadNibNamed:name owner:owner options:options];      }

 Objective-C有相當多的動態特性,基本上也是最常用的有動態型別(Dynamic typing)、動態繫結(Dynamic binding)和動態載入(Dynamic loading),這些都是在Cocoa程式開發中非常常用的語言特性,在此之後OC底層也提供了相當豐富的執行時特性,比如列舉類屬性方法、獲取方法實現等等。雖然在平常的Cocoa開發中這些底層的執行特性基本用不著,但是在某些情況下如果你知道這些特性併合理加以運用的話,往往能事半功倍。

  動態語言基礎:

  1.動態型別

  也就是執行時再決定物件的型別。這類動態特性在日常應用中非常常見,簡單的說就是id型別。id型別即通用的物件類,任何物件都可以被id指標指向,而在實際應用中往往使用introspection(內省)來確定該物件實際所屬類:

id obj = someInstance;//id指標指向了obj物件
if ([obj isKindOfClass:someClass])//採用內省來判斷這個obj物件的確切型別也可以使用isMemberOfClass方法(NSObject方法)
{
    someClass *classSpecifiedInstance  = (someClass *)obj;//對obj進行安全的強制型別轉換,並且用一個確定型別的指標來指向它         
}

  2.動態繫結

  基於動態型別,在某個例項物件被確定後,其型別便被確定了。該物件對應的屬性和相應訊息也被完全確定了,這就是動態繫結的實質。在繼續之前,需要明確Objective-C中的訊息機制。由於OC的動態特性,OC中很少提及"函式"的概念,傳統的函式一般在編譯時就已經把引數資訊和函式實現打包到編譯後的原始碼中了。而在OC中最常使用的是訊息機制。呼叫一個例項的方法,所做的是向該例項的指標傳送訊息,例項在接收到訊息之後,從自身的實現中尋找響應這條訊息的方法。

  動態繫結所做的,即是在例項所屬的類確定後,將某些屬性和相應的方法繫結到例項上。這裡所指的屬性和方法當然包括了原來沒有在類中實現的,而是在執行時才需要的新加入的實現。在Cocoa層次上,我們一般會向一個NSObject物件傳送-respondToSelector:或者-instancesRespondToSelector:來確定物件是否可以對某個SEL做出相應,而在OC轉發訊息機制被觸發之前,對應的類的+resolveClassMethod:和+resolveInstanceMethod:將會被呼叫,在此時有機會動態地向類或者例項新增新的方法,也就是類的實現是可以動態繫結的。

  3.動態載入

  根據需求載入所需的資源,對於iOS開發來說基本就是根據不同的機型來適配。最經典的例子就是在Retina裝置上載入@2x的圖片,而在老一些的普通屏裝置上載入原圖。

  深入執行時

  基本的動態特性在常規的Cocoa開發中非常常用,特別是動態型別和動態繫結。由於Cocoa程式大量地使用Protocol-Delegate的設計模式,因此大部分的delegate指標型別必須是id,以滿足執行時delegate的動態替換。而OC中還有一些高階或者說更加底層的執行時特性,在一般的Cocoa開發中較為少見,基本被運用在OC和其他語言的介面上。但是如果有所瞭解並且使用得當的話,在Cocoa中往往可以輕易解決棘手的問題。

  這類執行時特性大多由/usr/lib/libobjc.A.dylib這個動態庫提供,裡面包括對類、例項成員、成員方法和訊息傳送的很多的API,包括獲取類例項變數列表,替換類中方法,為類成員新增變數,動態改變方法實現等等,十分強大。雖然文件開頭表明是對於Mac OS X Objective-C 2.0適用,但是由於這些是OC的底層方法,因此對於iOS開發來說也是完全相同的。

  舉一個簡單的例子。比如在進行Universal應用或者遊戲時,如果使用IB構建大量的自定義的UI,那麼iPhone版本轉向iPad版本的過程中面臨的一個重要問題就是如何從不同的nib中載入介面。在iOS5以前,所有的UIViewController在使用預設的頁面載入時(init 或者initWithNibName:),都會走-loadNibNamed:owner:options:。而因為我們無法拿到-loadNibNamed:owner:options:的實現,因此對其過載是比較困難而且存在風險的。因此在做iPad版本的nib時,一個簡單的辦法是將所有的nib的命名方式統一,然後使用自己實現的新的類似-loadNibNamed:owner:options:的方法將原方法替換掉,同時保證非iPad的裝置還走原來的loadNibNamed:owner:options:方法。使用OC執行時特性可以較簡單地完成這一任務。

  程式碼如下,在程式執行時呼叫+swizze,交換自己實現的loadNibNamed:owner:options:和系統的loadNibNamed:owner:options:,之後所有的loadNibNamed:owner:options:訊息都將會發為loadNibNamed:owner:options: