iOS 深入理解Runtime运行原理,即消息发送和消息转发

摘要:Objective-C是基于C语言加入了面向对象特性和消息转发机制的动态语言,这意味着它不仅需要一个编译器,还需要Runtime系统来动态创建类和对象,进行消息发送和转发。

一、相关概念:

1、消息:发送给对象的名称和一组参数。在Objective-C中方法调用是一个消息发送的过程。消息转发是一种功能强大的技术,可以大大增加Objective-C的表现力。什么是消息转发?简而言之,它允许未知的消息被困住并作出反应。换句话说,无论何时发送未知消息,它都会以一个很好的包发送到您的代码中,此时您可以随心所欲地执行任何操作。注意:当我们向一个对象发(实例方法,即减号方法)送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类(类方法,即加号方法)发送消息时,会在这个类的meta-class的方法列表中查找。methodLists存储着一个类的实例方法, meta-class的存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

2、方法:与一个类相关的一段实际代码,并给出一个特定的名字。

3、选择器:表示消息或方法名称的一种特殊方式,表示为类型SEL(方法的指针)。选择器本质上就是不透明的字符串,它们被管理,因此可以使用简单的指针相等来比较它们,从而提高速度。与此相关还有一个IMP:一个函数指针,保存了方法地址。

4、消息发送:接收信息并查找和执行适当方法的过程。Objective-C在调用方法的时候实际上是转成C函数调用,objc_msgSend。例如:

Message *msg = [[Message alloc] init];
msg.target = @"Y73923894JR4HR4UR874R498R49";
[msg sendMasage];
//本质:让对象发送消息
objc_msgSend(msg, @selector(sendMasage));

二、objc/runtime.h、objc/objc.h和objc/message.h相关API

1、objc_class

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;// 父类
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;// 类名
    long version                                             OBJC2_UNAVAILABLE;// 类版本
    long info                                                OBJC2_UNAVAILABLE;// 类信息,供运行期使用的一些位标识
    long instance_size                                       OBJC2_UNAVAILABLE;// 该类的实例变量大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;// 该类的成员变量链表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;// 方法(实例方法,即减号方法)定义的链表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;// 方法缓存
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;// 协议链表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

#endif

2、objc_object

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

3、objc_method

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

4、objc_method_list

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

4、objc_ivar

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
}                                                            OBJC2_UNAVAILABLE;

5、IMP:   IMP是指向实际执行函数体的指针 ,保存了方法地址

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

5、meta-class

meta-class,就像Class一样,也是一个对象。你依旧可以向它发送消息调用函数,自然的,meta-class也会有一个isa指针指向其所属类。所有的meta-class使用基类的meta-class作为他们的所属类。具体而言,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己所属的类。根据这个规则,所有的meta-class使用基类的meta-class作为它们的类,而基类的meta-class也是属于它自己,也就是说基类的meta-class的isa指针指向它自己。

三、消息发送过程:

  • 检查目标对象:在发送消息时,Objective-C 运行时会首先检查接收对象是否为 nil。如果接收对象为 nil,则发送消息不会导致崩溃,而是会被简单地忽略掉。

  • 查找方法实现:如果目标对象不为 nil,Objective-C 运行时会根据选择器在接收对象的类及其父类中查找方法的实现。首先会在方法缓存中查找方法的实现,如果找到了就直接执行;如果没有找到,则会在类的方法列表中查找。如果在当前类中找不到对应的方法实现,会继续在父类的方法列表中查找,直到找到对应的方法实现或者到达 NSObject 类为止。

  • 消息转发机制:如果在以上步骤中无法找到对应的方法实现,Objective-C 运行时会启动消息转发机制。在消息转发过程中,程序可以通过动态添加方法或者将消息转发给其他对象来处理消息。

四、消息转发:

没有方法的实现,程序会在运行时挂掉并抛出 unrecognized selector sent to … 的异常。但在异常抛出前,Objective-C 的运行时会给你三次拯救程序的机会:

1、Method resolution:(动态解析)

Objective-C 运行时会调用 + (BOOL)resolveInstanceMethod:或者 + (BOOL)resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。

void flowerOpenMethod(id obj, SEL _cmd) {
    NSLog(@"flower open");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if(sel == @selector(flowerOpenMethod:)){
        class_addMethod([self class], sel, (IMP)flowerOpenMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

这里第一字符v代表函数返回类型void,第二个字符@代表self的类型id,第三个字符:代表_cmd的类型SEL。

2、Fast forwarding:(快速转发)

消息转发机制执行前,runtime系统允许我们替换消息的接收者为其他对象。通过- (id)forwardingTargetForSelector:(SEL)aSelector方法。如果此方法返回的是nil 或者self,则会进入消息转发机制(- (void)forwardInvocation:(NSInvocation *)invocation),否则将会向返回的对象重新发送消息。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(flowerOpenMethod:)){
        return [[Flower alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

3、Normal forwarding:(消息转发)

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    if([alternateObject respondsToSelector:sel]) {
        [invocation invokeWithTarget:alternateObject];
    } else {
        [self doesNotRecognizeSelector:sel];
    }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
    }
    return methodSignature;
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

forwardInvocation: 方法就是一个不能识别消息的分发中心,将这些不能识别的消息转发给不同的消息对象,或者转发给同一个对象,再或者将消息翻译成另外的消息,亦或者简单的“吃掉”某些消息,因此没有响应也不会报错。例如:我们可以为了避免直接闪退,可以当消息没法处理时在这个方法中给用户一个提示,也不失为一种友好的用户体验。

其中,参数invocation是从哪来的?在forwardInvocation:消息发送前,runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。所以重写forwardInvocation:的同时也要重写methodSignatureForSelector:方法,否则会抛出异常。当一个对象由于没有相应的方法实现而无法响应某个消息时,运行时系统将通过forwardInvocation:消息通知该对象。每个对象都继承了forwardInvocation:方法,我们可以将消息转发给其它的对象。

总结:

1).resolveInstanceMethod:方法(或resolveClassMethod:);(动态消息解析)
        2).forwardingTargetForSelector:方法 (消息接受者重定向)
        3).methodSignatureForSelector:方法 (方法重签名)
        4).forwardInvocation:方法 (消息重定向)
        5).doesNotRecognizeSelector:方法 (未找到消息)
       第一步:+(BOOL)resolveInstanceMethod:(SEL)sel实现方法,指定是否动态添加方法。若返回NO,则进入下一步,如返回YES,则通过class_addMethod函数动态地添加方法,消息得到处理,此流程完毕。
       第二步:若第一步返回的是NO,就会进入-(id)forwardingTargetForSelector:(SEL)aSelector方法,用于指定哪个对象响应这个selector。不能指定为self。若返回为nil,表示没有响应者,则会进入第三步。若返回某个对象,则会调用该对象的方法。
       第三步:若第二步返回的是nil,则我们首先要通过-(NSMethodSignaure *)methodSignatureForSelector:(SEL)aSelector指定方法前面,若返回nil,则表示不处理。若返回方法签名,则进入下一步。
       第四步:当第三步返回方法签名以后,就会调用-(void)forwardInvocation:(NSInvocation *)aInvocation方法,我们可以通过aInvocation修改实现方法,修改响应对象等。
       第五步:若没有实现-(void)forwardInvocation:(NSInvocation)aInvocation方法,那么就会进入-(void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方法,那么就会crash,然后提示找不到响应方法。消息转发到此结束。

 

​​​​​​​

五、Runtime应用:

  • 1、方法未实现、数组越界处理崩溃。

    • 方法未实现,消息转发应用,数组越界,方法交换应用

  • 2、方法交换

  • /// 交换方法
    /// @param theClass 需要进行方法交换的类
    /// @param originalSelector 原方法
    /// @param swizzledSelector 需要交换的好好
    static inline void swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
        Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
        BOOL didAddMethod = addMethod(theClass, @selector(flowerOpenMethod), swizzledMethod);
        if (didAddMethod) {
            replaceMethod(theClass, swizzledSelector, originalMethod);
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    static inline BOOL addMethod(Class theClass, SEL selector, Method method) {
        return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
    }
    static inline BOOL replaceMethod(Class theClass, SEL selector, Method method) {
        return class_replaceMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
    }
    + (void)load {
        swizzleSelector([self class], @selector(flowerOpenMethod), @selector(swizzled_flowerOpenMethod));
    }
    - (void)flowerOpenMethod {
        NSLog(@"原方法");
    }
    - (void)swizzled_flowerOpenMethod {
        NSLog(@"需要交换方法");
    }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

  • 3、自定义解析

  • + (id)modelWithDict:(NSDictionary *)dict modelClass:(NSString *)className {
        
        if (dict == nil || className ==nil || className.length ==0) {
            return nil;
        }
        //取得类对象
        Class ModelClass = objc_getClass([className UTF8String]);
        //NSClassFromString: 判断是否存在字符串对应的类:返回类名或nil
        
        id model = [[ModelClass alloc] init];
        
        unsigned int propertiesCount =0;
        
        unsigned int ivarsCount =0;
            
        objc_property_t *properties = class_copyPropertyList(ModelClass, &propertiesCount);
        
        Ivar *ivars = class_copyIvarList(ModelClass, &ivarsCount);
        
        for(int i =0; i < ivarsCount; i++) {
            
            NSString *memberName = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            
            for (int j =0; j < propertiesCount; j++) {
                
                NSString *propertyName = [[NSString alloc]initWithCString:property_getName(properties[j])encoding:NSUTF8StringEncoding];
                NSRange range = [memberName rangeOfString:propertyName];
                
                if (range.location ==NSNotFound) {
                    
                    continue;
                    
                }
                else {
                    
                    id propertyValue = [dict objectForKey:propertyName];
                    if (!propertyValue) {
                        NSLog(@"json中'%@'字段不存在或数据为NULL", propertyName);
                        continue;
                    }
                    [model setValue:propertyValue forKey:memberName];
                }
                
            }
            
        }  return model;
        
    }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    4、给分类添加属性

  • //定义常量 必须是C语言字符串
    static char *kFlowerNameKey = "kFlowerNameKey";
    - (void)setName:(NSString *)name {
        /*
        OBJC_ASSOCIATION_ASSIGN;            //assign策略
        OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略
        OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略
        OBJC_ASSOCIATION_RETAIN;
        OBJC_ASSOCIATION_COPY;
        */
        /*
        * id object 给哪个对象的属性赋值
        const void *key 属性对应的key
        id value  设置属性值为value
        objc_AssociationPolicy policy  使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择NONATOMIC
        objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
        */
        objc_setAssociatedObject(self, kFlowerNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    - (NSString *)name {
        return objc_getAssociatedObject(self, @"name");
    }
    wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值