iOS底層探索對象的本質&NONPOINTER_ISA

1.編譯器Clang的使用

Clang是一個C++編寫、基於LLVM、發佈於LLVM BSD許可證下的C/C++/Objective-C/ Objective-C++編譯器。ios

使用Clangmain.m文件編譯成C++文件c++

clang -rewrite-objc main.m -o main.cpp 把目標文件編譯成c++文件markdown

遇到UIKit報錯問題:架構

image.png

解決clang編譯遇到UIKit報錯的兩種方式:app

  • 第一種是配置sdk版本及路徑

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.3.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk main.miphone

路徑中的系統版本按照xCode中SDK版本修改(當前爲模擬器SDK)ide

image.png

  • 第二種是使用xcrun命令

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp函數

image.png

2.對象的本質

使用clang -rewrite-objc main.m -o main.cpp命令編譯main.m文件,main.m文件內容以下:post

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

@interface ABPerson : NSObject
@property (nonatomic,strong) NSString *name;
@end

@implementation ABPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
複製代碼

main.cpp文件中的ABPerson以下:優化

struct ABPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
複製代碼
struct NSObject_IMPL {
	Class isa;
};
複製代碼
//Class就是結構體指針
typedef struct objc_class *Class;
複製代碼

因此,對象在底層的本質就是結構體

3.對象屬性底層分析

//getter方法
static NSString * _I_ABPerson_name(ABPerson * self, SEL _cmd)
{
    return (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name));
}
//setter方法
static void _I_ABPerson_setName_(ABPerson * self, SEL _cmd, NSString *name)
{
    (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)) = name;
}
複製代碼

成員變量的內存訪問是經過先拿到對象的首地址加上變量的偏移地址,再根據計算的地址訪問內存

  • (char *)self:對象的首地址
  • OBJC_IVAR_$_ABPerson$_name:成員變量name的偏移地址
  • *(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)成員變量name的內存

4. 位域

struct StructCar1 {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};
struct StructCar2 {
    BOOL front: 1;
    BOOL back: 1;
    BOOL left: 1;
    BOOL right: 1;
};
複製代碼
  • 結構體StructCar14個字節,由於其內部成員變量都是BOOL類型,其實用1個字節就能放下
  • 結構體StructCar21個字節,節省了3個字節。

Untitled Diagram.png

5. 共用體(聯合體)

image.png

  • 結構體共存:name和age都有值

image.png

  • 共用體互斥:name和age只能有一個有值,最後一個被賦值,前面的被清空。

結構體(struct)中全部變量是「共存」的,優勢是「有容乃大」,全面;缺點是結構體內存空間分配是粗放的,無論用不用全分配。

共用體(union)中是各變量是「互斥」的,缺點是不夠「包容」;優勢是內存使用更爲精細靈活,也節省了內存空間

6.NONPOINTER_ISA

ISA分爲純的ISANONPOINTER_ISANONPOINTER_ISA除了包含一個純指針外,還包含了類的一些信息。

探索alloc流程一文中,最終會經過initIsa將從堆申請的結構體指針和當前的class綁定在一塊兒。

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}
複製代碼
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);
    //省略部分代碼
 }
複製代碼
//共用體
union isa_t {
   //構造方法
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    //變量
    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};
複製代碼

ISA_BITFIELDarm64x86_64架構下的定義

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
//模擬器
#   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__
//macOS
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif
複製代碼
  • nonpointer:表示是否對 isa 指針開啓指針優化,0:純isa指針,1:不止是類對象地址,isa 中包含了類信息、對象的引用計數等
  • has_assoc:關聯對象標誌位,0沒有,1存在
  • has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象
  • shiftcls:存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位用來存儲類指針

-magic:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間

  • weakly_referenced:志對象是否被指向或者曾經指向一個 ARC 的弱變量,

沒有弱引用的對象能夠更快釋放

  • unused:是否未被使用
  • has_sidetable_rc:散列表,當對象引用技術大於 10 時,則須要借用該變量存儲進位
  • extra_rc:當表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc 爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc

ISA_BITFIELD各元素存儲分佈圖

a.png

7.ISA的位運算

經過ISA的位運算拿到shiftcls。以macOS爲例,shiftcls64位結構中的位置:右邊有3位,左邊有17位。本身包含44位。

aa.png

代碼lldb驗證

image.png 倆紅框值是相等的,說明位運算成功拿到了shiftcls

8.總結

  • 對象在底層的本質就是結構體
  • ISA是經過共用體(聯合體)互斥的特性,肯定ISA是純的ISA仍是NONPOINTER_ISA,並經過位域來實現節約空間。
相關文章
相關標籤/搜索