概述
Objective-C Runtime是一个运行时库,它为Objective-C语言的动态属性提供支持,所有Objective-C应用程序都链接到该库。Objective-C运行时库支持功能是在共享库中实现。
详见:苹果开发者文档
废话少说,直接上代码!
应用举例
1.动态创建一个类,并创建成员变量和方法
//自定义方法
void PlayMethod(id self, SEL _cmd, id some)
{
NSLog(@"%@岁的%@玩:%@", object_getIvar(self, class_getInstanceVariable([self class], "_age")),[self valueForKey:@"name"],some);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//动态创建对象 ,继承于NSObject
Class Animal=objc_allocateClassPair([NSObject class], "Annial", 0);
// 为该类添加NSString *_name成员变量
class_addIvar(Animal, "_name", sizeof(NSString*), sizeof(NSString*), @encode(NSString*));
class_addIvar(Animal, "_age", sizeof(int), sizeof(int), @encode(int));
// 注册方法名为play的方法
SEL s=sel_registerName("_play:");
// 为该类增加名为play的方法
class_addMethod(Animal, s, (IMP)PlayMethod, "�v@:@");
// 注册该类
objc_registerClassPair(Animal);
//创建一个实例
id catAnimal=[[Animal alloc]init];
// KVC 动态改变 对象peopleInstance 中的实例变量
[catAnimal setValue:@"小猫" forKey:@"name"];
// 从类中获取成员变量Ivar
Ivar ageIvar=class_getInstanceVariable(Animal, "_age");
object_setIvar(catAnimal, ageIvar, @2);
((void (*)(id, SEL, id))objc_msgSend)(catAnimal, s, @"篮球");
//因此这里要先销毁实例对象后才能销毁类;
catAnimal = nil;
// 销毁类
objc_disposeClassPair(Animal);
// insert code here...
}
return 0;
}
打印可得到
2017-07-20 15:15:32.115435+0800 RuntimeTest1[1721:166720] 2岁的小猫玩:篮球
2.获取所有成员变量
.h文件
#import <Foundation/Foundation.h>
@interface Annimal : NSObject
{
NSString *_color;
NSString *_type;
NSString *_test;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
- (NSDictionary *)getallProperties;
- (NSDictionary *)getallIvars;
- (NSDictionary *)getallMethods;
@end
.m文件
#import "Annimal.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation Annimal
//获取所有属性
- (NSDictionary *)getallProperties
{
unsigned int count = 0;
// 获取类的所有属性,如果没有属性count就为0
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableDictionary *resultDict = [@{} mutableCopy];
for (NSUInteger i = 0; i < count; i ++) {
// 获取属性的名称和值
const char *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id propertyValue = [self valueForKey:name];
if (propertyValue) {
resultDict[name] = propertyValue;
} else {
resultDict[name] = @"字典value不能为nil";
}
}
// 这里properties是一个数组指针,我们需要使用free函数来释放内存。
free(properties);
return resultDict;
}
//获取所有成员变量
- (NSDictionary *)getallIvars
{
unsigned int count = 0;
NSMutableDictionary *resultDict = [@{} mutableCopy];
Ivar *ivars = class_copyIvarList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
const char *varName = ivar_getName(ivars[i]);
NSString *name = [NSString stringWithUTF8String:varName];
id varValue = [self valueForKey:name];
if (varValue) {
resultDict[name] = varValue;
} else {
resultDict[name] = @"字典的value不能为nil";
}
}
free(ivars);
return resultDict;
}
//获取所有方法
- (NSDictionary *)getallMethods
{
unsigned int count = 0;
NSMutableDictionary *resultDict = [@{} mutableCopy];
// 获取类的所有方法,如果没有方法count就为0
Method *methods = class_copyMethodList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
// 获取方法名称
SEL methodSEL = method_getName(methods[i]);
const char *methodName = sel_getName(methodSEL);
NSString *name = [NSString stringWithUTF8String:methodName];
// 获取方法的参数列表
int arguments = method_getNumberOfArguments(methods[i]);
resultDict[name] = @(arguments-2);
}
free(methods);
return resultDict;
}
@end
打印出来结果为
2017-07-20 15:28:11.357512+0800 RuntimeTest2[1767:178185] propertyName:block, propertyValue:<__NSGlobalBlock__: 0x100002110>
2017-07-20 15:28:11.357761+0800 RuntimeTest2[1767:178185] propertyName:name, propertyValue:狗
2017-07-20 15:28:11.357807+0800 RuntimeTest2[1767:178185] propertyName:age, propertyValue:2
2017-07-20 15:28:11.358441+0800 RuntimeTest2[1767:178185] ivarName:_type, ivarValue:哈士奇
2017-07-20 15:28:11.358488+0800 RuntimeTest2[1767:178185] ivarName:_color, ivarValue:白色
2017-07-20 15:28:11.358510+0800 RuntimeTest2[1767:178185] ivarName:_test, ivarValue:字典的value不能为nil
2017-07-20 15:28:11.358556+0800 RuntimeTest2[1767:178185] ivarName:_name, ivarValue:狗
2017-07-20 15:28:11.358602+0800 RuntimeTest2[1767:178185] ivarName:_age, ivarValue:2
2017-07-20 15:28:11.358726+0800 RuntimeTest2[1767:178185] methodName:setName:, argumentsCount:1
2017-07-20 15:28:11.358762+0800 RuntimeTest2[1767:178185] methodName:block, argumentsCount:0
2017-07-20 15:28:11.358822+0800 RuntimeTest2[1767:178185] methodName:getallProperties, argumentsCount:0
2017-07-20 15:28:11.358844+0800 RuntimeTest2[1767:178185] methodName:getallMethods, argumentsCount:0
2017-07-20 15:28:11.358857+0800 RuntimeTest2[1767:178185] methodName:getallIvars, argumentsCount:0
2017-07-20 15:28:11.358883+0800 RuntimeTest2[1767:178185] methodName:setBlock:, argumentsCount:1
2017-07-20 15:28:11.359527+0800 RuntimeTest2[1767:178185] methodName:age, argumentsCount:0
2017-07-20 15:28:11.359552+0800 RuntimeTest2[1767:178185] methodName:setAge:, argumentsCount:1
2017-07-20 15:28:11.359568+0800 RuntimeTest2[1767:178185] methodName:.cxx_destruct, argumentsCount:0
2017-07-20 15:28:11.359581+0800 RuntimeTest2[1767:178185] methodName:name, argumentsCount:0
Program ended with exit code: 0
这一看很方便吧,有什么用呢?不急,后面的数据与模型转换,归档解档就用上了,慢慢来!
3.动态生成方法
// 判断对象方法有没有实现
+(BOOL)resolveInstanceMethod:(SEL)sel
// 判断类方法有没有实现
+ (BOOL)resolveClassMethod:(SEL)sel
3, 进入苹果系统内部查看如何动态添加方法(这是官方文档中的方法)
// dynamicMethodIMP方法
// 动态添加这个dynamicMethodIMP方法
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
// 苹果内部的动态添加方法
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
#import <Foundation/Foundation.h>
@interface Student : NSObject
@end
void play(id self,SEL sel)
{
NSLog(@"%@,%@",self,NSStringFromSelector(sel));
}
//默认方法都有两个隐式参数
/*
2, C函数中两个隐式参数的意思 :
1, self:方法调用者
_cmd:当前调用方法编号
方法的隐式参数即: 没有暴露出来参数.
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel==NSSelectorFromString(@"play")) {
class_addMethod(self, sel,(IMP)play,"v@:");
}
return [super resolveInstanceMethod:sel];
}
Student *student=[Student new];
[student performSelector:@selector(play) withObject:nil afterDelay:1];
2017-07-20 15:39:21.737 BobRunTimeDemo[1997:188750] <Student: 0x60800000a4c0>,play
如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
4.关联对象
这个就用的比较多了,通过这个方法可以给类别添加属性,我们使用的上下拉刷新就是通过这种方式实现的。
具体不多说,直接上代码。
#import <UIKit/UIKit.h>
@interface UIViewController (addProperty)
@property(nonatomic,copy)NSString*name;
@end
#import "UIViewController+addProperty.h"
#import <objc/runtime.h>
static const char name_key;
-(NSString*)name{
return objc_getAssociatedObject(self, &name_key);
}
-(void)setName:(NSString *)name
{
objc_setAssociatedObject(self, &name_key, name, OBJC_ASSOCIATION_COPY);
}
self.name=@"888";
NSLog(@"%@",self.name);
2017-07-20 16:03:00.557 BobRunTimeDemo[2152:206973] 888
5.归档解档
通过获取所有属性的key和value值进行了归档和解档,避免了一个个属性去操作,那样太麻烦了。通过runtime可以一步到位。
-(void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int count=0;
Ivar *ivars=class_copyIvarList([self class], &count);
NSLog(@"%d",count);
for (NSUInteger i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
unsigned int count=0;
Ivar *ivars=class_copyIvarList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
return self;
}
#import "Animal.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Animal *anni=[[Animal alloc]init];
anni.name=@"狗";
anni.age=@2;
anni.color=@"黄色";
anni.type=@"阿拉斯加";
NSString *path=NSTemporaryDirectory();
path=[path stringByAppendingPathComponent:@"dog.dog"];
NSLog(@"%@",path);
[NSKeyedArchiver archiveRootObject:anni toFile:path];
Animal *newAnimal=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"%@-%@-%@-%@",newAnimal.name,newAnimal.age,newAnimal.type,newAnimal.color);
}
return 0;
}
2017-07-20 15:32:45.444895+0800 RuntimeTest3[1810:182292] /var/folders/5d/43zfbw550rvc1cjsq_0bt4h80000gn/T/dog.dog
2017-07-20 15:32:45.446739+0800 RuntimeTest3[1810:182292] 4
2017-07-20 15:32:45.448192+0800 RuntimeTest3[1810:182292] 狗-2-阿拉斯加-黄色
个人封装了一下,只要类继承下,就能调用方法进行归档和解档了,有兴趣的可以看一下,demo在后面。
6.方法交换
如果开发中,某些系统的方法不符合我们的要求,而我们有不想一个个作判断,runtime的方法交换就能实现。列举了2个例子:
1.图片为空时,加载默认图
实例一
#import "UIImageView+PlaceHolderImage.h"
#import <objc/runtime.h>
@implementation UIImageView (PlaceHolderImage)
+ (void)load
{
// 获取 UIImage 方法 -imageNamed: 的 Method
Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
// 获取 UIImage+PlaceHolderImage 方法 -replaced_imageNamed: 的 Method
Method replaced_imageNamedMethod = class_getClassMethod(self, @selector(replaced_imageNamed:));
// 将两个方法进行交换,现在如果调用 -imageNamed: 则调用的是下方 +replaced_imageNamed: 的实现
method_exchangeImplementations(imageNameMethod, replaced_imageNamedMethod);
}
+ (UIImage *)replaced_imageNamed:(NSString *)imageName
{
// 这里是递归调用吗?不是。因为现在调用 +replaced_imageNamed: 实现则是苹果框架内的 -imageNamed: 的实现。
UIImage *image = [[UIImage class] replaced_imageNamed:imageName];
if (!image)
{
image = [[UIImage class] replaced_imageNamed:@"placeImage"];
}
return image;
}
@end
2.数组可加入nil,而不崩溃
实例二
#import "NSMutableArray+safe.h"
#import <objc/runtime.h>
@implementation NSMutableArray (safe)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
id obj = [[self alloc] init];
[obj swizzleMethod:@selector(addObject:) withMethod:@selector(safeAddObject:)];
[obj swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(safeObjectAtIndex:)];
});
}
- (void)safeAddObject:(id)anObject
{
if (anObject) {
[self safeAddObject:anObject];
}else{
NSLog(@"obj is nil");
}
}
- (id)safeObjectAtIndex:(NSInteger)index
{
if(index<[self count]){
return [self safeObjectAtIndex:index];
}else{
NSLog(@"index is beyond bounds ");
}
return nil;
}
- (void)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
{
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, origSelector);
Method swizzledMethod = class_getInstanceMethod(class, newSelector);
BOOL didAddMethod = class_addMethod(class,
origSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
NSMutableArray *arr=[NSMutableArray array];
[arr addObject:nil];
2017-07-20 16:06:01.881 BobRunTimeDemo[2166:209480] obj is nil
哈哈体会到Runtime的强大了吧! 当然方法有很多,大家按需求去实现就好了。
7.JSON与Model互转
对于数据模型转换,大家都用过比如jsonmodel,MJExtension,等第三方库,如果自己写,怎么写呢?我这里就写了个简单的字典转换,复杂的大家去研究吧!只是提供这种思想而已,不重复造轮子了。
#import <Foundation/Foundation.h>
@interface Animal : NSObject
@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年龄
@property (nonatomic, copy) NSString *color; //颜色
@property (nonatomic, copy) NSString *type; //品种
// 生成model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
// 转换成字典
- (NSDictionary *)ModelToDictionary;
@end
#import "Animal.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation Animal
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
for (NSString *key in dictionary.allKeys) {
id value = dictionary[key];
SEL setter = [self propertySetterByKey:key];
if (setter) {
((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
}
}
}
return self;
}
- (NSDictionary *)ModelToDictionary
{
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
if (count != 0) {
NSMutableDictionary *resultDict = [@{} mutableCopy];
for (NSUInteger i = 0; i < count; i ++) {
const void *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
SEL getter = [self propertyGetterByKey:name];
if (getter) {
id value = ((id (*)(id, SEL))objc_msgSend)(self, getter);
if (value) {
resultDict[name] = value;
} else {
resultDict[name] = @"字典的value不能为nil";
}
}
}
free(properties);
return resultDict;
}
free(properties);
return nil;
}
#pragma mark - private methods
// 生成setter方法
- (SEL)propertySetterByKey:(NSString *)key
{
// 首字母大写
NSString *propertySetterName = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
SEL setter = NSSelectorFromString(propertySetterName);
if ([self respondsToSelector:setter]) {
return setter;
}
return nil;
}
// 生成getter方法
- (SEL)propertyGetterByKey:(NSString *)key
{
SEL getter = NSSelectorFromString(key);
if ([self respondsToSelector:getter]) {
return getter;
}
return nil;
}
@end
//使用
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSDictionary *dict = @{
@"name" : @"猫",
@"age" : @1,
@"color" : @"白色",
@"type" : @"波斯猫"
};
Animal *dog=[[Animal alloc]initWithDictionary:dict];
NSLog(@"%@,%@,%@,%@",dog.name,dog.age,dog.color,dog.type);
NSDictionary *newdict=[dog ModelToDictionary];
NSLog(@"%@",newdict);
}
return 0;
}
2017-07-20 16:27:09.165359+0800 RuntimeTest4[596:9548] 猫,1,白色,波斯猫
2017-07-20 16:27:09.165845+0800 RuntimeTest4[596:9548] {
age = 1;
color = "\U767d\U8272";
name = "\U732b";
type = "\U6ce2\U65af\U732b";
}
Program ended with exit code: 0
8.block回调
大家肯定用过BlocksKit,这个库呢,让代码非常简洁,有些原理也是用Runtime实现的,大家可以去研究下,下面就拿个例子来说吧!
1.手势回调
#import <UIKit/UIKit.h>
typedef void(^BobTapGestureBlock)(id gestureRecongnizer);
@interface UIGestureRecognizer (BobGestureBlock)
+(instancetype)TapgestureWithBlock:(BobTapGestureBlock)block;
@end
#import "UIGestureRecognizer+BobGestureBlock.h"
#import <objc/runtime.h>
static const char target_key;
@implementation UIGestureRecognizer (BobGestureBlock)
+(instancetype)TapgestureWithBlock:(BobTapGestureBlock)block
{
return [[self alloc]initWithActionBlock:block];
}
-(instancetype)initWithActionBlock:(BobTapGestureBlock)block
{
self=[self init];
if (block) {
objc_setAssociatedObject(self, &target_key, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
[self addTarget:self action:@selector(tapAction:)];
return self;
}
-(void)tapAction:(id)sender
{
BobTapGestureBlock block=objc_getAssociatedObject(self, &target_key);
if (block) {
block(sender);
}
}
@end
//直接一句代码就解决了
[self.view addGestureRecognizer:[UITapGestureRecognizer TapgestureWithBlock:^(id gestureRecongnizer) {
NSLog(@"点击");
}]];
2017-07-20 16:14:59.074 BobRunTimeDemo[2166:209480] 点击
终于写完了,写篇文章真不容易啊!