iOS课程观看笔记(四)---内存管理

在这里插入图片描述

内存布局

在这里插入图片描述
stack:栈包括:方法调用
heap:堆,通过alloc等分配的对象
bss:未初始化的全局变量以及静态变量等
data:已初始化的全局变量以及静态变量等
text:程序代码


内存管理方案

问:iOS系统怎样对内存进行管理的?

根据不同的场景,提供不同的内存管理方案,大致包括三种:

  • TaggedPointer:NSNumber小对象
  • NONPOINTER_ISA:64位中某些字段存储的是其他信息,不全是指针
  • 散列表:引用计数表、weak弱引用表(平时说的这种)

NONPOINTER_ISA

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

散列表方式

在这里插入图片描述
在这里插入图片描述

struct SideTable 
{
    spinlock_t slock;// 保证原子操作的自旋锁
    RefcountMap refcnts;//引用计数器存储地,是一个哈希map表
    weak_table_t weak_table;//弱引用表,也是哈希map存储
}
问:SideTables存储在哪?SideTable存储在哪?

在源码中,全局搜索SideTables
在这里插入图片描述
注释里面有一个关于initialize SideTables

We cannot use a C++ static initializer to initialize SideTables because libc calls us before our C++ initializers run. We also don't want a global pointer to this struct because of the extra indirection.
Do it the hard way.


我们不能使用C++静态初始化器初始化SideTables,因为libc在我们的C++初始化器运行之前调用我们。
我们也不希望用一个全局的指针指向这个结构体,因为指针是间接访问。

也就是,我们不能用C++静态初始化SideTables
也没有用一个指针指向这个结构体

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

该类型是一个函数模板,模板类型是StripedMap

StripedMap头部有一个注释:

StripedMap< T > is a map of void* -> T

StripedMap是一个以void*为key,T为value的map

也就是SideTables是一个全局的hash表

参考:
Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t

这是个大佬,可以多学习

问:为什么不是一个SideTable,而是多个?

在这里插入图片描述

如果只有一个SideTable,那么所有的对象都在一个表里面
当对某一个对象进行操作的时候,为了安全,加锁,其他对象就没法操作了
因此,存在效率问题

因此,为了改变这种情况,系统运用了分离锁的技术

在这里插入图片描述
将一张表分成8张不同的表。
如果对象A在表A,对象B在表B,则可以同时对对象AB操作

问:如何实现快速分流?

题目解析:指的是,通过一个对象的指针,如何快速的定位到其所属哪一张SideTable表

在这里插入图片描述


SideTable的数据结构

1spinlock_t slock;// 保证原子操作的自旋锁
2RefcountMap refcnts;//引用计数器存储地,是一个哈希map表
3weak_table_t weak_table;//弱引用表,也是哈希map存储

spinlock_t

其是一种自旋锁,也就是处于“忙等”状态
适用于轻量访问

RefcountMap引用计数表

在这里插入图片描述

其是一个哈希map表
使用哈希表,插入查找都是通过同一个函数,效率高

在这里插入图片描述
上图表面该size_t里面存储的内容(与NONPOINTER_ISA的存储内容无关)

以对象的地址为key,size_t为value
size_t向右偏移两位,就是引用计数器的个数

weak_table_t弱引用表

在这里插入图片描述

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

从注释可以看出:
key是Stores object ids,存储对象的地址
value是weak_entry_t

在特定语义环境下
int *a;是一个int 型数组。数组名为a,数组元素类型是int
类似的,
weak_entry_t *weak_entries;是一个结构体数组
数组名是weak_entries,数组元素类型是weak_entry_t
且weak_entry_t是一个结构体

一个对象,可能有多个weak对象修饰,是一对多的关系

__weak NSObject *object = [[NSObject alloc] init];
 __weak NSObject *object1 = object;
 __weak NSObject *object2 = object;

key是[[NSObject alloc] init]的地址,也是object存储的内容
value是weak_entry_t
weak_entry_t里面存储有object、object1、object2的地址

题外话

YZPerson *person = [[YZPerson alloc] init];
一般我们说的person对象,其实指的是[YZPerson alloc] init],也就是person指针指向的对象
因为,YZPerson继承NSObject,而NSObject其实是一个结构体
也就是person是一个结构体类型的指针
也就是person里面存储的内容是一个结构体类型的地址
person是一个指针变量,当然不是一个对象
对象里面要有isa指针的,person里面是一个地址值,没有isa指针
[[YZPerson alloc] init]里面才是建立起来的真正的对象
只是将新建的对象地址,赋值给person指针变量
也就是,我们一般说的person对象,其实指的是等号后面的内容


MRC和ARC

MRC

手动引用计数
alloc、retain、release、retainCount、autorelease、dealloc
其中,retain、release、retainCount、autorelease四个方法是MRC特有的

MRC中有autorelease,这个需要注意下

ARC

ARC,自动引用计数
ARC是编译器LLVM和Runtime协作的结果

ARC禁止调用retain、release、retainCount、dealloc
可以重写dealloc,但是禁止使用[super dealloc]
ARC新增weak、strong属性关键字


引用计数管理

alloc、retain、release、retainCount、dealloc的方法实现

alloc的实现

alloc的实现比较简单,最终调用了C函数的calloc
此时,并没有设置引用计数器为1。

大致过程:

+ (id)alloc {
    return _objc_rootAlloc(self);
}

进入_objc_rootAlloc(self)
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

进入callAlloc
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());//C语言的calloc函数
if (!obj) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;

obj->initInstanceIsa(cls, dtor);里面有
initIsa(cls, true, hasCxxDtor);

进入initIsa
inline void 
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!indexed) {
        isa.cls = cls;
    } else {
        assert(!DisableIndexedIsa);
        isa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.indexed is part of ISA_MAGIC_VALUE
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

完成
在里面没有发现关于sidatable表的操作

这个有点粗暴了,一直以来认为的alloc会增加引用计数器,原来并没有

retain的实现

在这里插入图片描述

- (id)retain {
    return _objc_rootRetain(self);
}

进入
NEVER_INLINE id
_objc_rootRetain(id obj)
{
    ASSERT(obj);

    return obj->rootRetain();
}

进入
inline id 
objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

进入
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
        //#define SIDE_TABLE_RC_ONE            (1UL<<2)左移两位,是4(实际反应出来是1)
    }
    table.unlock();

    return (id)this;
}

可以看到图片中的三句代码:
SideTable& table = SideTables()[this];//找到所属SideTable表
size_t& refcntStorage = table.refcnts[this];//找到现在的引用计数器个数
refcntStorage += SIDE_TABLE_RC_ONE;//引用计数器+1

问:在进行retain操作的时候,系统如何查找引用计数?

从SideTables里通过当前对象指针,找到对应的SideTable表
在SideTable表中,通过当前对象指针,找到对应的size_t
反应出来,就是引用计数器+1
也就是通过两次hash查找,找到引用计数器

release的实现

在这里插入图片描述

- (oneway void)release {
    ((id)self)->rootRelease();
}

进入
ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

进入
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
	......
	return sidetable_release(performDealloc);
}

进入sidetable_release
uintptr_t 
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.indexed);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    if (table.trylock()) {
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) {
            do_dealloc = true;
            table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
        } else if (it->second < SIDE_TABLE_DEALLOCATING) {
            // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
            it->second -= SIDE_TABLE_RC_ONE;
        }
        table.unlock();
        if (do_dealloc  &&  performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return do_dealloc;
    }

    return sidetable_release_slow(table, performDealloc);
}

可以看到图片中的三句代码:
SideTable& table = SideTables()[this];//找到所属SideTable表
RefcountMap::iterator it = table.refcnts.find(this);//找到现在的引用计数器个数
it->second -= SIDE_TABLE_RC_ONE;//引用计数器-1


retainCount的实现

在这里插入图片描述

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

进入rootRetainCount()
inline uintptr_t 
objc_object::rootRetainCount()
{
    assert(!UseGC);
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    if (bits.indexed) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

进入sidetable_retainCount()
uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

可以看到图片中的四句代码:
SideTable& table = SideTables()[this];//找到所属SideTable表
size_t refcnt_result = 1;//定义refcnt_result = 1
RefcountMap::iterator it = table.refcnts.find(this);//找到现在的引用计数器个数
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;//引用计数器+1

return refcnt_result;//返回新的引用计数器

问:新创建的对象,alloc其引用计数器为多少?

新创建的对象,引用计数为0
当调用retainCount查看其引用计数个数的时候,引用计数+1,返回的是1

dealloc的实现

在这里插入图片描述

object_dispose()的实现
在这里插入图片描述

objc_destructInstance()的实现
在这里插入图片描述
clearDeallocating()的实现
在这里插入图片描述
从图中可以看出:
dealloc会将弱引用指针置为nil
dealloc会将对象的引用计数表中的引用计数进行擦除

问:我们通过关联对象方法,给某个类添加了一些实例变量,是否有必要在对象的dealloc方法中将关联对象进行移除销毁操作?

不需要,因为dealloc方法中,系统已经帮我们关联对象的移除操作


弱引用管理

在这里插入图片描述
从图中可以看出,
左边:将obj的地址,赋值给id类型的obj1
右边:首先,创建id obj1。然后通过objc_initWeak函数方法,将obj1与obj关联起来
__weak不是指针什么的,只是告诉我们一个方式,也就是
使用__weak,我们使用objc_initWeak()函数
使用strong,我们使用objc_initStrong()函数(如果有的话)

在这里插入图片描述

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

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}


进入storeWeak
static id 
storeWeak(id *location, objc_object *newObj)
{
	if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, 
                                                      (id)newObj, location, 
                                                      CrashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
}

在这里插入图片描述

若有则通过hash算法找到对应的弱引用表weak_entry_t,则将弱引用插入weak_entry_t表中
若没有弱引用表weak_entry_t,则新建一个弱引用表weak_entry_t,并插入

在这里插入图片描述

问:weak变量,自动变为nil,是如何实现与操作的?
void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

在这里插入图片描述

总结: 当一个对象被dealloc后,dealloc内部实现当中会调用 弱引用清除 的相关函数,然后在这个函数的内部,会根据当前对象指针,查找弱引用表,把当前对象相对应的弱引用都拿出来(是一个数组),然后遍历数组中所有的弱引用指针,分别置为nil。


自动释放池

在这里插入图片描述

问:array是在什么时候被释放的(自动释放池与dealloc的关系)

在当次runloop调用将要结束的时候,调用AutoreleasePool::pop()方法,调用array的release方法,由于array指向的对象引用计数器为0,则调用dealloc方法,然后释放

也就是:runloop的自动释放池 --> 对象的引用计数为0 --> dealloc方法 --> 对象真正的释放

注意:
是先release后,引用计数器为0,才调用dealloc方法
而不是调用dealloc方法,去取将引用计数器设置为0

题外话

这个,准确来说,array是在{}结束的就没有了
因为,array是一个局部变量,而且是在栈上的,不需要程序员手动管理,在}时,array被销毁

上面说的array被释放,其实是指的array指针变量指向的对象,也就是=后面的内容。=后面的内容存储在堆上,需要程序员手动管理,也就是autorelease管理的内容对象。

再有,释放array,就是指的释放array指向的内容

在这里插入图片描述
因此,说成,array什么时候释放,指的是array指针变量指向的对象什么时候释放。

问:AutoreleasePool为何可以嵌套使用?

多层嵌套,就是多次插入哨兵对象

问:AutoreleasePool的实现原理是怎样的?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

批量操作是指:每一次的pop,会将{}内部的所有对象,进行一次release操作。

问:什么是自动释放池?
问:自动释放池的实现结构是怎样的?

自动释放池
是以为结点,通过双向链表的形式组合而成
和线程一一对应

在这里插入图片描述

AutoreleasePoolPage

在这里插入图片描述

class AutoreleasePoolPage 
{
	id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
}

在这里插入图片描述

在这里插入图片描述

[obj autorelease]的实现

在这里插入图片描述

源码分析:
经过:

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

rootAutorelease2()
return AutoreleasePoolPage::autorelease((id)this);
autoreleaseFast(obj)

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {//page有值,且没有满
            return page->add(obj);
        } else if (page) {//page有值,但是满了
            return autoreleaseFullPage(obj, page);
        } else {//page为空
            return autoreleaseNoPage(obj);
        }
    }

autoreleaseFullPage(obj, page);的函数方法:

static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) 
            	page = page->child;
            else 
            	page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

找page->child,有的话拿page->child
没有page->child,则新建page = new AutoreleasePoolPage(page);
最后page->add(obj)并返回

autoreleaseNoPage(obj);的方法函数:

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // No pool in place.
        assert(!hotPage());

        if (obj != POOL_SENTINEL  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);

        // Push an autorelease pool boundary if it wasn't already requested.
        if (obj != POOL_SENTINEL) {
            page->add(POOL_SENTINEL);
        }

        // Push the requested object.
        return page->add(obj);
    }

新建一个page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
page->add(obj);将内容添加进去

总结:

如果page有值,且没有满,则插入obj
如果page有值,且满了,则查找其子类,子类有调用,没有就新建page,并插入obj
如果page为空,则新建page,并插入obj

在这里插入图片描述
在这里插入图片描述


循环引用

循环引用可以分为三种:
自循环引用
相互循环引用
多循环引用

自循环引用

在这里插入图片描述

YZPerson里面有一个成员变量YZStudent类型的student
YZStudent *student = [[YZPerson alloc] init];

在这里插入图片描述

在这里插入图片描述

一般需要注意的循环引用有:
代理、block、NSTimer、大环引用
我们重点看下NSTimer

问:如何破除循环引用?

避免产生循环引用
在合适的时机手动断环

问:具体的解决方案有哪些?

__weak
__block
__unsafe_unretained

在这里插入图片描述

__block破解循环引用

MRC下,__block修饰对象不会增加其引用计数,避免了循环引用
ARC下,__block修饰的对象会被强引用,无法避免循环引用,需手动解环

在这里插入图片描述

__unsafe_unretained破解循环引用

修饰对象,不会增加其引用计数,避免了循环引用
如果被修饰的对象在某一时机被释放,会产生悬垂指针
因此,尽量不是有__unsafe_unretained

在这里插入图片描述
VC拥有一个对象,对其有一个强指针引用。
对象有一个NSTimer,假如是强引用
NSTimer会对target有一个强引用,也就是NSTimer强引用对象,从而造成循环引用。

那么,能否将对象拥有NSTimer改成弱引用,解除循环引用呢?

在这里插入图片描述

NSTimer分配调用后,其实会加入到当前线程的Runloop中,也就是当前线程的Runloop会对NSTimer有一个强引用。
如果是主线程,那么也就是主线程的Runloop对NSTimer有一个强引用关系,NSTimer又引用了对象。即使VC对对象的强引用消失,对象还是有一个强指针引用,不会被销毁,从而造成内存泄漏。

NSTimer有重复定时器,和非重复定时器。
如果是非重复定时器,一般我们会在定时器的回调方法中,将定时器销毁,并置为nil,这样的话,是可以解除循环引用并且没有内存泄漏。

但是,如果是重复定时器,就不可以在定时器的回调方法中,将定时器销毁,并置为nil

此处讲的使用中间变量解除循环引用的方法,没有MJ那个方法好,此处不表。
MJ里面是尝试对NSTimer对对象的弱引用
此处,是对象对NSTimer的弱引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值