isa指针

867 阅读7分钟

1.isa,superClass总结

isa

isa和superClass总结

2.实例

实例1

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

// MJPerson
@interface MJPerson : NSObject
{

    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation MJPerson
- (void)personInstanceMethod
{
    
}
+ (void)personClassMethod
{
    
}

@end

// MJStudent
@interface MJStudent : MJPerson
{

    int _weight;
}
@property (nonatomic, assign) int height;

- (void)studentInstanceMethod;
+ (void)studentClassMethod;

@end

@implementation MJStudent

- (void)studentInstanceMethod
{
    
}
+ (void)studentClassMethod
{
    
}

@end



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJStudent *stu =[[MJStudent alloc]init];
        
        [stu studentInstanceMethod];
        
        //stu对象不保存对象方法,通过isa指针找到class对象,找到对象方法。
        //这个方法底层实现:objc_msgSend(stu, @selector(studentInstanceMethod));
        //C/C++源码: objc_msgSend(stu,Sel_registerName("studentInstanceMethod"));
        //为啥不直接[MJStudent studentInstanceMethod]; ? 语法设计问题,这样不能体现面对对象思想
        
        [MJStudent studentClassMethod];
        
        //MJStudent的class对象没有类方法,通过isa指针找到元类的类方法
        //底层实现类似:objc_msgSend([MJStudent class], @selector(studentClassMethod));
        

        [MJStudent personClassMethod];
        //底层实现类似:objc_msgSend([MJStudent class], @selector(personClassMethod));
        //MJStudent的class对象没有personClassMethod方法,通过superClass指针找到MJPerson的class对象,MJPerson的class对象,通过isa指针找到MJPerson的元类方法,从而找到personClassMethod。从而给[MJStudent class],发送“personClassMethod”消息
        
       
        
    }
    return 0;
}
  • [stu studentInstanceMethod];

    1. stu对象不保存对象方法,通过isa指针找到class对象,找到对象方法。
    2. 这个方法底层实现:objc_msgSend(stu, @selector(studentInstanceMethod));
    3. C/C++源码: objc_msgSend(stu,Sel_registerName("studentInstanceMethod"));
    4. 为啥不直接[MJStudent studentInstanceMethod]; ? 语法设计问题,这样不能体现面对对象思想.
    5. 生成C/C++代码,看上面这部分代码的本质。有NSLog会报错,因为是Foundation框架里的。需要链接Foundation框架。
  • [MJStudent studentClassMethod];

    1. MJStudent的class对象没有类方法,通过isa指针找到元类的类方法
    2. 底层实现类似:objc_msgSend([MJStudent class], @selector(studentClassMethod));
  • [MJStudent personClassMethod];

    1. 底层实现类似:objc_msgSend([MJStudent class], @selector(personClassMethod));
    2. MJStudent的meta-class对象没有personClassMethod方法,通过superClass指针找到MJPerson的meta-class对象,从而找到personClassMethod。从而给[MJStudent class],发送“personClassMethod”消息。
  • 注意:[MJStudent personClassMethod];

    MJStudent的class对象,找到MJPerson的class对象,又找到MJPerson的meta-class对象,但是最好并不是MJPerson的meta-class对象调用personClassMethod方法,从而给[MJStudent class],发送“personClassMethod”消息。

  • 如果找到基类也没找到方法,会发生什么呢?会抛出经典的错误:unrecognized selector sent to instance

  • [MJStudent abc]如果基类没有+abc方法,会找到基类的-abc方法。(基类的meta-class的superClass指针指向基类的class对象)

实例2

#import <Foundation/Foundation.h>

@interface NSObject (Test)

+ (void)test;

@end

#import "NSObject+Test.h"

@implementation NSObject (Test)

+ (void)test
{
    NSLog(@"+[NSObject test] - %p", self);
}

- (void)test
{
    NSLog(@"-[NSObject test] - %p", self);
}

@end



@interface MJPerson : NSObject

+ (void)test;

@end

@implementation MJPerson

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        NSLog(@"[MJPerson class] - %p", [MJPerson class]);
        NSLog(@"[NSObject class] - %p", [NSObject class]);
        
        [MJPerson test];
//        objc_msgSend([MJPerson class], @selector(test))
        
        [NSObject test];
//        objc_msgSend([NSObject class], @selector(test))
    }
    return 0;
}

  • [MJPerson test];打印结果显示self的地址是[MyPerson class]的地址,虽然调的是NSObject的+test,但是self并不是说一定是NSObject对象。因为是给[MJPerson class]发生消息。
  • [Super xxx].涉及到运行时,消息机制。后面runtime再讲。
  • 把NSObject的+test方法去掉,换成-test方法。最后发现会调用-test方法。因为本质发送消息,不会区分是类方法还是对象方法。MJPerson的class对象 --> isa --> meta-class --> superClass --> NSObject的meta-class --> superClass --> NSObject的class对象,然后找到-test方法.

2.isa细节

instance的isa指针指向了class对象,那么isa指针存放的地址值就是class对象的地址吗?

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// MJPerson
@interface MJPerson : NSObject
{
    @public
    int _age;
}

@end

@implementation MJPerson

@end

// MJStudent
@interface MJStudent : MJPerson
{
    @public
    int _weight;
}

@end

@implementation MJStudent

@end

struct mj_objc_class {
    Class isa;
    Class superclass;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        
            
        MJPerson *person = [[MJPerson alloc] init];

        struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);
        
        Class personMetaClass = object_getClass((__bridge id _Nullable)(personClass));

        NSLog(@"%p %p %p", person, personClass, personMetaClass);
        

        MJStudent *student = [[MJStudent alloc] init];
        
        struct mj_objc_class *studentClass = (__bridge struct mj_objc_class *)([MJStudent class]);


        NSLog(@"stu:%p__%p",student,studentClass);

        
    }
    return 0;
}


1.上图点击1处右键,选择print可以打印这个对象,但是打印的不是具体内存地址。p (long)person->isa 打印的isa的值(十进制), p/x打印的是16进制。

2.isa指针的值并不是指向的class对象或者meta对象的值。因为64位设备以后,isa指针的值经过,ISA_MASK位运算,即 isa & ISA_MASK为指向的值。

  1. ISA_MASK的值在objc源码里有。

  2. superClass并不需要位运算,直接就是指向的对象地址值。

class和meta-class的结构

源码:

class和meta-class结构都是一样的,因为都是Class.

点击Class,点进去看一下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

里面确实有一些方法列表,缓存,协议列表等,但是有个条件, !OBJC2才会有这个东西,而且OBJC2_UNAVAILABLE,OBJC2后这个结构体已经不可用了。

那么我们来看源码: 源码在objc4里。

搜索struct objc_class.找最新的在objc-runtime-new.h里。(objc-runtime-old里也有)

struct objc_class : objc_object {
    // Class ISA; 继承自objc_object
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    
    ...
    
}
    
  • 发现这个结构体继承自objc_object。这是C++代码,C++代码是可以继承的。
  • 搜索 “struct objc_object {”
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
...
}

发现objc_object成员变量只有一个isa指针。其他的都是方法。

  • class_rw_t 是只读的表。
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro; // ro是readonly

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ...
}

发现里面有calss_ro_t和方法、属性、协议。

class_data_bits_t bits;   

class_rw_t *data() { 
        return bits.data();
}

class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
}

bits是类的信息,bits与FAST_DATA_MASK进行异或,获得表的内存地址。

  • class_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

1. 有instanceSize,就是class_getInstanceSize方法获取的成员变量的内存大小。
2. char *name 类名
3. ivar_list_t * ivars。成员变量

总结如下:

窥看在内存中的结构

如果我们要窥看在内存中的结构要怎么做呢?就要造一个与源码struct objc_class类似的结构。

代码如下:

MJClassInfo.h 文件。(不编译MJClassInfo.h)

#import <Foundation/Foundation.h>

#ifndef MJClassInfo_h
#define MJClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

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

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 属性列表
    const protocol_list_t * protocols;  // 协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC对象 */
struct mj_objc_object {
    void *isa;
};

/* 类对象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* MJClassInfo_h */

main.mm文件。因为main.m是OC文件,无法识别c++语法,要识别c++语法,必须改成.mm。变成objective-c++语法。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MJClassInfo.h"

// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
@public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation MJPerson

- (void)test
{
    
}

- (void)personInstanceMethod
{
    
}
+ (void)personClassMethod
{
    
}
- (id)copyWithZone:(NSZone *)zone
{
    return nil;
}
@end

// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
    int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation MJStudent
- (void)test
{
    
}
- (void)studentInstanceMethod
{
    
}
+ (void)studentClassMethod
{
    
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
    return nil;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    
}
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJStudent *stu = [[MJStudent alloc] init];
        stu->_weight = 10;
        
        mj_objc_class *studentClass = (__bridge mj_objc_class *)([MJStudent class]);//C++的结构体可以省略 struct
        mj_objc_class *personClass = (__bridge mj_objc_class *)([MJPerson class]);
        
        class_rw_t *studentClassData = studentClass->data();
        class_rw_t *personClassData = personClass->data();
        
        class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
        class_rw_t *personMetaClassData = personClass->metaClass()->data();

        NSLog(@"1111");
    }
    return 0;
}

面试题

  1. 对象的isa指针指向哪里?
    instance对象的isa指向class对象
    class对象的isa指向meta-class对象
    meta-class对象的isa指向基类的meta-class对象

  2. OC的类信息存放在哪里?

    • 对象方法,属性信息,协议信息,成员变量信息,存放在class对象中。
    • 类方法,存放在meta-class对象中
    • 成员变量的具体值,存放在instance对象中。

其他问题

1.类对象和元类对象什么时候分配空间。 main函数之后就开始加载类信息,因为分别都只有一份,所以一直在内存中,程序杀死的时候释放。
2.实例对象里的成员变量和类对象成员变量区别。 实例对象里的成员变量放的是值,类对象放的是成员变量的信息。
3.如果父类的一个成员变量是私有的,子类会继承吗? 子类会继承,不过对子类透明。