iOS 内存管理机制与原理

内存分区


内存一般分为五大区:栈区、堆区、常量区、全局区、代码区。如图

1.栈区

是由编译器自动分配并释放的,主要用来存储局部变量、函数的参数等,是一块连续的内存区域,遵循先进后出(FILO)原则。一般在运行时分配。它的分配由高地址空间向低地址空间分配。

优点:因为栈是由编译器自动分配并释放的,不会产生内存碎片,所以快速高效。
缺点:栈的内存大小有限制,数据不灵活。

例如:下图,创建两个变量,存放在栈区,地址是递减4。

iOS 内存管理机制与原理_第1张图片

2.堆区

堆区是由程序员手动管理。 主要用来存放动态分配的对象类型数据。是不连续的内存区域。在MRC环境下,由程序员手动释放,在ARC环境下,由编译器自动释放。一般在运行时分配。它的分配是从低地址空间向高地址空间分配。
优点:灵活方便,数据适应面广泛。
缺点:需手动管理,速度慢、容易产生内存碎片。

例如:下图,创建两个对象,存放在堆区,地址递增。

 iOS 内存管理机制与原理_第2张图片

3.常量区

常量区存放的就是字符串常量和基本类型常量。在编译时分配,程序结束后回收

4.全局区/静态区

全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在.data段,未初始化的全局变量和静态变量在相邻的.bss区域,在编译时分配,程序结束后由系统释放。

5.代码区

代码区是在编译时分配,主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的

内存管理


iOS 内存管理机制与原理_第3张图片

 内存管理方案

1.TaggedPointer

2013 年 9 月苹果推出了首个采用 64 位架构的 A7 双核处理器的手机 iPhone5s,为了改进从 32 位 CPU 迁移到 64 位 的内存浪费和效率问题,在 64 位 环境下,苹果工程师提出了 Tagged Pointer 的概念。采用这一机制,系统会对 NSStringNSNumberNSDate 等对象进行优化。建议大家看看 WWDC2020 这个视频的介绍。

Tagged Pointer 专门用来存储小对象,例如NSNumber,NSDate等,Tagged Pointer指针的值不再是单纯的地址了,而是真正的值,所以,实际上它也不再是一个对象了,它只是一个披着对象皮的普通变量而已,所以它的内存并不存储在堆区,也不需要malloc和free。这样在读取上有着3倍的效率,创建时候比以前快106倍。

iOS 内存管理机制与原理_第4张图片

由上图可以看出NSdate是NSTaggedPointer,此外当字符串的长度为10个以内时,字符串的类型都是NSTaggedPointerString类型,当超过10个时,字符串的类型才是__NSCFString

 NSTaggedPointer标志位

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

上面这个方法我们看到,判断一个对象类型是否为NSTaggedPointerString类型实际上是讲对象的地址与_OBJC_TAG_MASK进行按位与操作,结果在跟_OBJC_TAG_MASK进行对比,我们在看下_OBJC_TAG_MASK的定义:

#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif

我们都知道一个对象地址为64位二进制,它表明如果64位数据中,最高位是1的话,则表明当前是一个tagged pointer类型。

那么我们在看下上面打印出的地址,所有NSTaggedPointerString地址都是0xd开头,d转换为二进制1110,根据上面的结论,我们看到首位为1表示为NSTaggedPointerString类型。在这里得到验证。

注意:TaggedPointer类型在iOS和MacOS中标志位是不同的iOS为最高位而MacOS为最低位

对象类型

正常情况下一个对象的类型,是通过这个对象的ISA指针来判断的,那么对于NSTaggedPointer类型我们如何通过地址判断对应数据是什么类型的呢?

在objc4-723之前,我们可以通过与判断TaggedPointer标志位一样根据地址来判断,而类型的标志位就是对象地址的61-63位,比如对象地址为0xa开头,那么转换成二进制位1010,那么去掉最高位标志位后,剩余为010,即10进制中的2。

接着我们看下runtime源码objc-internal.h中有关于标志位的定义如下:

#if __has_feature(objc_fixed_enum)  ||  __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
    // 60-bit payloads
    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,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};
#if __has_feature(objc_fixed_enum)  &&  !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif

objc4-750之后

// Returns a pointer to the class's storage in the tagged class arrays.
// Assumes the tag is a valid basic tag.
static Class *
classSlotForBasicTagIndex(objc_tag_index_t tag)
{
    uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
                                >> _OBJC_TAG_INDEX_SHIFT)
                               & _OBJC_TAG_INDEX_MASK);
    uintptr_t obfuscatedTag = tag ^ tagObfuscator;
    // Array index in objc_tag_classes includes the tagged bit itself
#if SUPPORT_MSB_TAGGED_POINTERS 高位优先
    return &objc_tag_classes[0x8 | obfuscatedTag];
#else
    return &objc_tag_classes[(obfuscatedTag << 1) | 1];
#endif
}

 classSlotForBasicTagIndex() 函数的主要功能就是根据指定索引 tag 从数组objc_tag_classes中获取类指针,而下标的计算方法发是根据外部传递的索引tag。比如字符串 tag = 2。当然这并不是简单的从数组中获取某条数据。

获取TaggedPointer的值

objc4-750之后源码:

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr) 
{
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t value = _objc_decodeTaggedPointer(ptr);
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
    } else {
        return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
    }
}

static inline intptr_t
_objc_getTaggedPointerSignedValue(const void * _Nullable ptr) 
{
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t value = _objc_decodeTaggedPointer(ptr);
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return ((intptr_t)value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
    } else {
        return ((intptr_t)value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
    }
}

实例代码和结果

  NSString *str1 = [NSString stringWithFormat:@"1"];
            NSString *str11 = [NSString stringWithFormat:@"11"];
            NSString *str2 = [NSString stringWithFormat:@"2"];
            NSString *str22 = [NSString stringWithFormat:@"22"];


            // 0x31 1 0x32 1
            uintptr_t value1 = objc_getTaggedPointerValue((__bridge void *)str1);
            uintptr_t value2 = objc_getTaggedPointerValue((__bridge void *)str2);
            uintptr_t value11 = objc_getTaggedPointerValue((__bridge void *)str11);
            uintptr_t value22 = objc_getTaggedPointerValue((__bridge void *)str22);
            // 以16进制形式输出
            NSLog(@"%lx", value1);
            NSLog(@"%lx", value11);
            NSLog(@"%lx", value2);
            NSLog(@"%lx", value22);
TaggedPointer[89535:3033433] 311
TaggedPointer[89535:3033433] 31312
TaggedPointer[89535:3033433] 321
TaggedPointer[89535:3033433] 32322

TaggedPoint对象是一个特殊的对象,不会涉及到引用计数retainrelease等内存操作。对象的值就存在指针中,不过值通过了一份加密。

2.NONPOINTER_ISA

上面我们说了,对于一个对象的存储,苹果做了优化,那么对于ISA指针呢?

对象的isa指针,用来表明对象所属的类类型。

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

结合下图

iOS 内存管理机制与原理_第5张图片

从图中可以看出,我们所谓的isa指针,最后实际上落脚于isa_t的联合类型。那么何为联合类型呢? 联合类型是C语言中的一种类型,是一种n选1的关系,联合的作用在于,用更少的空间,表示了更多的可能的类型,虽然这些类型是不能够共存的。比如isa_t 中包含有clsbitsstruct三个变量,它们的内存空间是重叠的。在实际使用时,仅能够使用它们中的一种,你把它当做cls,就不能当bits访问,你把它当bits,就不能用cls来访问。

对于isa_t联合类型,主要包含了两个构造函数isa_t(),isa_t(uintptr_t value)和三个变量cls,bits,struct,而uintptr_t的定义为typedef unsigned long

当isa_t作为Class cls使用时,这符合了我们之前一贯的认知:isa是一个指向对象所属Class类型的指针。然而,仅让一个64位的指针表示一个类型,显然不划算。

因此,绝大多数情况下,苹果采用了优化的isa策略,即,isa_t类型并不等同而Class cls, 而是struct

下面我们先来看下struct的结构体

// ISA_BITFIELD定义如下
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   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 deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

注意:成员后面的:表明了该成员占用几个bit 而每个成员的意义如下表

nonopointer:表示是否对isa指针开启了指针优化,

0:纯isa指针,1:不止是类对象地址,isa中包含了类信息,对象的引用计数等。

has_assoc:关联对象标志位,0:没有,1:存在

has_cxx_dtor:该对象是否有c++或者Objc的析构器,如果有析构器函数,则需要做析构逻辑,如果没有,则可以更快的释放。

iOS 内存管理机制与原理_第6张图片

 shiftcls:存储指针的值,开启指针优化的情况下,再arm64架构中有33位用来存储类指针,

iOS 内存管理机制与原理_第7张图片

 magic:用于调试器判断当前对象是真的对象还是未初始化的空间。

weakly_referenced:标致对象是否指向或者曾经指向一个ARC的若变量,没有弱引用的对象可以更快的释放。

deallcating:标志对象是否正在释放内存

has_sidetable_rc:当前对象引用计数大于10的时,则需要借用变量存储进位

iOS 内存管理机制与原理_第8张图片

extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9,如果用于计数大于10则需要使用下面的has_sidetable_rc。

3.散列表、引用计数表

Sidetable主要包含spinlock,引用计数(存放extra_rc接收的另一半引用计数),弱引用表。

truct SideTable {
    spinlock_t slock;
    // 存放从extra_rc接收的那一半引用计数
    RefcountMap refcnts;
    // 弱引用表
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

1.spinlock_t 加锁

spinlock_t 不是自旋锁,在底层代码查找的过程中,我们可以发现他是一把os_unfair_lock锁,在使用sidetable的时候,频繁的读取需要加锁,一张表无疑影响了效率,因此,我们采用stripedMap来分散压力,且stripedMap的数量是根据系统来确定的(真机模式下sidetable最多为8张,虚拟机等为64张).

// 上面 SideTables 的实现
static StripedMap& SideTables() {
    return SideTablesMap.get();
}

2.RefcountMap(引用计数表)

  • RefcountMap本身从DenseMap得来
    typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap;
    

存放从extra_rc接收的那一半引用计数

if (variant == RRVariant::Full) {
    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        // 将引用计数一半存在散列表中的方法
        sidetable_addExtraRC_nolock(RC_HALF);
    }
    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else {
    ASSERT(!transcribeToSideTable);
    ASSERT(!sideTableLocked);
}

接着,来看一下 sidetable_addExtraRC_nolock 方法:

bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    // 获取SideTables,也就是StripeMap
    SideTable& table = SideTables()[this];

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}

3.WeakTable(弱引用表)

弱引用底层调用objc_initWeak:

id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak
        (location, (objc_object*)newObj);
}

storeWeak:

  • 添加引用的时候调用storeWeak一共有五个参数,其中3个参数定义在了template模版参数中(HaveOld:weak指针是否指向一个弱引用;HavNew:weak指针是否需要指向一个新的引用;crashIfDeallocating表示被弱引用的对象是否正在析构)。
  • weak_unregister_no_lock:清除原来弱引用表中的数据
  • weak_register_no_lock:将weak的指针地址添加到对象的弱引用表
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};

// HaveOld:weak指针是否指向一个弱引用
// HavNew:weak指针是否需要指向一个新的引用
template 
static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {//如果有拿到旧表
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {//如果没有创建新表
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {//如果旧表不存在对应的obj
        SideTable::unlockTwo(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {//有新表和新对象
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) //如果类没有初始化就重新初始化
        {
            SideTable::unlockTwo(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {//如果指针曾经指向别的对象,就清除
        // 清除原来弱引用表中数据
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            // 将weak的指针地址添加到对象的弱引用表
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            // 将对象曾经指向过弱引用的标识置为true,没有弱引用的释放更快
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }

    SideTable::unlockTwo(oldTable, newTable);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}

weak_entry_t:

struct weak_table_t {
    // 弱引用数组
    weak_entry_t *weak_entries;
    // 数组个数
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

weak_entries是一个哈希数组,一个对象可以被多个弱引用指针引用,因此,这里用数组的形式表示一个对象的多个弱引用;数组中存储的内容就是弱引用对象的指针地址。当对象的弱引用个数小于等于4时走静态存储(在weak_entry_t初始化的时候一并分配好),大于4走动态存储。

sidetables总结:

  • sidetables可以理解为一个全局的hash数组,里面存储了sidetables类型的数据,其中长度为8或者64
  • 一个obj(oc对象)对应了一个sideTable,但是一个SideTable,会对应多个obj,因为sidetabels的数量只有8或者64个,所以有很多obj会共用一个sidetable
  • 在弱引用表中,key是对象的地址,value是weak指针地址的数组(weak_entry_t)
  • weak_unregister_no_lock 和 weak_register_no_lock 中都是对 weak_entry_t 类型的数组进行操作
  • _ _weak修饰对象(不会放入自动释放池),会调用objc_loadWeakRetained;使得引用计数加一,但仅是临时变量;被引用的对象不会增加他的引用计数。

iOS 内存管理机制与原理_第9张图片 iOS 内存管理机制与原理_第10张图片

 ARC&MRC


Object-C提供了两种内存管理机制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),为Objective-C提供了内存的手动和自动管理。

MRC

基本思想:通过手动引用计数来进行对象的内存管理

涉及方法

  1. alloc/new/copy/mutableCopy:生成对象并自己持有,引用计数+1(从0变为1)
  2. retain :持有对象,使对象的引用计数加1
  3. release : 释放对象,使对象的引用计数减1
  4. retainCount : 获取当前对象的引用计数值
  5. autorelease : 当前对象会在autoreleasePool结束的时候,调用这个对象的release操作,进行引用计数减1
  6. dealloc : 在MRC中若调用dealloc,需要显示的调用[super dealloc],来释放父类的相关成员变量

autorelease

autorelease即“自动释放”,是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池来回收统一释放。自动释放池销毁的时候,池子里面所有的对象都会做一次release操作

iOS 内存管理机制与原理_第11张图片

那么,autorelease释放与简单的release释放有什么区别呢?

调用 autorelease 方法,就会把该对象放到离自己最近的自动释放池中(栈顶的释放池,多重自动释放池嵌套是以栈的形式存取的),即:使对象的持有权转移给了自动释放池(即注册到了自动释放池中),调用方拿到了对象,但这个对象还不被调用方所持有。当自动释放池销毁时,其中的所有的对象都会调用一次release操作。
iOS 内存管理机制与原理_第12张图片

本质上,区别在于autorelease 方法不会改变调用者的引用计数,它只是改变了对象释放时机,不再让程序员负责释放这个对象,而是交给自动释放池去处理 。

 autorelease 方法相当于把调用者注册到 autoreleasepool 中,ARC环境下不能显式地调用 autorelease 方法和显式地创建 NSAutoreleasePool 对象,但可以使用@autoreleasepool { }块代替(并不代表块中所有内容都被注册到了自动释放池中)。
对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。iOS 内存管理机制与原理_第13张图片

RunLoop和AutoReleasePool是通过线程的方式一一对应的
在非手动添加Autorelease pool下,Autorelease对象是在当前runloop进入休眠等待前被释放的
当一个runloop在不停的循环工作,那么runloop每一次循环必定会经过BeforeWaiting(准备进入休眠):而去BeforeWaiting(准备进入休眠) 时会调用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush()释放旧的池并创建新池,那么这两个方法来销毁要释放的对象,所以我们根本不需要担心Autorelease的内存管理问题。

ARC

内存管理方案

iOS内存管理方案有:

  1. MRC和ARC
  2. Tagged Pointer:专门用来处理小对象,例如NSNumber、NSDate、小NSString等
  3. NONPOINTER_ISA :非指针类型的isa,主要是用来优化64位地址。在 64 位架构下,isa 指针是占 64 比特位的,实际上只有 30 多位就已经够用了,为了提高利用率,剩余的比特位存储了内存管理的相关数据内容。
  4. nonpointer: 表示是否对 isa 指针开启指针优化
  5. • 0: 纯 isa 指针
  6. • 1: 不止是类对象地址, isa 中包含了类信息、对象的引用计数等
  7. SideTables:散列表,在散列表中主要有两个表,分别是引用计数表、弱引用表。通过 SideTables()结构来实现的,SideTables()结构下,有很多 SideTable 的数据结构。 而 sideTable 当中包含了自旋锁,引用计数表,弱引用表。 SideTables()实际上是一个哈希表,通过对象的地址来计算该对象的引用计数在哪个 sideTable 中。
     

修饰符

当ARC有效时,id类型和对象类型必须附加所有权修饰符,一共有如下四种。

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符。

__weak修饰符

弱引用表示并不持有对象,当所引用的对象销毁了,这个变量就自动设为nil。
可以利用__weak修饰符来解决循环引用问题。

__unsafe_unretained修饰符

__unsafe_unretained和__weak很像,唯一区别就是,__unsafe_unretained变量引用的对象再被销毁以后,不会被自动设置为nil,仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe,此时你再尝试通过变量访问这个对象的属性或方法就会crash。一旦对象释放,则会成为悬垂指针,程序崩溃,因此__unnsafe_unretained修饰符的变量一定要在赋值的对象存在的情况下使用。
 

__autoreleasing修饰符

ARC无效时使用autorelease,在ARC下__autoreleasing的使用:

@autoreleasepool {
	id __autoreleasing obj = [[NSObject alloc] init];
}

你可能感兴趣的:(iOS,开发语言,xcode,ios,objective-c)