一、运行时机制
动态语言:编译时确定变量的数据类型。
静态语言:运行时确定变得的数据类型。
1、【动态语言】:Objective-C语言(以下简称“OC”)是一门动态语言,其优势有:代码编程更灵活,如可以将消息转成给我们想要的对象,或者随意交换一个方法的实现等。
2、【编译器 + 运行时操作系统】:OC作为动态语言,其需要编译器和运行时系统来执行编译的代码,而这个运行时系统就是我所要讲的Objc Runtime(Runtime)。Runtime本身是一个库,它基本上是用C和汇编写的,有了这个库使得C语言有面向对象的能力。
3、【函数的调用区别】:C语言,在编译的时候决定调用哪个函数;OC语言,在运行的时候决定调用哪个函数。
4、【在编译阶段报错】:OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错;C语言调用未实现的函数就会报错。
二、应用
1、 能获得某个类的所有成员变量
2、能获得某个类的所有属性
3、能获得某个类的所有方法
4、交换方法实现
5、能动态添加一个成员变量
6、能动态添加一个属性
7、字典转模型
8、runtime归档/解档
三、基本函数和头文件
#import<objc/runtime.h> : //成员变量,类,方法 class_copyIvarList : 获得某个类内部的所有成员变量 class_copyMethodList : 获得某个类内部的所有方法 class_getInstanceMethod : 获得某个具体的实例方法 (对象方法,减号-开头) class_getClassMethod : 获得某个具体的类方法 (加号+开头) method_exchangeImplementations : 交换两个方法的实现 #import<objc/message.h> : //消息机制 objc_msgSend(...)
四、应用
1、更改属性值
#import <Foundation/Foundation.h> @interface ZMPerson : NSObject /** 姓名 **/ @property (nonatomic, copy) NSString *name; /** 性别 **/ @property (nonatomic, copy) NSString *sex; - (NSString *)coding; - (NSString *)eating; - (NSString *)changeMethod; @end
#import "ZMPerson.h" #import <objc/runtime.h> @interface ZMPerson(){ NSString *mobile;//手机号 } /** 年龄 **/ @property (nonatomic, copy) NSString *age; @end @implementation ZMPerson{ NSString *address;//地址 } - (NSString *)coding { return @"coding"; } - (NSString *)eating { return @"eating"; } - (NSString *)changeMethod { return @"方法已被拦截并替换"; } // void(*)() // 默认方法都有两个隐式参数, void eat(id self, SEL _cmd, NSNumber *meter){ NSLog(@"%@ __ %@ __ %@",[self class],NSStringFromSelector(_cmd),meter); } // 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来. // 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法 + (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selStr = @"eat:"; SEL selFromStr = NSSelectorFromString(selStr); if (sel == selFromStr) { // 动态添加eat方法 // 第一个参数:给哪个类添加方法 // 第二个参数:添加方法的方法编号 // 第三个参数:添加方法的函数实现(函数地址) // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, sel, (IMP)eat, "v@:@"); } return [super resolveInstanceMethod:sel]; } @end
#import "ZMPerson.h" @interface ZMFirstVC () @property (nonatomic, strong) ZMPerson *person; @end @implementation ZMFirstVC - (void)initView { [super initView]; _person = [ZMPerson new]; } - (void)buttonClick:(UIButton *)sender { // ------ 设置属性值 ----- _person.sex = @"1"; unsigned int count = 0; // 动态获取类中的所有属性(包括私有) Ivar *ivar = class_copyIvarList(_person.class, &count); // 遍历属性找到对应字段 for (int i = 0; i < count; i ++) { Ivar tempIvar = ivar[i]; //ivar_getName:将IVar变量转化为字符串 const char *varChar = ivar_getName(tempIvar); //ivar_getTypeEncoding:获取IVar的类型 //const char *memberType = ivar_getTypeEncoding(tempIvar); NSString *varString = [NSString stringWithUTF8String:varChar]; if ([varString isEqualToString:@"_name"]) { // 修改对应的字段值 object_setIvar(_person, tempIvar, @"姓名"); } NSLog(@"----->%@",varString); //私有类 if ([varString isEqualToString:@"_age"]) { object_setIvar(_person, tempIvar, @"100"); } //私有类 if ([varString isEqualToString:@"address"]) { object_setIvar(_person, tempIvar, @"福建省厦门市"); } //私有类 if ([varString isEqualToString:@"mobile"]) { object_setIvar(_person, tempIvar, @"138****8888"); } } // ---- 利用KVC键值编码 获取属性值 ----- NSString *str0 = [_person valueForKey:@"_name"]; NSString *str1 = [_person valueForKey:@"_sex"]; NSString *str2 = [_person valueForKey:@"_age"]; NSString *str3 = [_person valueForKey:@"address"]; NSString *str4 = [_person valueForKey:@"mobile"]; NSMutableString *mstr = [NSMutableString string]; [mstr appendString:str0]; [mstr appendString:str1]; [mstr appendString:str2]; [mstr appendString:str3]; [mstr appendString:str4]; NSLog(@"非私有类:%@__%@\n私有类:%@__%@__%@__",str0,str1,str2,str3,str4); self.testLabelText = mstr ? mstr : @"更改属性值失败"; // ----- 键值编码设置属性值 ------ [_person setValue:@"我的家乡还是在福建啊" forKey:@"address"]; NSLog(@"=======%@", [_person valueForKey:@"address"]); } @end
执行结果:
2、动态添加属性
#import <Foundation/Foundation.h> @interface NSObject (ZMAddAttribute_h) @property (nonatomic, copy) NSString *name; @end #import "NSObject+ZMAddAttribute_h.h" #import <objc/message.h> @implementation NSObject (ZMAddAttribute_h) -(void)setName:(NSString *)name{ objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -(NSString *)name{ return objc_getAssociatedObject(self, @"name"); } @end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #import "NSObject+ZMAddAttribute_h.h" @interface ZMSecondVC () @property (nonatomic, strong) NSObject *person; @end @implementation ZMSecondVC - (void)initView { [super initView]; _person = [NSObject new]; _person.name = @"添加属性成功"; } - (void)buttonClick:(UIButton *)sender { self.testLabelText = _person.name.length ? _person.name : @"添加属性失败"; } @end
3、添加方法成功
#import "ZMPerson.h" @interface ZMThirdVC () @property (nonatomic, strong) ZMPerson *person; @end @implementation ZMThirdVC - (void)initView { [super initView]; _person = [ZMPerson new]; } - (void)buttonClick:(UIButton *)sender { /* 动态添加 coding 方法 (IMP)codingOC 意思是 codingOC 的地址指针; "v@:" 意思是,v 代表无返回值 void,如果是 i 则代表 int;@代表 id sel; : 代表 SEL _cmd; “v@:@@” 意思是,两个参数的没有返回值。 */ class_addMethod([_person class], @selector(test), (IMP)codingObjC, "v@:"); // 调用 coding 方法响应事件 if ([_person respondsToSelector:@selector(test)]) { [_person performSelector:@selector(test)]; self.testLabelText = @"添加方法成功"; }else{ self.testLabelText = @"添加方法失败"; } [_person performSelector:@selector(eat:) withObject:@100 afterDelay:3]; } -(void)test{ NSLog(@"测试测试"); } // 方法实现 void codingObjC(id self,SEL _cmd){ NSLog(@"添加方法成功"); } @end
4、交换方法实现
#import "ZMPerson.h" @interface ZMFourthVC () @property (nonatomic, strong) ZMPerson *person; @end @implementation ZMFourthVC - (void)initView { [super initView]; _person = [ZMPerson new]; NSLog(@"%@",_person.coding); NSLog(@"%@",_person.eating); } - (NSArray *)buttonTitleArray { return @[@"交换", @"不交换"]; } - (void)buttonClick:(UIButton *)sender { if (sender.tag == 0) { [self exchangeMethod]; } NSString *str = [NSString stringWithFormat:@"%@_%@",[_person coding],[_person eating]]; self.testLabelText = str; NSLog(@"%@",_person.coding); NSLog(@"%@",_person.eating); } ///交换方法 -(void)exchangeMethod{ Method aMethod = class_getInstanceMethod([_person class], @selector(coding)); Method bMethod = class_getInstanceMethod([_person class], @selector(eating)); method_exchangeImplementations(aMethod, bMethod); }
5、拦截并替换方法
#import <objc/runtime.h> @interface NSObject (Swizzle) ///拦截替换方法 + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel; @end
@implementation NSObject (Swizzle) ///拦截替换方法 + (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel { Method originMethod = class_getInstanceMethod(self, origSel); Method newMethod = class_getInstanceMethod(self, aftSel); if(originMethod && newMethod) {//必须两个Method都要拿到 if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) { //实现成功添加后 class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); } return YES; } return NO; } @end
#import "NSObject+Swizzle.h" @implementation MyString + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = object_getClass((id)self); [class swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)]; }); } + (BOOL)myResolveInstanceMethod:(SEL)sel { if(! [self myResolveInstanceMethod:sel]) { // NSString *selString = NSStringFromSelector(sel); // class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:"); // return YES; NSString *selString = NSStringFromSelector(sel); ///锁定一些方法如果不存在则创建 if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"] || [selString isEqualToString:@"test"]) { class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:"); return YES; }else { return NO; } } return YES; } - (void)dynamicMethodIMP { NSLog(@"我是动态加入的函数"); }
- (void)buttonClick:(UIButton *)sender { NSLog(@"begin test"); MyString *string = [[MyString alloc] init]; [string performSelector:@selector(countAll)]; [string performSelector:@selector(pushViewController)]; [string performSelector:@selector(test)]; NSLog(@"finish test"); }
6、在方法上添加额外功能
@interface ZMCountButton : UIButton @property (nonatomic, assign) NSInteger count; @end @interface ZMClickCount : NSObject + (instancetype)sharedInstance; - (NSInteger)clickCount; @end
#import <objc/runtime.h> @implementation ZMCountButton + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getInstanceMethod(self.class, @selector(sendAction:to:forEvent:)); Method cusMethod = class_getInstanceMethod(self.class, @selector(customSendAction:to:forEvent:)); // 判断自定义的方法是否实现, 避免崩溃 BOOL addSuccess = class_addMethod(self.class, @selector(sendAction:to:forEvent:), method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod)); if (addSuccess) { // 没有实现, 将源方法的实现替换到交换方法的实现 class_replaceMethod(self.class, @selector(customSendAction:to:forEvent:), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); } else { // 已经实现, 直接交换方法 method_exchangeImplementations(oriMethod, cusMethod); } }); } - (void)customSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { _count = [[ZMClickCount sharedInstance] clickCount]; [self customSendAction:action to:target forEvent:event]; } @end @interface ZMClickCount() @property (nonatomic, assign) NSInteger count; @end @implementation ZMClickCount + (instancetype)sharedInstance { static ZMClickCount *_instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [[self alloc] init]; }); return _instance; } - (NSInteger)clickCount { ++_count; NSLog(@"点击了 %ld次", _count); return _count; } @end
- (void)countButtonClick:(UIButton *)sender { self.testLabelText = [NSString stringWithFormat:@"点击 %ld 次了", self.testButton.count]; } #pragma mark - 懒加载 - (ZMCountButton *)testButton { if (!_testButton) { _testButton = [ZMCountButton buttonWithType:UIButtonTypeCustom]; [_testButton addTarget:self action:@selector(countButtonClick:) forControlEvents:UIControlEventTouchUpInside]; [_testButton setTitle:@"统计此按钮的点击数量" forState:UIControlStateNormal]; _testButton.frame = CGRectMake(20, self.view.center.y + 100, [UIScreen mainScreen].bounds.size.width - 40, 30); _testButton.backgroundColor = [UIColor redColor]; } return _testButton; }
7、字典转模型
#import <Foundation/Foundation.h> @protocol ModelDelegate <NSObject> @optional // 用在三级数组转换 + (NSDictionary *)arrayContainModelClass; @end @interface NSObject (NNKeyValues) /** 字典转模型 **/ + (instancetype)modelWithDict:(NSDictionary *)dict; @end
#import "NSObject+ZMKeyValues.h" #import <objc/runtime.h> @implementation NSObject (ZMKeyValues) /** 字典转模型 **/ + (instancetype)modelWithDict:(NSDictionary *)dict { id objc = [[self alloc] init]; unsigned int count = 0; // 获取成员属性数组 Ivar *ivarList = class_copyIvarList(self, &count); // 遍历所有的成员属性名 for (int i = 0; i < count; i ++) { // 获取成员属性 Ivar ivar = ivarList[i]; // 获取成员属性名 NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)]; NSString *key = [ivarName substringFromIndex:1]; // 从字典中取出对应 value 给模型属性赋值 id value = dict[key]; // 获取成员属性类型 NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // 判断 value 是不是字典 if ([value isKindOfClass:[NSDictionary class]]) { ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""]; ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""]; Class modalClass = NSClassFromString(ivarType); // 字典转模型 if (modalClass) { // 字典转模型 value = [modalClass modelWithDict:value]; } } if ([value isKindOfClass:[NSArray class]]) { // 判断对应类有没有实现字典数组转模型数组的协议 if ([self respondsToSelector:@selector(arrayContainModelClass)]) { // 转换成id类型,就能调用任何对象的方法 id idSelf = self; // 获取数组中字典对应的模型 NSString *type = [idSelf arrayContainModelClass][key]; // 生成模型 Class classModel = NSClassFromString(type); NSMutableArray *arrM = [NSMutableArray array]; // 遍历字典数组,生成模型数组 for (NSDictionary *dict in value) { // 字典转模型 id model = [classModel modelWithDict:dict]; [arrM addObject:model]; } // 把模型数组赋值给value value = arrM; } } // KVC 字典转模型 if (value) { [objc setValue:value forKey:key]; } } return objc; } @end
- (void)initView { [super initView]; NSDictionary *person = @{@"name":@"Chen", @"sex": @"男"}; NSDictionary *dict = @{@"coderID":@"99", @"nickName": @"rattanchen", @"phoneNumber": @"138****8888", @"person" : person}; NSArray *addarr = @[dict ,dict, dict]; NSMutableDictionary *mudict = [NSMutableDictionary dictionaryWithDictionary:dict]; [mudict setObject:person forKey:@"person"]; for (NSDictionary *item in addarr) { ZMCoding *coding = [ZMCoding modelWithDict:item]; [self.dataArray addObject:coding]; } if (self.dataArray.count) { self.testLabelText = @"字典转模型成功, 点击查看对应的值"; } } - (void)buttonClick:(UIButton *)sender { ZMCoding *coding = self.dataArray.firstObject; switch (sender.tag) { case 0: self.testLabelText = coding.coderID; break; case 1: self.testLabelText = coding.nickName; break; case 2: self.testLabelText = coding.phoneNumber; break; case 3: self.testLabelText = coding.person.name; break; case 4: self.testLabelText = coding.person.sex; break; default: break; } } - (NSArray *)buttonTitleArray { return @[@"ID", @"昵称", @"手机号", @"姓名", @"性别"]; } - (NSMutableArray *)dataArray { if (!_dataArray) { _dataArray = [NSMutableArray array]; } return _dataArray; } @end
8、归档解档
#import <Foundation/Foundation.h> #import "NSObject+ZMKeyValues.h" #import "ZMPerson.h" @interface ZMCoding : NSObject<NSCoding> @property (nonatomic, strong) ZMPerson *person; @property (nonatomic, copy) NSString *coderID; @property (nonatomic, copy) NSString *nickName; @property (nonatomic, copy) NSString *phoneNumber; @end
#import <objc/runtime.h> @implementation ZMCoding - (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int count = 0; // 获取类中所有属性 Ivar *ivars = class_copyIvarList(self.class, &count); // 遍历属性 for (int i = 0; i < count; i ++) { // 取出 i 位置对应的属性 Ivar ivar = ivars[i]; // 查看属性 const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; // 利用 KVC 进行取值,根据属性名称获取对应的值 id value = [self valueForKey:key]; [aCoder encodeObject:value forKey:key]; } free(ivars); } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { unsigned int count = 0; // 获取类中所有属性 Ivar *ivars = class_copyIvarList(self.class, &count); // 遍历属性 for (int i = 0; i < count; i ++) { // 取出 i 位置对应的属性 Ivar ivar = ivars[i]; // 查看属性 const char *name = ivar_getName(ivar); NSString *key = [NSString stringWithUTF8String:name]; // 进行解档取值 id value = [aDecoder decodeObjectForKey:key]; // 利用 KVC 对属性赋值 [self setValue:value forKey:key]; } } return self; } @end
- (void)initView { [super initView]; ZMCoding *coding = [ZMCoding new]; coding.coderID = @"99"; coding.nickName = @"rattanchen"; coding.phoneNumber = @"138****8888"; NSString *path = [ZMTools getDomainsPathWithFile:@"123"]; BOOL isSuc = [NSKeyedArchiver archiveRootObject:coding toFile:path]; if (isSuc == YES) { self.testLabelText = @"归档成功, 点击按钮取出模型中对应的值"; }else{ self.testLabelText = @"归档失败"; } } - (void)buttonClick:(UIButton *)sender { NSString *path = [ZMTools getDomainsPathWithFile:@"123"]; ZMCoding *coding = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; coding.coderID = @"999"; if (sender.tag == 0) { self.testLabelText = coding.coderID; } else if (sender.tag == 1) { self.testLabelText = coding.nickName; } else { self.testLabelText = coding.phoneNumber; } } - (NSArray *)buttonTitleArray { return @[@"ID", @"昵称", @"手机号"]; }