Objective C運行時(runtime)

詳解objc_msgSendhtml

 一、爲了性能,objc_msgSend用匯編寫成。存在於objc-msg-x86_64.s中。ios

  

 二、在上圖代碼中能夠看到,objc_msgSend被分爲2個過程:1)在cache中尋找SEL。2)在MethodTable尋找SEL。git

 三、CacheLookup中,不斷地拿SEL與cache中的緩存比較,比較失敗,則跳轉到 LCacheMiss標籤繼續在MethodTable中搜索。github

  

  若是想手動查找cache,則須要調用_cache_getimp函數(彙編實現),此函數是個對外接口層,用於保存與準備環境。objective-c

  

  _cache_getImp在頭文件中objc-private.h中,連接後objc/c代碼能夠直接調用。編程

  

 四、MethodTableLookup 是個接口層宏,主要用於保存環境與準備參數,來調用 __class_lookupMethodAndLoadCache3函數(此函數實現於objc-class.mm)。windows

  

 五、__class_lookupMethodAndLoadCache3函數也是個接口層(C編寫),此函數提供相應參數配置,實際功能在lookUpMethod函數中。數組

  

 六、lookUpMethod函數實現遍歷method_list_t,從子類開始,一直遍歷到根類。此函數代碼較大,不貼圖了。文件在objc-class中。緩存

Cache Of lookUpMethod數據結構

  To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used. There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class. Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again). If the method selector is in the cache, messaging is only slightly slower than a function call. Once a program has been running long enough to 「warm up」 its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.

#import <objc/runtime.h>
void setBeingRemoved(id __self, SEL _cmd)
{
    NSLog(@"------------UNSELECT BY INVOKE.");
}

//    Then these two lines:
    
    Class __class = NSClassFromString(@"WebActionDisablingCALayerDelegate");
    class_addMethod(__class,
                    @selector(willBeRemoved),
                    (IMP)setBeingRemoved,
                    NULL);

    class_addMethod(__class,
                    @selector(removeFromSuperview),
                    (IMP)setBeingRemoved,
                    NULL);

  

  class_addMethod的詳解

Obj-C用起來真是各類happy,好比如今有這樣一種狀況:有一個類,咱們但願它能響應一個消息(message),可是這個類沒有相應的方法(method),而你又恰恰不能重寫/繼承這個類。這時咱們可能會想到,能不能動態地給類添加一個方法呢?感謝Obj-C,僅需簡單幾步就能實現。

先看一段代碼

#if TARGET_IPHONE_SIMULATOR 
#import  
#else #import  
 #import  
#endif   
@interface EmptyClass:NSObject   
 @end   
 
 @implementation EmptyClass   
@end   
 
void sayHello(id self, SEL _cmd) 
 { NSLog(@"Hello"); }   
 
- (void)addMethod 
 { 
     class_addMethod([EmptyClass class], @selector(sayHello2), (IMP)sayHello, "v@:");   
     // Test Method 
    EmptyClass *instance = [[EmptyClass alloc] init]; [instance sayHello2];
    [instance release];   
}
咱們首先定義了一個EmptyClass,繼承NSObject,沒有任何自帶方法,接着定義了一個函數。這裏提一句,Obj-C的方法(method)就是一個至少須要兩個參數(self,_cmd)的C函數,這個函數僅僅輸出一句Hello。接下來在addMethod方法中,咱們調用class_addMethod()爲EmptyClass添加方法,class_addMethod()是這樣定義的:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

參數說明:

cls:被添加方法的類

name:能夠理解爲方法名,這個貌似隨便起名,好比咱們這裏叫sayHello2

imp:實現這個方法的函數

types:一個定義該函數返回值類型和參數類型的字符串,這個具體會在後面講

接着建立EmptyClass的實例,調用sayHello2,運行,輸出Hello,添加方法成功。

接下來講一下types參數,
好比咱們要添加一個這樣的方法:-(int)say:(NSString *)str;
相應的實現函數就應該是這樣:

int say(id self, SEL _cmd, NSString *str) 
{ 
    NSLog(@"%@", str); 
    return 100;//隨便返回個值 
 } 

class_addMethod這句就應該這麼寫:

1
class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "i@:@");

其中types參數爲"i@:@「,按順序分別表示:

i:返回值類型int,如果v則表示void

@:參數id(self)

::SEL(_cmd)

@:id(str)

這些表示方法都是定義好的(Type Encodings),關於Type Encodings的其餘類型定義請參考官方文檔

最後調用say:方法:

 
int a = [instance say:@"something"]; NSLog(@"%d", a);

輸出something和100。

關於本文所涉及內容的詳細信息請參考Objective-C Runtime Reference

本文參考了:

推薦去看看

 

Fun with Objective-C

Dynamic Subclassing

 

 

If you’ve seen my github.com page or my portfolio link, you’re probably aware of a project of mine called CHLayoutManager. CHLayoutManager, for those of you not in-the-know, is a way to define the layout of a user interface on the Mac via constraints (as opposed to autoresizing masks). For example, if you have two buttons 「A」 and 「B」, you can say 「I want the right edge of button ‘A’ to always stay 10 points to the left of the left edge of button 'B’」. Then, as button 「B」 moves around (via autoresizing masks or positioning it programmatically), button 「A」 automatically moves as well. If you do a lot of programmatic UI layouting, this can be insanely useful. In fact, a couple forthcoming products from Mozy will be using CHLayoutManager in them.

Internally, there’s a singleton object called the CHLayoutManager. This object is the one responsible for noticing when a view changes its frame, and then also determining if any other views need to change because of it. This layout manager also has an NSMapTable for storing all of the constraint information related to views. The key of the map table is the view itself, and the value is a container for storing the constraints and the layout name. Because this is supposed to operate silently in the background, the map table maintains a weak reference on the view, and a strong reference to the constraint container. That means if you’re using garbage collection and a view is deallocated, the garbage collector will automatically clean up the entry in the map table, and all will be well in the world.

However, if you’re not using garbage collection, some interesting behavior can crop up. For example, let’s say the map table contains a number of key-value pairs, and some of the keys point to views that have been deallocated (ie, the pointers are invalid). At some point in the future, when you attempt to add a constraint to a new view, the map table will need to resize itself. This means that it will reorganize its contents. Part of this step involves invoking -hash on each key.

But wait. Some of the keys are invalid pointers. If you try to send a message to a deallocated object, you’re (almost definitely) going to crash. Herein lies the crux of the problem: how can we ensure that each view automatically gets cleaned up from the map table without requiring the programmer to do it manually, and without relying on garbage collection?

The answer: dynamic subclassing.

When the user adds a constraint to a view, the layout manager is going to introspect this view and see if the view needs to be altered. If it does, then the manager is going to create a new class that’s a subclass of the view’s class. To this new class gets added a new method: a custom -dealloc method. This method performs the cleanup of the view’s constraints (and other layout information), then invokes [super dealloc]. Once we’ve created this subclass, we simply change the class of the view, and we’re good to go.

What does this look like? Like this:

- (void) dynamicallySubclassView:(NSView *)view {
  const char * prefix = "CHLayoutAutoremove_";
  Class viewClass = [view class];
  NSString * className = NSStringFromClass(viewClass);
  if (strncmp(prefix, [className UTF8String], strlen(prefix)) == 0) { return; }

  NSString * subclassName = [NSString stringWithFormat:@"%s%@", prefix, className];
  Class subclass = NSClassFromString(subclassName);

  if (subclass == nil) {
    subclass = objc_allocateClassPair(viewClass, [subclassName UTF8String], 0);
    if (subclass != nil) {
      IMP dealloc = class_getMethodImplementation([self class], @selector(dynamicDealloc));

      class_addMethod(subclass, @selector(dealloc), dealloc, "v@:");
      objc_registerClassPair(subclass);
    }
  }

  if (subclass != nil) {
    object_setClass(view, subclass);
  }
}

Here you can see what’s going on:

  1. Extract the view’s class
  2. See if the name of this class begins with the prefix used to indicate one of these dynamic subclasses
  3. If it doesn’t have the prefix, then build the name of the new class (+[NSString stringWithFormat:])
  4. Look in the runtime to see if a class of this name already exists
  5. If it doesn’t exist, create the class using objc_allocateClassPair()
  6. Add the custom -dealloc method to the new subclass
  7. Register the class with the runtime
  8. If everything went well, set the class of the view to the new subclass

So if you have an NSPopUpButton and add some constraints to it, it’s actually going to be an CHLayoutAutoremove_NSPopUpButton.

This is, incidentally, how Key-Value Observing is implemented in Cocoa and Cocoa Touch.

Isn’t Objective-C fun?

 

  1. funwithobjc posted this
Short URL for this post:  http://tmblr.co/Zt522y1OOOZz

 

Objective C運行時(runtime)技術的幾個要點總結

 

前言:

         Objective C的runtime技術功能很是強大,可以在運行時獲取並修改類的各類信息,包括獲取方法列表、屬性列表、變量列表,修改方法、屬性,增長方法,屬性等等,本文對相關的幾個要點作了一個小結。

目錄:

(1)使用class_replaceMethod/class_addMethod函數在運行時對函數進行動態替換或增長新函數

(2)重載forwardingTargetForSelector,將沒法處理的selector轉發給其餘對象

(3)重載resolveInstanceMethod,從而在沒法處理某個selector時,動態添加一個selector

(4)使用class_copyPropertyList及property_getName獲取類的屬性列表及每一個屬性的名稱

  (5) 使用class_copyMethodList獲取類的全部方法列表

  (6) 總結

 

(1)在運行時對函數進行動態替換 : class_replaceMethod

      使用該函數能夠在運行時動態替換某個類的函數實現,這樣作有什麼用呢?最起碼,能夠實現相似windows上hook效果,即截獲系統類的某個實例函數,而後塞一些本身的東西進去,好比打個log什麼的。

示例代碼:

複製代碼
IMP orginIMP;
NSString * MyUppercaseString(id SELF, SEL _cmd)
{
    NSLog(@"begin uppercaseString");
    NSString *str = orginIMP (SELF, _cmd);(3)
    NSLog(@"end uppercaseString");
    return str;
}
-(void)testReplaceMethod
{
      Class strcls = [NSString class];
      SEL  oriUppercaseString = @selector(uppercaseString);
      orginIMP = [NSStringinstanceMethodForSelector:oriUppercaseString];  (1)  
      IMP imp2 = class_replaceMethod(strcls,oriUppercaseString,(IMP)MyUppercaseString,NULL);(2)
      NSString *s = "hello world";
      NSLog(@"%@",[s uppercaseString]];
}
複製代碼

執行結果爲:

begin uppercaseString

end uppercaseString

HELLO WORLD

這段代碼的做用就是

(1)獲得uppercaseString這個函數的函數指針存到變量orginIMP中

(2)將NSString類中的uppercaseString函數的實現替換爲本身定義的MyUppercaseString

(3)在MyUppercaseString中,先執行了本身的log代碼,而後再調用以前保存的uppercaseString的系統實現,這樣就在系統函數執行以前加入本身的東西,後面每次對NSString調用uppercaseString的時候,都會打印出log來

 與class_replaceMethod相仿,class_addMethod能夠在運行時爲類增長一個函數。

(2)當某個對象不能接受某個selector時,將對該selector的調用轉發給另外一個對象- (id)forwardingTargetForSelector:(SEL)aSelector

     forwardingTargetForSelector是NSObject的函數,用戶能夠在派生類中對其重載,從而將沒法處理的selector轉發給另外一個對象。仍是以上面的uppercaseString爲例,若是用戶本身定義的CA類的對象a,沒有uppercaseString這樣一個實例函數,那麼在不調用respondSelector的狀況下,直接執行[a performSelector:@selector"uppercaseString"],那麼執行時必定會crash,此時,若是CA實現了forwardingTargetForSelector函數,並返回一個NSString對象,那麼就相對於對該NSString對象執行了uppercaseString函數,此時就不會crash了。固然實現這個函數的目的並不只僅是爲了程序不crash那麼簡單,在實現裝飾者模式時,也可使用該函數進行消息轉發。

示例代碼:

複製代碼
 1 @interface CA : NSObject
 3 -(void)f;
 4 
 5 @end
 6 
 7 @implementation CA
 8 
 9 - (id)forwardingTargetForSelector:(SEL)aSelector
11 {
13     if (aSelector == @selector(uppercaseString))
15     {
17         return@"hello world";
19     }
21 }
複製代碼

 

測試代碼:

CA *a = [CA new];

 NSString * s = [a performSelector:@selector(uppercaseString)];

NSLog(@"%@",s);

 

測試代碼的輸出爲:HELLO WORLD 

 ps:這裏有個問題,CA類的對象不能接收@selector(uppercaseString),那麼若是我在forwardingTargetForSelector函數中用class_addMethod給CA類增長一個uppercaseString函數,而後返回self,可行嗎?通過試驗,這樣會crash,此時CA類其實已經有了uppercaseString函數,可是不知道爲何不能調用,若是此時new一個CA類的對象,並返回,是能夠成功的。

(3)當某個對象不能接受某個selector時,向對象所屬的類動態添加所需的selector

+ (BOOL) resolveInstanceMethod:(SEL)aSEL 

     這個函數與forwardingTargetForSelector相似,都會在對象不能接受某個selector時觸發,執行起來略有差異。前者的目的主要在於給客戶一個機會來向該對象添加所需的selector,後者的目的在於容許用戶將selector轉發給另外一個對象。另外觸發時機也不徹底同樣,該函數是個類函數,在程序剛啓動,界面還沒有顯示出時,就會被調用。

     在類不能處理某個selector的狀況下,若是類重載了該函數,並使用class_addMethod添加了相應的selector,並返回YES,那麼後面forwardingTargetForSelector就不會被調用,若是在該函數中沒有添加相應的selector,那麼無論返回什麼,後面都會繼續調用forwardingTargetForSelector,若是在forwardingTargetForSelector並未返回能接受該selector的對象,那麼resolveInstanceMethod會再次被觸發,這一次,若是仍然不添加selector,程序就會報異常

複製代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
代碼示例一:
 
  @implementation  CA
  void  dynamicMethodIMP( id  self SEL  _cmd)
  5 {   
  7      printf( "SEL %s did not exist\n" ,sel_getName(_cmd));
  9 }
10
11 + ( BOOL ) resolveInstanceMethod:( SEL )aSEL
13 {
15      if  (aSEL ==  @selector (t))
17     {
19         class_addMethod([selfclass], aSEL, (IMP) dynamicMethodIMP,  "v@:" );
21          return  YES ;
23     }
25      return  [superresolveInstanceMethod:aSEL];
27 }
28
29  @end
  
 
測試代碼:
 
   CA * ca = [CA  new ]
 
   [ca performSelector: @selector (t)]; 
複製代碼

  

 

執行結果

   SEL t did not exist

 

複製代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
示例代碼二:
 
@implementation  CA
 
void  dynamicMethodIMP( id  self SEL  _cmd)
{
     printf( "SEL %s did not exist\n" ,sel_getName(_cmd));
}
 
+ ( BOOL ) resolveInstanceMethod:( SEL )aSEL
{
     return   YES ;
}
- ( id )forwardingTargetForSelector:( SEL )aSelector
{
     if  (aSelector ==  @selector (uppercaseString))
     {
         return  @ "hello world" ;
     }
}
測試代碼 :
 
  a = [[CA alloc]init];
  NSLog (@ "%@" ,[a performSelector: @selector (uppercaseString)];
複製代碼

  

該測試代碼的輸出爲:HELLO WORLD 

對於該測試代碼,因爲a沒有uppercaseString函數,所以會觸發resolveInstanceMethod,可是因爲該函數並無添加selector,所以運行時發現找不到該函數,會觸發

forwardingTargetForSelector函數,在forwardingTargetForSelector函數中,返回了一個NSString "hello world",所以會由該string來執行uppercaseString函數,最終返回大寫的hello world。

 

複製代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
示例代碼三:
 
@implementation  CA
 
+ ( BOOL ) resolveInstanceMethod:( SEL )aSEL
{
     return   YES ;
}
- ( id )forwardingTargetForSelector:( SEL )aSelector
{
     return  nil ;
   }
 測試代碼:
 
1  a = [[CA alloc]init];
2   NSLog (@ "%@" ,[a performSelector: @selector (uppercaseString)];

  

複製代碼

  

這段代碼的執行順序爲:

 (1):首先在程序剛執行,AppDelegate都尚未出來時,resolveInstanceMethod就被觸發,

 

(2)等測試代碼執行時,forwardingTargetForSelector被調用

(3)因爲forwardingTargetForSelector返回了nil,所以運行時仍是找不到uppercaseString selector,這時又會觸發resolveInstanceMethod,因爲仍是沒有加入selector,因而會crash。

 

(4) 使用class_copyPropertyList及property_getName獲取類的屬性列表及每一個屬性的名稱

  

複製代碼
u_int               count;
objc_property_t*    properties= class_copyPropertyList([UIView class ], &count);
for  ( int  i = 0; i < count ; i++)
{
     const  char * propertyName = property_getName(properties[i]);
     NSString  *strName = [ NSString   stringWithCString:propertyName encoding: NSUTF8StringEncoding ];
     NSLog (@ "%@" ,strName);
}
複製代碼

 以上代碼獲取了UIView的全部屬性並打印屬性名稱, 輸出結果爲:

複製代碼
skipsSubviewEnumeration
viewTraversalMark
viewDelegate
monitorsSubtree
backgroundColorSystemColorName
gesturesEnabled
deliversTouchesForGesturesToSuperview
userInteractionEnabled
tag
layer
_boundsWidthVariable
_boundsHeightVariable
_minXVariable
_minYVariable
_internalConstraints
_dependentConstraints
_constraintsExceptingSubviewAutoresizingConstraints
_shouldArchiveUIAppearanceTags
複製代碼

 

 

(5) 使用class_copyMethodList獲取類的全部方法列表

    獲取到的數據是一個Method數組,Method數據結構中包含了函數的名稱、參數、返回值等信息,如下代碼以獲取名稱爲例:

複製代碼
u_int               count;
Method*    methods= class_copyMethodList([UIView class ], &count);
for  ( int  i = 0; i < count ; i++)
{
     SEL  name = method_getName(methods[i]);
     NSString  *strName = [ NSString   stringWithCString:sel_getName(name) encoding: NSUTF8StringEncoding ];
     NSLog (@ "%@" ,strName);
}
複製代碼

  代碼執行後將輸出UIView全部函數的名稱,具體結果略。

     其餘一些相關函數:

    

複製代碼
1.SEL method_getName(Method m) 由Method獲得SEL
2.IMP method_getImplementation(Method m)  由Method獲得IMP函數指針
3.const char *method_getTypeEncoding(Method m)  由Method獲得類型編碼信息
4.unsigned int method_getNumberOfArguments(Method m)獲取參數個數
5.char *method_copyReturnType(Method m)  獲得返回值類型名稱
6.IMP method_setImplementation(Method m, IMP imp)  爲該方法設置一個新的實現
複製代碼

 

(6)總結

       總而言之,使用runtime技術能作些什麼事情呢?

       能夠在運行時,在不繼承也不category的狀況下,爲各類類(包括系統的類)作不少操做,具體包括:

  •    增長
增長函數:class_addMethod
增長實例變量:class_addIvar
增長屬性: @dynamic 標籤,或者class_addMethod,由於屬性其實就是由getter和setter函數組成
增長Protocol:class_addProtocol (說實話我真不知道動態增長一個protocol有什麼用,-_-!!)
  •    獲取  
獲取函數列表及每一個函數的信息(函數指針、函數名等等):class_getClassMethod method_getName ...
獲取屬性列表及每一個屬性的信息:class_copyPropertyList property_getName
獲取類自己的信息,如類名等:class_getName class_getInstanceSize
獲取變量列表及變量信息:class_copyIvarList
獲取變量的值
  •    替換
將實例替換成另外一個類:object_setClass
將函數替換成一個函數實現:class_replaceMethod
直接經過 char  *格式的名稱來修改變量的值,而不是經過變量

  

    

   參考資料:(1)Objective-C Runtime Reference

               (2)深刻淺出Cocoa之消息   

               (3)objective-c 編程總結(第六篇)運行時操做 - 方法交換

                (4)Runtime of Objective-C

相關文章
相關標籤/搜索