摘要: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指针指向它自己。

 


 


三、消息发送过程:

  • 1、首先检查这个selector是不是要忽略。比如Mac OS X开发,有了垃圾回收就不会理会retain,release这些函数。
  • 2、检测这个selector的target是不是nil,OC允许我们对一个nil对象执行任何方法不会Crash,因为运行时会被忽略掉。
  • 3、如果上面两步都通过了,就开始查找这个类的实现IMP,先从cache里查找,如果找到了就运行对应的函数去执行相应的代码。
  • 4、如果cache中没有找到就找类的方法列表中是否有对应的方法。
  • 5、如果类的方法列表中找不到就到父类的方法列表中查找,一直找到NSObject类为止。
  • 6、如果还是没找到就要开始进入动态方法解析

四、消息转发:

没有方法的实现,程序会在运行时挂掉并抛出 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];
}


ios 消息传递流程 ios 消息传递和转发_ios

这里第一字符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];
}


ios 消息传递流程 ios 消息传递和转发_objective-c_02

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


ios 消息传递流程 ios 消息传递和转发_runtime_03

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(@"需要交换方法");
}
  • 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;
    
}

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");
}