代码编织梦想

  • 兄弟们,最近实在是太忙了。不过~我又回来继续探索了。

  • 内存管理这个名词,我相信所有的iOS工程师都听说过,也是大多数兄弟们,面试最头疼的,今天!小谷带大家走一波源码。希望对大家有所帮助。

  • 关于内存管理,大家都会想到,ARC/MRC、retain、release、dealloc、autorelease。今天就浅谈一波。不对的地方,我在查源码找找,哈哈~

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

ARC/MRC  (我就不多说了)
ARC(Automatic Reference Count ) :自动的引用计数
其实ARC 是LLVM和runtime 结合的产物。
不让开发者主动调用dealloc/relesae/retain。
并且引进了,weak和strong,

1. retain 和 release

  • 大家对于retain和release一定不陌生。我当定义一个属性的时候就会产生,setter和getter方法。赋值的操作就是新值的retain和旧值的release。源码如下:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

今天我们研究下他是如何retain的。

1.1. 探究retain
首先,我们点进方法看看:

__attribute__((aligned(16), flatten, noinline))
id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

我不太知道怎么把关键字设置高亮,兄弟们对不起了。

  1. 这个判断是:如果是isTaggedPointer就返回。否则就进行retain操作
  2. 那这是不是说明了:isTaggedPointer类型,不会进行内存管理,会自动释放
  3. 那我们就简单的看下这个isTaggedPointer

1.1.1. 拓展isTaggedPointer类型

  1. taggedpointer就是大家常说的小对象,我们来看下他的实现:

// 左移63位,就只是保留了最高位
#   define _OBJC_TAG_MASK (1UL<<63)

inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

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


  1. 所以判断小对象就是判断最高位。

这个不是内存管理的重点,主要是遇到了,就提一嘴,taggedpointer类型是不进行内存管理的。不会retain和release。一般的小对象是NSNumber、NSData和小于11位的NSString

1.2. retain的实现
我们看一下retain的实现:下面是我追踪源码的过程:

  1. 这样我们就找到了retain 源码的实现(我在里面已经加了注释(虽然我知道兄弟们肯定可以看懂。。))
ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;


    bool sideTableLocked = false;
    bool transcribeToSideTable = false;


	//isa  的原因是要在extra_rc存值(如果有不了解isa结构的兄弟可以看下我的博客)
    isa_t oldisa;
    isa_t newisa;


    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
	//slowpath就是小几率走的意思。
	//不是nonpoint_isa
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);//清理bits(我看了下实现,好像啥都没干)
            if (rawISA()->isMetaClass()) return (id)this;//如果是元类,返回
            if (!tryRetain && sideTableLocked) sidetable_unlock();//如果散列表锁了,解锁。(里面的实现兄弟们可以自己探究了)
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {//如果是dealloc了,(这个一会我会在说)
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();//解锁
            return nil;//返回nil
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++ (这个英文注释不是我写的,哈哈)


        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {//查看是否溢出了(就是满了没有)
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);//没有满的话,就retain操作
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            //如果满了,就会把extra_rc的一半放入散列表
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));


    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();
    return (id)this;
}

//这个是中间有调用的,其实就是又调用回来了。
NEVER_INLINE id 
objc_object::rootRetain_overflow(bool tryRetain)
{
    return rootRetain(tryRetain, true);
}

我在里面加了一点注释,其实这个大家可能有些有疑惑,不知道散列表是什么。听起来高端大气,其实就是一张用来存储的表

1.3. retain 总结

我来根据上述代码总结下。(我知道兄弟们都看的懂,不过我比较笨 😆)

  1. 进行retain操作时,先判断是否是taggedpointer类型。是的话继续retain操作,taggedpointer不受内存管理

  2. 进行retain时首先操作的是isa中的extra_rc,(如果不明白isa结构可以看下我的博客isa结构分析)

  3. 进行retain时会判断是否dealloc,如果被释放掉就返回nil

  4. 进行retain操作会使 extra_rc++, 如果溢出的话,会把一半存在散列表中

1.4. release实现

  1. 大家看完retain之后,release就相对简单了很多,其实就是个反向操作
    我把源码贴出来啦(这个比较长,但操作差不多):
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.        
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

这个就留给兄弟们看了,我就不做注释污染了(其实就是isa中的extra_rc进行减减的操作)

2. dealloc

  1. 在retain和release代码中,它会判断对象是否dealloc
  2. 我们浅谈一波。(其实就是个析构~)
  3. 首先大家可以思考下,假如我要释放了,我需要怎么办?
  4. 我贴源码了啊。
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
  1. 是否是isa.nonpointer,只有是这才会有释放这回事哈
  2. 是否有弱引用表
  3. 是否有关联对象
  4. 是否有C++的析构,就是哪个cxx_dtor (这个也在isa结构分析的博客中有提到)
  5. 是否有散列表

其实就是这5个条件判断
否则就会调用object_dispose(其实也是发现有以上判断就处理的操作)
代码给兄弟们奉上~

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

好了兄弟们,就先到这了~ 希望可以给大家带来帮助。洗完了我继续回去加班~😔

原文作者:小谷先森
原文地址:https://juejin.cn/post/6899420127050072077

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/iOSxiaodaidai/article/details/111068631

Java 14都出来了,为什么还有那么多人执着于Java 8?-爱代码爱编程

比如我吧,我只是自己私下里研究一下 Java 11 新特性,公司还是用 Java 8 ,更有甚者,我身边有个朋友的公司还用 JDK 1.6,你说神奇不。 Java 都已经 25岁了,想必比在座的很多同学年龄还大吧。 在 JDK 版本的世界里,从来都是 Oracle 发他的新版本,我们继续用我们的老版本。4 年之前用 JDK 7,后来终于升级到

高效能IT人士不二法门,实现35岁退休-爱代码爱编程

高效能IT人士不二法门,实现35岁退休 工作十年,观察了很多高效能人士的工作习惯,这些人基本都实现了35岁左右脱离I苦海,有些甚至能直接退休。总结了一些他们好的特质,分享给大家。 不懂就问,刻不容缓 只要遇到问题就去问其他人,千万不要试图浪费时间去尝试自己解决。如果自己要去解决一个问题,你还得查各种资料,做实验,每次解决一个问题都要走好几条弯路,太浪

双非渣硕,苦学71天,啃透5800多页架构师修炼手册,终于拿到美团35K的offer-爱代码爱编程

前言 长文干货提示,文章为大家完整记录了一位在北京做了3年的JAVA开发的朋友,如何通过美团的面试及拿到35K的offer。全篇内容由全程电话录音再手打腾稿,原创手打不易,请记得点赞收藏支持哦! 文章末尾有为大家准备好的JAVA面试资料。 面试总结 JAVA基础 1. JAVA中的几种基本数据类型是什么,各自占用多少字节,Integer占几个字节.

如何使用VIPER构建iOS应用-爱代码爱编程

用VIPER构建iOS应用 为避免撕逼,提前声明:本文纯属翻译,仅仅是为了学习,加上水平有限,见谅! 【原文】https://www.objc.io/issues/13-architecture/singletons/ 用VIPER构建iOS应用 ——by Jeff Gilbert and Conrad Stoll 众所周知,在建筑领域,我们塑造我

面试官:简单说一下RocketMQ整合SpringBoot吧-爱代码爱编程

前言 在使用SpringBoot的starter集成包时,要特别注意版本。因为SpringBoot集成RocketMQ的starter依赖是由Spring社区提供的,目前正在快速迭代的过程当中,不同版本之间的差距非常大,甚至基础的底层对象都会经常有改动。例如如果使用rocketmq-spring-boot-starter:2.0.4版本开发的代码,升级到

layui中table表格下checkbox保存状态赋值checkbox与禁止使用表头多选-爱代码爱编程

保存状态赋值 var checkedSet = new Set(); table.on('checkbox(dataguid1Table)', function(obj){                       console.log(obj.checked); //当前是否选中状态                       consol

开发也可以改变下,RxSwift-让你的开发变得简洁高效。-爱代码爱编程

RxSwift到底是什么? RxSwift是一种函数式响应式编程。那什么是函数式编程呢,函数式编程最重要的概念就是“无状态(immutable)”,看到这有些小伙伴可能会很开心,无状态(知名LOL职业选手)嘛,我是他的粉丝!言归正传,到底什么是“无状态(immutable)”呢?我看了很多文章,但是都被他们专业的描述整的一头雾水,我来说说我的看法:有丰富

Python脚本文件和函数的基本运用-爱代码爱编程

一:脚本文件 1.脚本文件的操作 import sys p=sys.argv print(p) #将python代码放到cmd中运行,在后面添加参数,会自动保存在输出的列表中,默认输出的列表中只有一个值,那就是当前文件的地址。 2.常用的环境 例:常用于程序的调试,省去了中间繁琐输出的时间,可以更快时间的调试程序。 二:函数的基本运用 1

Java 14都出来了,为什么还有那么多人执着于Java 8?-爱代码爱编程

比如我吧,我只是自己私下里研究一下 Java 11 新特性,公司还是用 Java 8 ,更有甚者,我身边有个朋友的公司还用 JDK 1.6,你说神奇不。 Java 都已经 25岁了,想必比在座的很多同学年龄还大吧。 在 JDK 版本的世界里,从来都是 Oracle 发他的新版本,我们继续用我们的老版本。4 年之前用 JDK 7,后来终于升级到

Python文件的多种读写方式及游标-爱代码爱编程

一:文件的多种读写方式 主方式:w r a 从方式:t b + 了解方式:x u 1.按t(按照字符进行操作): with open("data_1.txt","wt",encoding="utf-8") as f1: f1.write("你好,世界!") #with open......as用于代替close()完成对打开的文件的释放

Python 常见语法逻辑错误收集-爱代码爱编程

每次1. list 问题: 某地方参数需要传入一个list 当时采用的方法为: phone_list = [] send_message(phone_list.append(get_phone_numbers(authorization_code)),....) # phone_list = [] # phone_list.append(ge

阿里P8大佬带你深入解析JVM与java-爱代码爱编程

阿里P8大佬带你深入解析JVM与java 什么是Java 经过了多年的发展,Java早已由一门单纯的计算机编程语言,演变为了一套强大的技术体系。是的,什么是Java,我想技术体系四个字应该是最好的概括了吧。Java设计者们将Java划分为3种结构独立但却彼此依赖的技术体系分支,它们分别对应着不同的规范集合和组件: 1、Java SE(标准版),