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 

@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 
     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 
#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 
#include 

@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 

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 

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 

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已经对归档和解档、数据解析做的很好了。这里就不举例子了。实现思路类似遍历类的所有成员变量。

你可能感兴趣的:(runTimer的实际应用)