主要包含 字典与模型互转 、对象关联、遍历变量、遍历方法、遍历协议
字典与模型互转
字典转模型的时候:
根据字典的 key 生成 setter 方法.
使用 objc_msgSend 调用 setter 方法为 Model 的属性赋值(或者 KVC).
模型转字典的时候:
调用 class_copyPropertyList 方法获取当前 Model 的所有属性.
调用 property_getName 获取属性名称.
根据属性名称生成 getter 方法.
使用 objc_msgSend 调用 getter 方法获取属性值(或者 KVC).
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #import "NSObject+KeyValues.h" #import <objc/runtime.h> #import <objc/message.h> @implementation NSObject (KeyValues )+(id )objectWithKeyValues:(NSDictionary *)aDictionary{ id objc = [[self alloc] init]; for (NSString *key in aDictionary.allKeys) { id value = aDictionary[key]; objc_property_t property = class_getProperty(self , key.UTF8String); unsigned int outCount = 0 ; objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount); objc_property_attribute_t attribute = attributeList[0 ]; NSString *typeString = [NSString stringWithUTF8String:attribute.value]; if ([typeString isEqualToString:@"@\"TestModel\"" ]) { value = [self objectWithKeyValues:value]; } NSString *methodName = [NSString stringWithFormat:@"set%@%@:" ,[key substringToIndex:1 ].uppercaseString,[key substringFromIndex:1 ]]; SEL setter = sel_registerName(methodName.UTF8String); if ([objc respondsToSelector:setter ]) { ((void (*) (id ,SEL,id )) objc_msgSend) (objc,setter ,value); } } return objc; } -(NSDictionary *)keyValuesWithObject{ unsigned int outCount = 0 ; objc_property_t *propertyList = class_copyPropertyList([self class ], &outCount); NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (int i = 0 ; i < outCount; i ++) { objc_property_t property = propertyList[i]; const char *propertyName = property_getName(property); SEL getter = sel_registerName(propertyName); if ([self respondsToSelector:getter ]) { id value = ((id (*) (id ,SEL)) objc_msgSend) (self ,getter ); if ([value isKindOfClass:[self class ]] && value) { value = [value keyValuesWithObject]; } if (value) { NSString *key = [NSString stringWithUTF8String:propertyName]; [dict setObject:value forKey:key]; } } } return dict; } @end
对象关联
对已经存在的类在 Category 中添加自定义的属性:
要取出被关联的对象使用 objc_getAssociatedObject 方法即可,
要删除一个被关联的对象,使用 objc_setAssociatedObject 方法将对应的 key 设置成 nil 即可
objc_removeAssociatedObjects 方法将会移除源对象中所有的关联对象.
示例:
为类别添加chineseName属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #import "XiaoMing+MutipleName.h" #import <objc/runtime.h> @implementation XiaoMing (MutipleName )char cName;-(void )setChineseName:(NSString *) chineseName{ objc_setAssociatedObject(self , &cName, chineseName, OBJC_ASSOCIATION_COPY_NONATOMIC); } -(NSString *)chineseName{ return objc_getAssociatedObject(self , &cName);} @end
举个栗子,假如我们要给 UIButton 添加一个监听单击事件的 block 属性,新建 UIButton 的 Category,其.m文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #import "UIButton+ClickBlock.h" #import <objc/runtime.h> static const void *associatedKey = "associatedKey" ;@implementation UIButton (ClickBlock )-(void )setClick:(clickBlock)click{ objc_setAssociatedObject(self , associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC); [self removeTarget:self action:@selector (buttonClick) forControlEvents:UIControlEventTouchUpInside ]; if (click) { [self addTarget:self action:@selector (buttonClick) forControlEvents:UIControlEventTouchUpInside ]; } } -(clickBlock)click{ return objc_getAssociatedObject(self , associatedKey); } -(void )buttonClick{ if (self .click) { self .click(); } }
然后在代码中,就可以使用 UIButton 的属性来监听单击事件了:
1 2 3 4 5 6 7 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom ];button.frame = self .view.bounds; [self .view addSubview:button]; button.click = ^{ NSLog (@"buttonClicked" ); };
遍历变量
使用 class_copyIvarList 方法获取当前 Model 的所有成员变量.
使用 ivar_getName 方法获取成员变量的名称.
通过 KVC 来读取 Model 的属性值
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #import <objc/runtime.h> #import <objc/message.h> ... unsigned int outCount = 0 ; Ivar *vars = class_copyIvarList([obj class ], &outCount); for (int i = 0 ; i < outCount; i ++) { Ivar var = vars[i]; const char *name = ivar_getName(var); const char *type = ivar_getTypeEncoding(ivar); NSString *key = [NSString stringWithUTF8String:name]; id value = [obj valueForKey:key]; ... }
自动归档
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #import "TestModel.h" #import <objc/runtime.h> #import <objc/message.h> @implementation TestModel - (void )encodeWithCoder:(NSCoder *)aCoder{ unsigned int outCount = 0 ; Ivar *vars = class_copyIvarList([self class ], &outCount); for (int i = 0 ; i < outCount; i ++) { Ivar var = vars[i]; const char *name = ivar_getName(var); NSString *key = [NSString stringWithUTF8String:name]; id value = [self valueForKey:key]; [aCoder encodeObject:value forKey:key]; } } - (nullable instancetype )initWithCoder:(NSCoder *)aDecoder{ if (self = [super init]) { unsigned int outCount = 0 ; Ivar *vars = class_copyIvarList([self class ], &outCount); for (int i = 0 ; i < outCount; i ++) { Ivar var = vars[i]; const char *name = ivar_getName(var); NSString *key = [NSString stringWithUTF8String:name]; id value = [aDecoder decodeObjectForKey:key]; [self setValue:value forKey:key]; } } return self ; } @end
修改变量值
使用 class_copyIvarList 方法获取当前 Model 的所有成员变量.
使用 ivar_getName 方法获取成员变量的名称.
通过 KVC 来读取 Model 的属性值
修改对应的字段值 object_setIvar(id obj, Ivar ivar, id value)
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned int outCount = 0 ; Ivar *vars = class_copyIvarList([obj class ], &outCount); for (int i = 0 ; i < outCount; i ++) { Ivar var = vars[i]; const char *name = ivar_getName(var); NSString *key = [NSString stringWithUTF8String:name]; if ([name isEqualToString:@"_shuxingming" ]) { object_setIvar(self , var, @"新的值" ); break ; } }
遍历属性
通过class_copyPropertyList函数来获取属性列表,其中属性列表是使用@property声明的列表,对于直接使用声明为成员变量的,都不会出现在属性列表中,这也是正常的。
我们通过runtime提供的property_getName函数来获取属性名称。
若有获取属性的详细描述,可通过runtime提供的property_getAttributes函数来获取。
若有获取属性中的objc_property_attribute_t列表,可以通过property_copyAttributeList函数来获取。
若有获取单独的objc_property_attribute_t的name或者value,直接使用点语法即可,它是一个结构体。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #import <objc/runtime.h> #import <objc/message.h> ... unsigned int outCount = 0 ;objc_property_t *properties = class_copyPropertyList([obj class ], &outCount); for (int i = 0 ; i < outCount; i ++) { objc_property_t property = properties[i]; const char *name = property_getName(property); NSString *key = [NSString stringWithUTF8String:name]; const char *propertyAttributes = property_getAttributes(property); NSLog (@"%s %s" , propertyName, propertyAttributes); unsigned int count = 0 ; objc_property_attribute_t *attrbutes = property_copyAttributeList(property, &count); for (unsigned int j = 0 ; j < count; ++j) { objc_property_attribute_t attribute = attrbutes[j]; const char *name = attribute.name; const char *value = attribute.value; NSLog (@"name: %s value: %s" , name, value); } free(attrbutes); }
遍历方法
使用 class_copyMethodList 方法获取当前 Model 的所有成员变量.
使用 method_getName 方法获取成员变量的名称.
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #import <objc/runtime.h> #import <objc/message.h> ... unsigned int outCount = 0 ; Method *methods = class_copyMethodList([obj class ], &outCount); for (int i = 0 ; i < outCount; i ++) { Method method = methods[i]; SEL methodSEL = method_getName(method); const char *name = sel_getName(methodSEL); NSString *key = [NSString stringWithUTF8String:name]; int arguments = method_getNumberOfArguments(method);char argName[512 ] = {};for (unsigned int j = 0 ; j < arguments; ++j) { method_getArgumentType(method, j, argName, 512 ); NSLog (@"第%u个参数类型为:%s" , j, argName); memset(argName, '\0' , strlen(argName)); } char returnType[512 ] = {}; method_getReturnType(method, returnType, 512 ); NSLog (@"返回值类型:%s" , returnType); NSLog (@"TypeEncoding: %s" , method_getTypeEncoding(method)); } free(methods);
动态交换方法
Method class_getInstanceMethod(Class cls, SEL name)
找到方法实例
void method_exchangeImplementations(Method m1, Method m2)
交换方法
示例
1 2 3 4 5 6 7 8 9 10 11 -(void )answer{ Method m1 = class_getInstanceMethod([self .xiaoMing class ], @selector (firstSay)); Method m2 = class_getInstanceMethod([self .xiaoMing class ], @selector (secondSay)); method_exchangeImplementations(m1, m2); NSString *secondName = [self .xiaoMing firstSay];self .nameTf.text = secondName;NSLog (@"XiaoMing:My name is %@" ,secondName);}
Method Swizzling 原理 归根结底,都是偷换了selector的IMP
method_exchangeImplementations 来交换2个方法中的IMP,
利用 class_replaceMethod 来修改类,
利用 method_setImplementation 来直接设置某个方法的IMP
动态添加方法
void class_addMethods(Class, struct objc_method_list *)
添加多个方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
添加方法
(IMP)guessAnswer 意思是guessAnswer的地址指针;
“v@:” 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;“v@:@@” 意思是,两个参数的没有返回值。
示例:
假设XiaoMing的中没有guess这个方法,后来被Runtime添加一个名字叫guess的方法,最终再调用guess方法做出相应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -(void )answer{ class_addMethod([self .xiaoMing class ], @selector (guess), (IMP)guessAnswer, "v@:" ); if ([self .xiaoMing respondsToSelector:@selector (guess)]) { [self .xiaoMing performSelector:@selector (guess)]; } else { NSLog (@"Sorry,I don't know" ); } self .cityTf.text = @"GuangTong" ;} void guessAnswer(id self ,SEL _cmd){NSLog (@"He is from GuangTong" );}
调用:
`[self.xiaoMing performSelector:@selector(guess)];`
遍历协议
使用 class_copyProtocolList 方法获取当前 Model 的所有成员变量.
使用 protocol_getName 方法获取成员变量的名称.
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #import <objc/runtime.h> #import <objc/message.h> ... unsigned int outCount = 0 ; _unsafe_unretained Protocol **protocols = class_copyProtocolList([obj class ], &outCount); for (int i = 0 ; i < outCount; i ++) { Protocol *protocol = protocols[i]; const char *name = protocol_getName(protocol); NSString *key = [NSString stringWithUTF8String:name]; }