Runtime

Posted Jk_Chan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Runtime相关的知识,希望对你有一定的参考价值。

可以新增加一个类Class(期间可以为该类增加属性,成员变量,方法,设置其父类,所遵循的协议等)

可以取到类Class的名字,大小,指定成员变量的结构体指针(然后用一系列操作成员变量结构体的方法从该结构体获取信息),指定属性的结构体指针(然后用一系列操作属性结构体的方法从该结构体获取信息),整个成员变量列表,整个属性列表,替换属性,指定实例方法的结构体指针(然后用一系列操作方法结构体的方法从该结构体获取信息),指定类方法的结构体指针(然后用一系列操作方法结构体的方法从该结构体获取信息),所有方法的数组,替换方法的实现,方法的实现(然后可以调用该实现),是否响应指定的选择子,是否遵循某协议,增加遵循协议,所遵循的协议列表(然后用一系列操作协议列表数组里面的协议结构体指针元素的方法获取结构体信息),元类,父类,是否是元类,改变所属类

 

类与对象基础数据结构:
Class:
OC类由Class类型表示,Class类型是一个指向objc_class结构体的指针,定义如下:
typedef struct objc_class* Class;//struct objc_class* == Class
objc_class结构体定义如下:
struct objc_class{
Class isa; //指向元类(metaClass)
#if !__OBJC2__
Class super_class; //指向父类(如果当前该类已是根类,则为NULL)
const char *name; //类名
long version; //类的版本信息,默认0
long info; //类信息,供运行期使用的一些位标识
long instance_size; //该类的实例变量大小
struct objc_ivar_list *ivars; //该类的成员变量链表
struct objc_method_list **methodLists; //方法定义的链表
struct objc_cache *cache; //方法缓存(缓存最近使用的方法,每次调用一个方法,被缓存到cache列表中,下次调用runtime先从cache查找,没有才根据isa去methodLists中查找)
struct objc_protocol_list *protocols; //协议链表
#endif
}

objc_object与id:
objc_object:表示一个类的实例的结构体,定义如下:
struct objc_object{
Class isa;
}
typedef struct objc_object* id;//struct objc_object* == id
id类型是一个指向objc_object结构体的指针,可以转换为任何一种对象,因为任何一种对象其实就是一个objc_object结构体,id类似C中的void *
向一个对象发送消息时,根据该实例对象的isa指针能找到这个实例对象所属的类,在类的方法列表以及父类的方法列表中寻找对应的selector指向的方法,找到便运行该方法

objc_cache:
objc_cache结构体定义如下:
struct objc_cache{
unsigned int mask; //指定分配的缓存bucket的总数
unsigned int occupied; //指定实际占用的缓存bucket的总数
Method buckets[1]; //指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素
}
元类:
OC中类也是一个对象,既然是一个对象就是objc_object结构体,那么该结构体中的isa指针所指的就是meta-class(元类)
meta-class是一个类对象的类,存储一个类的所有类方法
向对象发消息时,会在这对象所属的类的方法列表中查找方法;
向类发消息时,会在这个类的元类的方法列表中查找
元类的isa指向基类的元类,即:
任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

示范例子://用代码创建一个类,并给类添加成员方法且实现成员方法,并实例化对象调用成员方法
#import <objc/runtime.h>
void ex_registerClassPair();
int main(int argc, const char * argv[]){
@autoreleasepool {
ex_registerClassPair();
}
return 0;
}
void TestMetaClass(id self, SEL _cmd) {
NSLog(@"This objcet is %p", self);
NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 0; i < 4; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = objc_getClass((__bridge void *)currentClass);
}
NSLog(@"NSObject‘s class is %p", [NSObject class]);
NSLog(@"NSObject‘s meta class is %p", objc_getClass((__bridge void *)[NSObject class]));
}
/*
通过对Class变量使用objc_getClass()获取objc_class结构体中的isa(元类的objc_class结构体指针),
isa也是一个Class变量,再通过它使用objc_getClass()一直获取到根元类
在一个类对象调用class方法是无法获取meta-class,它只是返回类而已
*/
void ex_registerClassPair() {
Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);//创建一个类实例对象叫newClass,是继承NSError的TestClass类型
class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass, "[email protected]:");//往newClass对象添加成员方法testMetaClass,并且该成员方法的实现函数是自定义void TestMetaClass(id self, SEL _cmd)函数
objc_registerClassPair(newClass);//注册newClass实例对象
id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];//给该实例对象分配内存并把内存头赋给instance变量
[instance performSelector:@selector(testMetaClass)];//调用变量的testMetaClass成员方法
}

类与对象操作函数
类的操作方法大部分前缀为class,这些方法主要就是针对objc_class这个结构体中的各个字段
对象的操作方法大部分前缀为objc或object_
类相关操作函数:
0.由类名获取该类型Class对象:
Class someClass = NSClassFromString(@"SomeClassName");
id obj = [[someClass alloc] init];
1.获取类名:
const char * class_getName(Class cls);
2.获取类的父类:
Class class_getSuperclass(Class cls);
3.判断给定的Class是否是一个元类:
BOOL class_isMetaClass(Class cls);
4.实例变量大小:
size_t class_getInstanceSize(Class cls);
5.成员变量及属性:
成员变量包括.h与.m中@interface MyClass()[email protected]之间声明的@property与非@property变量
属性只包括.h与.m中@property变量
所有成员变量,属性的信息放在objc_class结构体的链表ivars中
ivars是一个数组,元素是指向Ivar(变量信息objc_ivar结构体)的指针
a.成员变量操作函数:
获取类中指定名称实例成员变量的信息:
Ivar class_getInstanceVariable(Class cls,const char *name);
获取类成员变量的信息:
Ivar class_getClassVariable(Class cls,const char *name);
添加成员变量:
BOOL class_addIvar(Class cls,const char *name,size_t size,uint8_t alignment,const char *types);
/*
该函数只能在objc_allocateClassPair函数与objc_registerClassPair函数之间调用
另外添加成员变量的类也不能是元类
如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))
*/
获取整个成员变量列表:
Ivar * class_copyIvarList(Class cls,unsigned int *outCount);
/*
返回一个指向成员变量信息的数组
每个元素指向该成员变量信息的objc_ivar结构体,也就是每个元素是Ivar
返回的数组不包含父类中声明的变量
outCount指针返回数组大小
返回的数组必须使用free()释放
*/
b.属性操作函数:
获取指定属性:
objc_property_t class_getProperty(Class cls,const char *name);
获取属性列表:
objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount);
为类添加属性:
BOOL class_addProperty(Class cls,const char *name,const objc_property_attribute_t *attributes,unsigned int attributeCount);
替换类的属性:
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);
6.方法:
添加方法:
BOOL class_addMethod(Class cls,SEL name,IMP imp,const char *types);
/*
会覆盖父类的方法实现,但不会取代本类中已存在的实现
如果奔雷中包含一个同名的实现,函数返回NO
所有IMP所指向的具体实现函数至少包含两个参数:
_self
_cmd
如:
void myMethodIMP(id self,SEL _cmd){
//implementation...
}
参数types是一个描述传递给方法的参数类型的字符数组(涉及到类型编码)
*/
获取实例方法:
Method class_getInstanceMethod(Class cls,SEL name);
/*
会搜索父类的实现
*/
获取类方法:
Method class_getClassMethod(Class cls,SEL name);
/*
会搜索父类的实现
*/
获取所有实例方法的数组:
Method * class_copyMethodList(Class cls,unsigned int *outCount);
/*
只搜索本类的实现
若需要获取本类中所有类方法:
使用class_copyMethodList(object_getClass(cls), &count);
一个类的实例方法是定义在元类里面
需要使用free()释放所获取的数组
*/
替代方法的实现:
IMP class_replaceMethod(Class cls,SEL name,IMP imp,const char *types);
/*
两种情况:
1.类中不存在name指定的方法,会添加该方法
2.类中存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现
*/
返回方法的具体实现:
IMP class_getMethodImpLementation(Class cls,SEL name);
/*
该函数在向类实例发送消息时会被调用,返回一个指向方法实现函数的指针
速度比method_getImplementation(class_getInstanceMethod(cls, name));函数快
当类实例无法响应selector,返回的函数指针将是运行时消息转发机制的一部分
*/
IMP class_getMethodImpLementation_stret(Class cls,SEL name);
类实例是否响应指定的selector
BOOL class_respondsToSelector(Class cls,SEL sel);
/*
类似NSObject类的respondsToSelector:或instancesRespondToSelector:方法
*/
7.协议:
添加协议:
BOOL class_addProtocol(Class cls,Protocol *protocol);
返回类是否实现指定的协议
BOOL class_conformsToProtocol(Class cls,Protocol *protocol);
/*
等价NSObject类的conformsToProtocol:方法
*/
返回类实现的协议列表:
Protocol *class_copyProtocolList(Class cls,unsigned int *outCount);
/*
返回的数组需要使用free()释放;
*/
8.版本:
获取版本号:
int class_getVersion(Class cls);
设置版本号:

示范例子:
MyClass *myClass=[[MyClass alloc]init];
unsigned int outCount=0;
Class cls=myClass.class;
//类名:
NSLog(@"class name:%s",class_getName(cls));
NSLog(@"==========================================================");
//父类:
NSLog(@"super class name:%s",class_getName(class_getSuperclass(cls)));
NSLog(@"==========================================================");
//是否是元类:
NSLog(@"MyClass is %@ a meta-class",(class_isMetaClass(cls)[email protected]"yes":@"not"));
NSLog(@"==========================================================");
Class meta_class=objc_getMetaClass(class_getName(cls));
NSLog(@"%s`s meta-class is %s",class_getName(cls),class_getName(meta_class));
NSLog(@"==========================================================");
//变量实例大小
NSLog(@"instance size :%zu",class_getInstanceSize(cls));
NSLog(@"==========================================================");
//成员变量:
Ivar *ivars=class_copyIvarList(cls, &outCount);
for (int i=0; i<outCount; i++) {
Ivar ivar=ivars[i];
NSLog(@"instance variable`s name :%s at index:%d",ivar_getName(ivar),i);
}
free(ivars);

Ivar string=class_getInstanceVariable(cls, "_string");
if (string != NULL) {
NSLog(@"instance variable %s",ivar_getName(string));
}
NSLog(@"==========================================================");
//属性操作
objc_property_t *properties=class_copyPropertyList(cls, &outCount);
for (int i=0; i<outCount; i++) {
objc_property_t property=properties[i];
NSLog(@"property`s name:%s",property_getName(property));
}
free(properties);

objc_property_t array=class_getProperty(cls, "array");
if (array != NULL) {
NSLog(@"property %s",property_getName(array));
}
NSLog(@"==========================================================");
//方法操作:
Method *methods=class_copyMethodList(cls, &outCount);
for (int i=0; i<outCount; i++) {
Method method=methods[i];
NSLog(@"method`s signature:%s",method_getName(method));
}
free(methods);

Method method1=class_getInstanceMethod(cls, @selector(method1));//@selector(method1)的参数需要cls中存在的方法名
if (method1 != NULL) {
NSLog(@"method %s",method_getName(method1));
}

Method classMethod=class_getClassMethod(cls, @selector(classMethod1));
if (classMethod !=NULL) {
NSLog(@"class method : %s", method_getName(classMethod));
}
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"yes" : @" not");
IMP imp=class_getMethodImplementation(cls, @selector(method1));
imp(cls,NULL);
NSLog(@"==========================================================");
//协议
Protocol * __unsafe_unretained *protocols=class_copyProtocolList(cls, &outCount);
Protocol *protocol;
for (int i=0; i<outCount; i++) {
protocol=protocols[i];
NSLog(@"protocol name:%s",protocol_getName(protocol));
}
NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"yes" : @" not", protocol_getName(protocol));
NSLog(@"==========================================================");

动态创建类和对象:
动态创建类:
创建一个新类和元类
Class objc_allocateClassPair(Class superclass,const char *name,size_t extraBytes);
/*
若创建一个根类,superclass指定为Nil
extraBytes通常指定为0
*/
在应用中注册由objc_allocateClassPair创建的类
void objc_registerClassPair(Class cls);
销毁一个类及其相关联的类
void objc_disposeClassPair(Class cls);
/**
如果程序运行中还存在类或其子类的实例,则不能调用针对类调用该方法。
/
/*
创建新类调用objc_allocateClassPair后,
使用如class_addMethod,class_addIvar等函数为新类添加方法(实例方法和实例变量应该添加到类自身上,而类方法应该添加到类的元类上),实例变量和属性等
最后调用objc_registerClassPair函数注册类后,这个新类就可以在程序中使用了。
*/
示范例子:
#import <objc/runtime.h>
#import "MyClass.h"
void imp_submethod1();
int main(int argc, const char * argv[]){
@autoreleasepool {
MyClass *myClass=[[MyClass alloc]init];
Class cls=objc_allocateClassPair(myClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "[email protected]:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "[email protected]:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
objc_property_attribute_t type={"T","@\"NSString\""};
objc_property_attribute_t ownership={"C",""};
objc_property_attribute_t backingivar={"V","_ivar1"};
objc_property_attribute_t attrs[]={type,ownership,backingivar};
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);

id instance=[[cls alloc]init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];

}
return 0;
}
void imp_submethod1(){
NSLog(@" run sub method 1");
}
动态创建对象:
创建类实例:
id class_createInstance(Class cls,size_t extraBytes);
/*
ARC下无法使用
*/
在指定位置穿件类实例:
id objc_constructInstance(Class cls,void *bytes);
销毁类实例
void * objc_destructInstance(id obj);
/*
销毁一个类的实例,但不会释放并移除任何与其相关的引用
*/
示范例子:
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);


实例操作函数:
1.针对整个对象进行操作的函数,包含:
返回指定对象的一份拷贝:
id object_copy(id obj,size_t size);
释放指定对象占用的内存:
id object_dispose(id obj);
示范例子:
/*
通过拷贝一个父类实例并且指定新实例大小为子类实例大小(子类实例比父类实例占用更多内存)得到一个新父类实例(但分配到的内存大小不是父类实例而是子类实例的大小)
对新父类实例转换为子类类型的实例
最后需要手动释放
*/
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
2.针对对象的实例变量进行操作的函数,包含:
修改类实例的实例变量的值:
Ivar object_setInstanceVariable(id obj,const char *name,void *value);
获取对象实例变量的值:
Ivar object_getInstanceVariable(id obj,const char *name,void **outValue);
返回指定给定对象分配的任何额外字节的指针
void *object_getIndexedIvars(id obj);
返回对象中实例变量的值
id object_getIvar(id obj,Ivar ivar);
设置对象中实例变量的值
void object_setIvar(id obj,Ivar ivar,id value);
/*
如果实例变量的Ivar已经知道,
那么调用object_getIvar会比object_getInstanceVariable函数快,
相同情况下,object_setIvar也比object_setInstanceVariable快
*/
3.针对对象的类进行操作的函数,包含:
返回给定对象的类名
const char * object_getClassName(id obj);
返回对象的类
Class object_getClass(id obj);
设置对象的类
Class object_setClass(id obj,Class cls);
获取类定义:
获取已注册的类定义的列表:
int objc_getClassList(Class *buffer,int bufferCount);
示范例子:
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog(@"number of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classes);
}
创建并返回一个指向所有已注册类的指针列表:
Class *objc_copyClassList(unsigned int *outCount);
返回指定类的定义:
Class objc_lookUpClass(const char *name);
/*
如果类在运行时未注册,则objc_lookUpClass会返回nil
*/
Class objc_getClass(const char *name);
/*
如果类在运行时未注册,会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil
*/
Class objc_getRequiredClass(const char *name);
/*
与objc_getClass相同,只不过如果没有找到类,则会杀死进程。
*/
返回指定类的元类:
Class objc_getMetaClass(const char *name);
/*
如果指定的类没有注册,则该函数会调用类处理回调,并再次确认类是否注册,如果确认未注册,再返回nil
*/

编码类型:
方法的返回值类型与参数类型编码为一个字符串,并将其与方法的selector关联一起
通过使用@encode(类型)得到该类型的编码类型
使用范例:
float a[] = {1.0, 2.0, 3.0};
NSLog(@"array encoding type: %s", @encode(typeof(a)));
/*输出*/
2014-10-28 11:44:54.731 RuntimeTest[942:50791] array encoding type: [3f]
具体各类型编码下面表格有。

成员变量,属性:
基础数据类型:
Ivar:
表示实例变量的类型,一个指向objc_ivar结构体的指针,定义如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar{
char *ivar_name; //变量名
char *ivar_type; //变量类型
int ivar_offset; //基地址偏移字节
}
objc_property_t:
表示Objective-C声明的属性的类型,指向objc_property结构体的指针,定义如下:
typedef struct objc_property *objc_property_t;
objc_property_attribute_t:
定义了属性的特性,是一个结构体,定义如下:
typedef struct{
const char *name; //特性名
const char *value; //特性值
}
关联对象:
类似于成员变量,不过实在运行时添加的
可以把关联对象想象成一个Objective-C对象如(NSString),该对象通过
给定的key连接到类的一个实例上
key是一个void指针(const void *)
还需要指定一个内存管理策略,策略有:
OBJC_ASSOCIATION_ASSIGN
宿主对象被释放时,关联对象不会被释放
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
宿主释放时,关联对象会被释放
OBJC_ASSOCIATION_COPY
宿主释放时,关联对象会被释放(若关联对象是block则需要使用该内存策略)
给对象添加关联对象如下:
static char myKey;
objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
/*
self对象将获取一个新的关联对象anObject
如果我们使用同一个key关联另外一个对象时,会自动释放之前关联的对象,新对象会使用旧的关联对象的地址
*/
获取对象的关联对象:
id anObject=objc_getAssociatedobject(self,&myKey);
移除对象的关联对象:
通过使用objc_removeAssociatedObjects函数

通过使用objc_setAssociatedObject函数

使用范例:
/*
动态将一个Tap手势操作连接到任何UIView中,
根据束腰指定点击后的实际操作
可以将一个手势对象及操作的block对象关联到UIView对象中
1.先创建一个手势识别对象
2.将它和block做关联对象
*/
- (void)setTapActionWithBlock:(void (^)(void))block{
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerTapGestureKey);
if (!gesture){
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture{
if (gesture.state == UIGestureRecognizerStateRecognized){
void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey);
if (action){
action();
}
}
}
成员变量的操作方法:
获取成员变量名:
const char *ivar_getName(Ivar v);
获取成员变量类型编码:
const char *ivar_getTypeEncoding(Ivar v);
获取成员变量的偏移量:
ptrdiff_t ivar_getOffset(Ivar v);
/*
对于类型id或其它对象类型的实例变量,可以调用object_getIvar和object_setIvar来直接访问成员变量,而不使用偏移量。
*/
关联对象的操作方法:
设置关联对象:
void objc_setAssociatedObject(id object,const void *key,id value,objc_AssociationPolicy policy);
获取关联对象:
id objc_getAssociatedObject(id object,const void *key);
移除关联对象:
void objc_removeAssociatedObjects(id object);
属性的操作方法:
获取属性名:
const char *property_getName(objc_property_t property);
获取属性特性描述字符串:
const char *property_getAttributes(objc_property_t property);
获取属性中指定的特性:
char *property_copyAttributeValue(objc_property_t property,const char *attributeName);
/*
使用后需要使用free()释放
*/
获取属性的特性列表:
objc_property_attribute_t * property_copyAttributeList(objc_property_t property, unsigned int *outCount);
/*
使用后需要使用free()释放
*/
示范例子:
/*
相同的信息使用不同的字段表示,接收数据时,可以将数据保存到相同的对象中
*/
//有如下两个数据:
@{@"name1":"张三",@"status1":@"start"}
@{@"name2":"张三",@"status2":@"end"}

//通常需要思想连个方法分别做转换,但使用Runtime,可以只实现一个转换方法,需要先定义一个映射字典(全局变量)
static NSMutableDictionary *map=nil;
@implementation MyObject
+(void)load{
map = [NSMutableDictionary dictionary];
map[@"name1"] [email protected]"name";
map[@""status1"][email protected]"status";
map[@"name2"] [email protected]"name";
map[@"status2"] [email protected]"status";
}
@end
//转换方法如下:
- (void)setDataWithDic:(NSDictionary *)dic{
[dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *propertyKey=[self propertyForKey:key];
if(propertyKey){
objc_property_t property = class_getProperty([self class], [propertyKey UTF8String]);
NSString *attributeString = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
...
[self setValue:obj forKey:propertyKey];
}
}];

}

方法与消息:
基础数据类型:
SEL:
选择器,表示一个方法的selector的指针,定义如下:
typedef struct objc_selector *SEL;
selector:
用于表示运行时方法的名字
依据每个方法的名字,参数序列而生成一个唯一的整型标识(Int类型的地址),也就是SEL
只要方法名相同,那么生成的SEL都一样,因此同一个类及类的继承体系中即使参数类型不同,也不能存在两个同名的方法,因为参数类型不参与生成方法标识SEL
SEL [email protected](method1);
NSLog(@"sel1:%p",sel1);
/*
输出:
2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72
*/
可以通过下面三种方法获取SEL:
1.sel_registerName函数
[email protected]()函数
3.NSSelectorFromString()方法
IMP:
一个函数指针,指向方法实现的首地址,定义如下:
id(*IMP)(id,SEL,...)
/*
第一个参数指向self的指针(实例方法,则是类实例的内存地址;类方法,则是指向元类的指针)
第二个参数是方法选择器selector
接下来是方法的实际参数列表
*/
通过SEL,可以获得它对应的IMP,由于IMP是实现方法的首地址,因此获得了执行这个方法代码的入口点,就可以像调普通C函数一样使用该函数指针
通过获得IMP,就可以跳过Runtime消息传递机制,直接执行IMP指向的函数实现,比向对象发送消息更高效
Method:
用于表示类定义中的方法的类型,定义如下:
typedef struct objc_method *Method;
struct objc_method{
SEL method_name; //方法名
char *method_types; //
IMP method_imp; //方法实现
}
方法操作相关函数:
调用指定方法的实现:
id method_invoke(id receiver,Method m,...);
调用返回一个数据结构的方法的实现:
void method_invoke_stret(id receiver,method m,...);
获取方法名:
SEL method_getName(Method m);
/*
如果向获取方法名的C字符串,可以使用sel_getName(method_getName(method));
*/
返回方法的实现:
IMP method_getImpLementation(Method m);
获取描述方法参数和返回值类型的字符串
const char *method_getTypeEncoding(Method m);
获取方法的返回值类型的字符串
char *method_copyReturnType(Method m);
获取方法的指定位置参数的类型字符串
char *method_copyArgumentType(Method m,unsigned int index);
通过引用返回方法的返回值类型字符串
void method_getReturnType(Method m,char *dst,size_t dst_len);
/*
返回类型字符串会被拷贝到dst中
*/
返回方法的参数的个数
unsigned int method_getNumberOfArguments(Method m);
通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );
设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );
交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );

选择器相关操作函数:
返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );
在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str );
/*
将一个方法添加到类定义时,我们必须在Objective-C Runtime系统中注册一个方法名以获取方法的选择器。
*/
在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );
比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );

方法调用流程:
编译器——》[receiver message]——》objc_msgSend(消息接受者receiver,方法名selector)/objc_msgSend(receiver, selector, arg1, arg2, ...)
——》完成动态绑定
1.根据接收者的类找到selector对应的方法实现:receiver-->isa-->Class/superClass/rootClass-->objc_method_list-->IMP/没有IMP就走消息转发流程
2.objc_msgSend()调用方法实现同时把接收者对象与方法所需参数传入:调用IMP
3.将实方法实现的返回值作为自己的返回值:获得IMP返回值并返回
objc_msgSend()的隐藏参数:
1.消息接收对象(通过self来引用)
2.方法的selector(通过_cmd来引用)
示范例子:
- strange{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd ){
return nil;
}
return [target performSelector:method];
}
获取方法地址:
methodForSelector:方法可以获取方法的指针
需要将该方法返回的指针转换为何时的函数类型,函数参数和返回值都需匹配上
示范例子:
/*
当频繁调用一个特定的方法时,通过获取方法实现的地址直接调用它比动态绑定方式性能更高
*/
void (*setter)(id, SEL, BOOL);
int i;
setter=(void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];//获取到的方法的指针做类型转换为自己定义的一个方法指针类型并赋值
for (i = 0 ; i < 1000 ; i++){
setter(targetList[i], @selector(setFilled:), YES);
}
/*
函数指针的前两个参数必须是id和SEL
这种方式只适合于在类似于for循环这种情况下频繁调用同一方法
*/

消息转发:
默认情况下
如果是以[object message]的方式调用方法,如果object无法响应message消息时,编译器会提示报错,无法编译;
但如果是以perform…的形式来调用,则需要等到运行时才能确定object是否能接收message消息,不能则程序崩溃。
respondsToSelector:函数可以判断一个对象是否能接收某个消息:
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
运行时,对象无法处理某一消息时,会启动消息转发(message forwarding)机制
默认对象接收到未知消息,会导致程序崩溃
消息转发机制分3个步骤:
1.动态方法解析:
收到未知消息时,调用所属类的类方法+resolveInstanceMethod:或者+resolveClassMethod:
在方法中,我们可以为该未知消息新增一个处理方法
前提需实现该处理方法,通过class_addMethod函数动态添加到类里
示范例子:
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
2.备用接收者
在1中无法处理消息,Runtime会继续调用以下函数进行消息转发:
- (id)forwardingTargetForSelector:(SEL)aSelector;//把aSelector交由该函数的返回值参数处理
使用范例:
@interface SUTRuntimeMethodHelper : NSObject
- (void)method2;
@end
@implementation SUTRuntimeMethodHelper
- (void)method2 {
NSLog(@"%@, %p", self, _cmd);
}
@end

@interface SUTRuntimeMethod () {
SUTRuntimeMethodHelper *_helper;
}
@end
@implementation SUTRuntimeMethod
+ (instancetype)object {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self != nil) {
_helper = [[SUTRuntimeMethodHelper alloc] init];
}
return self;
}
- (void)test {
[self performSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector");
NSString *selectorString = NSStringFromSelector(aSelector);
// 将消息转发给_helper来处理
if ([selectorString isEqualToString:@"method2"]) {
return _helper;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
3.完整转发
在2中仍无法处理消息,则启用完整消息转发机制,会调用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
NSInvocation对象包含未处理的消息有关的全部细节,如:selector,target和参数
可以在该方法选择将消息转发给其他对象
forwardInvocation:方法的实现有两个任务:
1.定位可以响应封装在anInvocation中的消息的对象
2.使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者
我们可以对消息的内容进行修改,比如追回一个参数等,然后再去触发消息
还有,必须重写以下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象,为给定的selector提供一个合适的方法签名。
使用范例:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}

消息转发与多重继承:
通过上面2.3.步,表面上是该对象在处理消息,可以模拟多重继承的某些特性
不过两者有一个重要区别:
多重继承将不同的功能集成到一个对象中,它会让对象变得过大,涉及的东西过多;
而消息转发将功能分解到独立的小的对象中,并通过某种方式将这些对象连接起来,并做相应的消息转发。

Method Swizzling:
Method Swizzling是改变一个selector的实际实现的技术。
通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。
示范例子:
#import <objc/runtime.h>
@implementation UIViewController (Tracking)//建分类
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod =class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];//实际运行时该调用被重新指定到UIViewController类的-viewWillAppear:中
NSLog(@"viewWillAppear: %@", self);
}
@end
/*
通过method swizzling修改了UIViewController的@selector(viewWillAppear:)对应的函数指针,使其实现指向了我们自定义的xxx_viewWillAppear的实现
*/
Swizzling应该总是在+load中执行
Swizzling应该总是在dispatch_once中执行
使用注意事项:
1.总是调用方法的原始实现(除非有更好的理由不这么做)而不使用Swizzling
2.避免冲突:给自定义的分类方法加前缀,从而使其与所依赖的代码库不会存在命名冲突。

协议与分类:
基础数据类型:
Category:
一个指向分类的结构体的指针,定义如下:
typedef struct objc_category *Category;
struct objc_category {
char *category_name ; // 分类名
char *class_name ; // 分类所属的类名
struct objc_method_list *instance_methods ; // 实例方法列表
struct objc_method_list *class_methods ; // 类方法列表
struct objc_protocol_list *protocols ; // 分类所实现的协议列表
}
操作函数:
分类中的信息都包含在objc_class中,我们可以通过针对objc_class的操作函数来获取分类的信息
示范例子:
@interface RuntimeCategoryClass : NSObject
- (void)method1;
@end
@interface RuntimeCategoryClass (Category)
- (void)method2;
@end
@implementation RuntimeCategoryClass
- (void)method1 {
}
@end
@implementation RuntimeCategoryClass (Category)
- (void)method2 {
}
@end
#pragma mark -
NSLog(@"测试objc_class中的方法列表是否包含分类中的方法");
unsigned int outCount = 0;
Method *methodList = class_copyMethodList(RuntimeCategoryClass.class, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methodList[i];
const char *name = sel_getName(method_getName(method));
NSLog(@"RuntimeCategoryClass‘s method: %s", name);
if (strcmp(name, sel_getName(@selector(method2)))) {
NSLog(@"分类方法method2在objc_class的方法列表中");
}
}
/*
输出结果:
2014-11-08 10:36:39.213 [561:151847] 测试objc_class中的方法列表是否包含分类中的方法
2014-11-08 10:36:39.215 [561:151847] RuntimeCategoryClass‘s method: method2
2014-11-08 10:36:39.215 [561:151847] RuntimeCategoryClass‘s method: method1
2014-11-08 10:36:39.215 [561:151847] 分类方法method2在objc_class的方法列表中
*/
Protocol:
定义如下:
typedef struct objc_object Protocol;
操作函数:
返回指定的协议:
Protocol * objc_getProtocol ( const char *name );
/*
如果仅仅是声明了一个协议,而未在任何类中实现这个协议,则该函数返回的是nil
*/
获取运行时所知道的所有协议的数组:
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
/*
获取到的数组需要使用free()来释放
*/
创建新的协议实例:
Protocol * objc_allocateProtocol ( const char *name );
在运行时中注册新创建的协议
void objc_registerProtocol ( Protocol *proto );
/*
创建一个新的协议后,必须调用该函数以在运行时中注册新的协议。协议注册后便可以使用,但不能再做修改,即注册完后不能再向协议添加方法或协议
*/
为协议添加方法:
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
添加一个已注册的协议到协议中:
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
为协议添加属性:
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
返回协议名:
const char * protocol_getName ( Protocol *p );
测试两个协议是否相等:
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
获取协议中指定条件的方法的方法描述数组
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
获取协议中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
获取协议中的属性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
获取协议的指定属性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
获取协议采用的协议
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
查看协议是否采用了另一个协议
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
库相关操作:
获取所有加载的Objective-C框架和动态库的名称
const char ** objc_copyImageNames ( unsigned int *outCount );
获取指定类所在动态库
const char * class_getImageName ( Class cls );
获取指定库或框架中所有类的类名
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );
示范例子:
NSLog(@"获取指定类所在动态库");
NSLog(@"UIView‘s Framework: %s", class_getImageName(NSClassFromString(@"UIView")));
NSLog(@"获取指定库或框架中所有类的类名");
const char ** classes = objc_copyClassNamesForImage(class_getImageName(NSClassFromString(@"UIView")), &outCount);
for (int i = 0; i < outCount; i++) {
NSLog(@"class name: %s", classes[i]);
}

块操作:
创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );
/*
参数block的签名必须是method_return_type ^(id self, method_args …)形式的。该方法能让我们使用block作为IMP
*/
示范例子:
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "[email protected]:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
/*
输出结果:
2014-11-09 14:03:19.779 [1172:395446] hello world!
*/
返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );
解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );

弱引用操作:
加载弱引用指针引用的对象并返回
id objc_loadWeak ( id *location );
/*
加载一个弱指针引用的对象,并在对其做retain和autoreleasing操作后返回它,对象就可以在调用者使用它时保持足够长的生命周期
*/
存储__weak变量的新值
id objc_storeWeak ( id *location, id obj );
/*
该函数的典型用法是用于__weak变量做为赋值对象时
*/
拾遗:
类中通过self与super调用方法,实际上指向的都是相同的消息接收者
[super viewDidLoad]; -runtime生成-> struct objc_super { id receiver; Class superClass; };/*receiver就是self*/ -runtime转换-> id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );/*在super中的superClass的方法列表查找viewDidLoad*/ --> 找到后 -->objc_msgSend(objc_super->receiver, @selector(viewDidLoad));/*objc_super->receiver就是self本身*/








 

 

 

isa指针:
指向对象的Class对象(保存有对象的方法列表)
一个类所有对象的Class对象都是同一个(保证内存中每一个类型有唯一的类型描述)
Class对象也有一个isa指向该Class对象上一级父类的Class对象
调用myObject对象中myAction()方法时:
从myObject中isa寻到myObject的Class对象,然后在这个Class对象中寻找myAction()方法,找到调用,
没有找到就从该Class对象中isa寻到父类的Class对象中查找,直到根父类的Class对象中也找不到就抛出异常
即:myObject--isa-->myObject的Class对象-->寻找方法-->有-->调用
-->没有--Class对象的isa-->myObject的Class对象的父类的Class对象-->寻找方法-->有-->调用
-->没有-->继续往父类的Class对象寻找...
SEL(方法选择器)与IMP:
SEL:
OC编译时依据每一个定义的方法的名字与参数序列,生成的一个唯一整数标识
获取方法:
1.(SEL) @selector(方法的名字)
2.(SEL) NSSelectorFromString(方法的名字的字符串)
IMP:
函数指针
表示指向实现方法的指针
使用IMP直接定位到方法体,不像[myObject myAction];通过对象的isa寻找SEL后才定位到IMP才能调用
从而直接使用IMP更高效
objc_msgSend函数:
编译时[object method]转换为objc_msgSend(id receiver,SEL selector,参数...)的函数调用
objc_msgSend的调用过程:
1.通过receiver,找到isa指针,通过isa指针找到Class对象使用第二个参数selector查找方法
2.如果没有找到,使用当前Class对象中新的isa指针到上一级父类的Class对象中查找
3.找到方法后,依据receiver中的self指针找到当前的对象,调用当前对象的具体实现方法(IMP指针函数),然后传递参数,调用实现方法
4.如一直找到NSObject的Class对象,也没有该方法,就报告不能识别发送消息的错误
动态方法解析:
在类.m中重写+(BOOL) resolveInstanceMethod:(SEL)sel方法,指定动态实现的方法或函数
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString *methodName=NSStringFromSelector(sel);
//看看是不是我们要动态实现的方法名称
if ([methodName isEqualToString:@"testAction"]){
class_addMethod([self class], sel, (IMP)myAddMethod,"[email protected]:c");
result=YES;
}
return NO;
}
/*
class_addMethod([self class], sel, (IMP)myAddMethod,"[email protected]:c");
第三个参数:
指定该SEL动态方法sel的IMP函数体(函数指针),这里为自定义myAddMethod函数
第四个参数:
反应出SEL动态方法sel映射的IMP自定义函数的函数体的函数式(返回类型,参数列表类型)
其中该映射函数也就是动态方法的实现函数参数中
第一个必须是id类型
第二个必须为SEL类型
第三个开始,可以按照需要参数定义
"[email protected]:c"
第一个字符是IMP函数的返回类型编码
第二个字符是IMP函数的第一个参数类型编码
第三个字符是IMP函数的第二个参数类型编码
第四个往后是IMP函数接下来所需参数类型编码
动态方法的实现的前两个参数必须是id、SEL,所以
第四个参数中的字符串的第二、三个字符一定是@:
*/
void myAddMethod(id self,SEL _cmd,char w){
printf("Method-%s\n",[NSStringFromSelector(_cmd) cStringUsingEncoding:NSUTF8StringEncoding]);
printf("%c\n",w);
}
在使用该类对象时可以通过一下代码调用动态方法:
SEL testAction_sel=NSSelectorFromString(@"testAction");
IMP imp=[myObject methodForSelector:testAction_sel];
imp(myObject,testAction_sel,‘s‘);

消息转发:
没有实现对象所调用方法会报告错误的原因:
最终都没有找到对象调用的方法,
系统会NSObject的调用-(void) forwardInvocation: (NSInvocation*)invocation 方法
该方法如果没有被重写默认就是抛出错误异常
若重写了-(void) forwardInvocation: (NSInvocation*)invocation 方法
就可以改变处理方式,比如可以实现消息转发到别的类处理
invocation参数对象包含:
receiver对象
selector对象
也就是包含了向一个对象发送消息的所有元素:对象,方法名,参数序列

 

 

 

 

 

 

 

 

*****************************************************************************************************************************************************************************************************
@编译器指令
一个给定类型编码为一种内部表示的字符串
编码 意义
c ---> A char
i ---> An int
s ---> A short
l ---> A longl is treated as a 32-bit quantity on 64-bit programs.
q ---> A long long
C ---> An unsigned char
I ---> An unsigned int
S ---> An unsigned short
L ---> An unsigned long
Q ---> An unsigned long long
f ---> A float
d ---> A double
B ---> A C++ bool or a C99 _Bool
v ---> A void
* ---> A character string (char )(最前方的”是为了格式才添加)
@ ---> An object (whether statically typed or typed id)
# ---> A class object (Class)(最前方的“是为了格式才添加)
: ---> A method selector (SEL)
[array type] ---> An array
{name=type...} ---> A structure
(name=type...) ---> A union
bnum ---> A bit field of num bits
^type ---> A pointer to type
? ---> An unknown type (among other things, this code is used for function pointers)
尝试打印值:
NSLog(@"int : %s", @encode(int));
NSLog(@"float : %s", @encode(float));
NSLog(@"float * : %s", @encode(float*));
NSLog(@"char : %s", @encode(char));
NSLog(@"char * : %s", @encode(char *));
NSLog(@"BOOL : %s", @encode(BOOL));
NSLog(@"void : %s", @encode(void));
NSLog(@"void * : %s", @encode(void *));
NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));

int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(intArray)));

float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(floatArray)));

typedef struct _struct {
short a;
long long b;
unsigned long long c;
} Struct;
NSLog(@"struct : %s", @encode(typeof(Struct)));




以上是关于Runtime的主要内容,如果未能解决你的问题,请参考以下文章

用java编写代码实现关机

Java中的Runtime类

API--18--ProcessBuilder

清除android应用程序用户数据

API--17--Process

java执行linux命令