主頁 > 移動端開發 > iOS底層原理(一)Objective-C的本質

iOS底層原理(一)Objective-C的本質

2021-03-06 07:31:14 移動端開發

我們平時撰寫的Objective-C代碼,底層實作其實都是C\C++代碼,所以Objective-C的面向物件都是基于C\C++的資料結構實作的

OC物件的本質

Objective-C的物件、類主要是基于C\C++的結構體實作的

通過下面的命令可以將OC代碼轉換為C++代碼來查看

clang -rewrite-objc OC源檔案 -o 輸出的CPP檔案

由于Clang會根據不同平臺轉換的C++代碼有所差異,所以針對iOS平臺用下面的命令來轉換

// 意為:通過Xcode運行iPhone平臺arm64架構,重寫OC檔案到C++檔案
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源檔案 -o 輸出的CPP檔案

如果需要鏈接其他框架,使用-framework引數,比如-framework UIKit

凡是繼承自NSObject的物件,都會自帶一個型別是Class的isa的成員變數,將其轉成C++,就可以看到NSObject本質上是一個叫做NSObject_IMPL的結構體,其成員變數isa本質上也是一個指向objc_class結構體的指標

OC物件的記憶體布局

一個OC物件在記憶體中的布局是這樣的,系統會在堆中開辟一塊記憶體空間存放該物件,這塊空間里還包含成員變數和isa指標,然后堆疊里的區域變數指向這塊存盤空間的地址

OC物件的記憶體占用大小

系統會給NSObject物件自動分配16個位元組的記憶體,而NSObject物件實際只占用了8個位元組的記憶體,這8個位元組的大小就是成員變數isa指標的大小,多余的8個位元組是系統為了記憶體對齊而分配的

// 獲取實體物件的記憶體大小,實際是獲取物件成員變數的記憶體大小
#import <objc/runtime.h>class_getInstanceSize([NSObject class]);// 獲取實體物件的記憶體大小,實際是獲取系統真正分配了多少記憶體#import <malloc/malloc.h>malloc_size((__bridge const void *)obj);


NSObject *obj = [[NSObject alloc] init];
        
// 獲得NSObject實體物件的成員變數所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
   
// 獲得obj指標所指向記憶體的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));

驗證方法

1.原始碼驗證

下載蘋果開源框架 https://opensource.apple.com/tarballs/objc4/

選擇最大版本下載

在頭檔案objc-runtime-new.h中找到對應代碼

 inline size_t instanceSize(size_t extraBytes) const {
   if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
       return cache.fastInstanceSize(extraBytes);
   }

   size_t size = alignedInstanceSize() + extraBytes;
   // CF requires all objects be at least 16 bytes.
   // 只要小于16個位元組都會被賦值16
   if (size < 16) size = 16;
   return size;
}
2.記憶體驗證

運行Xcode,選擇Debug->Debug Workflow -> View Memory查看記憶體資料

輸入obj的記憶體地址可以看到只有前8個位元組有值,但已經分配了16個位元組的記憶體空間

3.LLDB列印驗證

利用LLDBmemory read讀取物件的記憶體地址,可以看到也是分配的16個位元組

OC物件的分類

OC物件主要分為三種

  • instance物件(實體物件)
  • class物件(類物件)
  • meta-class物件(元類物件)

instance物件

instance物件就是通過類alloc出來的物件,每次呼叫alloc都會產生新的instance物件

// object1、object2是NSObject的instance物件(實體物件)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

// 通過列印可以看出,它們是不同的兩個物件,分別占據著兩塊不同的記憶體
NSLog(@"instance - %p %p",
	    object1,
	    object2);

instance物件在記憶體中存盤的資訊

  • isa指標
  • 其他成員變數的具體值

class物件

每個類在記憶體中有且只有一個class物件

Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];

// 通過列印可以看出,上面幾個方法回傳的都是同一個類物件,記憶體地址都一樣
NSLog(@"class - %p %p %p %p %p %d",
		objectClass1,
		objectClass2,
		objectClass3,
		objectClass4,
		objectClass5);

注意:class方法回傳的一直是類物件,所以哪怕這樣寫還是會回傳類物件

Class objectMetaClass2 = [[[NSObject class] class] class];

class物件在記憶體中存盤的資訊

  • isa指標- superclass指標- 類的屬性資訊(@property)、類的物件方法資訊(instance method)- 類的協議資訊(protocol)、類的成員變數資訊(ivar)
  • ....

meta-class物件

objectMetaClass是NSObject的meta-class物件(元類物件),每個類在記憶體中有且只有一個meta-class物件

Class objectMetaClass = object_getClass(objectClass5);

meta-class物件和class物件的記憶體結構是一樣的,但是用途不一樣,在記憶體中存盤的資訊主要包括

  • isa指標- superclass指標- 類的類方法資訊(class method)
  • ....

使用class_isMetaClass(Class _Nullable cls)來查看Class是否為meta-class的方法

NSLog(@"objectMetaClass - %p %d", objectMetaClass, class_isMetaClass(objectMetaClass));

isa和superclass

每個類的實體物件、類物件、元類物件都有一個isa指標

  • instance的isa指向class - 當呼叫物件方法時,通過instance的isa找到class,最后找到物件方法的實作進行呼叫

  • class的isa指向meta-class - 當呼叫類方法時,通過class的isa找到meta-class,最后找到類方法的實作進行呼叫

  • meta-class的isa指向基類的meta-class每個類的類物件、元類物件都有一個superclass指標

  • class的superclass指標指向父類的class

    • 如果沒有父類,superclass指標為nil
  • meta-class的superclass指向父類的meta-class

    • 基類的meta-class的superclass指向基類的class

instance呼叫物件方法的軌跡

  • isa找到class,方法不存在,就通過superclass找父類

class呼叫類方法的軌跡

  • isa找meta-class,方法不存在,就通過superclass找父類

Class型別的底層結構

我們可以從原始碼objc-runtime-new.h檔案中找到Class型別的本質是結構體objc_class型別,里面包含了superclass指標、cache方法快取,以及獲取具體的類資訊的class_data_bits_t型別的屬性表

struct objc_class : objc_object {
    // Class ISA;
    // superclass指標
    Class superclass;
    // 方法快取
    cache_t cache;             // formerly cache pointer and vtable
    // 用于獲取具體的類資訊
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
    // rw意為readwrite,可讀可寫,t意為table,表格
    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
}

繼承的父類objc_object里有一個isa指標

// 繼承的父類結構體里面有一個isa指標
struct objc_object {
private:
	isa_t isa;
	
public:

    Class ISA(bool authenticated = false);
    Class rawISA();
    Class getIsa();
    uintptr_t isaBits() const;
    
    ....
};

分析class_data_bits_t這個型別里面的結構可以看出,bits & FAST_DATA_MASK就可以得到class_rw_t型別的表的記憶體

// class_data_bits_t結構體里的具體分析
struct class_data_bits_t {
    friend objc_class;

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
}

分析class_rw_t這個型別里面的結構可以看出,里面有方法串列、屬性串列、協議串列,以及class_ro_t型別的屬性表

// class_rw_t結構體里的具體分析
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

// ro意為readonly,只讀
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);
}

void set_ro(const class_ro_t *ro) {
   auto v = get_ro_or_rwe();
   if (v.is<class_rw_ext_t *>()) {
       v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
   } else {
       set_ro_or_rwe(ro);
   }
}

// 方法串列
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};
   }
}

分析class_ro_t這個型別的結構可以看出,instanceSize意為實體物件所占用的記憶體空間,name存盤的是類名,ivars存盤的成員變數列表

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    // 實體物件占用記憶體大小空間
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

	// 類名
	explicit_atomic<const char *> name;
	void *baseMethodList;
    protocol_list_t * baseProtocols;
    // 成員變數串列
    const ivar_list_t * ivars;
}

總結:

上述分析可以簡單用一張圖來概述

isa指標

在arm64架構之前,isa就是一個普通的指標,存盤著Class、Meta-Class物件的記憶體地址

從arm64架構開始,對isa進行了優化,變成了一個isa_t型別的共用體(union)結構,共用體就是多種資料結構都共用同一塊存盤空間,里面包含了bits、cls、ISA_BITFIELD結構體以及其他的函式或變數,它們都是共用同一塊記憶體空間的

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;
    
private:
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD; // 現在的版本用一個宏來定義 
    };
}

isa.h中查看ISA_BITFIELD這個結構體,里面的每一個值都是位域,不同架構下的掩碼和位域都是不一樣的,我們只以arm64架構的來分析

// 在isa.h中查看ISA_BITFIELD
// 每個變數后面標的數字就是位域
// 類似ISA_MASK這種宏的都叫掩碼

# if __arm64__
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
	....
# else
#   error unknown architecture for packed isa
# endif

// SUPPORT_PACKED_ISA
#endif

每一位位域對應的二進制位的排序都是從右向左的,下面是對應的每個位域的含義

上述代碼里類似ISA_MASK這樣的值都是掩碼,以掩碼ISA_MASK為例,轉成二進制發現對應是1的部分都是用來取值的

而且一共有33位的1,正好對應著shiftcls這個位域的位數,shiftcls又是存盤著類物件和元類物件的地址值,那么就能說明在arm64架構之后的isa里存盤著更多的資訊,需要&ISA_MASK進行一次位運算之后才能將類物件和元類物件的真實地址值取出來

位運算的運用實體

利用共用體和位運算來優化屬性的記憶體空間

創建Person.h檔案,然后手動實作setter和getter

@interface Person : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;

@end

利用共用體的本質,在Person.m的類擴展中創建一個私有的共用體型別的變數

@interface Person()
{
    union {
        char bits;
        
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    } _tallRichHandsome;
}
@end

該共用體一共只占有1個位元組,都是根據char bits來分配大小的,而sturct結構體是對這1個位元組大小的占用做說明的,里面每一個的1就是位域,指明占用了1個二進制位,雖然是char型別的,但都是根據位域后面給定的值來確定實際占用大小的,tall、rich、handsome三個變數都是占用著同一個記憶體區域,也就是值都會存盤在一個位元組里,這就是共用體的本質,這么做主要是為了做優化,節省記憶體空間,而且寫不寫這個結構體都是根據char bits來確定了分配空間大小的,沒有影響的

由于上述結構體里的三個變數占用一個位元組大小就足夠了,那么我們對應每一個變數用一個二進制位來存取值,我們先分別設定三個掩碼對應三個值

// 0x0000 0001
#define TallMask (1<<0)
// 0x0000 0010
#define RichMask (1<<1)
// 0x0000 0100
#define HandsomeMask (1<<2)

setter的實作如下,如果引數為YES,那么將掩碼進行按位或運算;如果引數為NO,那么先將掩碼取反,然后再進行按位與運算

@implementation Person

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= TallMask;
    } else {
        _tallRichHandsome.bits &= ~TallMask;
    }
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= RichMask;
    } else {
        _tallRichHandsome.bits &= ~RichMask;
    }
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= HandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~HandsomeMask;
    }
}

@end

getter的實作如下,先將掩碼進行按位與運算,然后再取反兩次;因為回傳值是BOOL型別,那么不是0就是1,所以按位與運算后的值只要不是0的都是有值的,那么取反兩次肯定就得到的不是0就是1了

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & TallMask);
}

- (BOOL)isRich
{
    return !!(_tallRichHandsome.bits & RichMask);
}

- (BOOL)isHandsome
{
    return !!(_tallRichHandsome.bits & HandsomeMask);
}

如此一來,我們就做到了優化了屬性的記憶體空間,而且也實作了setter和getter

利用位運算進行位移列舉的實作

創建一個位移列舉,每一個值都對應一個二進制位

typedef enum {
	OptionsNone = 0,    // 0b0000
  	OptionsOne = 1<<0,   // 0b0001
	OptionsTwo = 1<<1,   // 0b0010
  	OptionsThree = 1<<2, // 0b0100
  	OptionsFour = 1<<3   // 0b1000
} Options;

和對應的列舉值進行按位與運算,就能得到是否存在該列舉值

@implementation ViewController

- (void)setOptions:(Options)options
{
    if (options & OptionsOne) {
        NSLog(@"包含了OptionsOne");
    }
    
    if (options & OptionsTwo) {
        NSLog(@"包含了OptionsTwo");
    }
    
    if (options & OptionsThree) {
        NSLog(@"包含了OptionsThree");
    }
    
    if (options & OptionsFour) {
        NSLog(@"包含了OptionsFour");
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
	[self setOptions: OptionsOne | OptionsFour];
}

@end

面試題

1.一個NSObject物件占用多少記憶體?

系統分配了16個位元組給NSObject物件(通過malloc_size函式獲得)
但NSObject物件內部只使用了8個位元組的空間(64bit環境下,可以通過class_getInstanceSize函式獲得)

2.看下面代碼,分別描述Person和Student對應的記憶體占用

@interface Person : NSObject
{
    int _height;
}
@end

@interface Student : Person
{
    int _weight;
}
@end

Person *p = [[Person alloc] init];               
NSLog(@"%zd %zd", class_getInstanceSize([Person class]), // 16
              malloc_size((__bridge const void *)(p))); // 16
        
Student *s = [[Student alloc] init];
NSLog(@"%zd %zd", class_getInstanceSize([Student class]), // 16
              malloc_size((__bridge const void *)(s))); // 16

默認在64bit處理器下,由于Person繼承自NSObject,所以根據記憶體對齊,系統給NSObject物件分配了16個位元組存放isa指標,Person的成員變數height由于是Int型別,占用4個位元組,因為isa指標實際只占用了8個位元組,還有多余的8個位元組空間,所以無需再多分配記憶體,那么Person的實際占用和系統分配都是16個位元組(記憶體對齊一般以成員變數占比最大的倍數來增加:isa指標占用8個位元組,占用最大,所以是8的倍數)

Student繼承自Person,isa指標和成員變數height實際占用了12個位元組,還有多余的4個位元組,而成員變數weight正好又占用4個位元組,那么也不用再分配更多的記憶體空間,Stuent物件的實際占用和系統分配也都是16個位元組

3.看下面代碼,描述Person的記憶體占用

@interface Person : NSObject
{
    int _age;
    int _height;
    int _no;
}
@end

Person *p = [[Person alloc] init];               
NSLog(@"%zd %zd", class_getInstanceSize([Person class]), // 24
              malloc_size((__bridge const void *)(p))); // 32

默認在64bit處理器下,由于Person繼承自NSObject,里面的isa指標實際占用了8個位元組,而Person里面有三個Int型別的成員變數,實際占用是12個位元組,由于結構體的記憶體對齊原則,系統要分配24個位元組(也就是3倍的isa指標的8個位元組)才能容納所有的成員變數,所以Person物件的實際占用為24個位元組,

但系統本身都會以16的倍數來進行記憶體分配,所以要分配大于實際占用位元組的兩倍才可以,所以Person物件的系統分配為分配32個位元組

4.看下面代碼,簡述Student的物件方法呼叫軌跡,然后分別注釋掉 + (void)test 方法和 - (void)test 方法后會怎樣呼叫

@interface NSObject (Test)

+ (void)test;
- (void)test;

@end

@implementation NSObject (Test)

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

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

@interface Person : NSObject

+ (void)test;
- (void)test;
@end

@interface Student : Person

+ (void)test;
- (void)test;
@end


 Student *s = [[Student alloc] init];
[s test];
[Student test];

1.[s test] 這個方法呼叫,首先Student的實體物件會根據isa指標去Student的類物件里面查找- (void)test方法,如果找到了則呼叫該方法,如果沒找到,那么就根據superclass指標去父類Person的類物件里查找,如果找到了則呼叫Person的- (void)test方法,如果沒找到,那么就根據superclass指標去基類NSObject的類物件里查找,如果找到了則呼叫NSObject的- (void)test方法,

如果注釋掉了NSObject的- (void)test方法,那么Student實體物件在基類NSObject的類物件里也找不到該方法,由于NSObject類物件的superclass指標指向nil,那么就會crash

2.[Student test] 這個方法呼叫,首先Student的類物件會根據isa指標去Student的元類物件里查找+ (void)test方法,如果找到了則呼叫該方法,如果沒找到,那么就根據superclass指標去父類Person的元類物件里查找,如果找到了則呼叫Person的+ (void)test方法,如果沒找到,那么就根據superclass指標去基類NSObject的元類物件里查找,如果找到了則呼叫NSObject的+ (void)test方法,

如果注釋掉了NSObject的+ (void)test方法,那么Student的類物件在基類NSObject的元類物件里也找不到該方法,由于NSObject元類物件的superclass指標指向NSObject的類物件,所以就會呼叫NSObject類物件的- (void)test方法,

如果NSObject的兩個方法都注釋掉了,那么由于上一步的邏輯會去NSObject類物件里呼叫- (void)test方法,該方法也找不到,那么NSObject類物件的superclass指標是指向nil的,最后還是會crash

iOS的訊息機制本質就是訊息呼叫,所以不會真的區分類方法和物件方法,都是根據方法名進行查找

5.isMemberOfClass、isKindOfClass、isSubclassOfClass的區別,并說下原理

我們先通過一段代碼列印可以得知

Person *person = [[Person alloc] init]; // Person物件
NSObject *obj = [[NSObject alloc] init]; // NSObject物件
   
Class person_class = [person class]; // Person類物件
Class obj_class = [obj class]; // NSObject類物件
   
Class person_meta_class = object_getClass(person_class); // Person元類物件
Class obj_meta_class = object_getClass(obj_class); // NSObject元類物件
   
Class person_meta_meta_class = object_getClass(person_meta_class); // NSObject元類物件
Class obj_meta_meta_class = object_getClass(obj_meta_class); // NSObject元類物件
   
// Person物件, NSObject物件, Person類物件,NSObject類物件
NSLog(@"%@, %@, %@, %@", person, obj, person_class, obj_class);
// Person元類物件, NSObject元類物件, NSObject元類物件,NSObject元類物件
NSLog(@"%@, %@, %@, %@", person_meta_class, obj_meta_class, person_meta_meta_class, obj_meta_meta_class);

isMemberOfClass

我們在objc4原始碼的NSObject.mm里可以看到,isMemberOfClass的類方法會拿到isa指標所指的物件和傳進來的型別做比較;物件方法會拿當前類物件來做比較

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

我們可以通過一段代碼列印來分析比較

// Person類物件, Person類物件
NSLog(@"%d", [person isMemberOfClass:person_class]); // 1
// Person類物件, NSObject類物件
NSLog(@"%d", [person isMemberOfClass:obj_class]); // 0
// NSObject類物件, NSObject類物件
NSLog(@"%d", [obj isMemberOfClass:obj_class]); // 1
   
// Person元類物件, Person類物件
NSLog(@"%d", [person_class isMemberOfClass:person_class]); // 0
// Person元類物件, NSObject類物件
NSLog(@"%d", [person_class isMemberOfClass:obj_class]); // 0
// NSObject元類物件, NSObject類物件
NSLog(@"%d", [obj_class isMemberOfClass:obj_class]); // 0
   
// Person元類物件, Person元類物件
NSLog(@"%d", [person_class isMemberOfClass:person_meta_class]); // 1
// Person元類物件, NSObject元類物件
NSLog(@"%d", [person_class isMemberOfClass:obj_meta_class]); // 0
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [obj_class isMemberOfClass:obj_meta_class]); // 1
   
// 所有型別的元類物件的isa指標都指向NSObject的元類物件,包括NSObject的元類物件自己

// NSObject元類物件, Person元類物件
NSLog(@"%d", [person_meta_class isMemberOfClass:person_meta_class]); // 0
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [person_meta_class isMemberOfClass:obj_meta_class]); // 1
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [obj_meta_class isMemberOfClass:obj_meta_class]); // 1

isKindOfClass

isKindOfClass的類方法會拿到isa指標所指向的物件以及該物件的superclass指標所指向的物件和傳進來的型別做比較;物件方法會拿當前類物件以及該物件的superclass指標所指向的物件來做比較

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

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

我們可以通過一段代碼列印來分析比較

// Person類物件, Person類物件
NSLog(@"%d", [person isKindOfClass:person_class]); // 1
// Person類物件, NSObject類物件
NSLog(@"%d", [person isKindOfClass:obj_class]); // 1
// NSObject類物件, NSObject類物件
NSLog(@"%d", [obj isKindOfClass:obj_class]); // 1
    
// Person元類物件, Person類物件
NSLog(@"%d", [person_class isKindOfClass:person_class]); // 0
// Person元類物件的superclass指向NSObject元類物件,而NSObject元類物件的superclass指向的就是NSObject類物件
// Person元類物件, NSObject類物件
NSLog(@"%d", [person_class isKindOfClass:obj_class]); // 1
// NSObject元類物件, NSObject類物件
NSLog(@"%d", [obj_class isKindOfClass:obj_class]); // 1
    
// Person元類物件, Person元類物件
NSLog(@"%d", [person_class isKindOfClass:person_meta_class]); // 1
// Person元類物件, NSObject元類物件
NSLog(@"%d", [person_class isKindOfClass:obj_meta_class]); // 1
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [obj_class isKindOfClass:obj_meta_class]); // 1
    
// NSObject元類物件, Person元類物件
NSLog(@"%d", [person_meta_class isKindOfClass:person_meta_class]); // 0
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [person_meta_class isKindOfClass:obj_meta_class]); // 1
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [obj_meta_class isKindOfClass:obj_meta_class]); // 1

isSubclassOfClass

isSubclassOfClass的類方法會拿到當前類物件以及superclass指標所指向的對象和傳進來的型別做比較;該方法沒有物件方法

+ (BOOL)isSubclassOfClass:(Class)cls {
    for (Class tcls = self; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

我們可以通過一段代碼列印來分析比較

// Person類物件, Person類物件
NSLog(@"%d", [person_class isSubclassOfClass:person_class]); // 1
// Person類物件, NSObject類物件
NSLog(@"%d", [person_class isSubclassOfClass:obj_class]); // 1
// NSObject類物件, NSObject類物件
NSLog(@"%d", [obj_class isSubclassOfClass:obj_class]); // 1
    
// Person類物件, MJPerson元類物件
NSLog(@"%d", [person_class isSubclassOfClass:person_meta_class]); // 0
// Person類物件, NSObject元類物件
NSLog(@"%d", [person_class isSubclassOfClass:obj_meta_class]); // 0
// NSObject類物件, NSObject元類物件
NSLog(@"%d", [obj_class isSubclassOfClass:obj_meta_class]); // 0
    
// Person元類物件, Person元類物件
NSLog(@"%d", [person_meta_class isSubclassOfClass:person_meta_class]); // 1
// Person元類物件, NSObject元類物件
NSLog(@"%d", [person_meta_class isSubclassOfClass:obj_meta_class]); // 1
// NSObject元類物件, NSObject元類物件
NSLog(@"%d", [obj_meta_class isSubclassOfClass:obj_meta_class]); // 1
    
// Person元類物件, Person類物件
NSLog(@"%d", [person_meta_class isSubclassOfClass:person_class]); // 0
// Person元類物件, NSObject類物件
NSLog(@"%d", [person_meta_class isSubclassOfClass:obj_class]); // 1
// NSObject元類物件, NSObject類物件
NSLog(@"%d", [obj_meta_class isSubclassOfClass:obj_class]); // 1

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/266632.html

標籤:其他

上一篇:Android Studio中Button下的onClick失效

下一篇:Flutter 2.0 環境搭建 for windows

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more