OC runtime的4种用途

前言

什么是运行时(runtime)?

首先我们要先知道编程语言有静态和动态之分。所谓静态语言,就是在程序运行前决定了所有的类型判断,类的所有成员、方法在编译阶段就确定好了内存地址。也就意味着所有类对象只能访问属于自己的成员变量和方法,否则编译器直接报错。比较常见的静态的语言如:java,c++,c等等。

而动态语言,恰恰相反,类型的判断、类的成员变量、方法的内存地址都是在程序的运行阶段才最终确定,并且还能动态的添加成员变量和方法。也就意味着你调用一个不存在的方法时,编译也能通过,甚至一个对象它是什么类型并不是表面我们所看到的那样,只有运行之后才能决定其真正的类型。相比于静态语言,动态语言具有较高的灵活性和可订阅性。而oc,正是一门动态语言。

介绍到这里,我想可以解释一下运行时是什么了?所谓运行时,就是程序在运行时做的一些事。苹果提供了一套纯c语言的api,即runtime。在iOS开发中runtime的特性使得oc这门语言具有独特的魅力,我们可以利用运行时处理一些特殊的事情,甚至你可以轻松的玩出一些逼格很高的花样来。下面就开始一起进入运行时的世界吧。

在正式进入篇幅之前,首先声明一下,本编的主旨是简要阐述运行时的一些机制和原理,重点是讲述运行时的一些常用用法,不会去深入探究底层的C语言api。

要了解运行时,我们得先了解oc的消息机制

那么什么是消息机制?

在Objective-C中,任何方法的调用,本质是发送消息。比如我们下面方法:

[obj  method];

编译器会自动转化为:

objc_msgSend(obj, @selector (method));

也就是说我们在oc中调用任何一个方法,其实质是转换为runtime中的一个函数objc_msgSend(),这个函数的作用是向obj对象(方法的调用者)发送了一条消息,告诉它你该去执行某个方法。

所以,我们其实也可以直接用运行时去调用你想要调用的任何一个可调用的方法:

    如:

    Dog  *dog = [Dog alloc] init];  

    [dog run:100];

    等价于:

    Dog  *dog  = objc_msgSend(objc_getClass("Dog"), @selector(alloc));

    dog = objc_msgSend(dog, sel_registerName("init"));

     objc_msgSend(dog, sel_registerName("run:"),100); //调用带参数的方法

    注:使用objc_msgSend()函数,须要先import <objc/message.h>

讲到这里,我们就可以说一说什么是oc消息机制,也就是一个方法的调用流程。

1、编译器会先将代码[obj  method]转化为objc_msgSend(obj, @selector (method))函数去执行。

2、在objc_msgSend()函数中,首先通过obj的isa指针找到(对象)obj对应的(类)class。

3、在class中会先去cache中 通过SEL查找对应函数method(cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache中未找到。再去class中的消息列表methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

补充:

>在oc中,每一个对象都有一个isa指针变量,这个指针指向的是对象的类,我们可以通过isa指针访问一个对象的类

>方法都保存在类的消息列表中,这个列表其实是一个字典,key是selector,value是IMP(imp是一个指针类型,指向方法的实现),并且selector和IMP之间的关系是在运行时才决定的,而不是编译时。如此们就可以做出一些特别事情来。

我们可以用运行时做什么

1、互换方法的实现

上面说到selector和IMP之间的关系是在运行时才决定的,那我们是不是可以改变selector和IMP的对应关系呢?runtime就给我们提供了这么一个函数:

 void method_exchangeImplementations(Method m1, Method m2)

我们可以通过此函数交换两个方法的实现,在开发中,可能我们会经常遇到一种场景,想为系统的某个方法增加一些特定的功能,又不想改变原有的东西,想要做到无缝衔接,用runtime方法互换无疑是最完美的。下面以交换系统的dealloc方法的实现为例:

首先建一个NSObject类目NSObject+ExchangeMethod,在类目中为NSObject类扩展一个my_dealloc方法用于替换系统的dealloc方法,其.m文件实现如下:

这样,当一个类的dealloc方法被调用时,会执行my_dealloc方法里的实现,完全无需再对原有的代码做任何改动

 

2、动态添加方法

前面有说到,动态语言调用一个没有的方法时,编译阶段也不不会报错。比如:

    Dog *dog = [Dog alloc] init];

    [dog performSelector:@selector(eat)]];

    注:dog类中没有声明也没实现eat方法

上面代码,编译阶段肯定会通过,但程序一运行时便直接抛出异常闪退,抛出异常的打印闭着眼睛也知道是 :-[Dog eat]: unrecognized selector sent to instance 0x7fac91d0eba0‘。这也印证了动态语言的方法需要在运行阶段才最终确定。

从而,我们可以动态的为某个类添加方法,而苹果performSelector:这个方法也很好的为我们逃过编译报错提供了支持。示例代码如下:

void test(id self ,SEL _cmd){
    NSLog(@"??????");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == NSSelectorFromString(@"eat")) {
        class_addMethod(self, sel, (IMP)test, "[email protected]:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

届时,我们在如上面调用[dog performSelector:@selector(eat)]]时,就会去执行test函数了。

3、动态添加属性

这也是runtime的一个重量级功能了,我们经常会想为系统的类或者一些不便修改的第三方框架的类增加一些自定义的属性以满足开发的需求。这个时候我们还是首先会想到类目,但是问题来了,类目只能为一个类添加方法,不能添加属性。

怎么做呢,还是用到运行时,为类动态添加属性。示例代码:为NSobject类添加一个字符串类型的属性: NSString *name

首先我们还是为NSobject建一个类目,其.h文件如下:

这里我们用property,类目中用 property会自动生成set/get的声明,但是没有实现,也无法生成下划线的成员变量,我们需要手动实现set、get方法。其.m文件如下:

4、获取类中所有的成员变量和属性

在开发中,你可能会遇到想要改变系统自带的类的某一个值,却找不与之对应的api,然后你就在那找瞎了眼,找呀找,始终找不到。这个时候我们可以确定一点,苹果系统自带的类有很多私有的属性或成员变量没有公开出来,也就意味着苹果它不想让我们访问。我靠,那还搞毛,有时需求来了,我还非要访问不可,那怎么办?

用运行时获取类的所有成员变量,即便私有的也能获取的到,用的函数如下:

 Ivar *class_copyIvarList(Class cls, unsigned int *outCount)//获取类中所有的成员变量

     objc_property_t class_getProperty(Class cls, const char *name)//获取类中所有的属性

使用运行时获取类中所有成员变量,还是相当有用的,比如现在一些字典转模型框架,它需要获取到模型的所有属性名,以这个属性名为key,取到字典中对应的value,然后通过kvc给这个属性赋设置值。再比如,你要设置系统UITextField控件placeholder的颜色,你会发现你翻遍api也找不到一个属性和方法来设置,这时你用运行时获取UITextField类所有成员变量,你会发现有一个_placeholderLabel成员变量,我们只需:

[self.textField setValue:[UIColor blueColor] forKeyPath:@"_placeholderLabel.textColor"];

代码文件:.h

#import "LGaddSmallView.h"
#import <objc/runtime.h>

@interface LGaddSmallView (TTT)

@property NSString *name;

@end

代码文件.m

#import "LGaddSmallView+TTT.h"

@implementation LGaddSmallView (TTT)

+(void)load{
        Method m1 = class_getInstanceMethod(self, NSSelectorFromString(@"btn1Click"));
        Method m2 = class_getInstanceMethod(self, NSSelectorFromString(@"vvv"));
        method_exchangeImplementations(m1,m2);
}

-(void)vvv{
    NSLog(@"hhahahhah");
    [self vvv];
    [self performSelector:@selector(eat)];
    self.name = @"sss";
    NSLog(@"%@",self.name);
    unsigned int count;

    Ivar *ivars = class_copyIvarList([UITextField class], &count);

    for (int i = 0; i< count; i++) {
        Ivar ivar = ivars[i];
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        NSLog(@"%@和%@",type,name);

    }
}

void test(id self ,SEL _cmd){
    NSLog(@"??????");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == NSSelectorFromString(@"eat")) {
        class_addMethod(self, sel, (IMP)test, "[email protected]:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

-(NSString *)name{
    return objc_getAssociatedObject(self, "name");
}

-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

代码.h

#import <UIKit/UIKit.h>

@interface LGaddSmallView : UIView
@property(strong, nonatomic) UILabel *label1;
@property(strong, nonatomic) UITextField *TF1;

;
@end

代码.m

#import "LGaddSmallView.h"

//static float celly = 5;

@interface LGaddSmallView()
@end

@implementation LGaddSmallView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    {
        self.backgroundColor = [UIColor whiteColor];
        [self addSubview:self.btn1];
        [self addSubview:self.TF1];
    }
    return self;
}
-(void)btn1Click{
    NSLog(@"kukukuku");
    [self.TF1 setValue:[UIColor blueColor] forKeyPath:@"_placeholderLabel.textColor"];

}

#pragma mark - 懒加载--------------------------------------------------

-(UITextField *)TF1{
    if (_TF1 == nil) {
        _TF1 = [[UITextField alloc] initWithFrame:CGRectMake(self.btn1.right, self.label1.bottom, kSCreenWidth / 4, kSCreenWidth / 4)];
    }
    _TF1.placeholder = @"jagfgfakljfhafhasjfkagfadas";
    return _TF1;
}

- (UIButton *)btn1 {
    if (_btn1 == nil) {
        _btn1 = [[UIButton alloc]
                     initWithFrame:CGRectMake(0, self.label1.bottom, kSCreenWidth / 4, kSCreenWidth / 4)];
        [_btn1 setTitle:@"身份认证" forState:UIControlStateNormal];
        [_btn1 addTarget:self
                      action:@selector(btn1Click)
            forControlEvents:UIControlEventTouchUpInside];
        [_btn1 setImage:[UIImage imageNamed:@"shenfen"] forState:UIControlStateNormal];
        [_btn1 setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        _btn1.titleLabel.font = [UIFont systemFontOfSize:12];
        [self initButton:self.btn1];
    }
    return _btn1;
}

@end
时间: 2024-12-10 20:33:34

OC runtime的4种用途的相关文章

oc - runtime运行机制

Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时做的事放到了运行时来处理.同时OC也是一门简单的语言,很大一部分是C的内容,只是在语言层面上加了关键字和语法,真正让OC强大的是它的运行时,它很小却很强大,其中核心是消息分发.这种动态语言的优势在于:我们写代码时更加灵活,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现. 这种特性意味着OC不仅需要一个编译器,还需要一个运行时系统来执行编译的代码.对于OC来说,这个运行时系统就像一个操作系统一样.这个运行时系

oc/object-c/ios哪种遍历NSArray/NSDictionary方式快?测试报告

做app的时候,总免不了要多次遍历数组或者字典.究竟哪种遍历方式比较快呢?我做了如下测试:首先定义测试用宏: ? 1 2 3 4 5 6 7 8 9 #define MULogTimeintervalBegin(INFO) NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];\ NSTimeInterval duration = 0;\ NSLog(@"MULogTimeintervalBegin:%@", IN

使用OC runtime解决第三方库冲突

前几天在iOS app项目中添加了几个第三方库,各有各的用处,因为一些原因,有些库是不开源的. 添加后,发现app编译不通过,错误如下: 从错误描述中都能看出,app在连接过程中,发现了一些重复的符号,即同样的OC类和方法在不同的库中都有实现:liblibPDRCore.a和libsimpleconfiglib.a这两个库有冲突!恰好,这两个库都要用,而且都不开源,仿佛一下子就走进了死胡同,因为没有办法修改这两个库. 网上搜了一下,碰到这种问题的人还真不少,也提出了解决方案:用lipo命令分解其

使用OC和Swift两种语言写一个发射烟花的小项目

OC与Swift两种实现方式基本上区别不大,主要是在一些对象或方法的调用方式不同 OC代码样式: self.view.backgroundColor = [UIColor blackColor]; //加载颗粒状的火花图片 CAEmitterLayer *emitterLa = [CAEmitterLayer layer]; emitterLa.emitterPosition = CGPointMake(self.view.bounds.size.width/2, self.view.bound

Swift Runtime分析:还像OC Runtime一样吗?来自于转载

Swift Runtime分析:还像OC Runtime一样吗? 本文为手机淘宝资深无线开发工程师尹峥伟的投稿. 尹峥伟(花名 君展),来自手机淘宝技术团队的资深无线开发工程师,主要负责手机淘宝基础架构研发,Github开源库Wax的维护者,微信号yzwlvzxh,微博@君展. Swift 是苹果2014年发布的编程开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序.Swift已经开源,目前最新版本为2.2.我们知道Objective-C是具有

Python文件的两种用途

目录 Python文件的两种用途(掌握) Python文件的两种用途(掌握) python文件总共有两种用途,一种是执行文件:另一种是被当做模块导入. 编写好的一个python文件可以有两种用途: 脚本,一个文件就是整个程序,用来被执行 模块,文件中存放着一堆功能,用来被导入使用 # aaa.py x = 1 def f1(): print('from f1') def f2(): print('from f2') f1() f2() # run.py import aaa 如果直接运行run.

content属性的4种用途

content属性浏览器支持情况,兼容到IE8浏览器,IE7及以下不支持 用途一.配合:before及:after伪元素插入文本 <div>     <p>伪元素</p> </div> p:before{     content:'CSS3';     color:#4bb;     font-weight:bold;     margin-right:20px;     background:#f0f0f0;     /*如果要设置宽高的话,就必须变成块级

关于OC中的几种代码延迟执行方式

第一种: [UIView animateWithDuration:3 delay:3 options:1 animations:^{ self.btn.transform = CGAffineTransformMakeTranslation(300, 400); } completion:^(BOOL finished) { NSLog(@"view animation结束"); }];//不会阻塞线程,animations  block中的代码对于是支持animation的代码,才会

using的几种用途

using 常用来引用命名空间 1 using System; 2 using System.Data; 3 using System.Data.SqlClient; 4 using System.Collections.Generic; 5 using Model; 6 using IDAL; 7 using DBUnititly; using另一个用途是给类和命名空间指定别名 1 using spacename = system.io 2 3 using system 4 using Int