Runtime 你为何如此之屌?

Runtime 你为何如此之屌?_第1张图片

一、消息驱动机制

消息驱动机制: 运行的时候的一些机制,最主要的是消息机制。

消息驱动机制-动态调用过程 : 对于C语言,函数的调用在编译的时候会决定调用哪个函数。编译完成之后直接顺序执行,无任何二义性。
OC的函数调用为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错
Objective-C 语言不仅需要一个编译器,同时也需要一个运行时系统来执行编译好的代码。运行时系统扮演的角色类似于 Objective-C 语言的操作系统,Objective-C 基于该系统来工作。

在OC中的方法调用:
[object doSomeMethod];
其中object是一个对象,doSomeMethod是一个函数名称。对于这样一个简单的调用。在编译时RunTime会将上述代码转化成:
objc_msgSend(id object,@selector(doSomeMethod));

消息驱动机制-核心
发送消息:
objc_msgSend(receiver, selector) 消息不含有参数
objc_msgSend(receiver, selector, arg1, arg2, ...)消息含有参数

二、Runtime实现面向对象

一个面向对象的语言一定要实现类、属性、方法、继承、扩展。
一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。
通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。提高软件的重用性、灵活性和扩展性。
OC中的对象通过Runtime转化为对应的结构体:
在Runtime中定义了:objc_class和objc_object结构体 我们知道在OC代码中NSObject几乎是所有类的基类,它的核心就是结构体 下面这个结构体中包含一个实例变量链表 能够找到类中所有的实例变量 还需要有一个方法链表,找到这个类所有对应的方法,还要能找到它的父类,在这里我们可以看到Class是objc_class结构体指针的别名,而id是objc_object指针的别名。在objc_object这个结构体中包含了一个结构体指针
在objc_class这个结构体中主要包括以下:(它需要找到自己所有的实例变量,于是就有一个实例变量链表,需要找到自己所有的方法,于是就有了一个方法链表等)

typedef struct objc_class *Class; 
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY; 
};
/// A pointer to an instance of a class.
typedef struct objc_object *id; 
struct objc_class {
        struct objc_class super_class;  /*父类*/
        const char *name;               /*类名字*/
        long version;                   /*版本信息*/
        long info;                      /*类信息*/
        long instance_size;                      /*实例大小*/
        struct objc_ivar_list *ivars;          /*实例参数链表*/
        struct objc_method_list **methodLists;  /*方法链表*/
        struct objc_cache *cache;                    /*方法缓存*/
        struct objc_protocol_list *protocols;   /*协议链表*/
};

实例变量及实例变量表

struct objc_ivar {          // 单个实例变量
        char *ivar_name;    // 实例变量名
        char *ivar_type;    // 实例变量的类型(属于哪个类)
        int ivar_offset;    // 实例变量的偏移量(OC中访问实例变量是通过偏移量获取到实例变量的位置)
#ifdef __LP64__
        int space;          // 实例变量所占空间长度
#endif
}
struct objc_ivar_list {     // 实例变量列表结构体(以链表的形式管理多个实例变量)
        int ivar_count;     // 实例变量个数
#ifdef __LP64__
        int space;          // 实例变量总空间数
#endif
        /* variable length structure */
        struct objc_ivar ivar_list[1];  // 实例变量数组
}

方法及方法表

struct objc_method {            // 单个方法
        SEL method_name;        // 方法名
        char *method_types;     // 方法类型
        IMP method_imp;         // 指向方法实现代码的地址
};
struct objc_method_list {       // 方法链表
        struct objc_method_list *obsolete;
        int method_count;
#ifdef __LP64__
        int space;
#endif
        /* variable length structure */
        struct objc_method method_list[1];
}

关于Runtime中的结构体总结如下:

三、与Runtime系统交互

Objective-C 程序有三种途径和运行时系统交互:
通过 Objective-C 源代码;
通过 Foundation 框架中类 NSObject 的方法;
通过直接调用运行时系统的函数。

由于Runtime的作用就是讲OC的源代码转化为底层C++的实现,所以前两种交互方式是隐含在代码内部的,通过直接调用运行时系统中的函数,我们可以对运行时系统中的函数进行操作,主要是读取成员变量的值,获取信息以及一些简单的设置,关于这些操作函数总结如下:

四、动态绑定

所谓的动态绑定就是对一个对象发送消息,到运行时系统找到这个消息所对应的方法实现并调用的过程。
消息函数objc_megSend做了动态绑定所需要的一切:
1、它首先找到选标所对应的方法实现。因为不同的类对同一方法可能会有不同的实现,所以找到的 方法实现依赖于消息接收者的类型。
2、然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传给找到的方法实现。
3、最后,将方法实现的返回值作为该函数的返回值返回。

当对象收到消息时,消息函数首先根据该对象的isa指针找到该对象所对应的类的方法表,并从表中寻找该消息对应的方法选标。如果找不到,objc_msgSend 将继续从父类中寻找,直到 NSObject 类。一旦找到了方法选标, objc_msgSend 则以消息接收者对象为参数调用,调用该选标对应的方法实现。
这就是在运行时系统中选择方法实现的方式。在面向对象编程中,一般称作方法和消息动态绑定的过程。

五、动态方法解析

你可以通过实现 resolveInstanceMethod:和 resolveClassMethod:来动态地实现给定选标 的对象方法或者类方法。
Objective-C 方法可以认为是至少有两个参数——self 和_cmd—— 的 C 函数。您可以通过 class_addMethod 方法将一个函数加入到类的方法中。例如,有如下的函数:

void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
我们可以做如下操作在项目中创建两个类,一个继承自NSObject的Student 另一个继承自Student的BDStudent
#import "BDStudent.h"
#import 
@implementation LOStudent
+(BOOL)resolveInstanceMethod:(SEL)sel{
    //动态解析 实例方法
    //创建实例方法的方法实现
    void(^resolveInstanceBlock)(id, SEL) = ^(id objc_self,SEL objc_cmd){
        NSLog(@"如果实例方法未实现则会执行此方法");
    };
    //为本类添加实例方法
    class_addMethod([self class], sel, imp_implementationWithBlock(resolveInstanceBlock), "v@:");  //v表示返回值类型 @表示id :表示方法参数
    printf("%s\n",sel_getName(sel));
    return YES;
    
}
+(BOOL)resolveClassMethod:(SEL)sel{
    //动态解析 类方法
    //首先创建类方法的实现
    void(^resoveClassMethod)(id,SEL) = ^(id objc_self,SEL objc_cmd){
        NSLog(@"未实现的类方法,在这里执行");
    };
    //为本类添加类方法
    class_addMethod(object_getClass([self class]), sel, imp_implementationWithBlock(resoveClassMethod), "v@:");
    return YES;
}
@end

要是我们以上方法没写的话 就会得到如下结果 反之,则不然。

    /*
    //创建LOStudent的实例 调用不存在的方法
    LOStudent *stu  = [[LOStudent alloc]init];
    [stu performSelector:@selector(haha)];
    //会因为无法识别方法selector而抛出异常
    */

六、消息转发

消息转发很象继承,并且可以用来在Objective-C程序中模拟多重继承。如图 5-1所示, 一个对象通过转发来响应消息,看起来就象该对象从别的类那借来了或者”继承“了方法实现一样。
消息转发提供了多重继承的很多特性。
然而,两者有很大的不同:多重继承是将不同的行为封装到单个的对象中,有可能导致庞大的,复杂的对象。
而消息转发是将问题分解到更小的对象中,但是又以一种对消息发送对象来说完全透明的方式将这些对象联系起来。
消息转发只是将其他类引入消息链,而不是继承链。所以 respondsToSelector:、 isKindOfClass:返回值均为NO,如果您使用的是协议类, conformsToProtocol:也为NO。若我们以转发消息方式扩展类,那么有时需要重新实现这些方法,以达到对开发人员透明的效果。
我们假设有两个类都继承与NSObject :Student和BeiDa 学生需要实现消息转发,而学习作为一个方法是爱北大实现的:我们可以做如下处理

#import 
@interface BeiDa : NSObject
-(void)leart;
@end

#import "BeiDa.h"
@implementation BeiDa
//实现学习iOS开发的方法
-(void)leart{
    NSLog(@"在北京大学软件工程中心开发实践");
}
@end

而作为学生

#import 
#import "Lanou.h"
@interface Student : NSObject
//接收转发消息的对象
@property(nonatomic,strong)Lanou *otherObject;
@end

#import "Student.h"
@implementation Student
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(leart)) {
        return [self.otherObject methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}
//将消息当做参数anInvocation传到方法里来 通过invokeWithTarget将targrt切换为 otherObject 由otherObject来执行leart这个方法
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    if (anInvocation.selector == @selector(leart)) {
        [anInvocation invokeWithTarget:self.otherObject];
    }
}
@end

对于以上我们怎么理解呢?
学生没有实现学习的方法 真正的学习是在北大
实现转发消息的方法
1 需要有一个对象 在消息进行转发之前需要从对象里面取对应方法选标的方法签名 然后在forwardInvocation这个方法里面将这个消息转发给那个对象
2 .h定义接收转发消息的对象 OtherObject
3 这里去方法选标就需要在取这个方法选标在OtherObject这个类里面对应的方法签名
4 如果是学习的时候我们才会转发 判断之后 返回方法签名
5 先判断 在进行消息转发 将消息转发给另一个对象
当有learn这个方法选标传过来的时候首先会从otherObject里面去方法选标所对应的方法签名如果取到则会执行forwardInvocation:方法

七、Runtime的项目应用

1、实例变量遍例

遍例类的所有实例变量,实现自动归档-反归档
Ivar *ivars = class_copyIvarList([self class], &count);

//    KVC中setValue中使用
//    我们知道在KVC中如果直接setValue如果对象没有这个属性或者是变量就会直接Crash,如:
    SomeObj *obj = [[SomeObj alloc]init];
    [obj setValue:@"value" forKey:@"objName"];
//    SomeObj 没有objName这个属性
  //    这段代码会直接Crash,使用runtime遍例实例变量避免

我们创建一个NSObject的类目

#import 
@interface NSObject (AutoEncode)
-(instancetype)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
@end

#import "NSObject+AutoEncode.h"
#import 
@implementation NSObject (AutoEncode)
-(void)encodeWithCoder:(NSCoder *)aCoder{
    //获取这个类的实例变量链表
    //创建一个ivarCount保存实例变量个数
    unsigned int ivarCount = 0;
    Ivar *vars = class_copyIvarList([self class], &ivarCount);
    //循环遍历实例变量链表
    for (int i = 0; i < ivarCount; i++) {
        //获得实例变量的名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(vars[i])];
        //通过kvc获得实例变量的值
        id value = [self valueForKey:ivarName];
        //对此值 以实例变量名作为key进行归档
        [aCoder encodeObject:value forKey:ivarName];
    }
    free(vars);//释放链表
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    //初始化
    self = [self init];//根 NSObject
    if (self) {
        //获取实例变量链表
        unsigned int ivarCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &ivarCount);
        //循环便利实例变量链表
        for (int i = 0; i < ivarCount; i++) {
            //获取实例变量名字
            NSString *varName = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
            //以实例变量的名字进行反归档
            id value= [aDecoder decodeObjectForKey:varName];
            
            //通过KVC对此对象进行赋值
            [self setValue:value forKey:varName];
        }
        free(ivars);
    }
    return self;
}
@end

#import "ViewController.h"
#import "BeiDaStudent.h"
@interface ViewController ()
@end
#在此我们并没有直接遵守NSCoding协议 但是能够自动进行归档个反归档的操作
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //创建对象
    BeiDaStudent *stu = [BeiDaStudent new];
    stu.name = @"张三";
    stu.age = 25;
    //进行归档
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:stu];
    //进行反归档并打印出数据
    BeiDaStudent *stu1 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    NSLog(@"北大的学生name = %@,age = %ld",stu1.name,stu1.age);
    // Do any additional setup after loading the view, typically from a nib.
}

2、动态关联对象
使用Runtime来在一个已有对象上动态的挂载另一个对象
如:如果你在对象传递(传参)的时候需要用到某个属性,按照以往的思路:我继承这个类重新创建一个新类就完事了,这个思路没有问题,但麻烦,要是有一个方法能直接将我想要的属性挂载上去岂不是更好?代码简单、易懂。
实际开发中,一个UIViewController中多个UITableView(或UIAlertView)它们的代理相同,动态关联后在代理方法中就可以分别不同对象进行不同处理。
下面就来讲解下如何使用Runtime来 在已有对象上动态挂载另外一个对象。

//    关联对象
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//    获取关联对象
    id objc_getAssociatedObject(id object, const void *key)
//    移除关联对象
    void objc_removeAssociatedObjects(id object)

我们假设BeiDaStudent作为BeiDa的代理去执行学习iOS

#import 
@interface BeiDaStudent : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)NSInteger age;
//BeiDaStudent作为BeiDa的代理去执行学习iOS
-(void)learniOSProgram;
@end

#import "BeiDaStudent.h"
@implementation BeiDaStudent
-(void)learniOSProgram{
    NSLog(@"%@ 努力学习iOS编程",self.name);
}
@end

BeiDa类

#import 
@interface BeiDa : NSObject
-(void)addDelegate:(id)aDelegate;
-(void)removeDelegate:(id)aDelegate;
@end

#import "BeiDa.h"
#import "BeiDaStudent.h"
#import 

//动态关联对象的函数需要有一个key,通过key进行赋值取值
const char *kMultiDelegateKey = "kMultiDelegateKey";
@implementation BeiDa

-(void)addDelegate:(id)aDelegate{
//    关联数组
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, kMultiDelegateKey);
    if (!delegateArray) {
        
        delegateArray = [NSMutableArray new];
        //第一个参数:当前对象
        //第二个参数:关联是所需要的key
        //第三个参数:就是与当前对象进行关联的对象
        //第四个参数:关联规则 枚举类型
        objc_setAssociatedObject(self, kMultiDelegateKey, delegateArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    if(aDelegate){
        [delegateArray addObject:aDelegate];
    }
}
-(void)removeDelegate:(id)aDelegate{
    NSMutableArray *delegateArray = objc_getAssociatedObject([self class], kMultiDelegateKey);
    [delegateArray removeObject:aDelegate];
}

//消息转发
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMutableArray *delegateArray =objc_getAssociatedObject(self , kMultiDelegateKey);
    if (delegateArray) {
        for (id aDelegate in delegateArray) {
            return [aDelegate methodSignatureForSelector:aSelector];
        }
    }
    return [self methodSignatureForSelector:@selector(doNothing)];
}
//什么都不做
-(void)doNothing{
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    NSMutableArray *delegateArray = objc_getAssociatedObject(self, kMultiDelegateKey);
    if (delegateArray) {
        for (id aDelegate in delegateArray) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [anInvocation invokeWithTarget:aDelegate];
            });
        }
    }
}
@end

接下来我们在ViewController里面做:

BeiDa *beida = [BeiDa new];

    //创建多个student作为多代理
    BeiDaStudent *student1 = [[BeiDaStudent alloc]init];
    student1.name =@"张三";
    BeiDaStudent *student2 = [[BeiDaStudent alloc]init];
    student2.name = @"李四";
    BeiDaStudent *student3 = [[BeiDaStudent alloc]init];
    student3.name = @"王五";
    
    //将三个student对象添加为beida的代理
    [beida addDelegate:student1];
    [beida addDelegate:student2];
    [beida addDelegate:student3];
    
    //通过 performSelector:aSelector方法,调用代理方法
    [beida performSelector:@selector(learniOSProgram)];  

运行结果是:


Runtime 你为何如此之屌?_第2张图片

3、动态关联函数/方法
动态关联方法,在运时期动态增加、交换方法。
替换系统方法,自动实现功能。
遍例系统类的方法,查看新特性。

1、声明并实现函数
void newFunction(id self, IMP _imp) {
    // doSomething在这里实现功能
}
2、为类添加新方法
class_addMethod([self class], sel_registerName("someNewMethod"), (IMP)newFunction, “v@:");
3、调用此方法
objc_msgSend((id)self, sel_registerName("someNewMethod"));

接下来我们通过runtime事先观察者KVO

#import 

@interface NSObject (CKKVO)

- (void)ck_addObserver:(id)observer ForKey:(NSString *)key WithBlock:(void(^)(id observedObject,NSString *key,id oldValue,id newValue))block;

- (void)ck_removeObserver:(id)observer ForKey:(NSString *)key;

@end


#import "NSObject+CKKVO.h"
#import 

NSString *const kCKKVOClassPrefix = @"CKKVOClassPrefix_";
NSString *const kCKObserversAssociatedKey = @"CKObserversAssociatedKey";

typedef void(^ObserverBlock)(id observedObject, NSString *key, id oldValue, id newValue);
// 创建一个用于存放观察者info的类
@interface CKObserverInfo : NSObject
// 观察者属性
@property (nonatomic, weak) id observer;
// key属性
@property (nonatomic, copy) NSString *key;
// 回调block
@property (nonatomic, copy) ObserverBlock block;
@end


@implementation CKObserverInfo
// 初始化方法
- (instancetype)initWithObserver:(id)observer ForKey:(NSString *)key WithBlock:(ObserverBlock)block {
    self = [super init];
    if (self) {
        _observer = observer;
        _key = key;
        _block = block;
    }
    return self;
}

@end



@implementation NSObject (CKKVO)
- (void)ck_addObserver:(id)observer ForKey:(NSString *)key WithBlock:(void(^)(id,NSString *,id,id))block {
    // 获取 setterName
    NSString *setName = setterName(key);
    SEL setSelector = NSSelectorFromString(setName);
    // 通过SEL获得方法
    Method setMethod = class_getInstanceMethod(object_getClass(self), setSelector);
    if (!setMethod) {
        @throw [NSException exceptionWithName:@"CKKVO Error" reason:@"若无setter方法,无法KVO" userInfo:nil];
    }
    // 获得当前类
    // 判断是否已经创建衍生类
    Class thisClass = object_getClass(self);
    NSString *thisClassName = NSStringFromClass(thisClass);
    if (![thisClassName hasPrefix:kCKKVOClassPrefix]) {
        thisClass = [self makeKVOClassWithOriginalClassName:thisClassName];
        // 改变类的标识
        object_setClass(self, thisClass);
    }
    // 判断衍生类中是否实现了setter方法
    if(![self hasSelector:setSelector]) {
        const char *setType = method_getTypeEncoding(setMethod);
        class_addMethod(object_getClass(self), setSelector, (IMP)ck_setter, setType);
    }
    // 将observer添加到观察者数组
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kCKObserversAssociatedKey));
    if (!observers) {
        observers = [NSMutableArray new];
        objc_setAssociatedObject(self, (__bridge const void *)(kCKObserversAssociatedKey), observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    // 创建观察者info类
    CKObserverInfo *observerInfo = [[CKObserverInfo alloc]initWithObserver:observer ForKey:key WithBlock:block];
    [observers addObject:observerInfo];
}
void ck_setter(id objc_self, SEL cmd_p, id newValue) {
    // setterName转为name
    NSString *setName = NSStringFromSelector(cmd_p);
    NSString *key = nameWithSetName(setName);
    // 通过KVC获取key对应的value
    id oldValue = [objc_self valueForKey:key];
    // 将set消息转发给父类
    struct objc_super selfSuper = {
        .receiver = objc_self,
        .super_class = class_getSuperclass(object_getClass(objc_self))
    };
    objc_msgSendSuper(&selfSuper,cmd_p,newValue);
    // 调用block
    NSMutableArray *observers = objc_getAssociatedObject(objc_self, (__bridge const void *)kCKObserversAssociatedKey);
    for (CKObserverInfo *info in observers) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            if ([info.key isEqualToString:key]) {
                info.block(objc_self,key,oldValue,newValue);
            }
        });
    }
}
// 从setterName转为name
NSString *nameWithSetName(NSString *setName) {
    if (setName.length <= 4 || ![setName hasPrefix:@"set"] || ![setName hasSuffix:@":"]) {
        @throw [NSException exceptionWithName:@"CKKVO Error" reason:@"set方法not available" userInfo:nil];
    }
    NSString *Name = [setName substringWithRange:NSMakeRange(3, setName.length - 4)];
    NSString *firstCharacter = [Name substringToIndex:1];
    return [[firstCharacter lowercaseString] stringByAppendingString:[Name substringFromIndex:1]];
}
// 判断set方法是否存在
- (BOOL)hasSelector:(SEL)aSelector {
    unsigned int mCount = 0;
    Method *methods = class_copyMethodList(object_getClass(self), &mCount);
    for (int i = 0; i < mCount; i ++) {
        Method method = methods[i];
        SEL setSelector = method_getName(method);
        if (setSelector == aSelector) {
            free(methods);
            return YES;
        }
    }
    free(methods);
    return NO;
}
// 通过runtime创建类
- (Class)makeKVOClassWithOriginalClassName:(NSString *)className {
    NSString *kvoClassName = [kCKKVOClassPrefix stringByAppendingString:className];
    Class kvoClass = NSClassFromString(kvoClassName);
    if (kvoClass) {
        return kvoClass;
    }
    // objc_allocateClassPair创建类
    kvoClass = objc_allocateClassPair(object_getClass(self), kvoClassName.UTF8String, 0);
    objc_registerClassPair(kvoClass);
    return kvoClass;
}
// 通过key获取对应的setterName
NSString *setterName(NSString *key) {
    if (key.length == 0) {
        @throw [NSException exceptionWithName:@"CKKVO Error" reason:@"没有对应的key" userInfo:nil];
    }
    NSString *firstCharacter = [key substringToIndex:1];
    NSString *Name = [[firstCharacter uppercaseString]stringByAppendingString:[key substringFromIndex:1]];
    return [NSString stringWithFormat:@"set%@:",Name];
}
- (void)ck_removeObserver:(id)observer ForKey:(NSString *)key {
    // 删除观察者
    CKObserverInfo *removeInfo = nil;
    NSMutableArray *observers = objc_getAssociatedObject(self, (__bridge const void *)(kCKObserversAssociatedKey));
    for (CKObserverInfo *info in observers) {
        if (info.observer == observer && [info.key isEqualToString:key]) {
            removeInfo = info;
        }
    }
    [observers removeObject:removeInfo];
}
@end

然后在viewDidLoad:里面

    // 异步Block的KVO
    [p1 ck_addObserver:self ForKey:@"name" WithBlock:^(id observedObject, NSString *key, id oldValue, id newValue) {
        
        NSLog(@"oldValue = %@,%@",oldValue,[NSThread currentThread]);
        NSLog(@"newValue = %@,%@",newValue,[NSThread currentThread]);
    }];
    p1.name = @"456";
    p1.name = @"789";
    [p1 ck_removeObserver:self ForKey:@"name"];
    p1.name = @"hahahaha";

你可能感兴趣的:(Runtime 你为何如此之屌?)