- 接下来的内容可能会引起您对知识储备的不适,敬请谅解 :)
- 交叉方法
- 获取类的详细信息
- 给分类增加属性
- 打印描述信息
-
NSCoding
的自动归档和自动解档 - 字典自动转模型
1. 交叉方法(Method Swizzling) - 动态方法交换
-
交叉方法(Method Swizzling)
原理:通过Runtime
获取到方法实现的地址,进而动态交换两个方法 -
交叉方法(Method Swizzling)
更多的是应用于系统类库和第三方框架的方法替换。 - 在不可见源码的情况下,我们可以借助
交叉方法(Method Swizzling)
实现,为原有方法添加额外功能。
- (void)Vampire{
NSLog(@" 1 Vampire ");
}
- (void)June{
NSLog(@" 2 June ");
}
+ load{
Method nlV = class_getInstanceMethod([self class], @selector(Vampire));
Method nlJ = class_getInstanceMethod([self class], @selector(June));
method_exchangeImplementations(nlV, nlJ);
}
[self Vampire];
[self June];
// 打印
2018-11-05 15:02:03.122752+0800 [7456:138606] 2 June
2018-11-05 15:02:03.122984+0800 [7456:138606] 1 Vampire
- 将交叉方法写在
+load
中,是因为+load
是只要类所在文件被引用就会被调用,确保方法交换在程序运行开始就可以替换成我们的方法想实现的样子,关于+load
的用法 可看 load和initialize的区别详细了解 -
建议
:如果使用交叉方法,将系统的方法替换为了自定义的方法,那么如果想在系统的方法执行完后再执行一些操作,那就在自定义的方法里再调一次自己 - 比如,替换了
dealloc
方法实现, 还想在ViewController 的 dealloc
方法里 取消通知等。 - 看起来像是死循环,其实是调已经替换了的方法实现
- (void)Vampire{
NSLog(@" 1 Vampire ");
[self Vampire];
}
- (void)June{
NSLog(@" 2 June ");
[self June];
}
2. 获取类的详细信息
-
注意
-
注意
-
注意
-
注意
:C语言
中使用Copy
操作的方法,要释放指针
,防止内存泄漏free(指针);
-
注意
-
注意
-
注意
-
获取 成员变量 列表
unsigned count;
Ivar *ivarList = class_copyIvarList([self class], &count);
NSLog(@" count %d ",count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSLog(@" ivar_name = %@ , type = %@ ",[NSString stringWithUTF8String:ivar_getName(ivar)],[NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]);
}
free(ivarList);
- 获取 属性 列表
unsigned count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
NSLog(@" count = %d ",count);
for (int i = 0; i < count; i++) {
NSLog(@" property_name = %@ ",[NSString stringWithUTF8String:property_getName(propertyList[i])]);
}
free(propertyList);
- 获取 方法 列表
unsigned count;
Method *methodList = class_copyMethodList([self class], &count);
NSLog(@" count = %d ",count);
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSLog(@" method_getName = %@ ",NSStringFromSelector(method_getName(method)));
}
free(methodList);
- 获取 遵守的 协议 列表
unsigned count;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
NSLog(@" count = %d ",count);
for (int i = 0; i < count; i++) {
Protocol *protocol = protocolList[i];
NSLog(@" protocol_getName = %@ ",[NSString stringWithUTF8String:protocol_getName(protocol)]);
}
free(protocolList);
3. 给分类增加属性 - 关联对象(Associated Objects)
-
OC分类
并不支持直接添加属性,如果我们直接在分类
的声明中写入Property
属性,那么只能为其生成setter
与getter
方法声明,却不能生成成员变量
,直接调用这些属性还会造成崩溃 -
关联对象(Associated Objects)
能够帮助我们在运行阶段将任意的属性关联到一个对象上
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIImage (JuneImage)
/** 网络连接 */
@property (nonatomic, strong) NSString *urlString;
@end
NS_ASSUME_NONNULL_END
#import "UIImage+JuneImage.h"
@implementation UIImage (JuneImage)
- (void)setUrlString:(NSString *)urlString{
objc_setAssociatedObject(self, @selector(urlString),
urlString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/**
objc_setAssociatedObject( id _Nonnull object,
const void * _Nonnull key,
id _Nullable value,
objc_AssociationPolicy policy );
1.给对象设置关联属性
@param object 需要设置关联属性的对象,即给哪个对象关联属性
@param key 关联属性对应的key,可通过key获取这个属性,(传一个方法选择器)
@param value 给关联属性设置的值(就是它自己)
@param policy 关联属性的存储策略
(对应Property属性中的assign,copy,retain等)
OBJC_ASSOCIATION_ASSIGN @property(assign)。
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(strong, nonatomic)。
OBJC_ASSOCIATION_COPY_NONATOMIC @property(copy, nonatomic)。
OBJC_ASSOCIATION_RETAIN @property(strong,atomic)。
OBJC_ASSOCIATION_COPY @property(copy, atomic)。
*/
}
- (NSString *)urlString{
/**
2.通过key获取关联的属性
@param object 从哪个对象中获取关联属性
@param key 关联属性对应的key
@return 返回关联属性的值
return objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
*/
return objc_getAssociatedObject(self, @selector(urlString));
}
@end
UIImage *image = [[UIImage alloc] init];
image.urlString = @"http.ima";
NSLog(@" 11-11 %@ 11-11 ",image.urlString);
// 11-11 http.ima 11-11
UIImage *image2 = [[UIImage alloc] init];
image2.urlString = @"image2.urlString";
NSLog(@" 22-22 %@ 22-22 ",image2.urlString);
// 22-22 image2.urlString 22-22
-
*
使用关联对象
提高代码效率:)
- 场景示例:
高频
遍历模型属性时,这个模型里已有属性不变的情况下,可以使用关联对象
返回属性数组,提高运行效率
* VampireJune.h 文件 :)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface VampireJune : NSObject
@property (nonatomic, strong) NSString *vampire;
@property (nonatomic, strong) NSString *june;
@property (nonatomic, strong) NSString *vampirejune;
+ (NSArray *)getProperty;
@end
NS_ASSUME_NONNULL_END
* VampireJune.m 文件 :)
#import "VampireJune.h"
@implementation VampireJune
/* 定义一个 key 常量 */
const char * kPropertyList = "kPropertyList";
+ (NSArray *)getProperty{
/** 1> 从 `关联对象` 中获取 动态添加的属性,如果有,直接返回
参数
1. 对象 - self
2. 动态属性的 - key
3. 返回值 - 动态添加的 `属性值`
*/
NSArray *proList = objc_getAssociatedObject(self, kPropertyList);
if (proList != nil) {
return proList;
}
NSLog(@" ***** `关联对象`获取成功 只打印一次 ***** ");
NSMutableArray *proArr = [[NSMutableArray alloc] init];
unsigned count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t pro = propertyList[i];
[proArr addObject:[NSString stringWithUTF8String:property_getName(pro)]];
}
free(propertyList);
/** 2> 对象的属性数组已经获取完毕,利用 `关联对象` 动态添加属性
参数
1. 对象 - self (OC 中 class 也是一个特殊的对象)
2. 动态添加属性的 key,获取值的时候使用
3. 动态添加的属性值
4. 对象的引用关系
*/
objc_setAssociatedObject(self, kPropertyList, proArr.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return proArr.copy;
}
@end
- 实测 对比
// 基于内建时钟的,能够更精确更原子化地测量
// 并且不会因为外部时间变化而变化(例如时区变化、夏时制、秒突变等)
// 但它和系统的uptime有关,系统重启后CACurrentMediaTime()会被重置。
NSTimeInterval start = CACurrentMediaTime();
for (int i = 0; i < 10000; i++) {
NSArray *VJPropertyList = [VampireJune getProperty];
}
NSLog(@" 执行时间 start = %f , end = %f ",start,CACurrentMediaTime() - start);
/* 未设置 `关联对象`
* 1万次循环 执行 10次
* 基本平均是 0.0144 秒 1万次循环
执行时间 start = 7994.328514 , end = 0.017269
执行时间 start = 7994.838316 , end = 0.014821
执行时间 start = 7995.302563 , end = 0.014673
执行时间 start = 7995.766477 , end = 0.013680
执行时间 start = 7996.214354 , end = 0.014245
执行时间 start = 7996.672537 , end = 0.014086
执行时间 start = 7997.120546 , end = 0.014361
执行时间 start = 7997.576433 , end = 0.014445
执行时间 start = 7998.010728 , end = 0.014492
执行时间 start = 7998.426440 , end = 0.014406
*/
/* 已设置 `关联对象`
* 1万次循环 执行 10次
* 基本平均是 0.0016 秒 1万次循环
***** `关联对象`获取成功 只打印一次 *****
执行时间 start = 9903.351885 , end = 0.002677
执行时间 start = 9903.676558 , end = 0.001639
执行时间 start = 9904.016610 , end = 0.001565
执行时间 start = 9904.364504 , end = 0.001692
执行时间 start = 9904.712578 , end = 0.002156
执行时间 start = 9905.066463 , end = 0.001769
执行时间 start = 9905.454461 , end = 0.001632
执行时间 start = 9905.834233 , end = 0.001662
执行时间 start = 9906.158421 , end = 0.001640
执行时间 start = 9906.506426 , end = 0.001647
*/
* 0.0144 vs 0.0016
* 哪个大 :)
4. 打印描述信息,方便测试数据
- 在
.h
里定义VampireDescription
宏 - 在
.m
实现里写上VampireDescription
宏 - 子类只需 直接继承 此父类
- 均可实现打印出 属性值
* TestFather.h :)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestFather : NSObject
@property (nonatomic, strong) NSString *FTitle1;
@property (nonatomic, strong) NSString *FTitle2;
@end
NS_ASSUME_NONNULL_END
/* 定义`VampireDescription` 宏
* Model's descriptions
*/
#define VampireDescription \
- (NSString *)description { \
NSArray *keys = [self keysArr]; \
return [self dictionaryWithValuesForKeys:keys].description; \
} \
- (NSArray<NSString *> *)keysArr { \
NSMutableArray *keys = [[NSMutableArray alloc] init]; \
unsigned count; \
Ivar *ivarList = class_copyIvarList([self class], &count); \
for (int i = 0; i < count; i++) { \
Ivar ivar = ivarList[i]; \
[keys addObject:[NSString stringWithUTF8String:ivar_getName(ivar)]]; \
} \
free(ivarList); \
return keys; \
}
* TestFather.m :)
#import "TestFather.h"
@implementation TestFather
VampireDescription
@end
- 子类 TestSon 直接 继承 父类 TestFather
* TestSon.h :)
#import "TestFather.h"
NS_ASSUME_NONNULL_BEGIN
@interface TestSon : TestFather
@property (nonatomic, strong) NSString *sonTitle1;
@property (nonatomic, strong) NSString *sonTitle2;
@end
NS_ASSUME_NONNULL_END
* TestSon.m :)
#import "TestSon.h"
@implementation TestSon
@end
- 测试打印 前后打印
TestFather *f = [[TestFather alloc] init];
f.FTitle1 = @"* FTitle1 *";
f.FTitle2 = @"* FTitle2 *";
NSLog(@" TestFather = %@ ",f);
TestSon *s = [[TestSon alloc] init];
s.sonTitle1 = @"* sonTitle1 *";
s.sonTitle2 = @"* sonTitle2 *";
NSLog(@" TestSon = %@ ",s);
- 打印结果
* 没有写 `VampireDescription` 宏
2018-11-06 16:21:29.949963+0800 [27412:222098] TestFather = <TestFather: 0x6000021cc000>
2018-11-06 16:21:29.950198+0800 [27412:222098] TestSon = <TestSon: 0x600002ffb150>
* 写了 `VampireDescription` 宏
2018-11-06 16:22:10.938436+0800 [27445:223060] TestFather = {
"_FTitle1" = "* FTitle1 *";
"_FTitle2" = "* FTitle2 *";
}
2018-11-06 16:22:10.938780+0800 [27445:223060] TestSon = {
"_sonTitle1" = "* sonTitle1 *";
"_sonTitle2" = "* sonTitle2 *";
}
- 还可以通过
交叉方法
来实现打印描述信息 - 无需继承自定义基类,像往常一样创建自定义类
* 创建一个分类 `NSObject+VampireDescription.h`
#import "NSObject+VampireDescription.h"
#import <objc/runtime.h>
@implementation NSObject (VampireDescription)
+ (void)load{
Method des = class_getInstanceMethod([self class], @selector(description));
Method nlV = class_getInstanceMethod([self class], @selector(vampireDescription));
method_exchangeImplementations(des, nlV);
}
- (NSString *)vampireDescription{
// NSLog(@" * vampireDescription * ");
NSMutableArray *keys = [[NSMutableArray alloc] init];
unsigned count;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
[keys addObject:[NSString stringWithUTF8String:ivar_getName(ivar)]];
}
free(ivarList);
[self vampireDescription];
return [self dictionaryWithValuesForKeys:keys.copy].description;;
}
@end
5. 实现 NSCoding
的自动归档和自动解档
-
归档
是一种常用的轻量型文件存储方式 -
文件的后缀名随便写,即使写成
.txt
也不能打开看 -
弊端:一般一个Model都会有多个属性,需要对每个属性进行处理,非常繁琐。
-
归档
主要涉及两个方法:encodeObject
和decodeObjectForKey
-
现在就利用
Runtime
来改进它 -
注意
-
注意
-
注意
:父类一定要遵守<NSCoding>
协议 -
注意
-
注意
-
在
.h
里定义VampireCoding
宏 -
在
.m
实现里写上VampireCoding
宏 -
子类只需 直接继承 此父类
-
均可实现 自动归档和自动解档
-
再在父类 实现本文
4. 打印描述信息
,均可实现打印出 属性值
* TestFather.h :)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TestFather : NSObject <NSCoding>
@property (nonatomic, strong) NSString *FTitle1;
@property (nonatomic, strong) NSString *FTitle2;
@end
NS_ASSUME_NONNULL_END
/* 定义`VampireCoding` 宏
* Model's Coding
*/
#define VampireCoding \
- (void)encodeWithCoder:(NSCoder *)aCoder { \
unsigned count; \
Ivar *ivarList = class_copyIvarList([self class], &count); \
for (int i = 0; i < count; i++) { \
Ivar ivar = ivarList[i]; \
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; \
id value = [self valueForKey:key]; \
[aCoder encodeObject:value forKey:key]; \
} \
free(ivarList); \
} \
- (instancetype)initWithCoder:(NSCoder *)aDecoder { \
if (self = [super init]) { \
unsigned count; \
Ivar *ivarList = class_copyIvarList([self class], &count); \
for (int i = 0; i < count; i++) { \
Ivar ivar = ivarList[i]; \
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; \
id value = [aDecoder decodeObjectForKey:key]; \
[self setValue:value forKey:key]; \
} \
free(ivarList); \
} \
return self; \
}
* 此处省略,可查看本文 4. 打印描述信息
/*
* Model's descriptions
*/
......
* TestFather.m :)
#import "TestFather.h"
@implementation TestFather
VampireCoding
VampireDescription
@end
- 测试打印 前后打印 - 父类
TestFather
TestFather *enf = [[TestFather alloc] init];
enf.FTitle1 = @"* FTitle1 *";
enf.FTitle2 = @"* FTitle2 *";
NSString *cachesf = kCachesPath; // 获取 Caches目录路径
NSString *filePathFather = [cachesf stringByAppendingPathComponent:@"enf.VampireJune"];
[NSKeyedArchiver archiveRootObject:enf toFile:filePathFather];
TestFather *unf = [NSKeyedUnarchiver unarchiveObjectWithFile:filePathFather];
NSLog(@" 解档 unf = %@ ",unf);
/*
* 没有实现 VampireDescription
2018-11-06 16:51:43.905310+0800 [27825:236967] 解档 unf = <TestFather: 0x6000028944a0>
* 实现了 VampireDescription
* 所以可以直接打印出 unf 的属性值
2018-11-06 16:54:40.818610+0800 [27900:239329] 解档 unf = {
"_FTitle1" = "* FTitle1 *";
"_FTitle2" = "* FTitle2 *";
}
*/
- 测试打印 前后打印 - 子类
TestSon
TestSon *enS = [[TestSon alloc] init];
enS.sonTitle1 = @"* sonTitle1 *";
enS.sonTitle2 = @"* sonTitle2 *";
NSString *cachesS = kCachesPath; // 获取 Caches 目录路径
NSString *filePathSon = [cachesS stringByAppendingPathComponent:@"enS.VampireJune"];
[NSKeyedArchiver archiveRootObject:enS toFile:filePathSon];
TestSon *unS = [NSKeyedUnarchiver unarchiveObjectWithFile:filePathSon];
NSLog(@" 解档 unS = %@ ",unS);
/*
* 父类 TestFather 没有实现 VampireDescription
2018-11-06 17:04:45.203799+0800 [28096:245787] 解档 unS = <TestSon: 0x6000008733f0>
* 父类 TestFather 实现了 VampireDescription
2018-11-06 17:05:55.741714+0800 [28141:247115] 解档 unS = {
"_sonTitle1" = "* sonTitle1 *";
"_sonTitle2" = "* sonTitle2 *";
}
*/
-
归档后的文件
归档后的文件.png -
同样,这里也可通过
交叉方法
来实现NSCoding
的自动归档和自动解档,可自行尝试,感受Runtime
带来的乐趣
6. 字典自动转模型
字典转模型套路.png- 先创建一个分类
NSObject+JuneDic
NSObject+JuneDic.h :)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/* 协议方法返回一个字典,处理特殊的字段 */
@protocol JuneDic <NSObject>
// 可选是否实现方法
@optional
/* 返回 需要特别处理的 字段
* 字典格式 : key : value
* key : model 中定义的属性名称,如 ID
* value : 需要解析的,替换成的,名称,如 id
*/
+ (NSDictionary <NSString *, id>*)june_ReplaceKeyWithDic;
@end
@interface NSObject (JuneDic) <JuneDic>
/* 给定一个字典,创建 self 类对应的对象,并返回 */
+ (instancetype)june_modelWithDictionary:(NSDictionary *)dic;
@end
NS_ASSUME_NONNULL_END
NSObject+JuneDic.m :)
#import "NSObject+JuneDic.h"
#import <objc/runtime.h>
@implementation NSObject (JuneDic)
/// 给定一个字典,创建 self 类对应的对象,并返回
+ (instancetype)june_modelWithDictionary:(NSDictionary *)dic
{
// 创建当前模型对象
id Vampire = [[self alloc] init];
// 1. 获取当前对象的成员变量列表
unsigned count;
Ivar *ivarList = class_copyIvarList(self, &count);
// 2. 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 2.1 获取成员变量名
Ivar ivar = ivarList[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.2 截取成员变量名:去掉成员变量前面的 `_` 下划线
NSString *propertyName = [ivarName substringFromIndex:1];
// 2.3 以属性名为 key,在字典 `dic` 中查找 value
id value = dic[propertyName];
// 3. 获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 3.1 去除 `@""`
// 因为有的获取出来是 @"NSString",@"NSArray"等
// 所以要把对应的 类名 取出来,去掉 `@""`
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 3.2 处理 模型嵌套模型
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(ivarType);
if (modelClass != nil) {
value = [modelClass june_modelWithDictionary:value];
}
}
// 4. 获取 需要特别处理的 字段字典
// 4.1 判断当前类是否实现了 特别处理字段 的协议方法
NSDictionary *replayceDic;
if ([self respondsToSelector:@selector(june_ReplaceKeyWithDic)]) {
replayceDic = [self june_ReplaceKeyWithDic];
}
// 4.2 处理 需要特别处理的 字段,ID -> id
id propertyReName = replayceDic[propertyName];
if (propertyReName && [propertyReName isKindOfClass:[NSString class]]) {
// 获取 特别处理字段的 value
value = dic[propertyReName];
}
// 4.3 处理 模型嵌套模型数组
// 4.3.1 判断是否属于 数组类
// 4.3.2 判断是否实有 需特别处理的字段字典
// 4.3.3 判断字段中是否有 需要处理的 嵌套模型数组的 名称
if ([value isKindOfClass:[NSArray class]] && replayceDic && replayceDic[propertyName]) {
// 取出 嵌套模型数组的模型类
Class arrModelClass = replayceDic[propertyName];
// 封装数组,将每一个子数据转化为 Model
NSMutableArray *modelArr = [[NSMutableArray alloc] init];
for (NSDictionary *arrDic in value) {
id model = [arrModelClass june_modelWithDictionary:arrDic];
[modelArr addObject:model];
}
value = modelArr;
}
// 5. 使用 KVC 将 value 赋值到 object 中
if (value != nil) {
[Vampire setValue:value forKey:propertyName];
}
}
// 释放指针
free(ivarList);
return Vampire;
}
@end
- 创建一个模型类
VampireJune.h :)
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface VampireJune : NSObject
/** 普通属性 */
@property (nonatomic, copy) NSString *ID;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSArray *arr;
@property (nonatomic, strong) NSDictionary *dic;
/** 嵌套模型 */
@property (nonatomic, strong) VampireJune *june;
/** 装 VampireJune 的 模型数组 */
@property (nonatomic, strong) NSArray *VampiresArr;
@end
NS_ASSUME_NONNULL_END
VampireJune.m :)
#import "VampireJune.h"
#import "NSObject+JuneDic.h"
@implementation VampireJune
+ (NSDictionary<NSString *, id> *)june_ReplaceKeyWithDic
{
return @{@"ID" : @"id",
@"june" : [VampireJune class],
@"VampiresArr" : [VampireJune class]};
}
@end
- 解析
NSDictionary *dic = @{@"id" : @"12306",
@"name" : @"Vampire",
@"age" : @18,
@"arr" : @[@"arr1",@"arr2"],
@"dic" : @{@"dic1" : @"dic01",
@"dic2" : @"dic02"
},
@"june" : @{@"id" : @"12307",
@"name" : @"VampireJune"},
@"VampiresArr" : @[@{@"id" : @"12308",
@"name" : @"VampireJune1"},
@{@"id" : @"12309",
@"name" : @"VampireJune2"},]
};
VampireJune *vj = [VampireJune june_modelWithDictionary:dic];
解析1. 未处理特殊字段、未处理嵌套模型、未处理嵌套模型数组
1.1.png 1.2.png 1.3.png- 可以看到,解析出来的模型中
-
ID
没有值 -
june
嵌套模型,解析出来是字典类型 -
VampireArr
嵌套模型数组,解析出来内部是字典类型
网友评论