OC底层原理04-类结构分析

279 阅读10分钟

本文objc源码是objc4-818,⚠️不同源码apple官方可能会有改动。

类在内存中的唯一性

通过class方法和objc_getClass方法来验证类在内存中的唯一性。我们知道

  • object_getClass(obj)

    • 返回的是obj的isa指针;
  •  [obj class]

    • obj为实例对象 例如person。调用的是实例方法- (Class)class,返回的obj对象中的isa指针;
    • obj为类对象(包括元类、和根类(根元类)) 例如LGPerson(LGPerson、LGPerson、NSObject),调用的是类方法:+ (Class)class,返回的结果为 调用者本身。
void lgTestClassNum(){
    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    Class class4 = [LGPerson alloc].class;
    NSLog(@"\nclass1-%p\nclass2-%p\nclass3-%p\nclass4-%p",class1,class2,class3,class4);
}

地址打印结果如下: image.png 通过地址打印可以验证,类在内存中的唯一性。

isa指向

本文分别以LGPerson *personNSObject *obj为例分析isa流程指向。
示例代码:

//LGPerson.h
@interface LGPerson : NSObject
{
    NSString *ageStr;
}

@property (nonatomic, copy) NSString *nikeName;

- (void)instanceMethod;
+ (void)classMethod;

@end

//LGPerson.m
@implementation LGPerson

- (void)instanceMethod{
    NSLog(@"对象方法");
}

+ (void)classMethod{
    NSLog(@"类方法");
}

@end

//main.m
LGPerson *person = [LGPerson alloc];
NSObject *obj = [NSObject alloc];

person的isa流程指向

lldb x/4gx person打印person,得到isa是0x011d800100008315 po 0x011d800100008315 & 0x00007ffffffffff8ULL 得到0x0000000100008310 LGPerson。 image.png

ISA_MASK:

  • arm64中,ISA_MASK 宏定义的值为0x0000000ffffffff8ULL
  • x86_64中,ISA_MASK 宏定义的值为0x00007ffffffffff8ULL

继续x/4gx 0x0000000100008310 得到isa是0x00000001000082e8 po 0x00000001000082e8 & 0x00007ffffffffff8ULL 得到0x00000001000082e8 po 0x00000001000082e8 还是 LGPerson。 image.png

继续x/4gx 0x00000001000082e8得到isa是0x000000010036e0f0 po 0x000000010036e0f0 & 0x00007ffffffffff8ULL 得到0x000000010036e0f0 po 0x000000010036e0f0 是 NSObject。 image.png

继续x/4gx 0x000000010036e0f0得到isa是0x000000010036e0f0 po 0x000000010036e0f0 & 0x00007ffffffffff8ULL 得到0x000000010036e0f0 po 0x000000010036e0f0是NSObject. image.png

观察打印结果,如下图所示:

image.png person的isa流程指向: LGPerson实例对象person -> 类LGPerson -> 元类LGPerson -> 元类NSObject -> 元类NSObject自己(isa指向自己)。

NSObject实例对象isa流程指向

image.png 观察NSObject实例对象isa流程指向,如下图所示: image.png NSObject实例对象obj-> 类NSObject -> 元类NSObject -> 元类NSObject自己(isa指向自己)。

isa指向总结:

  1. LGPerson实例对象personisa指向LGPerson类LGPerson类(作为对象)的isa指向LGPerson元类
  2. NSObject实例对象objisa指向NSObject类NSObject类isa指向NSObject元类NSObject元类isa指向自己。
  3. 因为绝大部分类对象继承自NSObject,所以元类NSObject又称根元类
  4. 类作为对象,同样也有方法、协议、属性。类的归属来自于元类,元类定义和创建都是由编译器自动完成(这个后续文章会解读)。

superClass继承关系指向

image.png

superclass继承关系总结

  1. 实例对象之间不存在继承关系,类或元类才有继承关系。
  2. NSObject元类继承NSObjct类,NSObjct类继承自nil。

isa指向图和superclass图

image.png

类结构分析和探索

objc_object&objc_class源码

typedef struct objc_object {
    Class isa;
} *id;

struct objc_class : objc_object {
//...
}

typedef struct objc_class *Class;

objc_class源码查看

struct objc_class : objc_object {
//...
    // Class ISA; //8字节 从objc_object继承而来
    Class superclass;//8字节
    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() const {
        return bits.data();
    }
//...
}

源码分析

image.png 大致看下objc_class的源码,推测类的信息在class_data_bits_t中,因为结构体成员内存连续,所以可以通过类的地址加上特定指针内存平移来查看源码
要获取class_data_bits_t bits的地址,需算出superclasscache的大小,以及isa(objc_object里有isa)的所占大小。
OC对象原理-对象本质和isa得知isa占8字节。
从上面objc_object&objc_class关系得知Classobjc_class类型指针,占8字节。
cache_t不知道是啥,下面看下cache_t所占大小。

cache_t所占大小

先看下cache_t的源码

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;//8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;//8
    };

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    // _bucketsAndMaybeMask is a buckets_t pointer
    // _maybeMask is the buckets mask

    static constexpr uintptr_t bucketsMask = ~0ul;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    static constexpr uintptr_t maskShift = 48;
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
    
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
    static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // _bucketsAndMaybeMask is a buckets_t pointer in the low 48 bits
    // _maybeMask is unused, the mask is stored in the top 16 bits.

    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;

    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;

    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS,
            "Bucket field doesn't have enough bits for arbitrary pointers.");

#if CONFIG_USE_PREOPT_CACHES
    static constexpr uintptr_t preoptBucketsMarker = 1ul;
#if __has_feature(ptrauth_calls)
    // 63..60: hash_mask_shift
    // 59..55: hash_shift
    // 54.. 1: buckets ptr + auth
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x007ffffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        uintptr_t value = (uintptr_t)cache->shift << 55;
        // masks have 11 bits but can be 0, so we compute
        // the right shift for 0x7fff rather than 0xffff
        return value | ((objc::mask16ShiftBits(cache->mask) - 1) << 60);
    }
#else
    // 63..53: hash_mask
    // 52..48: hash_shift
    // 47.. 1: buckets ptr
    //      0: always 1
    static constexpr uintptr_t preoptBucketsMask = 0x0000fffffffffffe;
    static inline uintptr_t preoptBucketsHashParams(const preopt_cache_t *cache) {
        return (uintptr_t)cache->hash_params << 48;
    }
#endif
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
    // _maybeMask is unused, the mask length is stored in the low 4 bits

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
    static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif

    bool isConstantEmptyCache() const;
    bool canBeFreed() const;
    mask_t mask() const;

#if CONFIG_USE_PREOPT_CACHES
    void initializeToPreoptCacheInDisguise(const preopt_cache_t *cache);
    const preopt_cache_t *disguised_preopt_cache() const;
#endif

    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);

    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    void collect_free(bucket_t *oldBuckets, mask_t oldCapacity);

    static bucket_t *emptyBuckets();
    static bucket_t *allocateBuckets(mask_t newCapacity);
    static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
    void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));

public:
    // The following four fields are public for objcdt's use only.
    // objcdt reaches into fields while the process is suspended
    // hence doesn't care for locks and pesky little details like this
    // and can safely use these.
    unsigned capacity() const;
    struct bucket_t *buckets() const;
    Class cls() const;

#if CONFIG_USE_PREOPT_CACHES
    const preopt_cache_t *preopt_cache() const;
#endif

    mask_t occupied() const;
    void initializeToEmpty();

#if CONFIG_USE_PREOPT_CACHES
    bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = (uintptr_t)&_objc_empty_cache) const;
    bool shouldFlush(SEL sel, IMP imp) const;
    bool isConstantOptimizedCacheWithInlinedSels() const;
    Class preoptFallbackClass() const;
    void maybeConvertToPreoptimized();
    void initializeToEmptyOrPreoptimizedInDisguise();
#else
    inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
    inline bool shouldFlush(SEL sel, IMP imp) const {
        return cache_getImp(cls(), sel) == imp;
    }
    inline bool isConstantOptimizedCacheWithInlinedSels() const { return false; }
    inline void initializeToEmptyOrPreoptimizedInDisguise() { initializeToEmpty(); }
#endif

    void insert(SEL sel, IMP imp, id receiver);
    void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
    void destroy();
    void eraseNolock(const char *func);

    static void init();
    static void collectNolock(bool collectALot);
    static size_t bytesForCapacity(uint32_t cap);

#if __LP64__
    bool getBit(uint16_t flags) const {
        return _flags & flags;
    }
    void setBit(uint16_t set) {
        __c11_atomic_fetch_or((_Atomic(uint16_t) *)&_flags, set, __ATOMIC_RELAXED);
    }
    void clearBit(uint16_t clear) {
        __c11_atomic_fetch_and((_Atomic(uint16_t) *)&_flags, ~clear, __ATOMIC_RELAXED);
    }
#endif

#if FAST_CACHE_ALLOC_MASK
    bool hasFastInstanceSize(size_t extra) const
    {
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        }
        return _flags & FAST_CACHE_ALLOC_MASK;
    }

    size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

    void setFastInstanceSize(size_t newSize)
    {
        // Set during realization or construction only. No locking needed.
        uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
        uint16_t sizeBits;

        // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
        // to yield the proper 16byte aligned allocation size with a single mask
        sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
        sizeBits &= FAST_CACHE_ALLOC_MASK;
        if (newSize <= sizeBits) {
            newBits |= sizeBits;
        }
        _flags = newBits;
    }
#else
    bool hasFastInstanceSize(size_t extra) const {
        return false;
    }
    size_t fastInstanceSize(size_t extra) const {
        abort();
    }
    void setFastInstanceSize(size_t extra) {
        // nothing
    }
#endif
};

static变量不占用此结构体存储空间,关键代码如下:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;//4
#if __LP64__
            uint16_t                   _flags; //2
#endif
            uint16_t                   _occupied;//2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;//8
    };
    //...
}
cache_t源码说明
_bucketsAndMaybeMask

_bucketsAndMaybeMaskuintptr_t类型。

typedef unsigned long           uintptr_t;

_bucketsAndMaybeMask占8字节。

union

下面的共用体里结构体mask_t定义如下:

#if __LP64__

typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits  //4字节
#else
typedef uint16_t mask_t;
#endif

_maybeMask占用4字节,_flags占2字节,_occupied占2字节。
_originalPreoptCachepreopt_cache_t类型指针变量占8字节。所以共用体占8字节。 综上,cache_t cache_bucketsAndMaybeMaskunion之和,占16字节。 所以class_data_bits_t bits的地址,就是类地址偏移isa(8字节)、superclass(8字节)、cache(16字节)。

class_data_bits_t

代码:

//LGPerson.h
@interface LGPerson : NSObject
{
    NSString *ageStr;
}
@property (nonatomic, copy) NSString *nikeName;

- (void)instanceMethod;
+ (void)classMethod;

@end

//LGPerson.m
@implementation LGPerson

- (void)instanceMethod{
    NSLog(@"对象方法");
}

+ (void)classMethod{
    NSLog(@"类方法");
}

@end

1. 获取class_data_bits_t bits首地址。

objc_class源码得知 x/4gx LGPerson.class 获取类的首地址0x100008310.
首地址加32字节偏移得到class_data_bits_t bits首地址.
(lldb) p (class_data_bits_t *)0x100008330p (class_data_bits_t *)(0x100008330+0x20) image.png class_data_bits_t主要源码如下:

struct class_data_bits_t {
//...
public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
//...
};

2. 获取class_rw_t类型的 data地址

(lldb) p $1->data() image.png 得到class_rw_t类型的data地址$2.
class_rw_t指针$2取值,得到class_rw_t结构体的值,即:
(lldb) p *$2 image.png class_rw_t结构体源码如下:

struct class_rw_t {
    ...
    explicit_atomic<uintptr_t> ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;

public:
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
};

由源码看到,可以调用methods()查看方法列表或者调用properties()查看属性列表或调用protocols()查看协议列表。\

查看properties()

(lldb) p $3.properties() image.png 打印是一个property_array_t结构,里面是list_array_ttproperty_array_t源码如下:

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

继承自list_array_ttlist_array_tt源码如下:

class list_array_tt {
...
private:
    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };
public:
    list_array_tt() : list(nullptr) { }
    list_array_tt(List *l) : list(l) { }
    list_array_tt(const list_array_tt &other) {
        *this = other;
    }

    list_array_tt &operator =(const list_array_tt &other) {
        if (other.hasArray()) {
            arrayAndFlag = other.arrayAndFlag;
        } else {
            list = other.list;
        }
        return *this;
    }
...
}

property_tproperty_list_t源码如下:

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

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

struct entsize_list_tt {
...
  Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
...
}

继续打印,(lldb) p $4.list,内部是ptr。
image.png 继续打印p $5.ptr,得到property_list_t指针$6. image.png

$6指针取值(lldb) p *$6 得到property_list_t. image.png property_list_t继承entsize_list_tt,上面entsize_list_tt源码中提供了get方法返回list的数据。
(lldb) p $7.get(0) image.png 查看properties()完整lldb打印如下: image.png

查看properties()结论

我们发现调用properties()可以打印出属性nikeName,但是没有成员变量。

查看methods()

调用methods() image.png 发现(lldb) p $12.get(0)method_t类型,打印是空的。以下是method_t源码:

struct method_t {
...
    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    public:
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
...
};

method_list_t源码:

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
...
}

可通过(lldb) p $12.get(0).big()打印出方法。 我是用的MacBook Pro (16-inch, 2019)型号Mac可以正常打印,M1电脑(lldb) p $12.get(0).small() image.png 或者(lldb) p $12.get(0).name() image.png

查看methods()结论

调用methods()打印出的方法没有类方法,只有实例方法。 也可以通过代码打印实例方法:

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    NSLog(@"----------------");
    NSLog(@"%s",__func__);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        NSLog(@"%@实例方法: %@", pClass,key);
    }
    free(methods);
}

查看成员变量

属性和成员变量

测试代码:

//main.m
@interface LGPerson : NSObject
{
    NSString *hobby;
    NSObject *objc;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@end

@implementation LGPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}

通过clang -rewrite-objc main.m -o main.cpp
查看main.cpp文件 image.png image.png 由编译后的cpp文件看出:
属性=带下划线的成员变量+getter方法+setter方法

通过lldb探索成员变量ivar存储位置

image.png

struct class_rw_t {
...
    const class_ro_t *ro() const {
            auto v = get_ro_or_rwe();
            if (slowpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
            }
            return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }
...
}

class_rw_t可以获取ro(),推测在这里面。继续打印
image.png

struct class_ro_t {
...
const ivar_list_t * ivars;
...
}

struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};

获取ivar,继续打印如下:
image.png 通过lldb打印,可以看到成员变量hobby、objc,以及属性加下划线的_nikeName、_name.

也可以通过代码验证:

void lgObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //获取实例变量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        LGLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //获取属性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //获取属性值
        LGLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

         LGPerson *person = [LGPerson alloc];
         Class pClass     = object_getClass(person);
         lgObjc_copyIvar_copyProperies(pClass);
    }
    return 0;
}

打印如下:
image.png

类方法的归属

我们推测:

  • 方法归属于类
  • 实例方法在类里
  • 类方法在元类里。 可通过下列3种方式验证探索:

测试代码:

//LGPerson.h
@interface LGPerson : NSObject
{
    NSString *hobby;
    NSObject *objc;
}

@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
- (void)lgPersonInstanceMethod;
+ (void)lgPersonClassMethod;
@end

//LGPerson.m
@implementation LGPerson
- (void)lgPersonInstanceMethod{
    NSLog(@"LGPerson Instance Method");
}
+ (void)lgPersonClassMethod{
    NSLog(@"LGPerson Class Method");
}
@end

//main. m
void lgInstanceMethod_classToMetaclass(Class pClass){  
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(lgPersonInstanceMethod));
    Method method2 = class_getInstanceMethod(metaClass, @selector(lgPersonInstanceMethod));

    Method method3 = class_getInstanceMethod(pClass, @selector(lgPersonClassMethod));
    Method method4 = class_getInstanceMethod(metaClass, @selector(lgPersonClassMethod));
    
    LGLog(@"----------------");
    LGLog(@"%s",__func__);
    LGLog(@"class_getInstanceMethod - 方法%@ - 在%@类中地址-%p",@"lgPersonInstanceMethod",pClass,method1);
    LGLog(@"class_getInstanceMethod - 方法%@ - 在%@元类中地址-%p",@"lgPersonInstanceMethod",pClass,method2);
    LGLog(@"class_getInstanceMethod - 方法%@ - 在%@类中地址-%p",@"lgPersonClassMethod",pClass,method3);
    LGLog(@"class_getInstanceMethod - 方法%@ - 在%@元类中地址-%p",@"lgPersonClassMethod",pClass,method4);
}

int main(int argc, const char * argv[])){
    LGPerson *person = [LGPerson alloc];
    Class pClass     = object_getClass(person);//类LGPerson
    lgInstanceMethod_classToMetaclass(pClass);
    lgClassMethod_classToMetaclass(pClass);
}
方式1: 通过lldb打印探索
首先打印LGPerson类的方法

image.png image.png 从图中打印可以看出,LGPerson类有实例方法lgPersonInstanceMethod和两个属性name、nickNamegettersetter方法。

打印LGPerson元类的方法

image.png (lldb) p 0x0000000100008380 & 0x00007ffffffffff8ULL是获取LGPerson元类image.png 由打印看到LGPerson元类只有lgPersonClassMethod类方法。

方式2: 代码验证

以上测试代码打印如下: image.png 通过打印结果我们知道: 实例方法存在结构里。类方法存在元类结构里。
class_getInstanceMethod方法源码如下:

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    ...
    return _class_getMethod(cls, sel);
}

static Method _class_getMethod(Class cls, SEL sel)
{
    ...
    return getMethod_nolock(cls, sel);
}

static method_t *
getMethod_nolock(Class cls, SEL sel)
{
    method_t *m = nil;
    ...
    while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
        cls = cls->getSuperclass();
    }

    return m;
}

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    ...
    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

源码解析:
class_getInstanceMethod会在cls->data()->methods()里遍历,没有再去父类methods()中寻找.

class_getClassMethod方法 测试代码如下:

void lgClassMethod_classToMetaclass(Class pClass){ 
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(lgPersonInstanceMethod));
    Method method2 = class_getClassMethod(metaClass, @selector(lgPersonInstanceMethod));

    Method method3 = class_getClassMethod(pClass, @selector(lgPersonClassMethod));
    Method method4 = class_getClassMethod(metaClass, @selector(lgPersonClassMethod));
    LGLog(@"----------------");
    LGLog(@"%s",__func__);
    LGLog(@"class_getClassMethod - 方法%@ - 在%@类中地址-%p",@"lgPersonInstanceMethod",pClass,method1);
    LGLog(@"class_getClassMethod - 方法%@ - 在%@元类中地址-%p",@"lgPersonInstanceMethod",pClass,method2);
    LGLog(@"class_getClassMethod - 方法%@ - 在%@类中地址-%p",@"lgPersonClassMethod",pClass,method3);
    LGLog(@"class_getClassMethod - 方法%@ - 在%@元类中地址-%p",@"lgPersonClassMethod",pClass,method4);
}

打印结果如下: image.png 根据打印看到:类和元类调用class_getClassMethod都能获取类方法。
元类为什么可以通过调用class_getClassMethod获取到类方法?我们可以看下源码实现.
class_getClassMethod源码如下:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

class_getClassMethod会调用class_getInstanceMethod,传入cls->getMeta(),接下来看下cls->getMeta()源码:

Class getMeta() {
    if (isMetaClassMaybeUnrealized()) return (Class)this;//判断是元类就返回 避免查询递归
    else return this->ISA();
}

getMeta()会先判断是不是元类,如果是元类就返回元类自己,否则会返回ISA()class_getClassMethod(LGPerson元类, 类方法lgPersonClassMethod)会返回class_getInstanceMethod(cls->getMeta(), sel)class_getInstanceMethod(LGPerson元类, 类方法lgPersonClassMethod)),可以找到,并且方法地址是一个。 这就解释了LGPerson元类会有类方法lgPersonClassMethod

方式3: 通过MachoView辅助分析类方法的归属

把编译后的Products文件夹下的Unix可执行文件拖到MachoView里查看。 Unix可执行文件长这样 image.png

MachoView百度网盘地址:pan.baidu.com/s/1hMQl6QGr…  密码: 34p5 MachoView很强大,可自行把玩。

lldb打印遇到的问题

  • error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
    报错原因:是断点时间太长或者打印出一个错误的寄存器又重新打印之前的寄存器.
    解决办法:重新运行断点调试即可。

延伸的问题

isKindOfClass:和isMemberOfClass: 方法问题

isKindOfClass:

测试代码:

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
BOOL re2 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
BOOL re3 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re4 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
NSLog(@"\n re1:%hhd re2:%hhd re3:%hhd re4:%hhd",re1,re2,re3,re4);

打印结果:re1:1 re2:0 re3:1 re4:1 isKindOfClass:源码如下:

//类方法
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); 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;
}
说明
  • re1
    • 调用类方法isKindOfClass:tcls = self->ISA()NSObject元类,即根元类cls([NSObject class])不等于根元类
    • tcls取tcls->superclass根元类的父类是NSObject类,等于cls([NSObject class]),所以return YES。
  • re2
    • 调用类方法isKindOfClass:tcls = self->ISA()LGPerson元类cls([LGPerson class])不等于LGPerson元类
    • tcls取tcls->superclassLGPerson元类的父类是根元类cls([LGPerson class])不等于根元类
    • tcls取tcls->superclass根元类父类是NSObject类cls([LGPerson class])不等于NSObject类
    • tcls取tcls->superclassNSObject类父类是nil,for循环中止,return NO。
  • re3
    • 调用实例方法isKindOfClass:tcls = [self class]NSObject类,等于cls([NSObject class]),所以return YES。
  • re4
    • 调用实例方法isKindOfClass:tcls = [self class]LGPerson类,等于cls([LGPerson class]),所以return YES。

isMemberOfClass:

测试代码:

BOOL re5 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
BOOL re6 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
BOOL re7 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@"\n re5:%hhd re6:%hhd re7:%hhd re8:%hhd",re5,re6,re7,re8);

打印结果:re5:0 re6:0 re7:1 re8:1 isMemberOfClass:源码如下:

//类方法
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
//实例方法
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
说明
  • re5
    • 调用类方法isMemberOfClass:self->ISA()NSObject元类,即根元类cls([NSObject class])不等于根元类。所以return NO。
  • re6
    • 调用类方法isMemberOfClass:self->ISA()LGPerson元类cls([LGPerson class])不等于LGPerson元类。所以return NO。
  • re7
    • 调用实例方法isMemberOfClass:[self class]NSObject类,所以return YES。
  • re8
    • 调用实例方法isMemberOfClass:[self class]LGPerson类,所以return YES。

文章列表