runtime深入学习

#runtime
www.jianshu.com/p/46dd81402f63 在博客基础上补充一些
https://www.jianshu.com/p/eac6ed137e06 不错的介绍

说到runtime,它是Objective-c里面最核心的技术,被人们传呼的神乎其神,但是感觉有一层神秘的面纱笼罩其上,毕竟使用场景不多,相信大多数开发者都不会熟练的运用。而网络上也有无数的文章来讲解runtime,但是真的非常的乱,非常的碎片化,很少有讲解的比较全面的。

##基本概念
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation 框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。

  1. RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
  2. 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
  3. OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
  4. 只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。

##runtime的具体实现
我们写的oc代码,它在运行的时候也是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。
每一个oc的方法,底层必然有一个与之对应的runtime方法。 消息发送机制

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

与之对应的runtime是

 Person * p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));//方法1
 //    Person * p = objc_msgSend([Person class], @selector(alloc));//方法2   
    //    p = [p init];
    p = objc_msgSend(p, sel_registerName("init"));
  

PS xcode5开始苹果不建议用这么底层的函数,因此需要设置

设置

###方法本质

  1. SEL 方法编号
  2. IMP 方法实现(本质上是函数指针!!)
  3. 在OC中我们调用方法都是通过消息机制.给某个对象.发送方法编号消息!!
    通过SEL 可以找到对应的 IMP(方法实现)!
    SEL 和 IMP 是一一对应的关系!!

##常见方法
###1 获取属性列表

objc_property_t *propertyList = class_copyPropertyList([self class], &count);
 for (unsigned int i=0; i<count; i++) {
     const char *propertyName = property_getName(propertyList[i]);
     NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
 }

###2 获取方法列表

Method *methodList = class_copyMethodList([self class], &count);
 for (unsigned int i; i<count; i++) {
     Method method = methodList[i];
     NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
 }

###3 获取成员变量列表

Ivar *ivarList = class_copyIvarList([self class], &count);
  for (unsigned int i; i<count; i++) {
      Ivar myIvar = ivarList[i];
      const char *ivarName = ivar_getName(myIvar);
      NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
  } 

class_copyPropertyList返回的仅仅是对象类的属性(@property申明的属性),而class_copyIvarList返回类的所有属性和变量(包括在@interface大括号中声明的变量)

###4 获取协议列表

__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
  for (unsigned int i; i<count; i++) {
      Protocol *myProtocal = protocolList[i];
      const char *protocolName = protocol_getName(myProtocal);
      NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
  }

###5 获取类方法

class_getClassMethod([Person class], @selector(printDZ)); 

###6 获取实例方法

Person * p1 = [[Person alloc] init];  
      
 Method m1 = class_getInstanceMethod([p1 class], @selector(printDZL));  
   

###7 交换两个方法

Person * p1 = [[Person alloc] init];  
      
    Method m1 = class_getInstanceMethod([p1 class], @selector(printDZL));  
    Method m2 = class_getClassMethod([Person class], @selector(printDZ));  
   
   method_exchangeImplementations(m1, m2);  

8 添加方法

 SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
        
       
        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
       
       
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

ps

// 获取函数实现IMP
method_getImplementation
// 获取函数type encoding
method_getTypeEncoding

也可以手动添加实现IM和函数ype encoding

 /*
     1.cls 哪个类
     2.SEL
     3.IMP
     4.返回值类型!  https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
     */
class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");

void guessAnswer(id self,SEL _cmd){
    
    NSLog(@"i am from beijing");
    
}

PS

如果该类接收到一个没有实现的实例方法,就会来到resolveInstanceMethod这个里面

###9 替换原方法实现

class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

###10 交换两个方法

method_exchangeImplementations(oriMethod, cusMethod);

###11 扩展属性
用到三个api

/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 * 
 * @see objc_setAssociatedObject
 * @see objc_removeAssociatedObjects
 */
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 * 
 * @see objc_setAssociatedObject
 */
id objc_getAssociatedObject(id object, const void *key)

/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
void objc_removeAssociatedObjects(id object)

参数说明

  • object:与谁关联,通常是传self
  • key:唯一键,在获取值时通过该键获取,通常是使用static const void *来声明
  • value:关联所设置的值
  • policy:内存管理策略,比如使用copy
    关于policy是一个枚举型
/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
  • OBJC_ASSOCIATION_ASSIGN:表示弱引用关联,通常是基本数据类型,如int、float
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC:表示强(strong)引用关联对象
  • OBJC_ASSOCIATION_COPY_NONATOMIC:表示关联对象copy
  • OBJC_ASSOCIATION_RETAIN:表示强(strong)引用关联对象,但不是线程安全的
  • OBJC_ASSOCIATION_COPY:表示关联对象copy,但不是线程安全的

案例:

#import <UIKit/UIKit.h>

@interface UIViewController (Information)

@property (nonatomic, copy)   NSString *name;  
@end

#import "UIViewController+Information.h"
#import <objc/runtime.h>

static const void *kName = "name";


@implementation UIViewController (Information)

#pragma mark - 字符串类型的动态绑定
- (NSString *)name {
    return objc_getAssociatedObject(self, kName);
}

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, kName, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}


###12调用父类方法

  //定义objc_super 结构体
    struct objc_super hksuper = {p,class_getSuperclass(objc_getClass("LYJPerson"))};
    //给父类发消息
    objc_msgSendSuper(&hksuper, @selector(eatWith:),@"汉堡");

##常见runtime使用

  • 动态的添加对象的成员变量和方法
  • 动态交换两个方法的实现
  • 拦截并替换方法
  • 在方法上增加额外功能
  • 实现NSCoding的自动归档和解档
  • 实现字典转模型的自动转换

##代码实现
要使用runtime,要先引入头文件#import <objc/runtime.h>
###1. 动态变量控制
把person类的name值修改为daming

unsigned int count = 0;
    Ivar *ivar = class_copyIvarList([self.person class], &count);//获取所有属性
    for (int i = 0; i<count; i++) {
        Ivar var = ivar[i];
        const char *varName = ivar_getName(var);
        NSString *proname = [NSString stringWithUTF8String:varName];//得到属性
        
        if ([proname isEqualToString:@"_name"]) {   //这里别忘了给属性加下划线
            object_setIvar(self.person, var, @"daming");//修改属性值
            break;
        }
    }
    NSLog(@"XiaoMing change name  is %@",self.person.name);
    self.textfield.text = self.person.name;

###2 动态添加方法
动态添加一个guess方法,并执行

 - (void)sayFrom
{
   
    class_addMethod([self.person class], @selector(guess:), (IMP)guessAnswer, "v@:@");
    if ([self.person respondsToSelector:@selector(guess:)]) {
        //Method method = class_getInstanceMethod([self.xiaoMing class], @selector(guess));
       
        [self.person performSelector:@selector(guess:) withObject:@"haha"];
        
    } else{
        NSLog(@"Sorry,I don't know");
    }
    self.textview.text = @"beijing";
}

void guessAnswer(id self,SEL _cmd,NSString *brand){
    
    NSLog(@"i am from beijing ,%@",brand);
    
}```
参数说明

(IMP)guessAnswer 意思是guessAnswer的地址指针;
“v@:” 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;
“v@?@” 意思是,两个参数的没有返回值。

###这个有两个地方留意一下:

void的前面没有+、-号,因为只是C的代码。
必须有两个指定参数(id self,SEL _cmd)

###3 动态交换两个方法的实现

Person * p1 = [[Person alloc] init];  
      
    Method m1 = class_getInstanceMethod([p1 class], @selector(printDZL));  
    Method m2 = class_getClassMethod([Person class], @selector(printDZ));  
   NSLog(@"测试前:");  
    [p1 printDZL];  
    [Person printDZ];  
    method_exchangeImplementations(m1, m2);  
    NSLog(@"测试后:");  
    [p1 printDZL];  
    [Person printDZ];   

输出

2015-11-04 13:37:08.539 02-runtime[2776:69899] 测试前:
2015-11-04 13:37:08.539 02-runtime[2776:69899] this is a instance method
2015-11-04 13:37:08.539 02-runtime[2776:69899] this is a class method
2015-11-04 13:37:08.540 02-runtime[2776:69899] 测试后:
2015-11-04 13:37:08.540 02-runtime[2776:69899] this is a class method
2015-11-04 13:37:08.540 02-runtime[2776:69899] this is a instance method

方法交换了
###4 拦截并替换方法

在程序当中,假设XiaoMing的中有test1这个方法,但是由于某种原因,我们要改变这个方法的实现,但是又不能去动它的源代码(正如一些开源库出现问题的时候),这个时候runtime就派上用场了。

我们先增加一个tool类,然后写一个我们自己实现的方法-change,
通过runtime把test1替换成change。

Class PersionClass = object_getClass([Person class]);
Class toolClass = object_getClass([tool class]);

    源方法的SEL和Method

    SEL oriSEL = @selector(test1);
    Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);

    交换方法的SEL和Method

    SEL cusSEL = @selector(change);
    Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);

    先尝试給源方法添加实现,这里是为了避免源方法没有实现的情况

    BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
    if (addSucc) {
          // 添加成功:将源方法的实现替换到交换方法的实现     
        class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

    }else {
    //添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
method_exchangeImplementations(oriMethod, cusMethod);  
  }

###5 在方法上增加额外功能
有这样一个场景,出于某些需求,我们需要跟踪记录APP中按钮的点击次数和频率等数据,怎么解决?当然通过继承按钮类或者通过类别实现是一个办法,但是带来其他问题比如别人不一定会去实例化你写的子类,或者其他类别也实现了点击方法导致不确定会调用哪一个,runtime可以这样解决:

@implementation UIButton (Hook)

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        Class selfClass = [self class];

        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);

        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);

        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            method_exchangeImplementations(oriMethod, cusMethod);
        }

    });
}

- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [CountTool addClickCount];
    [self mySendAction:action to:target forEvent:event];
}

@end

UIControl的sendAction:to:forEvent:方法用于处理事件响应.
load方法会在类第一次加载的时候被调用,调用的时间比较靠前,适合在这个方法里做方法交换,方法交换应该被保证,在程序中只会执行一次。
程序在打开的时候,会先 将编译的源文件(m文件) 装载到内存,load会优先放到内存,调用比较靠前,孩子啊main入口之前
###6 实现NSCoding的自动归档和解档
如果你实现过自定义模型数据持久化的过程,那么你也肯定明白,如果一个模型有许多个属性,那么我们需要对每个属性都实现一遍encodeObject 和 decodeObjectForKey方法,如果这样的模型又有很多个,这还真的是一个十分麻烦的事情。下面来看看简单的实现方式。

假设现在有一个Movie类,有3个属性,它的h文件这这样的

#import <Foundation/Foundation.h>

//1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding
@interface Movie : NSObject<NSCoding>

@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;

@end

如果是正常写法, m文件应该是这样的:

#import "Movie.h"
@implementation Movie

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:_movieId forKey:@"id"];
    [aCoder encodeObject:_movieName forKey:@"name"];
    [aCoder encodeObject:_pic_url forKey:@"url"];

}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        self.movieId = [aDecoder decodeObjectForKey:@"id"];
        self.movieName = [aDecoder decodeObjectForKey:@"name"];
        self.pic_url = [aDecoder decodeObjectForKey:@"url"];
    }
    return self;
}
@end

如果这里有100个属性,那么我们也只能把100个属性都给写一遍。
不过你会使用runtime后,这里就有更简便的方法。
下面看看runtime的实现方式:

#import "Movie.h"
#import <objc/runtime.h>
@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Movie class], &count);

    for (int i = 0; i<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        const char *name = ivar_getName(ivar);
        // 归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        const char *name = ivar_getName(ivar);
       // 归档
       NSString *key = [NSString stringWithUTF8String:name];
      id value = [decoder decodeObjectForKey:key];
       // 设置到成员变量身上
        [self setValue:value forKey:key];

        }
        free(ivars);
    } 
    return self;
}
@end

这样的方式实现,不管有多少个属性,写这几行代码就搞定了。怎么,还嫌麻烦,下面看看更加简便的方法:两句代码搞定。
我们把encodeWithCoder 和 initWithCoder这两个方法抽成宏

#import "Movie.h"
#import <objc/runtime.h>

#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\

#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
    encodeRuntime(Movie)
}

- (id)initWithCoder:(NSCoder *)decoder
{
    initCoderRuntime(Movie)
}
@end

我们可以把这两个宏单独放到一个文件里面,这里以后需要进行数据持久化的模型都可以直接使用这两个宏。

###7 实现字典转模型的自动转换
字典转模型的应用可以说是每个app必然会使用的场景,虽然实现的方式略有不同,但是原理都是一致的:遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
像几个出名的开源库:JSONModel,MJExtension等都是通过这种方式实现的。

  • 先实现最外层的属性转换
// 创建对应模型对象
    id objc = [[self alloc] init];

    unsigned int count = 0;

    // 1.获取成员属性数组
    Ivar *ivarList = class_copyIvarList(self, &count);

    // 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
    for (int i = 0; i < count; i++) {

        // 2.1 获取成员属性
        Ivar ivar = ivarList[i];

        // 2.2 获取成员属性名 C -> OC 字符串
       NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 2.3 _成员属性名 => 字典key
        NSString *key = [ivarName substringFromIndex:1];

        // 2.4 去字典中取出对应value给模型属性赋值
        id value = dict[key];

        // 获取成员属性类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        }

如果模型比较简单,只有NSString,NSNumber等,这样就可以搞定了。但是如果模型含有NSArray,或者NSDictionary等,那么我们还需要进行第二步转换。

  • 内层数组,字典的转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { 

             //  是字典对象,并且属性名对应类型是自定义类型
            // 处理类型字符串 @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            // 自定义对象,并且值是字典
            // value:user字典 -> User模型
            // 获取模型(user)类对象
            Class modalClass = NSClassFromString(ivarType);

            // 字典转模型
            if (modalClass) {
                // 字典转模型 user
                value = [modalClass objectWithDict:value];
            }

        }

        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;

                // 获取数组中字典对应的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model =  [classModel objectWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型数组赋值给value
                value = arrM;

            }
        }

我自己觉得系统自带的KVC模式字典转模型就挺好的,假设movie是一个模型对象,dict 是一个需要转化的 [movie setValuesForKeysWithDictionary:dict]; 这个是系统自带的字典转模型方法,个人感觉也还是挺好用的,不过使用这个方法的时候需要在模型里面再实现一个方法才行,

  • (void)setValue:(id)value forUndefinedKey:(NSString *)key 重写这个方法为了实现两个目的:1. 模型中的属性和字典中的key不一致的情况,比如字典中有个id,我们需要把它赋值给uid属性;2. 字典中属性比模型的属性还多的情况。
    如果出现以上两种情况而没有实现这个方法的话,程序就会崩溃。
    这个方法的实现:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"id"]) {
        self.uid = value;
    }
}

几个参数概念

以上的几种方法应该算是runtime在实际场景中所应用的大部分的情况了,平常的编码中差不多足够用了。
如果从头仔细看到尾,相信你基本的用法应该会了,虽然会用是主要的目的,有几个基本的参数概念还是要了解一下的。

###1.objc_msgSend

/* Basic Messaging Primitives
 *
 * On some architectures, use objc_msgSend_stret for some struct return types.
 * On some architectures, use objc_msgSend_fpret for some float return types.
 * On some architectures, use objc_msgSend_fp2ret for some float return types.
 *
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */

这是官方的声明,从这个函数的注释可以看出来了,这是个最基本的用于发送消息的函数。另外,这个函数并不能发送所有类型的消息,只能发送基本的消息。比如,在一些处理器上,我们必须使用objc_msgSend_stret来发送返回值类型为结构体的消息,使用objc_msgSend_fpret来发送返回值类型为浮点类型的消息,而又在一些处理器上,还得使用objc_msgSend_fp2ret来发送返回值类型为浮点类型的消息。
最关键的一点:无论何时,要调用objc_msgSend函数,必须要将函数强制转换成合适的函数指针类型才能调用。
从objc_msgSend函数的声明来看,它应该是不带返回值的,但是我们在使用中却可以强制转换类型,以便接收返回值。另外,它的参数列表是可以任意多个的,前提也是要强制函数指针类型。
其实编译器会根据情况在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。
###2 SEL
objc_msgSend函数第二个参数类型为SEL,它是selector在Objc中的表示类型(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:
typedef struct objc_selector *SEL;
其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器。
不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器,于是 Objc 中方法命名有时会带上参数类型(NSNumber一堆抽象工厂方法),Cocoa 中有好多长长的方法哦。
###3.id
objc_msgSend第一个参数类型为id,大家对它都不陌生,它是一个指向类实例的指针:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object结构体包含一个isa指针,根据isa指针就可以顺藤摸瓜找到对象所属的类。
PS:isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用class方法来确定实例对象的类。因为KVO的实现机理就是将被观察对象的isa指针指向一个中间类而不是真实的类,这是一种叫做 isa-swizzling 的技术,详见官方文档

###4.Class

之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针:
typedef struct objc_class *Class;
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;

可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。
在objc_class结构体中:ivars是objc_ivar_list指针;methodLists是指向objc_method_list指针的指针。也就是说可以动态修改 *methodLists 的值来添加成员方法,这也是Category实现的原理.

www.jianshu.com/p/46dd81402f63 demo地址
https://github.com/suifengqjn/runtime
##ps
demo挺不错的,不过在NSObject+Item这个类有个小bug

###bug重现
Movie类

//
//  Movie.h
//  runtime
//
//  Created by qianjianeng on 16/4/11.
//  Copyright © 2016年 SF. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "NSObject+Item.h"
#import "HGUser.h"
//1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding
@interface Movie : NSObject<NSCoding, ModelDelegate>

@property (nonatomic, strong) HGUser * user;
@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;
@property (nonatomic, copy) NSArray *list;
@property (nonatomic, copy) NSDictionary *dici;
@property (nonatomic, strong) NSArray<HGUser*> * userlist;

@end

m文件

//
//  Movie.m
//  runtime
//
//  Created by qianjianeng on 16/4/11.
//  Copyright © 2016年 SF. All rights reserved.
//

#import "Movie.h"
#import <objc/runtime.h>


@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
    unsigned int count = 0;
    
    Ivar *ivars = class_copyIvarList([Movie class], &count);
    
    for (int i = 0; i<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        
        const char *name = ivar_getName(ivar);
        // 归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
        
    }
    
    free(ivars);
    
    
}

- (id)initWithCoder:(NSCoder *)decoder

{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i<count; i++) {
            // 取出i位置对应的成员变量
            Ivar ivar = ivars[i];
            // 查看成员变量
            const char *name = ivar_getName(ivar);
            // 归档
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [decoder decodeObjectForKey:key];
            // 设置到成员变量身上
            [self setValue:value forKey:key];
            
        }
        free(ivars);
        
    }
    
    return self;
    
}


+ (NSDictionary *)arrayContainModelClass{
    return @{@"userlist":@"HGUser"};
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"%@--%@--%@--%@", _movieName, _movieId, _pic_url, _user];
}


//如果用系统的方法字典转模型,一定要实现这个方法
//- (void)setValue:(id)value forUndefinedKey:(NSString *)key
//{
//    
//}
@end

调用地方

   user.age = @11;
    NSDictionary* dicU = @{@"age":@11};
     NSDictionary* dicU1 = @{@"1231":@11};
    NSDictionary* dic = @{@"movieName":@"aaaaaaaa",@"movieId":@"1222331",@"pic_url":@"llllllllll",@"user":dicU,@"list":@[@"12",@"34"],@"dici":dicU1,@"userlist":@[dicU]};
    
    Movie* m = [Movie objectWithDict:dic];
/

此时会崩溃
这里写图片描述

###原因
是因为里面有list的array不是自定义类型,
这里写图片描述
这里取到的type就是空的
###修改
添加一个判断 完美解决
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值