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 <Foundation/Foundation.h>

@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 <objc/runtime.h>
#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

消息转发

消息转发.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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容