简介绍
本文主要介绍自己在实际项目中,用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已经对归档和解档、数据解析做的很好了。这里就不举例子了。实现思路类似遍历类的所有成员变量。