Runtime 使用实例

学习了那么多关于 Runtime 的知识,但是如果不会用还是徒劳,这里记录下我在项目中学到的两种用法,这都是我的同事写的,最开始我都看不懂,现在至少能明白是在干什么了。

空的 backButtonItemTitle

系统的 UINavigationController 会自动在子 ViewController 的 navigationBar 左上角添加返回按钮,跟广大设计湿的审美不同,苹果让这个返回按钮的标题显示为上一层视图的标题,而通常的习惯是返回或者空,于是苦逼的程序员就开始一个页面一个页面的重复写。使用继承也是一个不错的选择,不过还是得多继承好几个,在这里我们用 Method swizzled 来完成这个需求会比较好一点。

首先我们需要创建一个 UIViewController 的 Category。名字就叫 UIViewController+CustomBackButtom。
.h 中不需要写任何代码,只看 .m。

#import "UIViewController+ CustomBackButtom"
#import <objc/runtime.h>

@implementation UIViewController (CustomBackButtom)

+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

    SEL originalSelecor = @selector(viewDidLoad);
    SEL swizzledSelector = @selector(emptyBack_viewDidLoad);

    Method originalMethod = class_getInstanceMethod(class, originalSelecor);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL methodAdded = class_addMethod(class, originalSelecor, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

    if (methodAdded) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
});
}


- (void)emptyBack_viewDidLoad {
    [self emptyBack_viewDidLoad];
    UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];
    [self.navigationItem setBackBarButtonItem:backButtonItem];
}

这个例子不需要多说什么,还是比较简单的。

存入 UserDefaults 类

这是一个比较厉害的想法了:平常我们写代码的时候,会使用 NSUserDefaults 存入和取出一些东西,其实代码也不多,就那么几行,不过程序员的风范就是,能少则少。所以,如果我们能写一个类,然后只需要设定属性,然后就像很简单的存取属性一样就能完成本地存储,那该多嗨皮。这里就是这么做了。这个想法的源头是 github 上面的一个开源工具 GVUserDefaults,而这个想法,是在这个工具的基础上,进行了更加深度的一步优化,让使用更加简单。

先说说思路:

  1. 每一个类的属性都会有 setter 和 getter 方法,@dynamic关键字可以允许程序员来写 setter 和 getter 方法。那么在 setter 和 getter 方法中做 NSUserDefaults 的存取操作的话就能达到我们的需求。
  2. 取出每个属性的名称和类型,把每种类型对应的存取方法都写一遍,然后根据属性的类型选择方法并获取到这个方法的 IMP,然后自己创建一个 SEL ,这样就可以使用 OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 来为属性来添加 setter 和 getter 方法。
  3. 每一个属性在保存的时候,userdefaults 是需要一个 key 的,在这里,将属性的名字当做 key,存入 userdefaults。在这里生成一个 NSDictionary,将 userdefaults 所要使用的 key 当做这个字典的 value,将方法的 SEL 当做字典的 key,存入字典。这样再方法调用的时候只需要使用 _cmd 就可以获取当然方法的 SEL 了。

创建一个类,叫做 XXDefaults ,在项目中,这样的存取,最方便和容易的是使用单例了。所以我们先写出来单例模式。这就不贴代码了。

然后在 .m 中写好两个私有属性:

@property (nonatomic, strong) NSMutableDictionary *propertyKeys;

@property (nonatomic, strong) NSUserDefaults *userDefaults;

对了,可别忘了 #import <objc/runtime.h>,还记得 Type Encoding 么?我们要先把它都写成枚举。就是为了后面我们区分参数的类型,这个也是 class_addMethod 中的一个重要参数。

enum TypeEncodings {
    Char                = 'c',
    Bool                = 'B',
    Short               = 's',
    Int                 = 'i',
    Long                = 'l',
    LongLong            = 'q',
    UnsignedChar        = 'C',
    UnsignedShort       = 'S',
    UnsignedInt         = 'I',
    UnsignedLong        = 'L',
    UnsignedLongLong    = 'Q',
    Float               = 'f',
    Double              = 'd',
    Object              = '@'
};

我们最重要的内容应该是,获取 SEL,获取 IMP,在这之前,我们先把所有类型对应的 IMP 都先写出来,有几个类型是可以通用的,所以方法不会太多:

#pragma mark - IMP for getter & setter

// long long value getter & setter
static long long longLongGetter(KLDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [[self.userDefaults objectForKey:key] longLongValue];
}

static void longLongSetter(KLDefaults *self, SEL _cmd, long long value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    NSNumber *object = [NSNumber numberWithLongLong:value];
    [self.userDefaults setObject:object forKey:key];
    [self.userDefaults synchronize];
}

// bool value getter & setter
static bool boolGetter(KLDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [self.userDefaults boolForKey:key];
}

static void boolSetter(KLDefaults *self, SEL _cmd, bool value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self.userDefaults setBool:value forKey:key];
    [self.userDefaults synchronize];
}

// integer value getter & setter
static int integerGetter(KLDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return (int)[self.userDefaults integerForKey:key];
}

static void integerSetter(KLDefaults *self, SEL _cmd, int value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self.userDefaults setInteger:value forKey:key];
    [self.userDefaults synchronize];
}

// float value getter & setter
static float floatGetter(KLDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [self.userDefaults floatForKey:key];
}

static void floatSetter(KLDefaults *self, SEL _cmd, float value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self.userDefaults setFloat:value forKey:key];
    [self.userDefaults synchronize];
}

// double value getter & setter
static double doubleGetter(KLDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [self.userDefaults doubleForKey:key];
}

static void doubleSetter(KLDefaults *self, SEL _cmd, double value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self.userDefaults setDouble:value forKey:key];
    [self.userDefaults synchronize];
}

// object value getter * setter
static id objectGetter(KLDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [self.userDefaults objectForKey:key];
}

static void objectSetter(KLDefaults *self, SEL _cmd, id object) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    if (object) {
        [self.userDefaults setObject:object forKey:key];
    } else {
        [self.userDefaults removeObjectForKey:key];
    }
    [self.userDefaults synchronize];
}

这段代码中使用到的方法 defalutsKeyForSelector: , 通过 SEL 找到我们 userdefaults 要使用的 key:

// 每一个属性在保存的时候,userdefaults 是需要一个 key 的,在这里,将属性的名字当做 key,存入 userdefaults。 
// 在这里生成一个 NSDictionary,将 userdefaults 所要使用的 key 当做这个字典的 value,将方法的 SEL 当做字典的 key,存入字典。
// 这样再方法调用的时候只需要使用 _cmd 就可以获取当然方法的 SEL 了。

// get propertyName with selector
- (NSString *)defaultsKeyForSelector:(SEL)selector {
    return [self.propertyKeys objectForKey:NSStringFromSelector(selector)];
}

下面就是最重要的部分,获取 SEL 和 IMP。

  • 获取所有属性内容

    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
  • 单个属性及属性的名字和类型, 在 for 循环内

    objc_property_t property = properties[i];
    const char *name = property_getName(property);
    const char *attributes = property_getAttributes(property);
    
  • 获取 getter SEL,在这里其实就是字符串的操作,strstrstrdupstrsep 是 C++ 的语法。而这个 ,G 是为了看看是不是有用户自定义过getter 方法,具体原因请看这里

    char *getter = strstr(attributes, ",G");
    if (getter) {
        getter = strdup(getter + 2);
        getter = strsep(&getter, ",");
    } else {
        getter = strdup(name);
    }
    SEL getterSel = sel_registerName(getter);
    free(getter);
    
  • 获取 setter SEL,同样也是字符串操作,asprintftoupper 是 C 的语法。

    char *setter = strstr(attributes, ",S");
    if (setter) {
        setter = strdup(setter + 2);
        setter = strsep(&setter, ",");
    } else {
        asprintf(&setter, "set%c%s:", toupper(name[0]), name + 1);
    }
    SEL setterSel = sel_registerName(setter);
    free(setter);
    
  • 然后我们要将属性名和属性的 setter、getter 方法联系起来:

    NSString *key = [self defaultsKeyForPropertyNamed:name];
    [self.propertyKeys setValue:key forKey:NSStringFromSelector(getterSel)];
    [self.propertyKeys setValue:key forKey:NSStringFromSelector(setterSel)];
    
  • 下面就是 IMP 和 type 了:

    // IMP for setter & getter
    IMP getterImp = NULL;
    IMP setterImp = NULL;
    // type
    char type = attributes[1];
    switch (type) {
      case Short:
      case Long:
      case LongLong:
      case UnsignedChar:
      case UnsignedShort:
      case UnsignedInt:
      case UnsignedLong:
      case UnsignedLongLong:
          getterImp = (IMP)longLongGetter;
          setterImp = (IMP)longLongSetter;
          break;
    
      case Bool:
      case Char:
          getterImp = (IMP)boolGetter;
          setterImp = (IMP)boolSetter;
          break;
    
      case Int:
          getterImp = (IMP)integerGetter;
          setterImp = (IMP)integerSetter;
          break;
    
      case Float:
          getterImp = (IMP)floatGetter;
          setterImp = (IMP)floatSetter;
          break;
    
      case Double:
          getterImp = (IMP)doubleGetter;
          setterImp = (IMP)doubleSetter;
          break;
    
      case Object:
          getterImp = (IMP)objectGetter;
          setterImp = (IMP)objectSetter;
          break;
    
      default:
          free(properties);
          [NSException raise:NSInternalInconsistencyException format:@"Unsupported type of property \"%s\" in class %@", name, self];
          break;
    }
    
  • 有了 SEL 、 IMP、再需要 type 就可以添加 setter、getter 方法了,当然这里 type 是使用 type encodeing 的:

    char types[5];
    
    snprintf(types, 4, "%c@:", type);
    class_addMethod([self class], getterSel, getterImp, types);
    
    snprintf(types, 5, "v@:%c", type);
    class_addMethod([self class], setterSel, setterImp, types);
    

    以上就是第二个例子的说明了。代码我放在了这里。可以直接使用,建议写一个 category 比较好。

结语

说实话 Runtime 确实不太容易理解,即是我看了这么久,也深入了解了这么多例子,还是觉得摸不着头脑。所以后面应该还有一个例子。 反正就是,不懂就使劲的钻研呗。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值