iOS Runtime实用详解(一)

2017-07-28  本文已影响65人  handsome5

基本概念

     //Person *p  = [[Person alloc] init]; 底层发送是这样
    Person *p  = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
    p = objc_msgSend(p, sel_registerName("init"));
    //把p替换下,
    Person *p = objc_msgSend(objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    // p对象发送方法
   objc_msgSend(p, sel_registerName("eat"));

接下来我们去验证下oc底层发送,如何验证请看下面


Snip20170728_18.png

关掉工程 command + S,找到消息验证目录,输入clang -rewrite-objc main.m 生成main.cpp


Snip20170728_19.png
最后打开main.cpp 把main.cpp拖到底部,如图
Snip20170728_21.png

看到的Person跟我们发送的完全一样,其实从这里我们可以发现objc_msgSend的函数调用过程:

第一步:检测这个selector是不是要忽略的;
第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉
第三步:
    调用实例方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,
  如果找不到则会通过class的super_class指针找到父类的类对象结构体,然后从methodLists中查找该方法,
如果仍找不到则继续通过super_class向上查找知道metaclass;
    调用类方法时,首先通过自己的isa指针找到metaclass,并从其中methodLists中查找该类方法,
如果找不到则会通过metaclass的super_class指针找到父类的metaclass对象结构体
第四步:如果前三步都找不到方法则进入动态方法解析
消息动态解析具体流程
第一步:通过resolveInstanceMethod:方法决定是否动态添加方法。如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No,则进入下一步;
第二步:这步会进入forwardingTargetForSelector:方法,用于指定备选对象响应这个selector,不能指定为self。如果返回某个对象则会调用对象的方法,结束。如果返回nil,则进入第三步;
第三步:这步我们要通过methodSignatureForSelector:方法签名,如果返回nil,则消息无法处理。如果返回methodSignature,则进入下一步;
第四步:这步调用forwardInvocation:方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector方法,若我们没有实现这个方法,那么就会crash。
    //获取类方法
    class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
     //获取实例方法
    class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
   案例实现:
#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

+ (void)load {
   
    Method CustomString = class_getClassMethod([NSURL class],@selector(TestURLWithString:));
    Method URLString = class_getClassMethod([NSURL class], @selector(URLWithString:));
    method_exchangeImplementations(URLString, CustomString);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com/中文"];
}
@end

#import "NSURL+url.h"
#import <objc/runtime.h>

@implementation NSURL (url)
#pragma mark - 这里运用了runtime
+ (instancetype)TestURLWithString:(NSString *)str
{
    NSURL *url = [NSURL TestURLWithString:str];
    if (!url) {
        NSLog(@"url为空");
    }
    return url;
}
@end
#import "Person.h"
#import <objc/runtime.h>

@implementation Person

//当这个类被调用类一个没有实现的类方法!就会来这里
//+ (BOOL)resolveClassMethod:(SEL)sel
//{
//    
//}

//当这个类被调用类一个没有实现的对象方法!就会来这里
+ (BOOL)resolveInstanceMethod:(SEL)sel
{

    NSLog(@"%@",NSStringFromSelector(sel));
    //动态的添加方法
    if (sel == sel_registerName("eat")) {
        
        /**
         class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, const char *types)
         1.Class 类类型
         2.SEL name 方法编号
         3.IMP implementation的简称,方法的实现,就是一个函数指针,指向一个实现
         4.type 返回类型 command + shift + 0 快速打开文档,搜索class_addMethod里面Parameters types 可以看到一张表,这里v表示无返回值(void)。
         */
        class_addMethod([Person class], sel, (IMP)eat, "v");
    }
    return [super resolveInstanceMethod:sel];
}
//记住任何一个函数都有两个参数,一个self,一个cmd,两个隐试参数
void eat(id self, SEL _cmd) {
    NSLog(@"哥们调用了%@的%@方法",self,NSStringFromSelector(_cmd));
    NSLog(@"哥们吃了");
}

////记住任何一个函数都有两个参数,一个self,一个cmd,两个隐试参数,带了个参数
//void eat(id self, SEL _cmd,id objc) {
//    
//    NSLog(@"哥们调用了%@的%@方法%@",self,NSStringFromSelector(_cmd),objc);
//
//    
//}

@end

#import "ViewController.h"
#import <objc/runtime.h>
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *p  = [[Person alloc] init];
    
    [p performSelector:@selector(eat)];
    
    //带参数的测试 懒加载方法
    //[p performSelector:@selector(eat) withObject:@"测试"];
}
@end
// KVO的底层实现原理
// KVO监听的是setter方法
#import "ViewController.h"
#import "Person.h"
#import "NSObject+KVO.h"

@interface ViewController ()

@property (nonatomic, strong)Person *p;

@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   
   Person *p = [[Person alloc] init];
   
   //KVO的基本写法
   [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    _p = p;
   
   //自定义 kvo底层
   [p test_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
   NSLog(@"观察到了%@的%@属性的变化了%@",object,keyPath,change);

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
   // KVO监听的是setter方法,
   //一个类的类型只能通过isa指针来看
   /*如果name是成员变量的话,KVO就不能监听了*/
   _p.name = @"test";//简单的说底部实现,就是利用runtime创建了个子类对象 
}
@end

#import "NSObject+KVO.h"
#import <objc/message.h>

@implementation NSObject (KVO)

- (void)test_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
   /**
    1.自定义NSTestKVO子类
    2.重写set方法,在内部恢复父类的做法,通知观察者
    3.修改self的isa指针!!恢复指向自定义的NSTestKVO子类!
    */
   
   //动态生成一个类
   NSString *oldClassName = NSStringFromClass([self class]);
   NSString *newClassName = [@"NSTestKVO" stringByAppendingString:oldClassName];
   const char *newName = [newClassName UTF8String];
   
   /**定义一个类
    
    1.继承那个类
    2.类的名称
    */
   
   Class MyClass = objc_allocateClassPair([self class], newName, 0);
   
   //重写setName方法,这里写死了的
   class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");
   
   //注册该类
   objc_registerClassPair(MyClass);
   
   //修改self的isa指针,指向自定义的子类MyClass
   object_setClass(self, MyClass);
   
   //将观察者保存到当前对象
   objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


void setName(id self, SEL _cmd,NSString *newName){
   
   //保存当前类型
   Class class = [self class];

   //改变isa指针
   object_setClass(self, class_getSuperclass(class));
   
   //调用父类的set方法
   objc_msgSend(self, @selector(setName:),newName);
   
   
   //拿出观察者
   id objc = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
   
   //通知观察者, 发送消息,注意设置msg
   objc_msgSend(objc,@selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,nil,nil);
   
   
   //改回子类类型,否则没法继续监听
   object_setClass(self, class);
}
@end
{
  "age" : "<null>",
  "money" : "1000",
  "cat" : {
    "name" : "Persia",
    "price" : "500",
    "fish" : {
      "name" : "鱼",
      "weight" : 50
    }
  },

 "students" : [
                  {
                  "name1" : "苦逼的码农",
                  "price1" : 20.8,
                  "publisher1" : "清华大学出版社"
                  }
                 ],
    
  "books" : [
    {
      "name" : "C语言程序设计",
      "price" : 20.8,
      "publisher" : "清华大学出版社",
      "bookUsers" : [
                     {
                   "name1" : "苦逼的码农",
                   "price1" : 20.8,
                   "publisher1" : "清华大学出版社"
                     }
                  ]
 
    },
    {
      "name" : "乔布斯传",
      "price" : 50.2,
      "publisher" : "苹果出版社",
      "bookUsers" : [
                     {
                     "name1" : "乔布斯传",
                     "price1" : 50.2,
                     "publisher1" : "苹果出版社"
                     }
                    ]
    }
  ],
  "name" : "Tom",
  "height" : "181"

}

#import <Foundation/Foundation.h>

@interface NSObject (JSONExtension)

- (void)setDict:(NSDictionary *)dict;
+ (instancetype )objectWithDict:(NSDictionary *)dict;
// 告诉数组中都是什么类型的模型对象
-(NSString *)arrayObjectClassWithKey:(NSString *)keyword;

@end

#import "NSObject+JSONExtension.h"
#import <objc/runtime.h>

@implementation NSObject (JSONExtension)

- (void)setDict:(NSDictionary *)dict {
    
    Class c = self.class;
    while (c &&c != [NSObject class]) {
        
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList(c, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            
            // 成员变量名转为属性名(去掉下划线 _ )
            key = [key substringFromIndex:1];
            // 取出字典的值
            id value = dict[key];
            // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
            if (value == nil || [value isEqual:[NSNull class]]) continue;
            
            // 获得成员变量的类型
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            
            // 如果属性是对象类型
            NSRange range = [type rangeOfString:@"@"];
            if (range.location != NSNotFound) {
                // 那么截取对象的名字(比如@"Dog",截取为Dog)
                type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
                

                
                // 排除系统的对象类型
                if (![type hasPrefix:@"NS"]) {
                    // 将对象名转换为对象的类型,将新的对象字典转模型(递归)
                    Class class = NSClassFromString(type);
                    value = [class objectWithDict:value];
                    
                }else if ([type isEqualToString:@"NSArray"]) {
                    
                    // 如果是数组类型,将数组中的每个模型进行字典转模型,先创建一个临时数组存放模型
                    NSArray *array = (NSArray *)value;
                    
                    NSLog(@"key====:%@",key);
                    
                    NSMutableArray *mArray = [NSMutableArray array];
                    
                    // 获取到每个模型的类型
                    id class ;
                    if ([self respondsToSelector:@selector(arrayObjectClassWithKey:)]) {
                        
                        NSString *classStr = [self arrayObjectClassWithKey:key];
                        class = NSClassFromString(classStr);
                    }
                   
                    else {
                        
                        NSLog(@"数组内模型是未知类型");
                        return;
                    }
                    // 将数组中的所有模型进行字典转模型
                    for (int i = 0; i < array.count; i++) {
                        
                        
                        [mArray addObject:[class objectWithDict:value[i]]];
                    }
                    
                    value = mArray;
                }
            }
            
            // 将字典中的值设置到模型上
            [self setValue:value forKeyPath:key];
        }
        free(ivars);
        c = [c superclass];
    }
}
+ (instancetype )objectWithDict:(NSDictionary *)dict {
    NSObject *obj = [[self alloc]init];
    [obj setDict:dict];
    return obj;
}
@end

#import "ViewController.h"
#import "Person.h"
#import "User.h"
#import "NSObject+JSONExtension.h"
#import "Book.h"
#import "BookUsers.h"
#import "Student.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self json];
}

/// 字典转模型demo
- (void)json {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"Model2.json" ofType:nil];
    NSData *jsonData = [NSData dataWithContentsOfFile:path];
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
    
    User *user = [User objectWithDict:json];
    Book *book= user.books[0];
    Student *englistBook = user.students[0];

    //NSLog(@"%@",book.name);
}

model 

#import <Foundation/Foundation.h>
#import "Cat.h"

@interface User : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) double height;
@property (nonatomic,assign) int age;

// 属性是一个对象
@property (nonatomic,strong) Cat*cat;
// 属性是一个数组
@property (nonatomic,strong) NSArray *books;

// 属性是一个数组
@property (nonatomic,strong) NSArray *students;

@end
#import "User.h"



@implementation User

 //返回数组中都是什么类型的模型对象
//- (NSString *)arrayObjectClass {
//   return @"Book";
//}

- (NSString *)arrayObjectClassWithKey:(NSString *)key {
    if ([key isEqualToString:@"books"]) {
        return @"Book";
    }else
    {
        return @"Student";
    }
}
@end

#cat model
#import <Foundation/Foundation.h>
#import "Fish.h"

@interface Cat : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) double price;
// 属性是一个对象
@property (nonatomic,strong) Fish *fish;

@end

#import "Cat.h"

@implementation Cat

@end

#Book model
#import <Foundation/Foundation.h>
#import "Book.h"

@interface Book : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) double price;
@property (nonatomic,copy) NSString *publisher;
@property (nonatomic,strong)NSArray *bookUsers;
@end
#import "Book.h"

@implementation Book
//返回数组中都是什么类型的模型对象
- (NSString *)arrayObjectClass {
    return @"BookUsers";
}
@end

#BookUsers model
#import <Foundation/Foundation.h>

@interface BookUsers : NSObject


@property (nonatomic,copy) NSString *name1;
@property (nonatomic,assign) double price1;
@property (nonatomic,copy) NSString *publisher1;
@end

#import "BookUsers.h"

@implementation BookUsers

@end

本文参考文献

(Runtime基本原理及Demo)http://www.jianshu.com/p/e28f6b279f5f#
([iOS] runtime 的使用场景--实战篇)http://www.jianshu.com/p/07b6c4a40a90

上一篇下一篇

猜你喜欢

热点阅读