• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

iOS,Objective-C Runtime

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

1.简介

2.与Runtime交互

3.Runtime术语

4.消息

5.动态方法解析

6.消息转发

7.健壮的实例变量(Non Fragile ivars)

8.Objective-C Associated Objects

9.Method Swizzling

10.总结

1.简介

    参考博客:http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

      因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。 Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。

      Runtime其实有两个版本:“modern”和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。

      面向切面编程:(AOP是Aspect Oriented Program的首字母缩写)这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

 

2.与Runtime交互

 

    Objc 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation 框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。

 

   2.1.Objective-C源代码

 

          大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。消息的执行会使用到一些编译器为实现动态语言特性而创建的数据结构和函数,Objc中的类、方法和协议等在 runtime 中都由一些数据结构来定义

 

   2.2.NSObject的方法

 

          Cocoa 中大多数类都继承于NSObject类,也就自然继承了它的方法。最特殊的例外是NSProxy,它是个抽象超类,它实现了一些消息转发有关的方法,可以通过继承它来实现一个其他类的替身类或是虚拟出一个不存在的类。

 

          有的NSObject中的方法起到了抽象接口的作用,比如description方法需要你重载它并为你定义的类提供描述内容。NSObject还有些方法能在运行时获得类的信息,并检查一些特性,比如class返回对象的类;isKindOfClass:和isMemberOfClass:则检查对象是否在指定的类继承体系中;respondsToSelector:检查对象能否响应指定的消息;conformsToProtocol:检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。

 

   2.3.Runtime的函数

           Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。虽然有一些方法构成了NSObject类的基础,但是你在写 Objc 代码时一般不会直接用到这些函数的,除非是写一些 Objc 与其他语言的桥接或是底层的debug工作。 

3.Runtime术语

  objc_msgSend:方法吧,都会说它的伪代码如下或类似的逻辑,反正就是获取 IMP (函数指针,保存了方法地址)并调用,因为 objc_msgSend 是用汇编语言写的,针对不同架构有不同的实现。它的真身是这样的:

id objc_msgSend(id self, SEL _cmd, ...) {

  Class class = object_getClass(self);

  IMP imp = class_getMethodImplementation(class, _cmd);

  return imp ? imp(self, _cmd, ...) : 0;

}

 

 

   3.1.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一堆抽象工厂方法)。

 

   3.2.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 的技术,详见官方文档

 

 

   3.3.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;

 

 

 可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。

 

PS:OBJC2_UNAVAILABLE之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,为的是兼容非Objective-C 2.0的遗留逻辑,但我们仍能从中获得一些有价值的信息,有兴趣的可以查看源代码。

Objective-C 2.0 的头文件虽然没暴露出objc_class结构体更详细的设计,我们依然可以从Objective-C 1.0 的定义中小窥端倪:

objc_class结构体中:ivarsobjc_ivar_list指针;methodLists是指向objc_method_list指针的指针。也就是说可以动态修改*methodLists的值来添加成员方法,这也是Category实现的原理,同样解释了Category不能添加属性的原因。

其中objc_ivar_list和objc_method_list分别是成员变量列表和方法列表:

 

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;

}                                                            OBJC2_UNAVAILABLE;

 

struct objc_method_list {

    struct objc_method_list *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;

}

 

 

      如果你C语言不是特别好,可以直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息。

      不知道你是否注意到了objc_class中也有一个isa对象,这是因为一个 ObjC 类本身同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似[NSObject alloc]的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc] 这条消息发给类对象的时候,objc_msgSend()会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。

上图实线是 super_class 指针,虚线是isa指针。 有趣的是根元类的超类是NSObject,而isa指向了自己,而NSObject的超类为nil,也就是它没有超类。

        3.3.1.Method

      Method是一种代表类中的某个方法的类型。

        typedef struct objc_method *Method;

objc_method在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:

struct objc_method {

    SEL method_name                                          OBJC2_UNAVAILABLE;

    char *method_types                                       OBJC2_UNAVAILABLE;

    IMP method_imp                                           OBJC2_UNAVAILABLE;

}   

  • 方法名类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
  • 方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
  • method_imp指向了方法的实现,本质上是一个函数指针,后面会详细讲到。

 

 

        3.3.2.lvar

         Ivar是一种代表类中实例变量的类型。

        typedef struct objc_ivar *Ivar;

objc_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

}  

 

 可以根据实例查找其在类中的名字,也就是“反射”:

#import <objc/runtime.h>

-(NSString *)nameWithInstance:(id)instance {

    unsigned int numIvars = 0;

    NSString *key=nil;

    Ivar * ivars = class_copyIvarList([self class], &numIvars);

    for(int i = 0; i < numIvars; i++) {

        Ivar thisIvar = ivars[i];

        const char *type = ivar_getTypeEncoding(thisIvar);

        NSString *stringType =  [NSString stringWithCString:type encoding:NSUTF8StringEncoding];

        if (![stringType hasPrefix:@"@"]) {

            continue;

        }

        if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌!

            key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];

            break;

        }

    }

    free(ivars);

    return key;

}

class_copyIvarList 函数获取的不仅有实例变量,还有属性。但会在原本的属性名前加上一个下划线。

遍历属性

     //遍历UIPageControl属性
      unsigned int count = 0;
      Ivar *ivars = class_copyIvarList([UIPageControl class], &count);
      for (int i = 0; i < count; i++) {
          Ivar ivar = ivars[i];
          //获取所有私有属性
          const char *property = ivar_getName(ivar);
          NSLog(@"%@",[[NSString alloc]initWithCString:property encoding:NSUTF8StringEncoding]);
      }

 

 

   3.4.IMP

 

     IMPobjc.h中的定义是:

 

   typedef id (*IMP)(id, SEL, ...);         

 

        它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面会提到。 

你会发现IMP指向的方法与objc_msgSend函数类型相同,参数都包含idSEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过一组idSEL参数就能确定唯一的方法实现地址;反之亦然。

 

 

   3.5.Cache

    在runtime.h中Cache的定义如下:

    typedef struct objc_cache *Cache

还记得之前objc_class结构体中有一个struct objc_cache *cache吧,它到底是缓存啥的呢,先看看objc_cache的实现:

struct objc_cache {

    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;

    unsigned int occupied                                    OBJC2_UNAVAILABLE;

    Method buckets[1]                                        OBJC2_UNAVAILABLE;

};

Cache为方法调用的性能进行优化,通俗地讲,每当实例对象接收到一个消息时,它不会直接在isa指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了,而是优先在Cache中查找。Runtime 系统会把被调用的方法存到Cache中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。这根计算机组成原理中学过的 CPU 绕过主存先访问Cache的道理挺像,苹果为提高Cache命中率。

 

   3.6.Property 

property标记了类中的属性,这个不必多说大家都很熟悉,它是一个指向objc_property结构体的指针:

 

typedef struct objc_property *Property;

 

typedef struct objc_property *objc_property_t;//这个更常用

 

可以通过class_copyPropertyList 和 protocol_copyPropertyList方法来获取类和协议中的属性:

 

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

 

 返回类型为指向指针的指针,哈哈,因为属性列表是个数组,每个元素内容都是一个objc_property_t指针,而这两个函数返回的值是指向这个数组的指针。

 

举个栗子,先声明一个类:

@interface Lender : NSObject {

    float alone;

}

@property float alone;

@end

 

你可以用下面的代码获取属性列表:

id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

 

你可以用property_getName函数来查找属性名称:

const char *property_getName(objc_property_t property)

 

你可以用class_getProperty protocol_getProperty通过给出的名称来在类和协议中获取属性的引用:

objc_property_t class_getProperty(Class cls, const char *name)

objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

 

你可以用property_getAttributes函数来发掘属性的名称和@encode类型字符串:

const char *property_getAttributes(objc_property_t property)

 

把上面的代码放一起,你就能从一个类中获取它的属性啦:

#import <objc/runtime.h>

#import "Lenaer.h"

 

 id LenderClass = objc_getClass("Lender");

unsigned int outCount, i;

objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

for (i = 0; i < outCount; i++) {

    objc_property_t property = properties[i];

    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));

} 

对比下 class_copyIvarList 函数,使用 class_copyPropertyList 函数只能获取类的属性,而不包含成员变量。但此时获取的属性名是不带下划线的。

 

 

4.消息

Objc 中发送消息是用中括号([])把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。

有关消息发送和消息转发机制的原理,可以查看这篇文章

   4.1.objc_msgSend函数

         编译器会根据情况在objc_msgSendobjc_msgSend_stretobjc_msgSendSuper, 或 objc_msgSendSuper_stret四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。排列组合正好四个方法。

        值得一提的是在 i386 平台处理返回类型为浮点数的消息时,需要用到objc_msgSend_fpret函数来进行处理,这是因为返回类型为浮点数的函数对应的 ABI(Application Binary Interface) 与返回整型的函数的 ABI 不兼容。此时objc_msgSend不再适用,于是objc_msgSend_fpret被派上用场,它会对浮点数寄存器做特殊处理。不过在 PPC 或 PPC64 平台是不需要麻烦它的。 

        PS:有木有发现这些函数的命名规律哦?带“Super”的是消息传递给超类;“stret”可分为“st”+“ret”两部分,分别代表“struct”和“return”;“fpret”就是“fp”+“ret”,分别代表“floating-point”和“return”。

 

         下面详细叙述下消息发送步骤:

  • 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain, release 这些函数了。
  • 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
  • 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
  • 如果 cache 找不到就找一下方法分发表。
  • 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
  • 如果还找不到就要开始进入动态方法解析了。

PS:这里说的分发表其实就是Class中的方法列表,它将方法选择器和方法实现地址联系起来。

   4.2.方法中的隐藏参数

     我们经常在方法中使用self关键字来引用实例本身,但从没有想过为什么self就能取到调用当前对象的方法吧。其实self的内容是在方法运行时被偷偷的动态传入的。(之所以说它们是隐藏的是因为在源代码方法的定义中并没有声明这两个参数。它们是在代码被编译时被插入实现中的。尽管这些参数没有被明确声明,在源代码中我们仍然可以引用它们。)

 

         当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

 

  • 接收消息的对象(也就是self指向的内容)
  • 方法选择器(_cmd指向的内容)

    而当方法中的super关键字接收到消息时,编译器会创建一个objc_super结构体:

    struct objc_super { id receiver; Class class; };

     

    这个结构体指明了消息应该被传递给特定超类的定义。但receiver仍然是self本身,这点需要注意,因为当我们想通过[super class]获取超类时,编译器只是将指向selfid指针和classSEL传递给了objc_msgSendSuper函数,因为只有在NSObject类才能找到class方法,然后class方法调用object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是指向selfid指针,与调用[self class]相同,所以我们得到的永远都是self的类型。

 

   4.3.获取方法地址

       在IMP那节提到过可以避开消息绑定而直接获取方法的地址并调用方法。这种做法很少用,除非是需要持续大量重复调用某方法的极端情况,避开消息发送泛滥而直接调用该方法会更高效。

 

5.动态方法解析

       可以动态地提供一个方法的实现。例如我们可以用@dynamic关键字在类的实现文件中修饰一个属性:

  @dynamic propertyName;

(@dynamic 意思是由开发人员提供相应的代码:对于只读属性需要提供 getter,对于读写属性需要提供 getter 和setter。

@synthesize 意思是,除非开发人员已经做了,否则由编译器生成相应的代码,以满足属性声明。)

     可以通过分别重载resolveInstanceMethod:resolveClassMethod:方法分别添加实例方法实现和类方法实现。因为当 Runtime 系统在Cache和方法分发表中(包括超类)找不到要执行的方法时,Runtime会调用resolveInstanceMethod:resolveClassMethod:来给程序员一次动态添加方法实现的机会。

//RuntimeMain.h文件

 

//
//  RuntimeMain.h
//  RuntimeTest
//
//  Created by Vie on 2017/2/8.
//  Copyright © 2017年 Vie. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface RuntimeMain : NSObject

@end

 

 

 

 

//RuntimeMain.m文件

 

//
//  RuntimeMain.m
//  RuntimeTest
//
//  Created by Vie on 2017/2/8.
//  Copyright © 2017年 Vie. All rights reserved.
//

#import "RuntimeMain.h"
#import <objc/runtime.h>
#import "RuntimeFoward.h"
@implementation RuntimeMain
#pragma mark 实例方法动态解析重定向
//动态实例方法解析,如果这里没有找到该执行的方法会指定到重定向forwardingTargetForSelector:方法
+(BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(goToSchool:)) {
            //用class_addMethod函数完成向特定类添加特定方法实现的操作
            //其中 “[email protected]:” 表示返回值和参数(为了兼容32位机型使用"[email protected]:@"),增加f处理float参数,这个符号涉及 Type Encoding
    
         return   class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "[email protected]:@");
    
        }
    
        return [super resolveInstanceMethod:sel];
}
//重定向实例方法,Lenaer实现了该实例方法;如果forwardingTargetForSelector:未找方法就转发给forwardInvocation:方法
//forwardingTargetForSelector:仅支持一个对象的返回,也就是说消息只能被转发给一个对象
-(id)forwardingTargetForSelector:(SEL)aSelector{

    if(aSelector == @selector(learnClass:)) {
        return [[NSClassFromString(@"RuntimeFoward") alloc] init];
    }
    //千万别返回self,因为那样会死循环。重定向的类未实现该方法会导致崩溃
    return [super forwardingTargetForSelector:aSelector];
}
//在forwardInvocation:之前创建一个有效的方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    //其中 “[email protected]:” 表示返回值和参数(为了兼容32位机型使用"[email protected]:@"),增加f处理float参数,这个符号涉及 Type Encoding
    return [NSMethodSignature signatureWithObjCTypes:"[email protected]:@"];
}
    //如果resolveInstanceMethod:和forwardingTargetForSelector:以及forwardInvocation:都未找到方法实现将崩溃,所以可以再最后else定义一个错误日志输出方法处理崩溃
    //forwardInvocation:可以将消息同时转发给任意多个对象
    //forwardInvocation:方法就像一个不能识别的消息的分发中心,将这些消息转发给不同接收对象。或者它也可以象一个运输站将所有的消息都发送给同一个接收对象。它可以将一个消息翻译成另外一个消息,或者简单的”吃掉“某些消息,因此没有响应也没有错误。forwardInvocation:方法也可以对不同的消息提供同样的响应,这一切都取决于方法的具体实现。该方法所提供是将不同的对象链接到消息链的能力。
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    return [anInvocation invokeWithTarget:[[RuntimeFoward alloc] init]];
}
    
-(void)myInstanceMethod:(NSString *)string{
    
    NSLog(@"myInstanceMethod = %@", string);
    
}
    
#pragma mark 类方法动态解析重定向
    
    //动态类方法解析,如果这里没有找到该执行的方法会指定到重定向forwardingTargetForSelector:方法
+(BOOL)resolveClassMethod:(SEL)sel{
    
    if (sel==@selector(classFouction:)) {
        
        //用class_addMethod函数完成向特定类添加特定方法实现的操作
        
        //其中 “[email protected]:” 表示返回值和参数(为了兼容32位机型使用"[email protected]:@")增加f处理float参数,这个符号涉及 Type Encoding
        
        return  class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "[email protected]:@f");
        
    }
    
    return [class_getSuperclass(self) resolveClassMethod:sel];
    
}
    //重定向类方法,Lenaer实现了该类方法;如果resolveClassMethod:和forwardingTargetForSelector:都未找到方法实现将崩溃,所以可以再最后else定义一个错误日志输出方法处理崩溃
+(id)forwardingTargetForSelector:(SEL)aSelector{
    if(aSelector == @selector(testNotMe:)) {
        return NSClassFromString(@"RuntimeFoward") ;
    }
    //千万别返回self,因为那样会死循环。重定向的类未实现该方法会导致崩溃
    return [super forwardingTargetForSelector:aSelector];
}
+(void)myClassMethod:(NSString *)string{
    
    NSLog(@"myClassMethod = %@", string);
    
}
@end

 

 

 

//RuntimeFoward.h文件

//
//  RuntimeFoward.h
//  RuntimeTest
//
//  Created by Vie on 2017/2/8.
//  Copyright © 2017年 Vie. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface RuntimeFoward : NSObject
@end

 

//RuntimeFoward.m文件

//
//  RuntimeFoward.m
//  RuntimeTest
//
//  Created by Vie on 2017/2/8.
//  Copyright © 2017年 Vie. All rights reserved.
//

#import "RuntimeFoward.h"

@implementation RuntimeFoward
-(void)fowardGet{
    NSLog(@"消息转发给RuntimeFoward");
}

-(void)fowardWithString:(NSString *)string{
    NSLog(@"消息转发给RuntimeFoward,并带参数%@",string);
}
-(void)learnClass:(NSString *)string{
     NSLog(@"消息转发给RuntimeFoward,learnClass并带参数%@",string);
}
    
-(NSString *)getInfo:(NSString *)name height:(float)aHeight{
    return [NSString stringWithFormat:@"%@身高%f",name,aHeight];
}
-(float)getRectangularArea:(float)aWidth height:(float)aHeight{
    return aWidth*aHeight;
}
    
+(void)testNotMe:(NSString *)string{
   NSLog(@"消息转发给RuntimeFoward,testNotMe并带参数%@",string);
}

@end

 

 使用

#import <objc/message.h>
#import "RuntimeMain.h"

//调用实例方法
    RuntimeMain *runMain=[[RuntimeMain alloc] init];
    //调用无参数无返回值方法
    ((void (*) (id, SEL)) objc_msgSend) (runMain, sel_registerName("fowardGet"));
    //调用有参数无返回值方法
    ((void (*) (id, SEL,NSString *)) objc_msgSend)(runMain,sel_registerName("fowardWithString:"),@"哈哈哈");
    ((void (*) (id, SEL,NSString *)) objc_msgSend)(runMain,sel_registerName("goToSchool:"),@"哈哈哈");
    ((void (*) (id, SEL,NSString *)) objc_msgSend)(runMain,sel_registerName("learnClass:"),@"哈哈哈");
    //调用返回String有参数方法
   NSString *infoString= ((NSString* (*)(id,SEL,NSString *,float)) objc_msgSend)(runMain,sel_registerName("getInfo:height:"),@"张三",175.81);
    NSLog(@"%@",infoString);
    //调用返回float方法, 
    float area=((float (*) (id,SEL,float,float)) objc_msgSend)(runMain,sel_registerName("getRectangularArea:height:"),12.0f,12.0f);
    NSLog(@"获得长方形面积为%f",area);
    
    //调用类方法
    [RuntimeMain performSelector:@selector(classFouction:) withObject:@"xxx"];
    [RuntimeMain performSelector:@selector(testNotMe:) withObject:@"xxx"];

运行结果:

2017-02-08 19:33:30.608 RuntimeTest[26467:721253] 消息转发给RuntimeFoward
2017-02-08 19:33:30.609 RuntimeTest[26467:721253] 消息转发给RuntimeFoward,并带参数哈哈哈
2017-02-08 19:33:30.609 RuntimeTest[26467:721253] myInstanceMethod = 哈哈哈
2017-02-08 19:33:30.610 RuntimeTest[26467:721253] 消息转发给RuntimeFoward,learnClass并带参数哈哈哈
2017-02-08 19:33:30.611 RuntimeTest[26467:721253] 张三身高175.809998
2017-02-08 19:33:30.612 RuntimeTest[26467:721253] 获得长方形面积为144.000000
2017-02-08 19:33:30.613 RuntimeTest[26467:721253] myClassMethod = xxx
2017-02-08 19:33:30.616 RuntimeTest[26467:721253] 消息转发给RuntimeFoward,testNotMe并带参数xxx

 

 

6.消息转发

   6.1.重定向

   在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会

替换对象方法接受者- (id)forwardingTargetForSelector:(SEL)aSelector方法替换消息的接受者为其他对象:

替换类方法的接受者,需要覆写 + (id)forwardingTargetForSelector:(SEL)aSelector 方法,并返回类对象

 

   6.2.转发

 

当动态方法解析未找到执行方法,消息转发机制会被触发。先methodSignatureForSelector:创建有效签名,再forwardInvocation:执行方法,我们可以重写这个方法来定义我们的转发逻辑:

 

 

 

   6.3.转发和多继承

          转发和继承相似,可以用于为Objc编程添加一些多继承的效果。就像下图那样,一个对象把消息转发出去,就好似它把另一个对象中的方法借过来或是“继承”过来一样。

这使得不同继承体系分支下的两个类可以“继承”对方的方法,在上图中WarriorDiplomat没有继承关系,但是Warriornegotiate消息转发给了Diplomat后,就好似DiplomatWarrior的超类一样。 

         消息转发弥补了 Objc 不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。它将问题分解得很细,只针对想要借鉴的方法才转发,而且转发机制是透明的。

   6.4.替代者对象(Surrogate Objects)

          转发不仅能模拟多继承,也能使轻量级对象代表重量级对象。弱小的女人背后是强大的男人,毕竟女人遇到难题都把它们转发给男人来做了。这里有一些适用案例,可以参看


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap