欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

iOS內(nèi)存管理Tagged Pointer使用原理詳解

 更新時(shí)間:2023年01月06日 09:13:08   作者:山海飛鳥  
這篇文章主要為大家介紹了iOS內(nèi)存管理Tagged Pointer使用原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋果在64bit程序中引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumberNSDateNSString等小對(duì)象的存儲(chǔ)。在引入 Tagged Pointer 技術(shù)之前,NSNumber等對(duì)象存儲(chǔ)在堆上,NSNumber的指針中存儲(chǔ)的是堆中NSNumber對(duì)象的地址值。

從內(nèi)存占用來(lái)看基本數(shù)據(jù)類型所需的內(nèi)存不大。比如NSInteger變量,它所占用的內(nèi)存是與 CPU 的位數(shù)有關(guān),如下。在 32 bit 下占用 4 個(gè)字節(jié),而在 64 bit 下占用 8 個(gè)字節(jié)。指針類型的大小通常也是與 CPU 位數(shù)相關(guān),一個(gè)指針?biāo)?32 bit 下占用 4 個(gè)字節(jié),在 64 bit 下占用 8 個(gè)字節(jié)。

#if __LP64__ || 0 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

假設(shè)我們通過(guò)NSNumber對(duì)象存儲(chǔ)一個(gè)NSInteger的值,系統(tǒng)實(shí)際上會(huì)給我們分配多少內(nèi)存呢?
由于Tagged Pointer無(wú)法禁用,所以以下將變量i設(shè)了一個(gè)很大的數(shù),以讓NSNumber對(duì)象存儲(chǔ)在堆上。

可以通過(guò)設(shè)置環(huán)境變量OBJC_DISABLE_TAGGED_POINTERSYES來(lái)禁用Tagged Pointer,但如果你這么做,運(yùn)行就Crash。

tagged pointers are disabled

因?yàn)?code>Runtime在程序運(yùn)行時(shí)會(huì)判斷Tagged Pointer是否被禁用,如果是的話就會(huì)調(diào)用_objc_fatal()函數(shù)殺死進(jìn)程。所以,雖然蘋果提供了OBJC_DISABLE_TAGGED_POINTERS這個(gè)環(huán)境變量給我們,但是Tagged Pointer還是無(wú)法禁用。

在 64 bit 下,如果沒(méi)有使用Tagged Pointer的話,為了使用一個(gè)NSNumber對(duì)象就需要 8 個(gè)字節(jié)指針內(nèi)存和 32 個(gè)字節(jié)對(duì)象內(nèi)存。而直接使用一個(gè)NSInteger變量只要 8 個(gè)字節(jié)內(nèi)存,相差好幾倍。

NSNumber等對(duì)象的指針中存儲(chǔ)的數(shù)據(jù)變成了Tag+Data形式(Tag為特殊標(biāo)記,用于區(qū)分NSNumber、NSDateNSString等對(duì)象類型;Data為對(duì)象的值)。這樣使用一個(gè)NSNumber對(duì)象只需要 8 個(gè)字節(jié)指針內(nèi)存。當(dāng)指針的 8 個(gè)字節(jié)不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)在將對(duì)象存儲(chǔ)在堆上。

Tagged Pointer 的原理

在現(xiàn)在的版本中,為了保證數(shù)據(jù)安全,蘋果對(duì) Tagged Pointer 做了數(shù)據(jù)混淆,開(kāi)發(fā)者通過(guò)打印指針無(wú)法判斷它是不是一個(gè)Tagged Pointer,更無(wú)法讀取Tagged Pointer的存儲(chǔ)數(shù)據(jù)。

所以在分析Tagged Pointer之前,我們需要先關(guān)閉Tagged Pointer的數(shù)據(jù)混淆,以方便我們調(diào)試程序。通過(guò)設(shè)置環(huán)境變量OBJC_DISABLE_TAG_OBFUSCATIONYES。

MacOS 分析

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSNumber *number1 = @1;
        NSNumber *number2 = @2;
        NSNumber *number3 = @3;
        NSNumber *number4 = @(0xFFFFFFFFFFFFFFFF);
        NSLog(@"%p %p %p %p", number1, number2, number3, number4);
    }
    return 0;
}
// 關(guān)閉 Tagged Pointer 數(shù)據(jù)混淆后:0x127 0x227 0x327 0x600003a090e0
// 關(guān)閉 Tagged Pointer 數(shù)據(jù)混淆前:0xaca2838a63a4fb34 0xaca2838a63a4fb04 0xaca2838a63a4fb14 0x600003a090e0

從以上打印結(jié)果可以看出,number1~number3指針為Tagged Pointer類型,可以看到對(duì)象的值都存儲(chǔ)在了指針中,對(duì)應(yīng)0x1、0x2、0x3。而number4由于數(shù)據(jù)過(guò)大,指針的8個(gè)字節(jié)不夠存儲(chǔ),所以在堆中分配了內(nèi)存。

注意:  MacOSiOS平臺(tái)下的Tagged Pointer有差別,下面會(huì)講到。

0x127 中的 2 和 7 表示什么?我們先來(lái)看這個(gè)7,0x127為十六進(jìn)制表示,7的二進(jìn)制為0111
最后一位1Tagged Pointer標(biāo)識(shí)位,代表這個(gè)指針是Tagged Pointer。
前面的011是類標(biāo)識(shí)位,對(duì)應(yīng)十進(jìn)制為3,表示NSNumber類。

備注:  MacOS下采用 LSB(Least Significant Bit,即最低有效位)為Tagged Pointer標(biāo)識(shí)位,而iOS下則采用 MSB(Most Significant Bit,即最高有效位)為Tagged Pointer標(biāo)識(shí)位。

可以在Runtime源碼objc4中查看NSNumberNSDate、NSString等類的標(biāo)識(shí)位。

// objc-internal.h
{
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,
    ......
}

0x127 中的 2(即倒數(shù)第二位)又代表什么呢?

倒數(shù)第二位用來(lái)表示數(shù)據(jù)類型。

示例:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        char a = 1;
        short b = 1;
        int c = 1;
        long d = 1;
        float e = 1.0;
        double f = 1.00;
        NSNumber *number1 = @(a);
        NSNumber *number2 = @(b);
        NSNumber *number3 = @(c);
        NSNumber *number4 = @(d);
        NSNumber *number5 = @(e);
        NSNumber *number6 = @(f);
        NSLog(@"%p %p %p %p %p %p", number1, number2, number3, number4, number5, number6);
    }
    return 0;
}
// 0x107 0x117 0x127 0x137 0x147 0x157

Tagged Pointer倒數(shù)第二位對(duì)應(yīng)數(shù)據(jù)類型:

Tagged Pointer 倒數(shù)第二位對(duì)應(yīng)數(shù)據(jù)類型
0char
1short
2int
3long
4float
5double

下圖是MacOSNSNumberTagged Pointer位視圖:

接下來(lái)我們來(lái)分析一下Tagged PointerNSString中的應(yīng)用。同NSNumber一樣,在64 bitMacOS下,如果一個(gè)NSString對(duì)象指針為Tagged Pointer,那么它的后 4 位(0-3)作為標(biāo)識(shí)位,第 4-7 位表示字符串長(zhǎng)度,剩余的 56 位就可以用來(lái)存儲(chǔ)字符串。

示例:

// MRC 環(huán)境
#define HTLog(_var) \
{ \
    NSString *name = @#_var; \
    NSLog(@"%@: %p, %@, %lu", name, _var, [_var class], [_var retainCount]); \
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *a = @"a";
        NSMutableString *b = [a mutableCopy];
        NSString *c = [a copy];
        NSString *d = [[a mutableCopy] copy];
        NSString *e = [NSString stringWithString:a];
        NSString *f = [NSString stringWithFormat:@"f"];
        NSString *string1 = [NSString stringWithFormat:@"abcdefg"];
        NSString *string2 = [NSString stringWithFormat:@"abcdefghi"];
        NSString *string3 = [NSString stringWithFormat:@"abcdefghij"];
        HTLog(a);
        HTLog(b);
        HTLog(c);
        HTLog(d);
        HTLog(e);
        HTLog(f);
        HTLog(string1);
        HTLog(string2);
        HTLog(string3);
    }
    return 0;
}
/*
a: 0x100002038, __NSCFConstantString, 18446744073709551615
b: 0x10071f3c0, __NSCFString, 1
c: 0x100002038, __NSCFConstantString, 18446744073709551615
d: 0x6115, NSTaggedPointerString, 18446744073709551615
e: 0x100002038, __NSCFConstantString, 18446744073709551615
f: 0x6615, NSTaggedPointerString, 18446744073709551615
string1: 0x6766656463626175, NSTaggedPointerString, 18446744073709551615
string2: 0x880e28045a54195, NSTaggedPointerString, 18446744073709551615
string3: 0x10071f6d0, __NSCFString, 1 */

從打印結(jié)果來(lái)看,有三種NSString類型:

類型描述
__NSCFConstantString1. 常量字符串,存儲(chǔ)在字符串常量區(qū),繼承于 __NSCFString。相同內(nèi)容的 __NSCFConstantString 對(duì)象的地址相同,也就是說(shuō)常量字符串對(duì)象是一種單例,可以通過(guò) == 判斷字符串內(nèi)容是否相同。 2. 這種對(duì)象一般通過(guò)字面值@"..."創(chuàng)建。如果使用 __NSCFConstantString 來(lái)初始化一個(gè)字符串,那么這個(gè)字符串也是相同的 __NSCFConstantString。
__NSCFString1. 存儲(chǔ)在堆區(qū),需要維護(hù)其引用計(jì)數(shù),繼承于 NSMutableString。 2. 通過(guò)stringWithFormat:等方法創(chuàng)建的NSString對(duì)象(且字符串值過(guò)大無(wú)法使用Tagged Pointer存儲(chǔ))一般都是這種類型。
NSTaggedPointerStringTagged Pointer,字符串的值直接存儲(chǔ)在了指針上。

打印結(jié)果分析:

NSString 對(duì)象類型分析
a__NSCFConstantString通過(guò)字面量@"..."創(chuàng)建
b__NSCFStringa 的深拷貝,指向不同的內(nèi)存地址,被拷貝到堆區(qū)
c__NSCFConstantStringa 的淺拷貝,指向同一塊內(nèi)存地址
dNSTaggedPointerString單獨(dú)對(duì) a 進(jìn)行 copy(如 c),淺拷貝是指向同一塊內(nèi)存地址,所以不會(huì)產(chǎn)生Tagged Pointer;單獨(dú)對(duì) a 進(jìn)行 mutableCopy(如 b),復(fù)制出來(lái)是可變對(duì)象,內(nèi)容大小可以擴(kuò)展;而Tagged Pointer存儲(chǔ)的內(nèi)容大小有限,因此無(wú)法滿足可變對(duì)象的存儲(chǔ)要求。
e__NSCFConstantString使用 __NSCFConstantString 來(lái)初始化的字符串
fNSTaggedPointerString通過(guò)stringWithFormat:方法創(chuàng)建,指針足夠存儲(chǔ)字符串的值。
string1NSTaggedPointerString通過(guò)stringWithFormat:方法創(chuàng)建,指針足夠存儲(chǔ)字符串的值。
string2NSTaggedPointerString通過(guò)stringWithFormat:方法創(chuàng)建,指針足夠存儲(chǔ)字符串的值。
string3__NSCFString通過(guò)stringWithFormat:方法創(chuàng)建,指針不足夠存儲(chǔ)字符串的值。

可以看到,為Tagged Pointer的有d、f、string1、string2指針。它們的指針值分別為
0x6115、0x66150x6766656463626175、0x880e28045a54195

其中0x61、0x660x67666564636261分別對(duì)應(yīng)字符串的 ASCII 碼。

最后一位5的二進(jìn)制為0101,最后一位1是代表這個(gè)指針是Tagged Pointer010對(duì)應(yīng)十進(jìn)制為2,表示NSString類。

倒數(shù)第二位1、1、7、9代表字符串長(zhǎng)度。

對(duì)于string2的指針值0x880e28045a54195,雖然從指針中看不出來(lái)字符串的值,但其也是一個(gè)Tagged Pointer。

下圖是MacOSNSStringTagged Pointer位視圖:

如何判斷 Tagged Pointer

objc4源碼中找到判斷Tagged Pointer的函數(shù):

// objc-internal.h
static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

可以看到,它是將指針值與一個(gè)_OBJC_TAG_MASK掩碼進(jìn)行按位與運(yùn)算,查看該掩碼:

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0  // MacOS
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1  // iOS
#endif
#define _OBJC_TAG_INDEX_MASK 0x7
// array slot includes the tag bit itself
#define _OBJC_TAG_SLOT_COUNT 16
#define _OBJC_TAG_SLOT_MASK 0xf
#define _OBJC_TAG_EXT_INDEX_MASK 0xff
// array slot has no extra bits
#define _OBJC_TAG_EXT_SLOT_COUNT 256
#define _OBJC_TAG_EXT_SLOT_MASK 0xff
#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)  // _OBJC_TAG_MASK
#   define _OBJC_TAG_INDEX_SHIFT 60
#   define _OBJC_TAG_SLOT_SHIFT 60
#   define _OBJC_TAG_PAYLOAD_LSHIFT 4
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK (0xfUL<<60)
#   define _OBJC_TAG_EXT_INDEX_SHIFT 52
#   define _OBJC_TAG_EXT_SLOT_SHIFT 52
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 12
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#else
#   define _OBJC_TAG_MASK 1UL       // _OBJC_TAG_MASK
#   define _OBJC_TAG_INDEX_SHIFT 1
#   define _OBJC_TAG_SLOT_SHIFT 0
#   define _OBJC_TAG_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_PAYLOAD_RSHIFT 4
#   define _OBJC_TAG_EXT_MASK 0xfUL
#   define _OBJC_TAG_EXT_INDEX_SHIFT 4
#   define _OBJC_TAG_EXT_SLOT_SHIFT 4
#   define _OBJC_TAG_EXT_PAYLOAD_LSHIFT 0
#   define _OBJC_TAG_EXT_PAYLOAD_RSHIFT 12
#endif

由此我們可以驗(yàn)證:

  • MacOS下采用 LSB(Least Significant Bit,即最低有效位)為Tagged Pointer標(biāo)識(shí)位;
  • iOS下則采用 MSB(Most Significant Bit,即最高有效位)為Tagged Pointer標(biāo)識(shí)位。

而存儲(chǔ)在堆空間的對(duì)象由于內(nèi)存對(duì)齊,它的內(nèi)存地址的最低有效位為 0。由此可以辨別Tagged Pointer和一般對(duì)象指針。

objc4源碼中,我們經(jīng)常會(huì)在函數(shù)中看到Tagged Pointer。比如objc_msgSend函數(shù):

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    // tagged
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
    add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
    cmp x10, x16
    b.ne    LGetIsaDone
    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

objc_msgSend能識(shí)別Tagged Pointer,比如NSNumberintValue方法,直接從指針提取數(shù)據(jù),不會(huì)進(jìn)行objc_msgSend的三大流程,節(jié)省了調(diào)用開(kāi)銷。

內(nèi)存管理相關(guān)的,如retain方法中調(diào)用的rootRetain

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    // 如果是 tagged pointer,直接返回 this
    if (isTaggedPointer()) return (id)this; 
    bool sideTableLocked = false;
    bool transcribeToSideTable = false; 
    isa_t oldisa;
    isa_t newisa;
    ......

 Tagged Pointer 注意點(diǎn)

我們知道,所有OC對(duì)象都有isa指針,而Tagged Pointer并不是真正的對(duì)象,它沒(méi)有isa指針,所以如果你直接訪問(wèn)Tagged Pointerisa成員的話,在編譯時(shí)將會(huì)有如下警告:

對(duì)于Tagged Pointer,應(yīng)該換成相應(yīng)的方法調(diào)用,如isKindOfClassobject_getClass。只要避免在代碼中直接訪問(wèn)Tagged Pointerisa,即可避免這個(gè)問(wèn)題。

當(dāng)然現(xiàn)在也不允許我們?cè)诖a中直接訪問(wèn)對(duì)象的isa了,否則編譯不通過(guò)。

我們通過(guò) LLDB 打印Tagged Pointerisa,會(huì)提示如下錯(cuò)誤:

以上就是iOS內(nèi)存管理Tagged Pointer使用原理詳解的詳細(xì)內(nèi)容,更多關(guān)于iOS內(nèi)存管理Tagged Pointer的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論