iOS --runtime掌握与行使

图片 13只小灰

1.什么是Runtime?

本身所知晓的runtime是三个行使C编写的库,为C增加了面向对象的个性,它是四个库(Runtime
Library粤语:运维时库).在那几个库中能够用C函数来贯彻情势,对象也得以用C语言的结构体来代表…全数oc的主意的幕后都是透过runtime来运营的.

书接上回,经过了小白理论篇,相信大家对此runtime是如何能有贰个大约的定义了,恭喜您装X神技已经加了几许本事点了。同偶然候也很光荣,再一次感激简书的小编能给自身拉到首页。然后那篇小说,介绍一下怎样利用,加深一下接头,大家共同升高。

2.runtime的使用

there is 正文

  • 选用处境:系统自带的方法效果相当不够,给系统自带的法子增添部分功用,并且保持原本的作用。(能够和后续系统类,重写方法达到一样效果)

-viewDidLoad {[super viewDidLoad];// Do any additional setup after
loading the view, typically from a nib.//
须要:给imageNamed方法提供功能,每一回加载图片就剖断下图片是或不是加载成功。//
步骤一:先搞个分类,定义叁个能加载图片而且能打字与印刷的法子+
(instancetype)imageWithName:(NSString *)name;//
步骤二:调换imageNamed和imageWithName的兑现,就能够调用imageWithName,直接调用imageWithName的贯彻。UIImage
*image = [UIImage imageNamed:@”123″];}

扩展

@implementation UIImage // 加载分类到内部存款和储蓄器的时候调用+load{// 调换方法//
获取imageWithName方法地址Method imageWithName =
class_getClassMethod(self, @selector(imageWithName:));//
获取imageWithName方法地址Method imageName =
class_getClassMethod(self, @selector(imageNamed:));//
交流方法地址,相当于交流完毕格局method_exchangeImplementations(imageWithName,
imageName);}//
无法在分拣中重写系统方法imageNamed,因为会把系统的效果给覆盖掉,并且分类中不能够调用super.//
既可以加载图片又能打字与印刷+(instancetype)imageWithName:(NSString *)name{//
这里调用imageWithName,约等于调用imageNameUIImage *image = [self
imageWithName:name];if (image == nil) {NSLog(@”加载空的图纸”);}return
image;}

  • 支付使用处境:加载类到内部存储器的时候也正如费用财富,须求给每一个方法生成映射表,能够利用动态给有个别类,增加格局化解。(杰出面试题:有没有使用performSelector,其实主要想问你有未有动态增进过方法。)

@implementation ViewController

  • viewDidLoad {[super viewDidLoad];// Do any additional setup after
    loading the view, typically from a nib.Person *p = [[Person
    alloc] init];//
    默许person,未有达成eat方法,能够透过performSelector调用,不过会报错。//
    动态增进方法就不会报错[p performSelector:@selector];}@end

@implementation Person// void// 暗许方法都有八个隐式参数,void eat(id
self,SEL sel){NSLog(@”%@ %@”,self,NSStringFromSelector;}//
当多少个对象调用未落到实处的措施,会调用这些措施管理,而且会把相应的艺术列表传过来.//
刚好能够用来推断,未得以实现的方式是还是不是大家想要动态增加的方法+resolveInstanceMethod:sel{if
(sel == @selector {// 动态增多eat方法// 首个参数:给哪个类增多方法//
第4个参数:增多情势的办法编号// 第八个参数:增加办法的函数完结//
第八个参数:函数的品类, v:void @:对象->self
:表示SEL->_cmdclass_addMethod(self, @selector, eat,
“v@:”);}return [super resolveInstanceMethod:sel];}@end

  • 原理:给二个类评释属性,其实本质正是给那个类增加关系,并非一向把那么些值的内部存款和储蓄器空间增加到类存空间。

@implementation ViewController-viewDidLoad {[super viewDidLoad];//
Do any additional setup after loading the view, typically from a
nib.// 给系统NSObject类动态增多属性nameNSObject *objc = [[NSObject
alloc] init];objc.name = @”小码哥”;

NSLog(@"%@",objc.name);

}@end

// 定义关联的keystatic const char *key = “name”;@implementation
NSObject

  • (NSString *)name{// 根据关系的key,获取涉及的值。return
    objc_getAssociatedObject(self, key);}
  • setName:(NSString *)name{// 第贰个参数:给哪个指标加多关联//
    第1个参数:关联的key,通过这一个key获取// 第多少个参数:关联的value//
    第四个参数:关联的计策objc_setAssociatedObject(self,key,name,OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end

  • 电动依照二个字典,生成对应的习性,和字典中的key一一对应。

@implementation NSObject //
自动打字与印刷属性字符串+resolveDict:(NSDictionary *)dict{//
拼接属性字符串代码NSMutableString *strM = [NSMutableString
string];//
1.遍历字典,把字典中的全数key收取来,生成对应的性质代码[dict
enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull
obj, BOOL * _Nonnull stop) {//类型常常变,抽取来NSString *type;if
([obj isKindOfClass:NSClassFromString(@”__NSCFString”)]) {type =
@”NSString”;}else if ([obj
isKindOfClass:NSClassFromString(@”__NSCFArray”)]){type =
@”NSArray”;}else if ([obj
isKindOfClass:NSClassFromString(@”__NSCFNumber”)]){type =
@”int”;}else if ([obj
isKindOfClass:NSClassFromString(@”__NSCFDictionary”)]){type =
@”NSDictionary”;}// 属性字符串NSString *str;if ([type
containsString:@”NS”]) {str = [NSString stringWithFormat:@”@property
(nonatomic, strong) %@ *%@;”,type,key];}else{str = [NSString
stringWithFormat:@”@property (nonatomic, assign) %@
%@;”,type,key];}// 每生成属性字符串,就活动换行。[strM
appendFormat:@”\n%@\n”,str];}];//
把拼接好的字符串打字与印刷出来,就好了。NSLog(@”%@”,strM);}@end

(1)利用runtime的音信发送机制调用方法

先是新建两个类RuntimeModel,并贯彻指标方法eat,在RuntimeViewController中调用eat措施,使用oc来语言来贯彻很简短了

RuntimeModel * model=[[RuntimeModel alloc]init];
[model eat];

接下去一小点用runtime完毕地方的代码,导入runtime的头文件#import <objc/message.h>,由于xcode5.0开端苹果不建议我们运用底层的代码,所以target->build setting->搜索msg->将YES改为NO,那样接下去我们用runtime的时候才会油但是生提醒。
大家运用objc_msgSend(<#id self#>, <#SEL op, ...#>)本条格局,能够看来需求三个参数,第多少个参数是id类型,代表何人要发送音信,第二个参数是要把音讯发送给何人,大家用runtime来兑现[model eat];其一措施。

objc_msgSend(model, sel_registerName("eat"));

而先导化对象同样是调用了alloc init那四个章程。将导入的头文件RuntimeModel去掉,用纯c语言的代码达成地方的意义。

objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("RuntimeModel"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));

运维,大家可以观看在eat方法中的nslog被调用了,就算大家兑现了职能,可是怎么技巧理解我们的oc语言在运维时确实是被转变来了c语言的代码呢?
新建工程,成立命令行工具。

图片 2

4BEFBE2C-70AE-490F-BE08-926725DC0C2B.png

新建贰个person类,然后进入main.m中。
调用头文件,在main中实例化person。person * p=[[person alloc]init];闭馆项目,展开终端,步向刚才建的公文夹下,ls开采方可看来刚才我们新建的类和main.m文件,接下去实施命令行clang -rewrite-objc main.m
此刻大家能够见到,在刚刚的工程中冒出二个main.cpp的文书,展开何况拖到最上面。

图片 3

783A655E-6F04-41F8-8E16-DAE6C21468E2.png

图片 4

teatime.gif

(2)交流方法

做为oc的程序猿最凄美的就是,运营--崩溃在main里面,笔者尼玛!!! 比方:

NSURL * url=[NSURL URLWithString:@"www.baidu.com.啦啦"];

当我们从不实行编码的时候,那几个url在编写翻译的时候是可行的,不过要是运转起来,那个url就能够化为nil。因为检查评定不到那是三个空头的url,会继续发送网络央求。
怎么做呢?在上边用if做一个料定?然则要在每一个url下边都做判定。这时候最初想到的任天由命是重写。
创设一个url的归类,重写URLWithString:<#(nonnull NSString *)#>但是,我们看:

+(instancetype)URLWithString:(NSString *)URLString
{
    //  首先创建一个URL
    NSURL * url= ????????
    if (url==nil) {
        NSLog(@"有问题");
    }
    return url;
}

大家该怎么开创呢,死循环了是还是不是,所以runtime就起效果了。
在分拣中自定义一个主意

+(instancetype)TY_URLWithStr:(NSString *)Str;
{
    NSURL * url =[NSURL URLWithString:Str];
    if (url == nil) {
        NSLog(@"是空!!!!");
    }
    return url;
}

但是大家并非要每二回创造url都调用那些主意,因为程序里有广大创建url的地点,大家还一而再选拔系统自带的点子。在分拣中实现load方法,当程序加载那么些类的时候最初调用这些点子。导入头文件,起头张开艺术交流。

+(void)load
{
    //  Method : 成员方法
    //class_getClassMethod   拿到类方法
    //class_getInstanceMethod   拿到对象方法
    Method URLWithStr = class_getClassMethod([NSURL class], @selector(URLWithString:));
    Method TYURLWithStr = class_getClassMethod([NSURL class], @selector(TY_URLWithStr:));
    //  开始交换方法
    method_exchangeImplementations(URLWithStr, TYURLWithStr);
}

纪念将地点我们自定义的办法中开创url的办法改动回去,否则再次死循环,改为

+(instancetype)TY_URLWithStr:(NSString *)Str;
{
    NSURL * url =[NSURL TY_URLWithStr:Str];
    if (url == nil) {
        NSLog(@"是空!!!!");
    }
    return url;
}

是或不是有种见微知著的认为。

图片 5

teatime.gif

(3)遍历属性列表简化种类化

oc的系列化在此地就没多少说了,让我们的话一种普及的意况,当必要归档的品质过多时,我们须要一条条的写出来,十三分麻烦,有未有望简化一些啊,假设只是的用for循环去做,那么不一致的品类该怎么管理啊,那时候我们的runtime又来了。首先创设四个Person类,多弄一些设想属性。

//  .h
@property(nonatomic,strong) NSString * name;
@property(nonatomic,strong) NSString * name1;
@property(nonatomic,assign) int age;
@property(nonatomic,assign) int age1;
@property(nonatomic,assign) double age2;

// .m
-(void)encodeWithCoder:(NSCoder *)Coder
{

}

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self=[super init];
    if (self) {

    }
    return self;
}

落到实处思路:

-(void)encodeWithCoder:(NSCoder *)Coder
{
    for (int i = 0; i < 属性数量; i++) {
    [Coder encodeObject:属性值 forKey:属性名称];
    }
}

那么
回到controller中,导入runtime头文件,使用三个办法class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)赢得属性列表,第2个参数,传入三个类[Person class],第一个参数,传入一个指南针,在上头定义unsigned int count = 0;,然后传入&count。那个count就是获得的属性的多少。同期在c语言中是不分.h.m的,所以随意在哪个文件中定义的性质,都能够取到。

unsigned int count = 0;
class_copyIvarList([Person class], &count);

接下来大家必要定义一个Ivar类型的指针,那些指针会指向每二个性质,上边这些图说雅培下,他实际不是还要针对每八个属性,而是一个叁个分级指一直获得属性。

图片 6

DCF79208-E155-4D1B-8797-DDFC8C6F83B5.png

我们利用三个主意通过这几个ivars去获取属性名称。

unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([Person class], &count);
    Ivar ivar = ivars[0];
    const char * name = ivar_getName(ivar);
    NSLog(@"%s",name);

打字与印刷看到第二个属性名,能够改换ivars[第几个],去猎取第几个。并且就算角标越界,仍然不会崩溃。
那么大家回来person类中,直接用刚刚的代码完结我们最最初建议的难题。

//  归档
-(void)encodeWithCoder:(NSCoder *)Coder
{
    unsigned int count = 0;
    Ivar * ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar= ivars[i];
        const char * name = ivar_getName(ivar);
        NSString * key = [[NSString alloc]initWithUTF8String:name];
        //  使用KVC  拿出属性的值
        [Coder encodeObject:[self valueForKey:key] forKey:key];
    }
}
//  解档
-(instancetype)initWithCoder:(NSCoder *)Decoder
{
    self=[super init];
    if (self) {
        unsigned int count = 0;
        Ivar * ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar= ivars[i];
            const char * name = ivar_getName(ivar);
            NSString * key = [[NSString alloc]initWithUTF8String:name];
            //  使用KVC  拿出属性的值
            id value = [Decoder decodeObjectForKey:key];
            //  设置属性
            [self setValue:value forKey:key];
        }

    }
    return self;
}

经过上边的陈述,这段代码就很轻易了解了。大家用的是kvc的赋值和取值,所以任何类型的存档解档都以尚未难题的。

图片 7

teatime.gif

(4)刨析KVO底层实现

我们先用oc落成贰个简单的KVO监听。

//   controller.m
self.c=[[Cat alloc]init];
self.d=[[Dog alloc]init];
    //  注册监听
[self.d addObserver:self.c forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

//   cat.m
//  监听到了object的对象keyPath属性变化为乐change
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"监听到了%@的对象%@属性变化为乐%@",object,keyPath,change);
}

这是KVO最简便的运用,那么接下去大家看一下KVO底层到底是怎么落到实处的啊?
率先在KVO运转的时候会动态的拉长多个类,承袭与被观看者的类。名字称为NSKVONotifying_Dog以此类。类名可不是瞎编的啊。然后在.m文件中,调用父类的set方法:

-(void)setAge:(int)age
{
    [super setAge:age];
    // 在子类中调用这两个方法
    //  这个是 将要被改变的值是什么
    [self willChangeValueForKey:@"age"];
    //   这个是   改变之后的新值是什么
    [self didChangeValueForKey:@"age"];
}

那样就能监听到改换并且传值,可是怎么说KVO是那般达成的啊?
将刚刚新建的NSKVONotifying_Dog类删掉,在controller中实现点击改变值的形式

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.d.age=99;
}

在赋值的地点打断点,倘使程序运转到这里,d的门类变为刚才十二分格局的项目,那么就印证KVO正是如此完结的。

图片 8

B528C61F-E2C4-46BD-9F23-2A06A67867BA.png

图片 9

teatime.gif

(5)动态拉长方法

先是创设一个Person类,然后在controller中实例化person间接能够这样直接调用贰个不设有的法子

    Person * p=[[Person alloc]init];
    [p performSelector:@selector(eat)];

如此那般固然编写翻译能够过,然而运行起来就能够崩溃
这时候作者没回去person.m中,来看四个办法

//  当这个类被调用没有实现的类方法  就会来到这里
+(BOOL)resolveClassMethod:(SEL)sel
{
    return [super resolveClassMethod:sel];
}
//  当这个类被调用没有实现的对象方法  就会来到这里
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    return [super resolveInstanceMethod:sel];
}

艺术中的参数正是被调用的方法名,然后我们需求实现多个名字为eat的函数

void eat(){
    NSLog(@"lalal");
}

此时,我们将在选取八个措施class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)先是个参数:类类型,第三个参数:方法编号,第两个参数:方法完结(函数指针),第八个参数:再次回到值类型。关于那第八个参数,这是c语音,大家该怎么写重回类型呢?去查一下官方文书档案关于第八个参数的陈说。

图片 10

047E0D3E-7A83-4E78-8EC4-CE7880A0B008.png

那就是说大家来兑现代码

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        class_addMethod([Person class], sel, (IMP)eat, "v");
    }
    return [super resolveClassMethod:sel];
    return [super resolveInstanceMethod:sel];
}

void eat(){
    NSLog(@"lalal");
}

那样就完事了一个动态增进方法,然后大家跟着看文书档案,文书档案中有一段代码示例

图片 11

BB20A099-E697-4F05-9580-0ECBB4BFD995.png

大家能够见见,当动态拉长方法是会流传多个参数,实际上每二个函数被调用时都会传来那八个参数,叫做隐式参数。参数一:调用了哪位类的,参数二:调用了哪些方法,大家用nslog打字与印刷一下那五个参数,在这在此以前须要改一下上边的第多少个参数为"v@:",因为我们回到值类型改动了。打字与印刷一下:

图片 12

43DBB6BD-18C3-4D6B-9C7B-C46D0F3B235C.png

接下去,正是什么样传递参数,我们在调用方法的时候传出五个参数

 Person * p=[[Person alloc]init];
    [p performSelector:@selector(eat:) withObject:@"6666"];

回来Person类,将判别的形式名改为eat:,并将eat函数扩张三个参数:

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    //  方法名的判断
    if (sel == @selector(eat:)) {
        class_addMethod([Person class], sel, (IMP)eat, "v@:");
    }
    return [super resolveClassMethod:sel];
    return [super resolveInstanceMethod:sel];
}

void eat(id self, SEL _cmd ,id obj){
    NSLog(@"%@ ",obj);
}

控制台:

图片 13

76FA3A6D-BF28-48B2-A3A4-B8C3BAFAD621.png

参数完美传递过来。

图片 14

teatime.gif

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图