iOS-runtime理解与运用

在我们刚刚开始学习oc这门语言时,总有一些词我们在各种基础入门教程上反复提到,例如,Objective-C语言是一门动态语言,且面向对象,why?

Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。

这种特性意味着Objective-C不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于Objective-C来说,这个运行时系统就像一个操作系统一样:它让所有的工作可以正常的运行。这个运行时系统即Objc Runtime。Objc Runtime其实是一个Runtime库,它基本上是用C和汇编写的,这个库使得C语言有了面向对象的能力。

一、什么是类与对象?

一个对象通过一个类来描述它的特性,就如car类,有价格price,载客量peopleNumber等成员变量,或者属性来描述车,还有一系列方法,包括实例方法和类方法来实现车的特性,比如-(void)begin;-(void)stop;
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:
typedef struct objc_class *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;  // 类的版本信息,默认为0
    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;

其实一个类oc中其实就是一个结构体,这个结构体又是一些什么属性能够描述一个类呢?

  • Class isa OBJC_ISA_AVAILABILITY isa指针指向它的metaClass(元类),可以看成储存这个类的类方法的一个类,而这个元类其中的结构体结构也有一个isa指针,是指向Nsobject的metaClass的,这样就形成完美闭环

  • metaClass 存储着这个类所有的类方法,当向一个类发送消息时,则会通过isa指针找到相应的方法,当向一个类实例对象发送消息,则通过struct objc_method_list **methodLists找到相应的方法

runtime提供了大量的函数来操作类与对象。类的操作方法大部分是以class为前缀的,而对象的操作方法大部分是以objc或object_为前缀。下面我们将根据这些方法的用途来分类讨论这些方法的使用。

-动态交换系统方法

#import 

@interface runtimeManage : NSObject
//自定义两个对象方法
-(void)run;
-(void)sleep;
@end

#import "runtimeManage.h"

@implementation runtimeManage
//实现两个对象方法
-(void)run
{
    NSLog(@"run");
}
-(void)sleep
{
    NSLog(@"sleep");
}
@end

接下来就是关键代码,怎么交换

//通过SEL映射 取到对应的方法,分别是run sleep
    Method m1=class_getInstanceMethod([runtimeManage class], @selector(run));
    Method m2=class_getInstanceMethod([runtimeManage class], @selector(sleep));
    //然后就交换
    method_exchangeImplementations(m1, m2);
    runtimeManage *manage=[[runtimeManage alloc]init];
    //调用方法检验是否交换成功
    [manage run];
    [manage sleep];

方法总结class_getInstanceMethodmethod_exchangeImplementations

-拦截系统方法并添加功能或者替换

当整个项目临时需要更换另外一种字体,不紧急的情况是可以搜索出来一个个改,任务量和修改的完整度视项目大小而定,可以使用runtime来实现整体修改功能

#import 
#import "UIFont+user.h"

@implementation UIFont (user)

+(UIFont *)UserSystemFontOfSize:(CGFloat)fontSize
{
    //这里可以增加判断,什么情况下要交换,在7以上使用带后缀的字体
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        // 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
        fontSize=fontSize+10;
    }
    return [UIFont UserSystemFontOfSize:fontSize];
}
+(void)load
{
    //此方法仅调用一次
    Method m1=class_getClassMethod([UIFont class], @selector(systemFontOfSize:
                                                             ));
    Method m2=class_getClassMethod([UIFont class], @selector(UserSystemFontOfSize:));
    method_exchangeImplementations(m1, m2);
}
@end

-动态添加方法与消息转发

当方法太多,在编译期间会把所有方法添加到相应的地方,这是会消耗内存的,动态添加会减少内存消耗,(具体原因做个标记,后期调研)-------------------按照正常流程,当一个消息发送后,会在消息接收的对象的缓存方法列表Cache里查找方法,找不到则会调用resolveInstanceMethod:或者resolveClassMethod:,在这两个方法里我们有一次机会来添加方法

//方法是c语言格式的方法,其中id,sel是两个隐藏参数,是必须要有的
void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
@implementation MyClass
//如果是-开头的实例方法,则在这个函数,类方法resolveClassMethod:中
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    //如果是调用的没有实现的方法,也就是我们要动态添加实现的这个方法名
    if (aSEL == @selector(resolveThisMethodDynamically)) {
    //第一个参数 :为哪个类添加方法,第三个参数:IMP理解为这个方法的具体实现的地址 第四个参数:是一种编码格式,需要去学习一下Type Encodings
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
//返回yes代表此次消息处理到此为止,不在往下进行,返回no则进行消息转发步骤
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end

消息转发

iOS-runtime理解与运用_第1张图片
消息转发.png

转发机制开始前,我们可以还有一次机会可以更换消息的接受者,在方法(id)forwardingTargetForSelector:(SEL)aSelector

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
  //此处返回一个另外的类,则会在另外的类里去查找是否有此方法的实现,返回nil或者self,则正式进入消息转发流程forwardInvocation:
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

最后一步,也是我们对这个没人要的消息作处理的最后一次机会forwardInvocation:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

//重点:注意:参数 anInvocation 是从哪来的?
在 forwardInvocation: 消息发送前,Runtime 系统会向对象发送methodSignatureForSelector: 消息,并取到返回的方法签名用于生成 NSInvocation 对象。所以重写 forwardInvocation: 的同时也要重写 methodSignatureForSelector: 方法,否则会抛异常。

这一步我们把没人接纳的这条消息都发给这个垃圾桶类来处理,这样不会崩溃,留了一个坑,后续加深学习在整理,这样通过消息转发我们可以实现类似多继承的效果,实际用到的地方只能实际问题实际分析,熟悉远离,遇到问题才能灵活的去利用这个知识点解决问题,直接复制来心里总是不踏实的。。。。

-分类增加属性

给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 给系统NSObject类动态添加属性name

    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"小码哥";
    NSLog(@"%@",objc.name);

}


@end


// 定义关联的key
static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name
{
    // 根据关联的key,获取关联的值。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
    // 第一个参数:给哪个对象添加关联
    // 第二个参数:关联的key,通过这个key获取
    // 第三个参数:关联的value
    // 第四个参数:关联的策略
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

-获取类的属性和方法以及运用

获取属性主要就是熟悉下面的方法,直接摘录一段其他文章的方法列表,作为纪录和查找之用
获得某个类的类方法Method class_getClassMethod(Class cls , SEL name)
获得某个类的实例对象方法Methodclass_getInstanceMethod(Class cls , SEL name)
交换两个方法的实现void method_exchangeImplementations(Method m1 , Method m2)
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)
利用参数key 将对象object中存储的对应值取出来id objc_getAssociatedObject(id object , const void *key)
获得某个类的所有成员变量(outCount 会返回成员变量的总数) 参数: 1、哪个类 2、放一个接收值的地址,用来存放属性的个数 3、返回值:存放所有获取到的属性,通过下面两个方法可以调出名字和类型Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
获得成员变量的名字const char *ivar_getName(Ivar v)
获得成员变量的类型const char *ivar_getTypeEndcoding(Ivar v)
获取协议列表protocol_getName
获取属性列表property_getName

你可能感兴趣的:(iOS-runtime理解与运用)