Objective C运行时(runtime)
【详解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 <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
本文参考了:
- Add Methods at Runtime in Objective-C(原文的class_addMethods已经废弃了)
- The Coolest iOS Hack
推荐去看看
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:
- Extract the view’s class
- See if the name of this class begins with the prefix used to indicate one of these dynamic subclasses
- If it doesn’t have the prefix, then build the name of the new class (
+[NSString stringWithFormat:]
) - Look in the runtime to see if a class of this name already exists
- If it doesn’t exist, create the class using
objc_allocateClassPair()
- Add the custom
-dealloc
method to the new subclass - Register the class with the runtime
- 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?
- funwithobjc posted this
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获取类的所有方法列表
(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
|
代码示例一: 1 @implementation CA 3 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之消息
Objective C运行时(runtime)的更多相关文章
- iOS运行时 -- Runtime(摘抄自网络)
运行时(iOS) 一.什么是运行时(Runtime)? 运行时是苹果提供的纯C语言的开发库(运行时是一种非常牛逼.开发中经常用到的底层技术) 二.运行时的作用? 能获得某个类的所有成员变量 能获得某个 ...
- Objective-O Runtime 运行时初体验
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一 ...
- iOS开发——高级特性&Runtime运行时特性详解
Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 ...
- 自定义注解之运行时注解(RetentionPolicy.RUNTIME)
对注解概念不了解的可以先看这个:Java注解基础概念总结 前面有提到注解按生命周期来划分可分为3类: 1.RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成clas ...
- iOS运行时编程(Runtime Programming)和Java的反射机制对比
运行时进行编程,类似Java的反射.运行时编程和Java反射的对比如下: 1.相同点 都可以实现的功能:获取类信息.属性设置获取.类的动态加载(NSClassFromString(@“clas ...
- Objective-C Runtime 运行时之四:Method Swizzling
理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Me ...
- Objective-C Runtime 运行时之一:类与对象
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一 ...
- ios - runtime运行时应用---交换方法
runtime运行时用法之一 --- 交换类的方法,此处简单写了把系统的UIView的setBackgroundColor的方法换成了自定义的pb_setBackgroundColor 首先创建UIV ...
- RTTI (Run-Time Type Identification,通过运行时类型识别) 转
参考一: RTTI(Run-Time Type Identification,通过运行时类型识别)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型. RTTI提供了以下两个 ...
- iOS-浅谈runtime运行时机制-runtime简单使用(转)
转自http://www.cnblogs.com/guoxiao/p/3583432.html 由于OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtim ...
随机推荐
- Python函数式编程学习笔记
High order function map(f,Iterable),将f依次作用在Iterable的每个元素然后返回一个Iterator,再用list(map(f,Iterable))进行转换得到 ...
- Hammer.js
一.前言 移动端框架当前还处在初级阶段,但相对于移动端的应用来说已经有很长时间了.虽然暂时还没有PC端开发的需求量大,但移动端的Web必然是一种趋势,在接触移动端脚本的过程中,最开始想到的是juqer ...
- socket.io与redis构建实时推送
准备工作 1:准备nodejs环境 2:使用npm工具安装 socket.io npm install socket.io 3:下载客户端socket.io文件 解压附件中的文件 node app. ...
- iOS_TCP和UDP的详解
TCP和UDP面试经常被问到,一些初学者也经常问我这种问题,由于TCP协议和UDP协议是基于三次“对话”,解释起来很费劲,所以在这里详细的描述一下自己对TCP协议和UDP协议的理解,如有不妥之处,望指 ...
- 实验一 cmd命令的编写
一.目的和要求 1. 实验目的 (1)掌握命令解释程序的原理: (2)*掌握简单的DOS调用方法: (3)掌握C语言编程初步. 2.实验要求 编写类似于DOS,UNIX的命令行解释程序 (1)自行定义 ...
- jstl_fmt
<fmt:formatDate value="${isoDate}" type="both"/>2004-5-31 23:59:59<fmt: ...
- 高性能javascript(记录二)
js中有四种基本的数据存取位置.分别是:字面量.本地变量.数组元素.对象成员. 字面量:只代表自身,不存储在特定位置.js的字面量有:字符串.数字.布尔值.对象.数组.函数.正则表达式.以及特殊的nu ...
- UIImagePickerController和UIAlertController结合使用
在处理个人资料 - 头像的时候,通常有两个选项,一个是调用系统相机,一个是调用系统相册.这里要使用的就是UIImagePickerController方法. 在头像位置的imageView添加一个手势 ...
- rocksDB 安装问题简单介绍
前一段时间准备测试rocksdb,按照帖子和官网的例子,在安装过程中遇到一些问题.这里给出的是在Ubuntu下安装python使用的版本. 首先,要感谢这些帖子对我的帮助: 1:http://tech ...
- 006-Map、Tuple、Zip实战解析
006-Map.Tuple.Zip实战解析 实战代码 Map实战 本身是映射,映射关系在实际中应用非常广泛,比如:配置信息都是Key-Value形式 键值对不存在下边关系 是一个immutable(不 ...