runTimer的实际应用

简介绍

本文主要介绍自己在实际项目中,用runTimer解决的实际问题。废话不多说,直接上干货。

Part1:使用关联技术为分类添加属性

  • 背景:为第三方库或系统类扩展类属性,或分解庞大的类。比如:为EasyPeripheral添加图片picUrl、productId、productName等属性。
  • 实现:
//  .h 文件  
#import "EasyPeripheral.h"
NS_ASSUME_NONNULL_BEGIN
@interface EasyPeripheral (device)
@property (nonatomic, copy) NSString *picUrl;
@property (nonatomic, copy) NSString *productName;
@property (nonatomic, assign) NSInteger productId;
@end

//  .m文件
#import "EasyPeripheral+device.h"
#import <objc/runtime.h>

@implementation EasyPeripheral (Device)

- (void)setPicUrl:(NSString *)picUrl {
  objc_setAssociatedObject(self, @selector(picUrl), picUrl, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)picUrl {
  return objc_getAssociatedObject(self, @selector(picUrl));
}

- (void)setProductName:(NSString *)productName {
  objc_setAssociatedObject(self, @selector(productName), productName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)productName {
  return objc_getAssociatedObject(self, @selector(productName));
}

- (void)setProductId:(NSInteger)productId {
  objc_setAssociatedObject(self, @selector(productId), @(productId), OBJC_ASSOCIATION_ASSIGN);
}

- (NSInteger)productId {
  return [objc_getAssociatedObject(self, @selector(productId))integerValue];
}
@end

Part2:黑魔法swizzling实现方法的交换

  • 背景:为减少项目中的崩溃,对arry和dic容器统一做处理。或者为了埋点数据,交换VC的viewDidLoad方法。

  • 实现:

    • NSObject的分类
     //.h
     #import <Foundation/Foundation.h>
     NS_ASSUME_NONNULL_BEGIN
     @interface NSObject (Swizzing)
     /*! 实例方法交换
    @abstract 对系统方法进行替换
     @param originalSelector 想要替换的方法
      @param swizzledSelector 实际替换为的方法
      */
       + (void)swizzleWithSysMethod:(SEL)originalSelector       swizzledMethod:(SEL)swizzledSelector;
    
     //.m
       + (void)swizzleWithSysMethod:(SEL)originalSelector swizzledMethod:(SEL)swizzledSelector {
             Method originalMethod = class_getInstanceMethod(self, originalSelector); //原方法
             Method swizzledMethod =               class_getInstanceMethod(self, swizzledSelector); //交换方法
            method_exchangeImplementations(originalMethod, swizzledMethod); //交换方法
         }
    
    • NSMutableDictionary的分类
     +(void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
         @autoreleasepool {
            [objc_getClass("__NSArrayM") swizzleWithSysMethod:@selector(objectAtIndex:) swizzledMethod:@selector(safeObjectAtIndex:)];
            [objc_getClass("__NSArrayM") swizzleWithSysMethod:@selector(addObject:) swizzledMethod:@selector(safeAddObject:)];
           };
        });
    }
    
    - (id)safeObjectAtIndex:(NSUInteger)index {
     if (index >= self.count) {
        NSLog(@"Feng %@ 数组越界啦",NSStringFromClass([self class]));
         return nil;
       }
     return [self safeObjectAtIndex:index];
    }
    - (void)safeAddObject:(id)anObject {
      if (!anObject || [anObject isKindOfClass:[NSNull class]]) {
            NSLog(@"Feng %@ anObject为nil",NSStringFromClass([self class]));
        }
     [self safeAddObject:anObject];
    }
    
    

    例子

    self.mutaDic = [[NSMutableDictionary alloc] init];
    [self.mutaDic setObject:nil forKey:@"123"];
    [self.mutaDic setObject:nil forKey:nil];
    [self.mutaDic setObject:nil forKey:@"123"];
    [self.mutaDic setValue:@"123" forKey:nil];
     //  输出
    2021-09-04 01:25:19.368811+0800 runtimerDemon[18304:3239618] Feng __NSDictionaryM value or key 为nil
    2021-09-04 01:25:19.368994+0800 runtimerDemon[18304:3239618] Feng __NSDictionaryM value or key 为nil
    2021-09-04 01:25:19.369152+0800 runtimerDemon[18304:3239618] Feng __NSDictionaryM value or key 为nil
    2021-09-04 01:25:19.369297+0800 runtimerDemon[18304:3239618] Feng __NSDictionaryM value为nil
    

Part3:利用消息转发机制解决方法找不到的异常的问题-动态方法解析

  • 背景:继Part2的减少项目中的崩溃这个需求,对于不不可变的,Part2方案可不行。而是需要Part3方法了,消息动态解析。
    利用消息转发机制有三次机会,每次机会都可以做很多事情,比如还可以解决很多实际问题比如实现多继承。这里只是示范三次机会中的第一次
  • 实现:
#import "NSDictionary+Swizzing.h"
#import <objc/runtime.h>
#import "NSObject+Swizzing.h"
@implementation NSDictionary (Swizzing)

+ (BOOL)resolveInstanceMethod:(SEL)sel {
   if (sel == @selector(setValue:forKey:) || sel == @selector(setObject:forKey:) || sel == @selector(removeAllObjects)|| sel == @selector(removeObject:) || sel == @selector(removeObjectForKey:) || sel == @selector(removeObjectsForKeys:)) {
       if (class_addMethod([self class], sel, (IMP) dynamicDicMethodIMP, "v@:")) {
           class_replaceMethod([self class],sel,(IMP) dynamicDicMethodIMP,"v@:");
       }
       return YES;
   }
   return [super resolveInstanceMethod:sel];
}

void dynamicDicMethodIMP(id self, SEL _cmd) {
   NSLog(@"Feng %@ 不支持%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));
}

@end
  • 例子:
   NSDictionary *dic =@{@"xing":@"kwok",@"ming":@"yule"};
    [dic valueForKey:nil];
    [dic valueForKey:@"2334"];
    self.mutaDic2 = [dic copy];
    [self.mutaDic2 setObject:@"123" forKey:@"12"];
    [self.mutaDic2 removeObjectForKey:@"123"];
    [self.mutaDic2 removeAllObjects];
    [self.mutaDic2 removeObjectsForKeys:[NSArray arrayWithObjects:@"123", @"234",nil]];
    //  打印
    2021-09-04 01:28:46.268601+0800 runtimerDemon[18357:3243436] Feng __NSDictionaryI 不支持setObject:forKey:
    2021-09-04 01:28:46.268842+0800 runtimerDemon[18357:3243436] Feng __NSDictionaryI 不支持removeObjectForKey:
    2021-09-04 01:28:46.269020+0800 runtimerDemon[18357:3243436] Feng __NSDictionaryI 不支持removeAllObjects
    2021-09-04 01:28:46.269207+0800 runtimerDemon[18357:3243436] Feng __NSDictionaryI 不支持removeObjectsForKeys:

Part4:动态创建类和动态方法调用

  • 背景:H5与原生有大量的交换,为避免大量的重复代码且统一入口处理,采用配置文件映射+runtimer实现动态实例创建和方法调用实现。
  • 实现:
#import "ViewController.h"
#import "FengJsMode.h"
#import "FengScene.h"
#import "FengDevice.h"
#import <objc/runtime.h>
#include <objc/message.h>

@implementation ViewController
#pragma mark --js发消息
- (IBAction)test2Action:(id)sender { 
  NSDictionary *jsDic = [NSDictionary dictionaryWithObjectsAndKeys:@"scene",@"cls",@"list",@"name",nil,@"jscallback",@{},@"params",nil];
  [self observeJSNotificationWithNotification:jsDic];
}
- (IBAction)test3Action:(id)sender { 
  NSDictionary *jsDic = [NSDictionary dictionaryWithObjectsAndKeys:@"device",@"cls",@"detail",@"name",nil,@"jscallback",@{},@"params",nil];
  [self observeJSNotificationWithNotification:jsDic];
}
#pragma mark --原生处理消息
-(void)observeJSNotificationWithNotification:(NSDictionary *)jsDic {
  FengJsMode *mode = [[FengJsMode alloc] init];
  mode.cls = [jsDic valueForKey:@"cls"];
  mode.name = [jsDic valueForKey:@"name"];
  mode.jscallback = [jsDic valueForKey:@"jscallback"];
  mode.params = [jsDic valueForKey:@"params"];

  NSDictionary *dic = [self readResourcesWithName:@"jsMapping"]; //读取映射表

  NSDictionary *dic0 = [dic valueForKey:mode.cls];
  
  mode.mapCls = [dic0 valueForKey:@"clsName"];
  mode.mapMethod = [dic0 valueForKey:mode.name];
  
  id clsInstance; //创建的实例
  if ([[dic0 valueForKey:@"type"] intValue] == 0) { //type为0:单列 其他非单列
      clsInstance = [NSClassFromString(mode.mapCls) shareInstance];//默认获取单例的方法为shareInstance
  }else {
     // clsInstance = [[NSClassFromString(mode.mapCls)alloc]init];
      clsInstance = objc_allocateClassPair([NSObject class],[mode.mapCls UTF8String], 0);
  }
  
  SEL m_selector = NSSelectorFromString([NSString stringWithFormat:@"%@:",mode.mapMethod]);//方法名

  if ([clsInstance respondsToSelector:m_selector]) {
      ((void (*)(id, SEL,FengJsMode *))objc_msgSend)(clsInstance, m_selector,mode); //调用方法
  }
}

-(NSDictionary *)readResourcesWithName:(NSString *)name {
NSString *filePath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.json",name]];
NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
if (data) {
  return [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
}
return nil;
}

@end

  • FengJsMode
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FengJsMode : NSObject

@property(nonatomic,copy) NSString *cls;//消息类型
@property(nonatomic,copy) NSString *name;//消息名
@property (nonatomic, copy) NSString *jscallback;//回调函数ID(返回给js)
@property (nonatomic, strong) NSDictionary *params;//参数

@property (nonatomic, copy) NSString *mapCls;//映射的类名
@property (nonatomic, copy) NSString *mapMethod;//映射的方法名

@end

NS_ASSUME_NONNULL_END
  • FengScene
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FengScene: NSObject

+ (instancetype)shareInstance;

//获取场景列表
-(void)fetchSceneListWithHouseId:(id)message;

//获取场景详情
-(void)fetchSceneDetailWithDeviceId:(id)message;

@end

NS_ASSUME_NONNULL_END

#import "FengScene.h"

static FengScene *shareManager = nil;

@implementation FengScene

+ (instancetype)shareInstance {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
      shareManager = [[self alloc] init];
  });
  return shareManager;
}

-(void)fetchSceneListWithHouseId:(NSDictionary *)message {
  //解析对应的字端
  NSLog(@"FengScene sceneList :%@",message);
}

-(void)fetchSceneDetailWithDeviceId:(NSDictionary *)message {
  //解析对应的字端
  NSLog(@"FengScene sceneDetail :%@",message);
}

@end
  • FengDevice
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN


@interface FengDevice : NSObject

//获取设备列表
-(void)fetchDeviceListWithHouseId:(NSDictionary *)params;

//获取设备详情
-(void)fetchSceneDetailWithDeviceId:(NSDictionary *)params;

@end

NS_ASSUME_NONNULL_END
#import "FengDevice.h"

@implementation FengDevice

-(void)fetchDeviceListWithHouseId:(NSDictionary *)params {
  //解析对应的字端
  NSLog(@"FengScene DeviceList :%@",params);
}

-(void)fetchSceneDetailWithDeviceId:(NSDictionary *)params {
  //解析对应的字端
  NSLog(@"FengScene DeviceDetail :%@",params);
}

  • jsMapping.json
{
"scene" : {
    "clsName" : "FengScene",
    "type" : 0,
    "list" : "fetchSceneListWithHouseId",
    "detail" : "fetchSceneDetailWithDeviceId",
  },
"device" : {
    "clsName" : "FengDevice",
    "type" : 1,
    "list" : "fetchDeviceListWithHouseId",
    "detail" : "fetchSceneDetailWithDeviceId",
  },
}

Part5:归档/解档和字典转模型

数据要支持存取的时候,就要使用归档和解档了。字典转模型开发中也很常用。不过现在很少自己实现,YYMode已经对归档和解档、数据解析做的很好了。这里就不举例子了。实现思路类似遍历类的所有成员变量。

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

推荐阅读更多精彩内容