Runtime

2017-08-15  本文已影响14人  AlanGe

一、runtime简介

二、runtime作用

1.发送消息

//  Person.h
//  Runtime(消息机制)
#import <Foundation/Foundation.h>

@interface Person : NSObject
+ (void)eat;
- (void)run:(int)age;
- (void)eat;
@end
//  Person.m
//  Runtime(消息机制)

#import "Person.h"

@implementation Person

- (void)eat

{
    NSLog(@"对象方法-吃东西");
}

+ (void)eat

{
    NSLog(@"类方法-吃东西");
}

- (void)run:(int)age

{
    NSLog(@"%d",age);
}
@end

//  ViewController.m
//  Runtime(消息机制)

#import "ViewController.h"

#import "Person.h"

// 运行时使用运行时的步骤
// 第一步:导入<objc/message.h>
// 第二步:Build Setting -> 搜索msg -> 设置属性为No
#import <objc/message.h>

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建person对象
    Person *p = [[Person alloc] init];
    
    // 调用对象方法
    [p eat];
    
    // OC:运行时机制,消息机制是运行时机制最重要的机制
    // 消息机制:任何方法调用,本质都是发送消息
    
    // SEL:方法编号,根据方法编号就可以找到对应方法实现
    // performSelector:动态添加方法
    [p performSelector:@selector(eat)];
    
    // 运行时,发送消息,谁做事情就那谁
    // xcode5之后,苹果不建议使用底层方法
    // xcode5之后,使用运行时.
    
    // 让p发送消息
    // 不带参数
    objc_msgSend(p, @selector(eat));
    // 带参数
    objc_msgSend(p, @selector(run:),10);
    
    // 调用类方法的方式:两种
    // 第一种通过类名调用,类名调用类方法,本质类名转换成类对象
    [Person eat];
    // 第二种通过类对象调用
    [[Person class] eat];
    
    // 获取类对象
    Class personClass = [Person class];
    
    [personClass performSelector:@selector(eat)];
    
    // 运行时
    // 用类名调用类方法,底层会自动把类名转换成类对象调用
    // 本质:让类对象发送消息
    objc_msgSend(personClass, @selector(eat));
}

@end

2.交换方法

//  UIImage+Image.h
//  Runtime(交换方法)

#import <UIKit/UIKit.h>

@interface UIImage (Image)
+ (__kindof UIImage *)ge_imageNamed:(NSString *)imageName;
@end
//  UIImage+Image.m
//  Runtime(交换方法)

#import "UIImage+Image.h"

#import <objc/message.h>

@implementation UIImage (Image)

// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
// 在分类里面不能调用super,分类木有父类
//+ (UIImage *)imageNamed:(NSString *)name
//{
//    [super im]
//}

// 利用运行时
// 先写一个其他方法,实现这个功能
// 既能加载图片又能打印
+ (UIImage *)ge_imageNamed:(NSString *)imageName
{
    // 1.加载图片
    UIImage *image = [UIImage ge_imageNamed:imageName];
    
    // 2.判断功能
    if (image == nil) {
        NSLog(@"加载image为空");
    }

    return image;
}

// 加载这个分类的时候调用
+ (void)load
{
    // 交换方法实现,方法都是定义在类里面
    // class_getMethodImplementation:获取方法实现
    // class_getInstanceMethod:获取对象
    // class_getClassMethod:获取类方法
    // IMP:方法实现
    
    // imageNamed
    // Class:获取哪个类方法
    // SEL:获取方法编号,根据SEL就能去对应的类找方法
    Method imageNameMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
    
    // ge_imageNamed
    Method ge_imageNamedMethod = class_getClassMethod([UIImage class], @selector(ge_imageNamed:));
    
    // 交换方法实现
    method_exchangeImplementations(imageNameMethod, ge_imageNamedMethod);
}
@end
//  ViewController.m
//  Runtime(交换方法)

#import "ViewController.h"
//#import "UIImage+Image.h"

@interface ViewController ()

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 需求:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
    // 步骤一:先搞个分类,定义一个能加载图片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
    // 步骤二:交换imageNamed和imageWithName的实现,就能调用imageWithName,间接调用imageWithName的实现。
    
//    UIImage *image = [UIImage imageNamed:@"123"];
    // 不好的地方
    // 1.每次使用,都需要导入头文件
    // 2.当一个项目开发太久,使用这个方式不靠谱
    
    // imageNamed:
    // 实现方法:底层调用ge_imageNamed
    
    // 本质:交换两个方法的实现imageNamed和ge_imageNamed方法
    // 调用imageNamed其实就是调用ge_imageNamed
    
    // 系统imageNamed加载图片,并不知道图片是否加载成功
    // 交换以后调用imageNamed的时候,就知道图片是否加载
    
    [UIImage imageNamed:@"123"];
}

@end

3.动态添加方法

//  Person.h
//  Runtime(动态添加方法)

#import <Foundation/Foundation.h>

@interface Person : NSObject

@end

//  Person.m
//  Runtime(动态添加方法)

#import "Person.h"

#import <objc/message.h>

@implementation Person

// 动态添加方法,首先实现这个resolveInstanceMethod
// resolveInstanceMethod调用:当调用了没有实现的方法没有实现就会调用resolveInstanceMethod
// resolveInstanceMethod作用:就知道哪些方法没有实现,从而动态添加方法
// sel:没有实现方法

// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
//    NSLog(@"%@",NSStringFromSelector(sel));
    
    // 动态添加eat方法
    if (sel == @selector(eat:)) {
        
        // 第一个参数:cls:给哪个类添加方法
        // 第二个参数:SEL:添加方法的方法编号是什么
        // 第三个参数:IMP:方法实现,函数入口,函数名
        // 第四个参数:types:方法类型
            // v 表示 void
            // @ 表示对象
            // : 表示SEL
        class_addMethod(self, sel, (IMP)aaaa, "v@:@");
        
        // 处理完
        return YES;
    }
    
    // 返回系统的方法,因为上面改了,尽量不要修改系统的方法
    return [super resolveInstanceMethod:sel];
}

// 默认一个方法都有两个参数,self,_cmd,属于隐式参数
// self:方法调用者
// _cmd:调用方法的编号

// 定义函数
// 没有返回值,参数(id,SEL)
// void(id,SEL)
void aaaa(id self, SEL _cmd, id param1)
{  
    NSLog(@"调用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);
}
@end
//  ViewController.m
//  Runtime(动态添加方法)

#import "ViewController.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // performSelector:动态添加方法
    Person *p = [[Person alloc] init];
    
    // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
    // 通过运行时后,动态添加方法就不会报错
    
    // 动态添加方法
    // 不带参数
    [p performSelector:@selector(eat)];
    // 带参数
    [p performSelector:@selector(eat:) withObject:@111];
}
@end

4.给分类添加属性

//  NSObject+Objc.h
//  Runtime(分类添加属性)

#import <Foundation/Foundation.h>

@interface NSObject (Objc)

// @property:只会生成set方法的声明,不会实现
@property (nonatomic, strong) NSString *name;

@end
//  NSObject+Objc.m
//  Runtime(分类添加属性)

#import "NSObject+Objc.h"

#import <objc/message.h>

@implementation NSObject (Objc)

// 定义关联的key
//static NSString *_name;

// set方法
- (void)setName:(NSString *)name {
    // 添加属性,跟对象
    // 给某个对象产生关联,添加属性
    // 第一个参数:object:给哪个对象添加属性
    // 第二个参数:key:属性名,根据key去获取关联的对象 ,void * == id
    // 第三个参数:value:关联的值
    // 第四个参数:policy:缓存策略
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// get方法
- (NSString *)name {
    // 根据关联的key,获取关联的值。
    return objc_getAssociatedObject(self, @"name");
}
@end
//  ViewController.m
//  Runtime(分类添加属性)

#import "ViewController.h"
#import "NSObject+Objc.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"123";
    NSLog(@"%@",objc.name);
}
@end

5.字典转模型

//  NSObject+Property.h
//  自动生成属性代码

//  通过解析字典自动生成属性代码

#import <Foundation/Foundation.h>

@interface NSObject (Property)

///  通过解析字典自动生成属性代码
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict;

@end
//  NSObject+Property.m
//  自动生成属性代码

//  通过解析字典自动生成属性代码

#import "NSObject+Property.h"

@implementation NSObject (Property)

///  通过解析字典自动生成属性代码
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict {
    // 拼接属性字符串代码
    NSMutableString *strM = [NSMutableString string];
    
    /*********************** 方法1 ***************************/
    // 遍历字典
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull propertyName, id  _Nonnull value, BOOL * _Nonnull stop) {
//        NSLog(@"%@ %@",propertyName,[value class]);
        
        // 类型经常变,抽出来
        NSString *code;
        
        if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, copy) NSString *%@;",propertyName]
            ;
        }else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName]
            ;
        }else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",propertyName]
            ;
        }else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName]
            ;
        }else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName]
            ;
        }
        // 每生成属性字符串,就自动换行。
        [strM appendFormat:@"\n%@\n",code];
    }];
    
    /*********************** 方法2 ***************************/
    // 1.遍历字典,把字典中的所有key取出来,生成对应的属性代码
//    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
//        // 类型经常变,抽出来
//        NSString *type;
//        if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
//            type = @"NSString";
//        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
//            type = @"NSArray";
//        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
//            type = @"int";
//        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
//            type = @"NSDictionary";
//        }
//        // 属性字符串
//        NSString *str;
//        if ([type containsString:@"NS"]) {
//            str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
//        }else{
//            str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
//        }
//        // 每生成属性字符串,就自动换行。
//        [strM appendFormat:@"\n%@\n",str];
//    }];
    
    // 把拼接好的字符串打印出来,就好了。
    NSLog(@"strM = %@",strM);
}

@end
//  ViewController.m
//  Runtime(自动生成属性代码)

#import "ViewController.h"

#import "NSObject+Property.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 解析Plist
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSArray *dictArr = dict[@"statuses"];
    
    // 设计模型属性代码,生成打印后赋值粘贴到模型即可
    [NSObject createPropertyCodeWithDict:dictArr[0]];
}
@end
//  Status.m
//  字典转模型KVC实现

#import "Status.h"

@implementation Status

// 字典转模型 - 模型的属性名跟字典一一对应
+ (Status *)statusWithDict:(NSDictionary *)dict {
    Status *status = [[self alloc] init];
    
    // KVC
    [status setValuesForKeysWithDictionary:dict];
    
    return status;
}
@end
//  Status.m
// 转模型KVC实现

#import "Status.h"

@implementation Status

// 字典转模型 - 模型的属性名跟字典一一对应
+ (Status *)statusWithDict:(NSDictionary *)dict
{
    Status *status = [[self alloc] init];
    
    // KVC
    [status setValuesForKeysWithDictionary:dict];
    
    return status;
}

// 解决KVC报错
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"id"]) {
        _ID = [value integerValue];
    }
    // key:没有找到key
    // value:没有找到key对应的值
    NSLog(@"没有找到key = %@,没有找到key对应的值 = %@",key,value);
}

@end
//  ViewController.m
//  Runtime(字典转模型KVC实现)

#import "ViewController.h"
#import "NSObject+Property.h"
#import "Status.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 解析Plist
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSArray *dictArr = dict[@"statuses"];
    
    // 设计模型属性代码,生成打印后赋值粘贴到模型即可
//    [NSObject createPropertyCodeWithDict:dictArr[0]];
    
    NSMutableArray *statuses = [NSMutableArray array];
    
    for (NSDictionary *dict in dictArr) {
        // 字典转模型
        Status *status = [Status statusWithDict:dict];
        
        [statuses addObject:status];
    }
    
    NSLog(@"%@",statuses);
}

@end
//  User.h
//  Runtime(字典转模型)

#import <Foundation/Foundation.h>

@interface User : NSObject

@property (nonatomic, copy) NSString *profile_image_url;

@property (nonatomic, assign) BOOL vip;

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int mbrank;

@property (nonatomic, assign) int mbtype;

@end
//  User.m
//  Runtime(字典转模型)

#import "User.h"

@implementation User

@end
//  Status.h
//  Runtime(字典转模型)

#import <Foundation/Foundation.h>

@class User;

@interface Status : NSObject

// 写一段程序自动生成属性代码

@property (nonatomic, assign) NSInteger ID;
// 解析字典自动生成属性代码
@property (nonatomic, strong) NSString *source;

@property (nonatomic, assign) NSInteger reposts_count;

@property (nonatomic, strong) NSArray *pic_urls;

@property (nonatomic, strong) NSString *created_at;

@property (nonatomic, assign) int attitudes_count;

@property (nonatomic, strong) NSString *idstr;

@property (nonatomic, strong) NSString *text;

@property (nonatomic, assign) int comments_count;

@property (nonatomic, strong) User *user;

@property (nonatomic, strong) NSDictionary *retweeted_status;

@end
//  Status.m
//  Runtime(字典转模型)

#import "Status.h"

@implementation Status

@end
//  NSObject+Model.h
//  Runtime(字典转模型)

//  Runtime字典转模型分类

#import <Foundation/Foundation.h>

@interface NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict;

@end
//  NSObject+Model.m
//  Runtime(字典转模型)

//  Runtime字典转模型分类

#import "NSObject+Model.h"

#import <objc/message.h>
/*
 Ivar ivar1;
 Ivar ivar2;
 Ivar ivar3;
 Ivar a[] = {ivar3,ivar1,ivar2};
 Ivar *ivar = &a;
 */

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict{
    
    // 1.创建对应类的对象
    id objc = [[self alloc] init];
    
    // 2.利用runtime给对象中的成员属性赋值
    
    // runtime:遍历模型中所有成员属性,去字典中查找
    // 属性定义在哪,定义在类,类里面有个属性列表(数组)
    
    // 遍历模型所有成员属性
    // ivar:成员属性
    // class_copyIvarList:把成员属性列表复制一份给你
    // Ivar *:指向Ivar指针
    // Ivar *:指向一个成员变量数组
    // class:获取哪个类的成员属性列表
    // count:成员属性总数
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0 ; i < count; i++) {
        // 获取成员属性
        Ivar ivar = ivarList[i];
        
        // 获取成员名
        NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
    
        // 获取key
        NSString *key = [propertyName substringFromIndex:1];
        
        // user value:字典
        // 获取字典的value
        id value = dict[key];
        // 给模型的属性赋值
        // value:字典的值
        // key:属性名
        
        // 获取成员属性类型
        NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        // user:NSDictionary
        // 二级转换
        // 值是字典,成员属性的类型不是字典,才需要转换成模型
        if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
            
            // 字典转模型
            // 获取模型的类对象,调用modelWithDict
            // 模型的类名已知,就是成员属性的类型
            
            // 需要字典转换成模型
            // 转换成哪个类型
//            NSLog(@"转换成哪个类型 = %@",propertyType);
            
            /*********** *********** 字符串截取 *********** ***********/
            // 字符串截取
            // 生成的是这种@"@\"User\"" 类型 -> @"User"  在OC字符串中 \" -> ",\是转义的意思,不占用字符
            // @"@\"User\"" User
            // \":算一个字符
            NSRange range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringFromIndex:range.location + range.length];
            // User\"";
            // 裁剪到哪个角标,不包括当前角标
            range = [propertyType rangeOfString:@"\""];
            propertyType = [propertyType substringToIndex:range.location];
            /*********** *********** 字符串截取 *********** ***********/
            
            // 获取需要转换类的类对象
            // 根据字符串类名生成类对象
            Class modelClass =  NSClassFromString(propertyType);
            
            // 有对应的模型才需要转
            if (modelClass) {
                // 字典转模型
                value =  [modelClass modelWithDict:value];
            }
        }
        
//        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
//        // 判断值是否是数组
//        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;
//            }
//        }
        
        // 有值,才需要给模型的属性赋值
        if (value) {
            // KVC赋值:不能传空
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}
@end
//  ViewController.m
//  Runtime(字典转模型)

#import "ViewController.h"
#import "Status.h"
#import "User.h"
#import "NSObject+Model.h"

@interface ViewController ()

@end

@implementation ViewController

/*
 KVC:遍历字典中所有key,去模型中查找有没有对应的属性名,没有对应的key就会报错
 runtime:遍历模型中所有属性名,去字典中查找,这样不会像KVC那样报错
 */

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 解析Plist
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSArray *dictArr = dict[@"statuses"];
        
    NSMutableArray *statuses = [NSMutableArray array];
    // 遍历字典数组
    for (NSDictionary *dict in dictArr) {
        //  Runtime(字典转模型)
        Status *status = [Status modelWithDict:dict];
        [statuses addObject:status];
        User *user = status.user;
        NSString *name = user.name;
        NSLog(@"name = %@",name);
    }
    
//    NSLog(@"%@",statuses);
}
@end
上一篇下一篇

猜你喜欢

热点阅读