Objective C运行时(runtime)


详解objc_msgSend

详解objc_msgSend

 1、为了性能,objc_msgSend用汇编写成。存在于objc-msg-x86_64.s中。

 2、在上图代码中可以看到,objc_msgSend被分为2个过程:1)在cache中寻找SEL。2)在MethodTable寻找SEL。

 3、CacheLookup中,不断地拿SEL与cache中的缓存比较,比较失败,则跳转到 LCacheMiss标签继续在MethodTable中搜索。

  如果想手动查找cache,则需要调用_cache_getimp函数(汇编实现),此函数是个对外接口层,用于保存与准备环境。

  _cache_getImp在头文件中objc-private.h中,链接后objc/c代码可以直接调用。

 4、MethodTableLookup 是个接口层宏,主要用于保存环境与准备参数,来调用 __class_lookupMethodAndLoadCache3函数(此函数实现于objc-class.mm)。

 5、__class_lookupMethodAndLoadCache3函数也是个接口层(C编写),此函数提供相应参数配置,实际功能在lookUpMethod函数中。

 6、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 
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

本文参考了:

  • Add Methods at Runtime in Objective-C(原文的class_addMethods已经废弃了)
  • The Coolest iOS Hack
推荐去看看

Fun with Objective-C

Cool and crazy things you can do with Objective-C and the Cocoa/Cocoa Touch frameworks. ArchiveAsk
7 October 2010 @ 8:08PM

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
4 notes#runtime 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) 总结

 

Objective-C Runtime Reference

               (2)深入浅出Cocoa之消息   

               (3)objective-c 编程总结(第六篇)运行时操作 - 方法交换

                (4)Runtime of Objective-C