iOS技术点ios开发iOS开发精选集

runtime知识点及项目中的应用

2017-06-27  本文已影响559人  lilei5

概论:Runtime是Objective-C中底层的一套C语言API,是一个将C语言转化为面向对象语言的拓展。OC是一种面向对象的动态语言,动态语言就是在运行时执行静态语言的编译连接的工作。OC编写的程序不能直接编译为及其读懂的机器语言,在程序运行时,须通过Runtime来转换。

而在某些在特定的场景下,运用runtime的知识,往往可以大大减少我们的工作量。并能使代码更加统一、简洁。

本文将以一下几个方面逐一讲解runtime:

目录:
一、系统方法中的应用
  1、消息机制
  2、KVO的底层实现
二、runtime常见作用
  1、动态获取成员属性
  2、动态获得成员变量
  3、动态获得实例方法
  4、动态添加方法实现
  5、方法交换
三、实际开发中的应用
  1、给控件添加block事件回调方法
  2、自定义类型自动归档、解归档
  3、数组越界crash处理

首先定义一个Dog类,本篇文章的讲解将围绕这个类展开。

@interface Dog : NSObject
{
    float _weight;
}
@property(nonatomic,copy) NSString *name;
- (void)run:(NSInteger)runLong street:(NSString *)streetName;
@end

@interface Dog ()
{
    NSInteger  _age;
}
@property(nonatomic,copy) NSString *address;
@end

@implementation Dog
- (void)run:(NSInteger)runLong street:(NSString *)streetName{
     NSLog(@"在%@跑了%li米",streetName,runLong);
}
- (void)_sleep{
     NSLog(@"%s",__func__);
}
@end

一、系统方法运用runtime

1、发送消息

在OC中,调用一个对象的方法,实际上是给对象发了一条消息,在编译Objective-C函数调用的语法时,会被翻译成一个C的函数调用:objc_msgSend()
当我们dog,run的方法时:
[dog run:100 street:@"华尔街"];
实际上会转换为:
objc_msgSend(dog, @selector(run:street:),100,@"华尔街");
注:由于xcode5.0开始苹果不建议我们使用底层的代码,所以我们需要在target->build setting->搜索msg->将YES改为NO之后import <objc/message.h>才能使用objc_msgSend方法。

2、KVO的底层实现

概述:KVO是通过"isa-swizzling"技术来实现的,当一个对象注册观察者时,这个对象的isa指针被修改指向一个中间类。
当观察A类型的对象时,在运行时会创建了一个继成自A类的NSKVONotifying_A类,且为NSKVONotifying_A重写观察属性的setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察者属性值的更改情况。
因为本文主要讲解runtime的作用以及项目中的应用,所以这里就不做过多的陈述,关于KVO和isa指针不了解的同学可以移步我的另一篇Objective-C isa指针及KVO实现原理进行学习。

二、runtime常见作用

1、动态获取成员属性
通过class_copyPropertyList获取属性列表,此方法会获得所有通过@property展开的成员属性,包括私有属性。

unsigned int count = 0;
NSMutableArray *nameArray = [NSMutableArray array];
/**
     @param class 你想要检测的类  Class类型
     @param count 数组的个数
     @return 返回c语言数组  数组里元素是  objc_property_t 类型
     */
objc_property_t *properList =  class_copyPropertyList([Dog class],&count);
for (int i = 0; i<count; i++) {
    const char *name =  property_getName(properList[i]);
    NSString *ocName = [NSString stringWithUTF8String:name];
    [nameArray addObject:ocName];
}
    NSLog(@"属性名:%@",nameArray);

2、动态获得成员变量
通过class_copyIvarList获取成员变量列表,此方法会获得所有通过@property展开的成员属性以及声明的成员变量,包括私有成员变量。

 unsigned int count = 0;
    NSMutableArray *nameArray = [NSMutableArray array];
    NSMutableArray *typeArray = [NSMutableArray array];
     /**
     @param class 你想要检测的类  Class类型
     @param count 数组的个数
     @return 返回c语言数组  数组里元素是  Ivar 类型
     */
    Ivar *vars = class_copyIvarList([Dog class], &count);
    for (int i = 0; i<count; i++) {
        
        const char *name = ivar_getName(vars[i]);
        const char *type = ivar_getTypeEncoding(vars[i]);
        NSString *ocName = [NSString stringWithUTF8String:name];
        NSString *ocType = [NSString stringWithUTF8String:type];
        [nameArray addObject:ocName];
        [typeArray addObject:ocType];
        
        /*
         类型说明:基本类型 q:NSInteger   d:double   f:float  i:int  c:BOOL
         引用类型:以字符串显示  如:NSString:"NSString"  NSArray:"NSArray"
         不同类型编码请参考:
         https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
         */
        
    }
    NSLog(@"变量名:%@-----变量类型:%@",nameArray,typeArray);

3、动态获得实例方法
通过class_copyMethodList获得实例方法列表,通过method_copyReturnType获得返回类型,通过method_copyArgumentType获得各个参数类型,代码如下

 unsigned int count = 0;
    NSMutableArray *methodArray = [NSMutableArray array];
    Method *methodList = class_copyMethodList([Dog class],&count);
    for (int i = 0 ; i<count; i++) {
        NSMutableDictionary *methodDic = [NSMutableDictionary dictionary];
        // 方法名
        SEL method = method_getName(methodList[i]);
        // 返回类型
        const char *returnTypeName = method_copyReturnType(methodList[i]);
        // 参数个数  默认会多两个  第一个接受消息的对象  第二个selector  原因:objc_msgSend(id, SEL)前两个参数
        int argumentCount = method_getNumberOfArguments(methodList[i]);
        
        methodDic[@"methodName"] = NSStringFromSelector(method);
        methodDic[@"returnType"] = [NSString stringWithUTF8String:returnTypeName];
        for (int j = 0; j<argumentCount; j++) {
            NSString *key = [NSString stringWithFormat:@"param%i",j+1];
            methodDic[key] = [NSString stringWithUTF8String:method_copyArgumentType(methodList[i], j)];
        }
        [methodArray addObject:methodDic];
        
    }
    //  @对象类型  v为void   :为selector
    NSLog(@"%@",methodArray);

4、动态添加方法实现
通过class_addMethod动态添加方法:通过[dog performSelector:@selector(haha)];执行haha的方法,因为Dog类中并没有haha方法,所以执行时会crash,我们可以通过如下代码,动态添加方法实现:

// class: 给哪个类添加方法
// SEL: 方法选择器,即调用时候的名称(只是一个编号)
// IMP: 方法的实现(函数指针)
// type: 方法类型,(返回值+隐式参数) v:void @对象->self :表示SEL->_cmd
class_addMethod([Dog class], @selector(haha),(IMP)abc, "v@:");
// 任何方法默认都有两个隐式参数,self,_cmd
void abc(id self, SEL _cmd) {
    NSLog(@"哈哈大笑");
}

5、方法交换
通过method_exchangeImplementations实现方法的交换:在Dog类的load方法里添加如下方法,实现方法的交换。(写在load方法中的原因:当类被加载到内存中是会调用,执行比较早,并且不会多次调用)

+(void)load{
     Method orginMethod = class_getInstanceMethod([Dog class], @selector(run:street:));
     Method myMethod = class_getInstanceMethod([Dog class], @selector(myRun:street:));
    method_exchangeImplementations(orginMethod, myMethod);
}

- (NSString *)myRun:(NSInteger)runLong street:(NSString *)streetName{
    NSLog(@"交换方法了");
    return nil;
}

三、实际开发中的应用

说明:runtime在开发中的应用有很多,但知识点都是大致相同,为了控制篇幅这里只给出了某个知识点的一种应用,其他的应用可自己尝试实现,如果想看其他实现的代码可以给我留言。

1、给控件添加block回调方法
知识点: 动态关联两个对象
需求:给UIButton添加点击点击事件回调的block方法
思路:给UIButton添加一个扩展方法,传入一个block,再在button的点击事件里执行block;
还可以做:给类的catagory添加属性
实现代码:

@interface UIButton (Blocks)
- (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block;
@end
@implementation UIButton (Blocks)
static char overviewKey;
- (void)handleControlEvent:(UIControlEvents)event withBlock:(void (^)(UIButton *btn))block {
// 关联对象方法
// object:给哪个对象添加关联
// key:被关联者的索引key
// value:被关联者
// policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
    objc_setAssociatedObject(self, &overviewKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self addTarget:self action:@selector(callActionBlock:) forControlEvents:event];
}
- (void)callActionBlock:(id)sender {
// 通过key 获取关联对象
    void (^block)(UIButton *btn) = objc_getAssociatedObject(self, &overviewKey);
    if (block) {
        block(sender);
    }
}
@end

2、自定义类型自动归档、解归档
概述: 归档,持久化保存数据的一种方法。系统的类如果遵循了NSCoding协议都可以直接进行归档(如:NSString,NSArray等),但是自定义对象如果要进行归档,需要:
1、遵循NSCoding协议
2、实现解档方法:-(id)initWithCoder:(NSCoder *)aDecoder和归档方法:-(void)encodeWithCoder:(NSCoder *)aCoder

知识点: 获取一个类的成员变量
需求:一劳永逸的给自定义类型实现自动归档
思路:获取自定义类的成员变量,获取成员变量字符串,再通过KVC取值,实现归/解档。
还可以做:字典转模型、获取系统类的私有属性,进行操作。如 UITextField占位文字的颜色 [self setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
实现代码:

// 归档
- (void)encodeWithCoder:(NSCoder *)aCoder{
    // 遍历,对父类的属性执行归档方法
    Class c = self.class;
    while (c &&c != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 通过KVC取值
            id value = [self valueForKeyPath:key];
            [aCoder encodeObject:value forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }


}

// 解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self =  [super init]) {
        // 遍历,对父类的属性执行解档方法
        Class c = self.class;
        while (c &&c != [NSObject class]) {
            unsigned int outCount = 0;
            Ivar *ivars = class_copyIvarList(c, &outCount);
            for (int i = 0; i < outCount; i++) {
                Ivar ivar = ivars[i];
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
                id value = [aDecoder decodeObjectForKey:key];
                [self setValue:value forKey:key];
            }
            free(ivars);
            c = [c superclass];
        }

    }
    return self;
}

3、数组越界crash处理
知识点:获取某个类的方法、方法交换
需求:如果数组越界,避免程序闪退
思路:首先看一下数组越界后所报的原因

数组越界
报错原因提到了"__NSArrayI"而不是"NSArray"这是因为:NSArray这个类在设计的时候采用了“抽象工厂”模式,内部是个类簇,真正起作用的是内部的:"__NSArray0"(空数组)、"__NSSingleObjectArrayI"(个数为1的数组),"__NSArrayI"(不可变数组)以及"__NSArrayM"(可变数组)。所以我们只需要给NSArray添加个category替换这几个类的objectAtIndex:方法即可。
还可以做:截获系统方法,替换成自己的方法。如:字典添加nil的crash,按钮暴力点击重复响应,字体大小适配等
代码:
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        @autoreleasepool {
            [self exchangeMethodWithClass:objc_getClass("__NSArray0") orginSelector:@selector(objectAtIndex:) customSelector:@selector(emptyObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSSingleObjectArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(singleArrObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSArrayI") orginSelector:@selector(objectAtIndex:) customSelector:@selector(arrObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(objectAtIndex:) customSelector:@selector(mutableObjectIndex:)];
            [self exchangeMethodWithClass:objc_getClass("__NSArrayM") orginSelector:@selector(insertObject:atIndex:) customSelector:@selector(mutableInsertObject:atIndex:)];
        }
    });
}

+ (void)exchangeMethodWithClass:(Class)class orginSelector:(SEL)orginS customSelector:(SEL)customS{
    Method orginMethod = class_getInstanceMethod(class, orginS);
    Method customMethod = class_getInstanceMethod(class, customS);
    method_exchangeImplementations(orginMethod, customMethod);
}

- (id)emptyObjectIndex:(NSInteger)index{
    return nil;
}

- (id)singleArrObjectIndex:(NSInteger)index{
    @autoreleasepool {
        if (index >= self.count || index < 0) {
            return nil;
        }
        return [self singleArrObjectIndex:index];
    }
}

- (id)arrObjectIndex:(NSInteger)index{
    @autoreleasepool {
        if (index >= self.count || index < 0) {
            return nil;
        }
        return [self arrObjectIndex:index];
    }
}

- (id)mutableObjectIndex:(NSInteger)index{
    @autoreleasepool {
        if (index >= self.count || index < 0) {
            return nil;
        }
        return [self mutableObjectIndex:index];
    }
}

- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
    @autoreleasepool {
        if (object) {
            [self mutableInsertObject:object atIndex:index];
        }
    }
}
- (id)myObjectAtIndex:(NSUInteger)index
{
    @autoreleasepool {
        if (index < self.count) {
            return [self myObjectAtIndex:index];
        } else {
            return nil;
        }
    }
}

到这里本篇文章基本结束,文中所涉及代码都在这里
最后:喜欢我文章的可以多多点赞和关注,您的鼓励是我写作的动力。O(∩_∩)O~

上一篇下一篇

猜你喜欢

热点阅读