runtime基础知识

Runtime数据结构:在Objective-C中,使用[receiver message]语法并不会马上执行receiver对象的message方法的代码,而是向receiver发送一条message消息,这条消息可能由receiver来处理,也可能由转发给其他对象来处理,也有可能假装没有接收到这条消息而没有处理。其实[receiver message]被编译器转化为:

id objc_msgSend ( id self, SEL op, ... );

 下面从两个数据结构idSEL来逐步分析和理解Runtime有哪些重要的数据结构。

SEL:

SEL是函数objc_msgSend第二个参数的数据类型,表示方法选择器,按照下面路径查看到SEL数据结构如下:

 

typedef struct objc_selector *SEL;

     其实它就是映射到方法的C字符串,你可以通过Objc编译器命令@selector()或者Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。

      如果你知道selector对应的方法名是什么,可以通过NSString* NSStringFromSelector(SEL aSelector)方法将SEL转化为字符串,再用NSLog打印。

id:

接下来看objc_msgSend第一个参数的数据类型idid是通用类型指针,能够表示任何对象,按下面路径打开objc.h文件

查看到id数据结构如下:

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

id其实就是一个指向objc_object结构体指针,它包含一个Class isa成员,根据isa指针就可以顺藤摸瓜找到对象所属的类

注意:根据Apple的官方文档Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技术实现的,isa指针在运行时被修改,指向一个中间类而不是真正的类。所以,你不应该使用isa指针来确定类的关系,而是使用class方法来确定实例对象的类。

Class:

isa指针的数据类型是ClassClass表示对象所属的类,按下面路径打开objc.h文件

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以查看到Class其实就是一个objc_class结构体指针,但这个头文件找不到它的定义,需要在runtime.h才能找到objc_class结构体的定义。

按下面路径打开runtime.h文件

查看到objc_class结构体定义如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
注意:OBJC2_UNAVAILABLE是一个Apple对Objc系统运行版本进行约束的宏定义,主要为了兼容非Objective-C 2.0的遗留版本,但我们仍能从中获取一些有用信息。

 让我们分析一些重要的成员变量表示什么意思和对应使用哪些数据结构:

    • isa表示一个Class对象的Class,也就是Meta Class。在面向对象设计中,一切都是对象,Class在设计中本身也是一个对象。我们会在objc-runtime-new.h文件找到证据,发现objc_class有以下定义:
struct objc_class : objc_object {
  // Class ISA;
  Class superclass;
  cache_t cache;             // formerly cache pointer and vtable
  class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

  ......
}

由此可见,结构体objc_class也是继承objc_object,说明Class在设计中本身也是一个对象

其实Meta Class也是一个Class,那么它也跟其他Class一样有自己的isasuper_class指针,关系如下:

上图实线super_class指针,虚线isa指针。有几个关键点需要解释以下:

             1、Root class (class)其实就是NSObjectNSObject是没有超类的,所以Root class(class)的superclass指向nil。

             2、每个Class都有一个isa指针指向唯一的Meta class

             3、Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。

             4、每个Meta class的isa指针都指向Root class (meta)

    • super_class表示实例对象对应的父类
    • name表示类名
    • ivars表示多个成员变量,它指向objc_ivar_list结构体。在runtime.h可以看到它的定义:

struct objc_ivar_list {
  int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
  int space                                                OBJC2_UNAVAILABLE;
#endif
  /* variable length structure */
  struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}

      objc_ivar_list其实就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的单个成员变量信息。

    • methodLists表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改*methodLists的值来添加成员方法,也是Category实现原理,同样也解释Category不能添加实例变量的原因。在runtime.h可以看到它的定义:
    • cache用来缓存经常访问的方法,它指向objc_cache结构体,后面会重点讲到。

    • protocols表示类遵循哪些协议

Method:

Method表示类中的某个方法,在runtime.h文件中找到它的定义:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

其实Method就是一个指向objc_method结构体指针,它存储了方法名(method_name)、方法类型(method_types)和方法实现(method_imp)等信息。而method_imp的数据类型是IMP,它是一个函数指针,后面会重点提及。

Ivar:

Ivar表示类中的实例变量,在runtime.h文件中找到它的定义:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

Ivar其实就是一个指向objc_ivar结构体指针,它包含了变量名(ivar_name)、变量类型(ivar_type)等信息。

IMP:

在上面讲Method时就说过,IMP本质上就是一个函数指针,指向方法的实现,在objc.h找到它的定义:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

当你向某个对象发送一条信息,可以由这个函数指针来指定方法的实现,它最终就会执行那段代码,这样可以绕开消息传递阶段而去执行另一个方法实现。

Cache

顾名思义,Cache主要用来缓存,那它缓存什么呢?我们先在runtime.h文件看看它的定义:

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiverisa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

消息发送:

前面从objc_msgSend作为入口,逐步深入分析Runtime的数据结构,了解每个数据结构的作用和它们之间关系后,我们正式转入消息发送这个正题。

objc_msgSend函数

在前面已经提过,当某个对象使用语法[receiver message]来调用某个方法时,其实[receiver message]被编译器转化为:

id objc_msgSend ( id self, SEL op, ... );

现在让我们看一下objc_msgSend它具体是如何发送消息:

    1. 首先根据receiver对象的isa指针获取它对应的class
    2. 优先在classcache查找message方法,如果找不到,再到methodLists查找
    3. 如果没有在class找到,再到super_class查找
    4. 一旦找到message这个方法,就执行它实现的IMP

 

 


下面通过实例学习runtime

当我们对一个实例发送消息时(-开头的方法),会在该 instance 对应的类的 methodLists 里查找。

      当我们对一个类发送消息时(+开头的方法),会在该类的 MetaClass 的 methodLists 里查找。

 创建person对象

    Person *p = [[Person alloc] init];

 调用对象方法

    [p run]; // 本质:让对象发送消息:objc_msgSend(p, @selector(run));

 调用类方法的方式:两种

第一种通过类名调用 :[Person run];

第二种通过类对象调用 :[[Person class] run];

 用类名调用类方法,底层会自动把类名转换成类对象调用

本质:让类对象发送消息 objc_msgSend([Person class], @selector(eat));

1、交换两个方法的实现

/*

     获得类方法:

     Method class_getClassMethod(Class cls , SEL name)

     获得某个类的实例对象方法

     Method class_getInstanceMethod(Class cls , SEL name)

     交换两个方法的实现

     void method_exchangeImplementations(Method m1 , Method m2)

     */

//类方法
Method class1 = class_getClassMethod([Person class], @selector(run)); Method class2 = class_getClassMethod([Person class], @selector(study)); method_exchangeImplementations(class1, class2); [Person run]; //调用study方法实现 [Person study];//调用run方法实现

//对象方法 Method instance1
= class_getInstanceMethod([Person class], @selector(run)); Method instance2 = class_getInstanceMethod([Person class], @selector(stydy)); method_exchangeImplementations(instance1, instance2); Person *person = [[Person alloc]init]; [person run];//调用study实现 [person study];//调用run实现

 2、用自己的方法替换系统方法

//创建UIImage的分类
//.h文件
#import
<UIKit/UIKit.h> @interface UIImage (Category)
+ (UIImage *)CZC_imageNamed:(NSString *)name; @end
//.m文件 #import "UIImage+Category.h" #import <objc/runtime.h> @implementation UIImage (Category) + (void)load { // 获取两个类的类方法 Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:)); Method m2 = class_getClassMethod([UIImage class], @selector(CZC_imageNamed:)); // 开始交换方法实现 method_exchangeImplementations(m1, m2); } + (UIImage *)CZC_imageNamed:(NSString *)name{ double version = [[UIDevice currentDevice].systemVersion doubleValue]; if (version >= 7.0) {
// 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片 name = [name stringByAppendingString:@"_7"]; } return [UIImage CZC_imageNamed:name]; } UIImageView *imageVIew = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
//最终调用的图片是ios_7 imageVIew.image
= [UIImage imageNamed:@"ios"]; [self.view addSubview:imageVIew];

 3、在分类中设置属性,给任何一个对象设置属性

/* set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)

 参数 object:给哪个对象设置属性

 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节

 参数 value:给属性设置的值

 参数policy:存储策略 (assign 、copy 、 retain就是strong)

 设置值:

 void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)

 获得值:

 id objc_getAssociatedObject(id object , const void *key)

 */
//Person分类
//.h文件
#import
"Person.h" @interface Person (Category)<NSCoding> @property (nonatomic, copy)NSString *name; @end
//.m文件 #import "Person+Category.h" #import <objc/runtime.h> char nameKey; @implementation Person (Category) - (void)setName:(NSString *)name{ objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY); } - (NSString *)name{ return objc_getAssociatedObject(self, &nameKey); } @end

4、 获得对象的所有成员变量

//创建Person类
#import
<Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface Person : NSObject<NSCoding> { NSString *_userName; NSUInteger _userAge; int _uerHeight; CGFloat _userMoney; CGFloat _userWeight; } @property (nonatomic, copy)NSString *name; @property (nonatomic, copy)NSString *age; //创建Person的分类
//.h文件
#import "Person.h" @interface Person (Category)<NSCoding> - (void)getAllIvar; @end //.m文件 #import "Person+Category.h" #import <objc/runtime.h> @implementation Person (Category) - (void)getAllIvar{ unsigned int outCount = 0; Ivar *ivars = class_copyIvarList([Person class], &outCount); // 遍历所有成员变量 for (int i = 0; i < outCount; i++) { // 取出i位置对应的成员变量 Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); const char *type = ivar_getTypeEncoding(ivar); NSLog(@"成员变量名:%s 成员变量类型:%s",name,type); } // 注意释放内存! free(ivars); }
//在controller中调用 [person getAllIvar];

5、利用runtime 获取所有属性来进行字典转模型

//创建Teacher类,其中包括Dog对象
//.h文件
#import
<Foundation/Foundation.h> #import <UIKit/UIKit.h> #import "Dog.h" @interface Teacher : NSObject @property (nonatomic, copy)NSString *name; @property (nonatomic, copy)NSString *secondName; @property (nonatomic, assign)NSUInteger age; @property (nonatomic, assign)NSUInteger height; @property (nonatomic, assign)CGFloat money; @property (nonatomic, strong)Dog *dog; //当字典的key和模型的属性匹配不上 - (void)setDict:(NSDictionary *)dict; - (void)sendMessage:(NSString *)word; @end //.m文件 Teacher #import "Teacher.h" #import <objc/runtime.h> @implementation Teacher - (void)setDict:(NSDictionary *)dict { Class c = self.class; while (c &&c != [NSObject class]) { unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); for (int i = 0; i < outCount; i++) { Ivar ivar = ivars[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 成员变量名转为属性名(去掉下划线 _ ) key = [key substringFromIndex:1]; // 取出字典的值 id value = dict[key]; // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错 if (value == nil) continue; // 将字典中的值设置到模型上 [self setValue:value forKeyPath:key]; } free(ivars); c = [c superclass]; } } @end //创建Dog类
//.h文件 #import <Foundation/Foundation.h> @interface Dog : NSObject @property (nonatomic, copy)NSString *name; @property (nonatomic, copy)NSString *color; @property (nonatomic, assign)NSUInteger age; @end //在controller中调用 NSDictionary *dict = @{ @"name":@"夏明", @"age":@33, @"money":@1000 }; //字典赋值模型 Teacher *teacher = [[Teacher alloc]init]; [teacher setDict:dict]; NSLog(@"------teacher---%@", teacher);
//两层模型 NSDictionary
*dict2 = @{ @"name" : @"小明", @"age" : @40, @"dog" : @{ @"name":@"黑子", @"color":@"" } }; Teacher *teacher2 = [[Teacher alloc]init]; [teacher2 setObjDict:dict2]; NSLog(@"----teacher2-----%@", teacher2);

6、动态添加一个方法//创建Person类

//.h文件
#import
<Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface Person : NSObject<NSCoding> @end
//.m文件Person #import "Person.h" #import "NSObject+Category.h" #import <objc/runtime.h> @implementation Person // void(*)() // 默认方法都有两个隐式参数, void eat(id self,SEL sel) { NSLog(@"%@ %@",self,NSStringFromSelector(sel)); } // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来. // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { // 动态添加eat方法 // 第一个参数:给哪个类添加方法 // 第二个参数:添加方法的方法编号 // 第三个参数:添加方法的函数实现(函数地址) // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, sel, eat, "v@:");//参数:eat可用(IMP)eat替换 } return [super resolveInstanceMethod:sel]; } @end //在controller中调用 //动态添加一个方法,p中没有这个方法
Person *p = [[Person alloc]init]; [p performSelector:@selector(eat)];
//第二种方式
+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(sendMessage:)) { class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) { NSLog(@"method resolution way : send message = %@", word); }), "v@*"); } return YES; } //动态添加方法, Person *p1 = [[Person alloc]init]; [p1 sendMessage:@"陈志超"];

7、消息的转发

//.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Dog.h"
@interface Teacher : NSObject

@end
//.m
#import "Teacher.h"
#import <objc/runtime.h>
@implementation Teacher

/* 实现方式一
//消息转发,让另一个类对象执行方法
- (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(sendMessage:)) { return [[Dog alloc]init]; } return nil; } */

//下面两个方式结合为方式二 #pragma mark - Normal Forwarding - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector]; if (!methodSignature) { methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"]; } return methodSignature; } - (void)forwardInvocation:(NSInvocation *)anInvocation { Dog *messageForwarding = [[Dog alloc]init]; if ([messageForwarding respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:messageForwarding]; } } @end //创建Dog类 #import <Foundation/Foundation.h> @interface Dog : NSObject - (void)sendMessage:(NSString *)word; @end #import "Dog.h" @implementation Dog - (void)sendMessage:(NSString *)word{ NSLog(@"Dog--sendMessage = %@", word); } @end

//此时在controller中,
Teacher *t = [[Teacher alloc] init];
//下面代码会调用Dog的sendMessage方法
[t sendMessage:@"你好,teacher"];

 

注(6、7结合看):[receiver message]调用方法时,如果在message方法在receiver对象的类继承体系中没有找到方法,那怎么办?一般情况下,程序在运行时就会Crash掉,抛出 unrecognized selector sent to …类似这样的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。

  1. Method Resolution
  2. Fast Forwarding
  3. Normal Forwarding

1、首先Objective-C在运行时调用+ resolveInstanceMethod:+ resolveClassMethod:方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程

2、如果目标对象实现- forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nilself,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding

3、如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。

三种方法的选择:

Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢?

  • Method Resolution:由于Method Resolution不能像消息转发那样可以交给其他对象来处理,所以只适用于在原来的类中代替掉。
  • Fast Forwarding:它可以将消息处理转发给其他对象,使用范围更广,不只是限于原来的对象。
  • Normal Forwarding:它跟Fast Forwarding一样可以消息转发,但它能通过NSInvocation对象获取更多消息发送的信息,例如:target、selector、arguments和返回值等信息。

 

转载于:https://www.cnblogs.com/czc-wjm/p/5651633.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值