初探runtime 类以及类的继承

这些知识老生常谈了,我也写过相关博客。可是感觉还是不咋地。今天从头来过,弄个专题分模块专门仔细研究下。

NSObject定义

objc2.0 以前

typedef struct objc_class *Class;

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
    
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
    
} OBJC2_UNAVAILABLE;

从上图可以看出,objc_class 中有父类的指针,类名,版本信息等。除此之外还有 变量列表指针ivars ,方法列表methodLists,缓存cache和协议protocols。

objc 2.0 以后

objc 2.0 以后,数据结构发生了变化

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

从上面我们 看出来,NSObject 只有一个成员变量就是 isa ,类型是Class ,接下来我们看看class 是什么。

typedef struct objc_class *Class;

从上面我们看出来了 Class 是struct objc_class结构体指针 ,那接下来我们看看这个结构体是什么

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits; 
}

objc_class 是继承objc_object 对象。接下来看objc_object 的结构体(在文件objc_private.h中)

struct objc_object {
private:
    isa_t isa;
}

这里又出现了一个union isa_t 。接下来我们看看这个isa_t 是什么

union isa_t 
{
  Class cls;
   uintptr_t bits;

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)
    };

# else
    // Available bits in isa field are architecture-specific.
#   error unknown architecture
# endif
}

UML 类结构图

类结构图

1.在objc2.0中,所有的对象都会包含一个isa_t类型的结构体。

  1. NSObject 类包含一个 object_class 的指针 isa
  2. 在objc_class中,有3个成员变量,一个是父类的指针,一个是方法缓存,最后一个这个类的实例方法链表。
    4.因为objc_class 中也包含一个isa ,因此类也是一个对象。(为什么这么说呢?我不认为只要有个isa 就说名类是个对象)

为什么说objc_class 也是一个对象呢?

因为类 可以和 对象 调用方法表现出一样的特性。

我们都知道,当一个对象的实例方法被调用的时候,会通过isa找到相应的类,然后在该类的class_data_bits_t中去查找方法。class_data_bits_t是指向了类对象的数据区域。在该数据区域内查找相应方法的对应实现。
要是想把类也当成对象,那么我们也应该让类调用方法同对象调用方法一致就可以了。这样就可以说类也是一个对象了。

怎么才能让类也表现出对象的特性呢?这里就引入了元类。

在引入元类之后,类对象和对象查找方法的机制就完全统一了。
对象的实例方法调用时,通过对象的 isa 在类中获取方法的实现。
类对象的类方法调用时,通过类的 isa 在元类中获取方法的实现。

meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

这里我们做个简单测试。类和元类之间的isa关系

我们创建三个类 YoungMan ,Man, Persion,类关系如图


关系图

测试代码

-(void)printClassInfo:(char *)className{
    Class objectClass;
    Class superClass;
    Class metaClass;
    Class superMetaClass;
    objectClass= objc_getClass(className);
    superClass= class_getSuperclass(objectClass);
    metaClass=  objc_getMetaClass(className);
    superMetaClass= class_getSuperclass(metaClass);
    struct objc_object * obj = (__bridge  struct objc_object *)metaClass;
      NSLog(@"className %s objectClass %p  superClass %p metaClass %p superMetaClass %p metaClass isa %p ",className,objectClass,superClass,metaClass,superMetaClass,obj->isa);
}

- (void)viewDidLoad {
    [super viewDidLoad];

    [self printClassInfo:"YoungMan"];
    [self printClassInfo:"Man"];
    [self printClassInfo:"Persion"];
    [self printClassInfo:"NSObject"];
   
}

测试结果

2018-04-13 16:01:10.553250+0800 Runtime-class[2551:9731051] className YoungMan objectClass 0x10074a040  superClass 0x100749fa0 metaClass 0x10074a018 superMetaClass 0x100749f78 metaClass isa 0x101701e58
2018-04-13 16:01:10.553455+0800 Runtime-class[2551:9731051] className Man objectClass 0x100749fa0  superClass 0x100749f00 metaClass 0x100749f78 superMetaClass 0x100749ed8 metaClass isa 0x101701e58
2018-04-13 16:01:10.553594+0800 Runtime-class[2551:9731051] className Persion objectClass 0x100749f00  superClass 0x101701ea8 metaClass 0x100749ed8 superMetaClass 0x101701e58 metaClass isa 0x101701e58
2018-04-13 16:01:10.553723+0800 Runtime-class[2551:9731051] className NSObject objectClass 0x101701ea8  superClass 0x0 metaClass 0x101701e58 superMetaClass 0x101701ea8 metaClass isa 0x101701e58

测试结果绘制成图如下图


测试结果

总结
1 实例对象的superclass 指向父类,最后指向rootClass(NSObject),RootClass 的父类是nil

  1. 实例对象的isa,指向元类
  2. 元类对象的superclass 指向元类父类最后指向RootClass的元类,而RootClass 的元类的父类执行RootClass
    4.元类对象的isa 都是指向根元类,包括根源类自己。

结构体isa_t

我们从依赖和继承关系来分析,isa_t 是最底层的。那么我们接下来看isa_t

isa_t 是个union类型结构体 ,因此cls bits 或者定义的结构体arm 或者x86等共用这一块内存。因为这些指针 、结构体或者bits 都是64 位的。因此isa_t 也是64位。

两种结构体见图


isa_t

这里我们重点讨论 这个x86 结构体

struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)
    };

isa 的初始化

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

inline void 
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!indexed) {
        isa.cls = cls;
    } else {
        assert(!DisableIndexedIsa);
        isa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.indexed is part of ISA_MAGIC_VALUE
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

从初始化我们能看出来,我们传入的参数
indexed hasCxxDotor 都是 false
因此我们进入的流程是 判断语句的else

 isa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.indexed is part of ISA_MAGIC_VALUE
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;

这里有个ISA_MAGIC_VALUE 宏定义

#   define ISA_MAGIC_VALUE 0x001d800000000001ULL

给bits 赋值,其实就是给_x86 结构体赋值,因此我们需要弄懂这个结构体给x86 的那些地方赋值了。
这里先把ISA_MAGIC_VALUE 转换成二进制

0 0 1 d 8 0 0 0 0 0 0 0 0 0 0 1
0000 0000 0001 1101 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001

然后和结构体对号入座,如下图


x86结构

从这张图中我们能看出来,我们其实初始化的位置是indexed 和margic 位置。

在结构体 x86 中 的首位 indexed 表示 isa_t 的类型

0 表示 raw isa,也就是没有结构体的部分,访问对象的 isa 会直接返回一个指向 cls 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型。
1 表示当前 isa 不是指针,但是其中也有 cls 的信息,只是其中关于类的指针都是保存在 shiftcls 中。

margic 的值是 111011 = 0x3b; 用于调试器判断当前对象是真的对象还是没有初始化的空间。

我们再看看has_cxx_dtor,这一位表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。

我们再来看 shiftcls ,给其赋值

isa.shiftcls = (uintptr_t)cls >> 3;

从这里看出来,shiftcls 其实是保存类的指针地址。

不过这里是先将类向右移动三位在赋值给了shiftcls。

这是因为将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清楚减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址。
ObjC 中的类指针的地址后三位也为 0
对象指针的后四位都是0

使用整个指针大小的内存来存储 isa 指针有些浪费,尤其在 64 位的 CPU 上。在 ARM64 运行的 iOS 只使用了 33 位作为指针(与结构体中的 33 位无关,Mac OS 上为 47 位),而剩下的 31 位用于其它目的。类的指针也同样根据字节对齐了,每一个类指针的地址都能够被 8 整除,也就是使最后 3 bits 为 0,为 isa 留下 34 位用于性能的优化。

Using an entire pointer-sized piece of memory for the isa pointer is a bit wasteful, especially on 64-bit CPUs which don't use all 64 bits of a pointer. ARM64 running iOS currently uses only 33 bits of a pointer, leaving 31 bits for other purposes. Class pointers are also aligned, meaning that a class pointer is guaranteed to be divisible by 8, which frees up another three bits, leaving 34 bits of the isa available for other uses. Apple's ARM64 runtime takes advantage of this for some great performance improvements. from [ARM64 and You](https://www.mikeash.com/pyblog/friday-qa-2013-09-27-arm64-and-you.html)

做个实验验证下。
测试代码


NSString *binaryWithInteger(NSUInteger decInt){
    NSString * string =@"";
    NSUInteger dec = decInt;
    while (dec>0) {
        string = [[NSString stringWithFormat:@"%lu",dec&1] stringByAppendingString:string];
        dec =dec>>1;
    }
    while(string.length!=64) {
        string = [@"0" stringByAppendingString:string];
    }
    return string;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct objc_object * obj = (__bridge  struct objc_object *)[NSObject new];
        NSLog(@"%@",binaryWithInteger(obj->isa));
        NSLog(@"%@", binaryWithInteger((uintptr_t)[NSObject class]));
    }
    return 0;
}

测试结果是

2018-04-13 16:49:05.683009+0800 测试Shifts 指针[3532:9769304] 0000000001011101111111111111111110010111100101100000000101000001
2018-04-13 16:49:05.684092+0800 测试Shifts 指针[3532:9769304] 0000000000000000011111111111111110010111100101100000000101000000

我们将所有的打印指针都补全到64 位
对象指针:
0000000001011101111111111111111110010111100101100000000101000001

类指针:
0000000000000000011111111111111110010111100101100000000101000000

对比下,指针完全相同。

接下来我们看其他的bits

has_assoc:对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存
weakly_referenced:对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放

deallocating:对象正在释放内存
has_sidetable_rc:对象的引用计数太大了,存不下
extra_rc:对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc 的值就为 9

这里我们使用结构体取代了原有的isa指针,因此,我们提供了一个方法返回原来类指针

#define ISA_MASK 0x00007ffffffffff8ULL
inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

这里有个宏定义 ISA_MASK 这个mask 就是获取指shiftcls位置的数据用的。


cache_t

我们看完类的结构,接下来我们看objc_object中的cache_t

结构

struct cache_t {
   struct bucket_t *_buckets;
   mask_t _mask;
   mask_t _occupied;
}
typedef uint32_t mask_t; 

struct bucket_t {
private:
   cache_key_t _key;
   IMP _imp;
}
typedef uintptr_t cache_key_t;

UML 图

cache_t

根据源码,我们知道
cache_t 持有** bucket_t 的指针**
mask:分配用来缓存bucket的总数。
occupied:表明目前实际占用的缓存bucket的个数。

bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。
(这里Cache_key_t _key 为什么是unsinged long,我们知道地址的长度也是unsinged long 其实就是把指针转换成了个数字)

cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。

Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。

class_data_bits_t bits

这里好好分析下这个 class_data_bits_t bits
结构体里对class_data_bits_t bits 定义的部分的参数和相关方法是

struct objc_class : objc_object{
 class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
   class_rw_t *data() { 
        return bits.data();
    }
  void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
  
  .....
下面的方法都是针对bits
}

好多方法都是针对这个bits 的那么我们就应该看看这个bits 是什么了。
set 方法

 void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

这里我们看出来了。这里其实是传入了一个 class_rw_t 类型的指针
bits 调用了setData 方法。bits8struct class_data_bits_t
调用该结构体的setData方法。

struct class_data_bits_t{
 uintptr_t bits;
public://标记public 公共方法
  class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
void setData(class_rw_t *newData)
    {
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        bits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
    }
}

在get 和 set data 方法中 ,有这个宏定义

#define FAST_DATA_MASK          0x00007ffffffffff8UL

这个我们能看出来,0x00000(44 个1 )000。指针是最多44 位 的。(因为x86 是44 位,其他的是33位,满足最长要求嘛)
接下来分析setData方法

1 第一步判断有没有data , 并且将第flag 的19 位 和 30 位设置值

  1. 就是给bits 位 赋值,其实就是记录class_rw_t 指针。

class_rw_t 结构体

class_rw_t

这里method_array_t property_array_t protocol_array_t 可以理解为一个数组吧。

因此这里把class 图完全绘制出来


0bjc2 完整图

上面这张图是objc1.0 和2.0 的对比图
我们把objc1.0的中的元素到objc 2.0中找对应关系

对应关系

这里怎么找对应关系呢?
就拿name 来说吧

找到 objc_class 类,从中找获取name 方法。

 const char *mangledName() { 
        // fixme can't assert locks here
        assert(this);

        if (isRealized()  ||  isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name;
        }
    }

data()函数就是找到class_rw_t 指针.接着找到该指针的ro 指针,ro 是class_ro_t 结构体,从这个结构体中获取name,如图。其他一样的方法。
图中没有标记objc info 对应0bjc2 中的位置,其实就是class_rw_t 指针的flags。

大神博客,入院考试这里我还是忒下吧。为了以后看方便

入院考试

(一)[self class] 与 [super class]

下面代码输出什么?

 @implementation Son : Father
- (id)init
{
    self = [super init];
    if (self)
    {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
return self;
}
@end

self和super的区别:

self是类的一个隐藏参数,每个方法的实现的第一个参数即为self。

super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。

在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )


/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。

入院考试第一题错误的原因就在这里,误认为[super class]是调用的[super_class class]。

objc_msgSendSuper的工作原理应该是这样的:
从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver,而不是super_class!
那么objc_msgSendSuper最后就转变成
// 注意这里是从父类开始msgSend,而不是从本类开始,谢谢@Josscii 和他同事共同指点出此处描述的不妥。

objc_msgSend(objc_super->receiver, @selector(class))

/// Specifies an instance of a class.  这是类的一个实例
    __unsafe_unretained id receiver;   


// 由于是实例调用,所以是减号方法
- (Class)class {
    return object_getClass(self);
}

由于找到了父类NSObject里面的class方法的IMP,又因为传入的入参objc_super->receiver = self。self就是son,调用class,所以父类的方法class执行IMP之后,输出还是son,最后输出两个都一样,都是输出son。

(二)isKindOfClass 与 isMemberOfClass

下面代码输出什么?

@interface Sark : NSObject
 @end
 @implementation Sark
 @end
 int main(int argc, const char * argv[]) {
@autoreleasepool {
    BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
    BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
   NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}

这里需要对着几个函数搞清楚

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

inline Class 
objc_object::getIsa() 
{
    if (isTaggedPointer()) {
        uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
    return ISA();
}

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
    return (Class)(isa.bits & ISA_MASK);
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
  • (BOOL)isKindOfClass:(Class)cls方法内部,会先去获得object_getClass的类,而object_getClass的源码实现是去调用当前类的obj->getIsa(),最后在ISA()方法中获得meta class的指针。

接着在isKindOfClass中有一个循环,先判断class是否等于meta class,不等就继续循环判断是否等于super class,不等再继续取super class,如此循环下去。

同理,[Sark class]执行完之后调用isKindOfClass,第一次for循环,Sark的Meta Class与[Sark class]不等,第二次for循环,Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等。第三次for循环,NSObject Meta Class的super class指向的是NSObject Class,和 Sark Class 不相等。第四次循环,NSObject Class 的super class 指向 nil, 和 Sark Class不相等。第四次循环之后,退出循环,所以第三行的res3输出为NO。

如果把这里的Sark改成它的实例对象,[sark isKindOfClass:[Sark class],那么此时就应该输出YES了。因为在isKindOfClass函数中,判断sark的isa指向是否是自己的类Sark,第一次for循环就能输出YES了。

isMemberOfClass的源码实现是拿到自己的isa指针和自己比较,是否相等。
第二行isa 指向 NSObject 的 Meta Class,所以和 NSObject Class不相等。第四行,isa指向Sark的Meta Class,和Sark Class也不等,所以第二行res2和第四行res4都输出NO。

**+ (BOOL)isKindOfClass:(Class)cls ** 寻找元类,以及元类的父类进行比较,这里注意,元类的最后父类是Root (NSObject ) root 指向nil
**+ (Class)class ** 返回的是self, 是对象自己。
**- (BOOL)isKindOfClass:(Class)cls **  比较的是class 不是元类
**+ (BOOL)isMemberOfClass:(Class)cls**, 直接比较

(三)Class与内存地址

下面的代码会?Compile Error / Runtime Crash / NSLog…?

这个有点难呀,感觉回到了高中考试一样。

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end
@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
}
@end

这道题有两个难点。难点一,obj调用speak方法,到底会不会崩溃。难点二,如果speak方法不崩溃,应该输出什么?

首先需要谈谈隐藏参数self和_cmd的问题。
当[receiver message]调用方法时,系统会在运行时偷偷地动态传入两个隐藏参数self和_cmd,之所以称它们为隐藏参数,是因为在源代码中没有声明和定义这两个参数。self在上面已经讲解明白了,接下来就来说说_cmd。_cmd表示当前调用方法,其实它就是一个方法选择器SEL。

难点一,能不能调用speak方法?

id cls = [Sark class]; 
void *obj = &cls;

答案是可以的。obj被转换成了一个指向Sark Class的指针,然后使用id转换成了objc_object类型。obj现在已经是一个Sark类型的实例对象了。当然接下来可以调用speak的方法。

调用 这个方法,发生了什么
第一步

id cls = [Sark class];  

见图


image

在栈上生成cls 而 Dark Class 在堆上

第二步

void *obj = &cls;

见图


image

这张图
这里obj 指向了cls 栈上的地址。

难点二,如果能调用speak,会输出什么呢?
很多人可能会认为会输出sark相关的信息。这样答案就错误了。
正确的答案会输出

my name is <ViewController: 0x7ff6d9f31c50>

内存地址每次运行都不同,但是前面一定是ViewController。why?

正常我们想要调用speak 方法流程是这样的

    Dark * dark = [[Dark alloc]init];
    [dark speak];

看内存变化
第一步

Dark * dark = [[Dark alloc]init];
image

我们会在堆区生成一个Dark对象,我们在栈上有dark 对象的引用

当我们调用 [dark speak];的时候,我们从栈上的dark 中取出地址,找到堆上的** Dark Object** dark 对象有方法表引用,在从方法表中找到speak 方法。(变量都是在堆上保存的)

而题目中上述例子,

  [(__bridge id)obj speak];

流程应该和dark 生成对象一样。
我们找到 obj内存中的id cls(是在栈上的)。
这里还有两个知识点

实质:Objc中的对象是一个指向ClassObject地址的变量,即 id obj = &ClassObject , 而对象的实例变量 void *ivar = &obj + offset(N)

oc 方法调用的时候,我们都知道有两个默认参数,self ,_cmd.

为什么是打印的是ViewController 呢?看大神博客吧。讲 的太详细了。(我就是把我刚看一脸懵逼的地方给重新梳理了下)

源代码地址

借鉴文章
借鉴文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容