Objective C运行时(runtime)

时间:2021-08-27 13:36:46

详解objc_msgSend

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

  Objective C运行时(runtime)

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

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

  Objective C运行时(runtime)

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

  Objective C运行时(runtime)

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

  Objective C运行时(runtime)

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

  Objective C运行时(runtime)

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

  Objective C运行时(runtime)

 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 <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);

  

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

Cool and crazy things you can do with Objective-C and the Cocoa/Cocoa Touch frameworks.

ArchiveAsk

Search

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. Objective C运行时(runtime)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什么的。

示例代码:

Objective C运行时(runtime)
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]];
}
Objective C运行时(runtime)

执行结果为:

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那么简单,在实现装饰者模式时,也可以使用该函数进行消息转发。

示例代码:

Objective C运行时(runtime)
 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 }
Objective C运行时(runtime)

测试代码:

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,程序就会报异常

Objective C运行时(runtime)
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 selfSEL _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)]; 
Objective C运行时(runtime)

  

执行结果

SEL t did not exist

Objective C运行时(runtime)
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 selfSEL _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)];
Objective C运行时(runtime)

  

该测试代码的输出为:HELLO WORLD

对于该测试代码,由于a没有uppercaseString函数,因此会触发resolveInstanceMethod,但是由于该函数并没有添加selector,因此运行时发现找不到该函数,会触发

forwardingTargetForSelector函数,在forwardingTargetForSelector函数中,返回了一个NSString "hello world",因此会由该string来执行uppercaseString函数,最终返回大写的hello world。

Objective C运行时(runtime)
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)];

  

Objective C运行时(runtime)

  

这段代码的执行顺序为:

(1):首先在程序刚执行,AppDelegate都还没有出来时,resolveInstanceMethod就被触发,

Objective C运行时(runtime)

(2)等测试代码执行时,forwardingTargetForSelector被调用

Objective C运行时(runtime)

(3)由于forwardingTargetForSelector返回了nil,因此运行时还是找不到uppercaseString selector,这时又会触发resolveInstanceMethod,由于还是没有加入selector,于是会crash。

Objective C运行时(runtime)

(4) 使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称

Objective C运行时(runtime)
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);
}
Objective C运行时(runtime)

 以上代码获取了UIView的所有属性并打印属性名称, 输出结果为:

Objective C运行时(runtime)
skipsSubviewEnumeration
viewTraversalMark
viewDelegate
monitorsSubtree
backgroundColorSystemColorName
gesturesEnabled
deliversTouchesForGesturesToSuperview
userInteractionEnabled
tag
layer
_boundsWidthVariable
_boundsHeightVariable
_minXVariable
_minYVariable
_internalConstraints
_dependentConstraints
_constraintsExceptingSubviewAutoresizingConstraints
_shouldArchiveUIAppearanceTags
Objective C运行时(runtime)

(5) 使用class_copyMethodList获取类的所有方法列表

获取到的数据是一个Method数组,Method数据结构中包含了函数的名称、参数、返回值等信息,以下代码以获取名称为例:

Objective C运行时(runtime)
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);
}
Objective C运行时(runtime)

  代码执行后将输出UIView所有函数的名称,具体结果略。

其他一些相关函数:

Objective C运行时(runtime)
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) 为该方法设置一个新的实现
Objective C运行时(runtime)

(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