runtime 问答与用法(摘)

注:文中问题摘自:

https://www.jianshu.com/p/8345a79fd572
https://juejin.im/post/5aa79411f265da237a4cb045

0. runtime 讲解

Snip20200319_35.png

1. runtime怎么添加属性和方法?

各种方法的声明都在 objc/runtime.h 中,可详细查看。举例:

class_addIvar

class_addMethod

class_addProperty

class_addProtocol

2. runtime 如何实现 weak 属性?

runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。

weak属性需要在dealloc中置nil么?

在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
在属性所指的对象遭到摧毁时,属性值也会清空

// 模拟下weak的setter方法,大致如下
- (void)setObject:(NSObject *)object{ 
    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    [object cyl_runAtDealloc:^{ 
        _object = nil; 
    }];
}

当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?

1、调用objc_release

2、因为对象的引用计数为0,所以执行dealloc

3、在dealloc中,调用了_objc_rootDealloc函数

4、在_objc_rootDealloc中,调用了object_dispose函数

5、调用objc_destructInstance

6、最后调用objc_clear_deallocating,详细过程如下:

a. 从weak表中获取废弃对象的地址为键值的记录

b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil

c. 将weak表中该记录删除

d. 从引用计数表中删除废弃对象的地址为键值的记录

3.runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

每一个类对象中都一个对象方法列表(对象方法缓存)

类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存),
方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

4. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放

补充:对象的内存销毁时间表,分四个步骤

>1、调用 -release :引用计数变为零
* 对象正在被销毁,生命周期即将结束. 
* 不能再有新的 __weak 弱引用,否则将指向 nil.
* 调用 [self dealloc]
>
2、 父类调用 -dealloc 
* 继承关系中最直接继承的父类再调用 -dealloc 
* 如果是 MRC 代码 则会手动释放实例变量们(iVars)
* 继承关系中每一层的父类 都再调用 -dealloc

>3、NSObject 调 -dealloc 
* 只做一件事:调用 Objective-C runtime 中object_dispose() 方法

>4. 调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release 
* 解除所有使用 runtime Associate方法关联的对象 
* 解除所有 __weak 引用 
* 调用 free()

5. _objc_msgForward 函数是做什么的?直接调用它将会发生什么?

_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

在“消息传递”过程中,objc_msgSend的动作比较清晰:首先在 Class 中的缓存查找 IMP (没缓存则初始化缓存),如果没找到,则向父类的 Class 查找。如果一直查找到根类仍旧没有实现,则用_objc_msgForward函数指针代替 IMP 。最后,执行这个 IMP .

一旦调用_objc_msgForward,将跳过查找 IMP的过程,直接触发“消息转发”,

如果调用了_objc_msgForward,即使这个对象确实已经实现了这个方法,你也会告诉objc_msgSend:
“我没有在这个对象里找到这个方法的实现”

6.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向编译后得到的类中增加实例变量;

能向运行时创建的类中添加实例变量;

分析如下:
因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量
运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。

7. 简述下Objective-C中调用方法的过程(runtime)

Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:

objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX,但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题《什么时候会报unrecognized selector的异常》中的说明

8.什么是method swizzling(俗称黑魔法)

  • 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
  • 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
  • 交换方法的几种实现方式:
    • 利用 method_exchangeImplementations 交换两个方法的实现
    • 利用 class_replaceMethod 替换方法的实现
    • 利用 method_setImplementation 来直接设置某个方法的IMP


      image

9.动态添加方法:美团面试题:有没有使用过performSelector?什么时候使用?动态添加方法的时候使用。 为什么动态添加方法?

OC都是懒加载机制,只要一个方法实现了,就会马上添加到方法列表中.如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类添加方法解决资源和时间消耗。

  • 动态添加方法,首要要实现要添加类的resolveInstanceMethod方法。
  • resolveInstanceMethod的作用就是当调用了没有实现的方法没有实现就会调用,然后就可以根据他的参数sel(参数sel就是没有实现的方法)来做一系列的操作。
    此方法定义于NSObject当中,所以说每个类其实都有他,只要重写,不过不要忘了方法后面的return [super resolveInstanceMethod:sel];

类的分类中添加如下代码:

+(BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == NSSelectorFromString(@"run:")) {
        /**
         *  动态添加方法
         *  @param self cls:为哪个类添加方法
         *  @param sel  SEL:添加方法的方法编号(方法名)是什么
         *  @param IMP  IMP:方法实现
         *  @param const char * types方法类型
         *  @return 返回是否添加成功
         */
        BOOL isSuccess = class_addMethod(self, sel, (IMP)run, "v@:@");
        return isSuccess;
    }
    return [super resolveInstanceMethod:sel];
}
/**
 @param self 为哪个类添加方法
 @param _cmd 方法编号
 @param meter 参数1
 */
void run(id self, SEL _cmd, NSNumber *meter) {
    NSLog(@"动态添加的方法:run= %@",meter);
}

调用时可以使用:

MyClass *class = [[MyClass alloc]init];
[class performSelector:@selector(run:) withObject:@10];

10.动态添加属性

①给NSObject添加分类,在分类中添加属性。问题:@property在分类中作用:仅仅是生成get,set方法声明,不会生成get,set方法实现和下划线成员属性,所以要在.m文件实现setter/getter方法,用static保存下滑线属性,这样一来,当对象销毁时,属性无法销毁

②用runtime动态添加属性:本质是让属性与某个对象产生一段关联
使用场景:给系统的类添加属性时。

#import 
@implementation NSObject (Property)
//static  NSString *_name;      //通过这样去保存属性没法做到对象销毁,属性也销毁,static依然会让属性存在缓存池中,所以需要动态的添加成员属性
// 只要想调用runtime方法,思考:谁的事情
-(void)setName:(NSString *)name
{
    // 保存name
    // 动态添加属性 = 本质:让对象的某个属性与值产生关联
    /*
        object:保存到哪个对象中 
        key:用什么属性保存 属性名
        value:保存值
        policy:策略,strong,weak
     objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
     */
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//    _name = name;

}

- (NSString *)name
{
    return objc_getAssociatedObject(self, "name");
//    return _name;

}
@end

11.自动生成属性代码

开发中,从网络数据中解析出字典数组,将数组转为模型时,如果有几百个key需要用,要写很多@property成员属性,下面提供一个万能的方法,可直接将字典数组转为全部@property成员属性,打印出来,这样直接复制在模型中就好了

@implementation NSDictionary (PropertyCode)

//1️⃣通过这个方法,自动将字典转成模型中需要用的属性代码

// 私有API:真实存在,但是苹果没有暴露出来,不给你。如BOOL值,不知道类型,打印得知是__NSCFBoolean,但是无法敲出来,只能用NSClassFromString(@"__NSCFBoolean")

// isKindOfClass:判断下是否是当前类或者子类,BOOL是NSNumber的子类,要先判断BOOL
- (void)createPropetyCode
{
    // 模型中属性根据字典的key
    // 有多少个key,生成多少个属性
    NSMutableString *codes = [NSMutableString string];
    // 遍历字典
    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
        NSString *code = nil;
//        NSLog(@"%@",[value class]);
        
        if ([value isKindOfClass:[NSString class]]) {
          code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
        } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
            code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
        } else if ([value isKindOfClass:[NSNumber class]]) {
             code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
        } else if ([value isKindOfClass:[NSArray class]]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
        } else if ([value isKindOfClass:[NSDictionary class]]) {
            code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
        }
        // 拼接字符串
        [codes appendFormat:@"%@\n",code];
    }];
    NSLog(@"%@",codes);
}
@end

12.字典转模型的实现

#import "NSObject+Model.h"
#import 

//    class_copyPropertyList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>) 获取属性列表

@implementation NSObject (Model)

/**
 字典转模型
 @param dict 传入需要转模型的字典
 @return 赋值好的模型
 */

+ (instancetype)modelWithDict:(NSDictionary *)dict

{
    id objc = [[self alloc] init];

    //思路: runtime遍历模型中属性,去字典中取出对应value,在给模型中属性赋值
    // 1.获取模型中所有属性 -> 保存到类
    // ivar:下划线成员变量 和 Property:属性
    // 获取成员变量列表
    // class:获取哪个类成员变量列表
    // count:成员变量总数
    //这个方法得到一个装有成员变量的数组
    //class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
    
    int count = 0;
    // 成员变量数组 指向数组第0个元素
    Ivar *ivarList = class_copyIvarList(self, &count);

    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        
        // 获取成员变量 user
        Ivar ivar = ivarList[i];
        // 获取成员变量名称,即将C语言的字符转为OC字符串
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 获取成员变量类型,用于获取二级字典的模型名字
        NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        
        //  将type这样的字符串@"@\"User\"" 转成 @"User"
        type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];
        type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        
        // 成员变量名称转换key,即去掉成员变量前面的下划线
        NSString *key = [ivarName substringFromIndex:1];
        
        // 从字典中取出对应value dict[@"user"] -> 字典
        id value = dict[key];
        // 二级转换
        // 并且是自定义类型,才需要转换
        if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典才需要转换
            Class className = NSClassFromString(type);
            // 字典转模型
            value = [className modelWithDict:value];
        }
        // 给模型中属性赋值 key:user value:字典 -> 模型
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}
@end

13.万能跳转界面方法

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    NSDictionary *param = @{@"class": @"1OneController",
                           @"property":@{
                                   @"ID": @"123",
                                   @"type": @"4"
                                   },
                           };
    
    NSString *classString = param[@"class"];
    
    const char *className = [classString cStringUsingEncoding:NSASCIIStringEncoding];
    
    Class newClass = objc_getClass(className);

    if (!newClass) {//如果 param 中的 class在 APP 中没有对应的已注册的类

        Class superClass = [NSObject class];

        newClass = objc_allocateClassPair(superClass, className, 0);
        // 注册你创建的这个类
        objc_registerClassPair(newClass);
    }
    //此方法可以代替上边runtime 创建类的方法,但是如果 param中给的类名不一定存在的话 ,此方法就无法创建类了。建议使用 runtime 方法创建类注册类
//    Class newClass = NSClassFromString(classString);
    //创建对象
    id instance = [[newClass alloc]init];
    
    NSDictionary *properties = param[@"property"];
    
    [properties enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
//        检测对象是否存在该属性
        if ([self containsPropertyName:key inInstance:instance]) {
            [instance setValue:obj forKey:key];
        }
    }];
    NSLog(@"%@",instance);
}
- (BOOL)containsPropertyName:(NSString *)verifyPropertyName inInstance:(id)instance {
    
    unsigned int count = 0;
    
    objc_property_t *properties = class_copyPropertyList([instance class], &count);
    
    for (NSInteger i = 0; i < count; i ++) {
        
        objc_property_t property = properties[i];
        
        NSString *propertyName = [[NSString alloc]initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        
        if ([propertyName isEqualToString:verifyPropertyName]) {
            free(properties);
            return YES;
        }
    }
    free(properties);
    return NO;
}

14.消息转发

消息发送转发概览

方法列表(methodLists)没找到对应的 selector ,runtime 会如何执行?

// ViewController.m 中 (未实现 myTestPrint 方法)
[self performSelector:@selector(myTestPrint:) withObject:@",你好 !"];

系统会提供三次补救的机会:

//对象在接收到未知的消息时,首先会调用这两个方法,一个针对实例方法;一个针对类方法。返回值都是 Bool。
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (实例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {}  (类方法)
// ViewController.m 中

void myMethod(id self, SEL _cmd,NSString *nub) {
    NSLog(@"ifelseboyxx%@",nub);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    if (sel == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
        class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
        return YES;
    }else {
        return [super resolveInstanceMethod:sel];
    }
}

我们只需要在 resolveInstanceMethod: 方法中,利用 class_addMethod 方法,将未实现的 myTestPrint: 绑定到 myMethod 上就能完成转发,最后返回 YES。

- (id)forwardingTargetForSelector:(SEL)aSelector {}

这个方法要求返回一个 id。使用场景一般是将 A 类的某个方法,转发到 B 类的实现中去。

使用示例:

想转发到 Person 类中的 -myTestPrint: 方法中:

@interface Person : NSObject
@end

@implementation Person
- (void)myTestPrint:(NSString *)str {
    NSLog(@"ifelseboyxx%@",str);
}
@end

// ViewController.m 中

- (id)forwardingTargetForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
        return [Person new];
    }else{
        return [super forwardingTargetForSelector:aSelector];
    }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}

第一个要求返回一个方法签名,第二个方法转发具体的实现。二者相互依赖,只有返回了正确的方法签名,才会执行第二个方法。

这次的转发作用和第二次的比较类似,都是将 A 类的某个方法,转发到 B 类的实现中去。不同的是,第三次的转发相对于第二次更加灵活,forwardingTargetForSelector: 只能固定的转发到一个对象;forwardInvocation: 可以让我们转发到多个对象中去。
signatureWithObjCTypes:"v@:@"
这里的 “v@:@”就代表:
"v":代表返回值void
"@":代表一个对象,这里指代的是消息的receiver
":":代表SEL
"@":代表参数

ObjCTypes为:"B@:@",其中:
"B":代表BOOL。 // NSLog(@"%s",@encode(BOOL))的结果为B
"@":一个id类型的对象,第一个参数类型,也就是objc _ msgSend的第一个参数
":":代表对应的SEL,第二个参数
"@":一个id类型的对象

作者:我不是掌柜
链接:https://www.jianshu.com/p/6fb4641e6ec5
来源:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

使用实例:

想转发到 Person 类以及 Animal 类中的 -myTestPrint: 方法中:

@interface Person : NSObject
@end

@implementation Person
- (void)myTestPrint:(NSString *)str {
    NSLog(@"ifelseboyxx%@",str);
}
@end


@interface Animal : NSObject
@end

@implementation Animal
- (void)myTestPrint:(NSString *)str {
    NSLog(@"tiger%@",str);
}
@end


// ViewController.m 中

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wundeclared-selector"
    if (aSelector == @selector(myTestPrint:)) {
    #pragma clang diagnostic pop
    return [NSMethodSignature  signatureWithObjCTypes:"v@:@"];
}
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    Person *person = [Person new];
    Animal *animal = [Animal new];
    if ([person respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:person];
    }
    if ([animal respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:animal];
    }
}

如果到了第三次机会,还没找到对应的实现,就会 crash:

unrecognized selector sent to instance 0x7f9f817072b0

15. isKindOfClass和isMemberOfClass

首先,查看源码中NSObject.mm的实现

@implementation NSObject
+ (BOOL)isMemberOfClass:(Class)cls {
    //通过object_getClass获取当前类对象的isa所指向的元类对象与cls进行比较
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    //比较当前实例对象的isa所指向的类对象与cls
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {  
    //遍历当前类对象的元类对象以及它的父类的元类对象,如果和cls相等就返回YES
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    //遍历当前实例对象所对应的类对象以及它的父类,如果和cls相等就返回YES
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
@end

· +isMemberOfClass:是通过isa获取当前类对象的元类对象,与参数中· 的对象进行对比,如果相等则返回YES。
· -isMemberOfClass:是通过isa获取当前实例对象的类对象,与参数中的对象进行对比,如果相等,则返回YES。
· +isKindOfClass:通过遍历当前类对象所对应的元类对象以及父类所对应的元类对象,如果遍历到的元类对象与参数中的对象相等,则返回YES。
· -isKindOfClass:通过遍历当前实例对象对应的类对象以及它的父类,如果遍历到的类对象和参数中的对象相等,则返回YES。
demo:

        BOOL result1 = [NSObject isMemberOfClass:[NSObject class]];
        BOOL result2 = [NSObject isKindOfClass:[NSObject class]];
        BOOL result3 = [Person isMemberOfClass:[Person class]];
        BOOL result4 = [Person isKindOfClass:[Person class]];
        
        NSLog(@"%d %d %d %d", result1, result2, result3, result4);
        
        Person *person = [[Person alloc] init];
        BOOL result5 = [person isMemberOfClass:[NSObject class]];
        BOOL result6 = [person isKindOfClass:[NSObject class]];
        BOOL result7 = [person isMemberOfClass:[Person class]];
        BOOL result8 = [person isKindOfClass:[Person class]];
        
        NSLog(@"%d %d %d %d", result5, result6, result7, result8);

[NSObject isMemberOfClass:[NSObject class]]会返回NO,原因是object_getClass(NSObject)拿到的是元类对象,而[NSObject class]是类对象,两者不相等。
[NSObject isKindOfClass:[NSObject class]]会返回YES,原本获取NSObject的元类对象和[NSObject class]来进行对比,应该是不相等,但是比较特殊的一点是NSObject的元类对象的superclass指向它的类对象,所以此处返回YES。此处可以参考Objective-C基础之一(深入理解OC对象)中isa和superclass的总结。
[Person isMemberOfClass:[Person class]]会返回NO,获取到Person的元类对象和[Person class]进行比较,两者不相等。
[Person isKindOfClass:[Person class]]会返回NO,首先会拿到Person的元类对象和[Person class]对比,发现不相等,然后会通过Person的superclass拿到父类的元类和[Person class]进行对比,发现还是不相等,一直遍历到基类NSObject的元类,NSObject的元类的superclass指向NSObject的类对象,因此最后一次遍历是拿NSObject的类对象和[Person class]进行比较,肯定不相等,所以返回NO。
[person isMemberOfClass:[NSObject class]]返回NO,原因是拿person的类对象和NSObject比较,显然不相等。
[person isKindOfClass:[NSObject class]]返回YES,因为Person原本就是继承自NSObject,而isKindOfClass则是通过继承链最终能找到NSObject的类对象和参数[NSObject class]进行对比,两者相等。
[person isMemberOfClass:[Person class]]返回YES,person的类对象就是Person。
[person isKindOfClass:[Person class]]返回YES,原因同上。

你可能感兴趣的:(runtime 问答与用法(摘))