iOS內(nèi)存管理底層原理

內(nèi)存布局

了解程序內(nèi)存布局請(qǐng)點(diǎn)擊程序的內(nèi)存布局以及棧汪拥、堆原理

內(nèi)存管理方案

在學(xué)習(xí)內(nèi)存管理之前先思考一下這幾個(gè)問題:

1因妇、對(duì)象的引用計(jì)數(shù)存放在什么地方化借?怎么讀寫的领曼?
2缆巧、對(duì)象釋放的時(shí)候怎么處理弱引用表、關(guān)聯(lián)對(duì)象的莉炉?弱引用為什么可以在對(duì)象時(shí)自動(dòng)置為nil钓账?
3、什么是SideTable呢袱?它跟引用計(jì)數(shù)表和弱引用表是什么關(guān)系官扣?
4、自動(dòng)釋放池是如何管理內(nèi)存的羞福?什么時(shí)候創(chuàng)建惕蹄?什么時(shí)候釋放對(duì)象?

MRC

MRC(Manual Reference Counting)翻譯出來就是手動(dòng)引用計(jì)數(shù)。在Xcode4之前卖陵,只能通過MRC機(jī)制管理內(nèi)存遭顶,MRC要求開發(fā)人員手動(dòng)管理內(nèi)存,維護(hù)OC對(duì)象的引用計(jì)數(shù)泪蔫。也就是說棒旗,在需要方手動(dòng)調(diào)用retain、release等內(nèi)存管理相關(guān)操作撩荣。

ARC

ARC(Automatic Reference Counting)铣揉,翻譯出來就是自動(dòng)引用計(jì)數(shù)。這是相對(duì)于MRC的改進(jìn)餐曹,本身內(nèi)存管理還是通過引用計(jì)數(shù)機(jī)制的逛拱,只不過是不需要開發(fā)人員手動(dòng)維護(hù),程序在編譯時(shí)期會(huì)在適當(dāng)?shù)牡胤阶詣?dòng)插入相關(guān)的retain台猴、release等代碼朽合,達(dá)到自動(dòng)管理引用計(jì)數(shù)的目的。

Tagged Pointer小對(duì)象

Tagged Pointer計(jì)數(shù)是將一些小對(duì)象諸如NSString饱狂、NSNumber曹步、NSDate等類型轉(zhuǎn)成Tagged Pointer對(duì)象,它們的值直接存儲(chǔ)在對(duì)象指針中休讳。不需要開辟堆內(nèi)存讲婚,也就是不需要malloc和free,也不需要引用計(jì)數(shù)retain衍腥、release等操作(點(diǎn)擊了解更多關(guān)于Tagged Pointer小對(duì)象的內(nèi)容)磺樱。

引用計(jì)數(shù)機(jī)制的底層原理

不管是MRC還是ARC纳猫,都是通過引用計(jì)數(shù)來管理內(nèi)存的婆咸。那么什么是引用計(jì)數(shù)?它是如何通過引用計(jì)數(shù)來管理內(nèi)存的呢芜辕?引用計(jì)數(shù)是計(jì)算機(jī)編程語(yǔ)言中的一種內(nèi)存管理技術(shù)尚骄,是指將對(duì)象的被引用次數(shù)保存起來,當(dāng)被引用次數(shù)變?yōu)榱銜r(shí)就將其釋放的過程侵续。在iOS中引用又分為強(qiáng)引用(strong)和弱引用(weak)倔丈,強(qiáng)引用是引用計(jì)數(shù)會(huì)增加,弱引用則不會(huì)状蜗。iOS中常見的引用計(jì)數(shù)相關(guān)的操作有:

  • alloc對(duì)象創(chuàng)建時(shí)(老版本源碼的alloc是不會(huì)初始化引用計(jì)數(shù)的需五,這里版本是objc4-818.2);
    retain(包括strong修飾的屬性),引用計(jì)數(shù)加1轧坎;
    release宏邮,引用計(jì)數(shù)減1;
    autorelease 自動(dòng)釋放,對(duì)象指針會(huì)被添加到釋放池中蜜氨,在自動(dòng)釋放池drain時(shí)釋放械筛;
    retainCount,獲取引用計(jì)數(shù)個(gè)數(shù)飒炎。

接下來通過源碼對(duì)各個(gè)操作進(jìn)行解析埋哟。

alloc時(shí)初始化引用計(jì)數(shù)

alloc是對(duì)象創(chuàng)建時(shí)會(huì)調(diào)用initIsa方法初始化isa(點(diǎn)擊了解對(duì)象創(chuàng)建過程),初始化isa的時(shí)候會(huì)初始化引用計(jì)數(shù)為1(點(diǎn)擊了解更多關(guān)于isa的信息):

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    const char *mangledName = cls->mangledName();
    if (strcmp("MyObject", mangledName) == 0) {
        if(!cls->isMetaClass()){//避免元類的影響
            printf("我來了 MyObject");//定位要調(diào)試的類
        }
    }
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa = newisa;
}

初始化引用計(jì)數(shù):

newisa.extra_rc = 1;
retain底層源碼解析

對(duì)象的retain操作最終是通過方法rootRetain來實(shí)現(xiàn)的郎汪。rootRetain主要做了如下幾件事情:
1赤赊、讀取出isa指針信息(點(diǎn)擊了解更多關(guān)于isa的信息)。

oldisa = LoadExclusive(&isa.bits);

2煞赢、判斷對(duì)象是否有自己的默認(rèn)實(shí)現(xiàn)的retain方法砍鸠。

 if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_retain()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                return swiftRetain.load(memory_order_relaxed)((id)this);
            }
            return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
        }
    }

如果有的話就走自己方法。
3耕驰、判斷是否是nonpointer對(duì)象爷辱,如果是nonpointer對(duì)象的話不需要引用計(jì)數(shù)管理,比如類對(duì)象等朦肘,直接return饭弓。點(diǎn)擊了解更多關(guān)于nonpointer的信息

if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return (id)this;
        }
    }

4媒抠、判斷對(duì)象是否正在釋放弟断,如果是正在釋放就沒必要retain了

 if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            if (slowpath(tryRetain)) {
                return nil;
            } else {
                return (id)this;
            }
        }

5、對(duì)引用技術(shù)加1趴生,因?yàn)閚onpointer對(duì)象的引用計(jì)數(shù)是存在isa指針里的有限為(指針長(zhǎng)度64位阀趴,引用計(jì)數(shù)extra_rc總共占8位,RC_ONE (1ULL<<45)苍匆,從地45位開始寫 )(點(diǎn)擊了解isa更多信息),所以有可能出現(xiàn)溢出情況刘急,如果溢出就讀取出當(dāng)前引用計(jì)數(shù)的一半(RC_HALF)存儲(chǔ)到SideTable(后面有分析)。

uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;//更新isa中的extra_rc
            newisa.has_sidetable_rc = true;
        }

如果溢出浸踩,則RC_HALF存儲(chǔ)到 SideTable:

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

增加完引用計(jì)數(shù)之后就返回了叔汁。

release源碼解析

release最終是通過方法objc_object::rootRelease來實(shí)現(xiàn)引用計(jì)數(shù)操作的。rootRelease主要做了以下幾件事情:
1检碗、讀取isa指針oldisa:

oldisa = LoadExclusive(&isa.bits);

2据块、判斷是否有自定義的release方法,如果有走自己的方法折剃,沒有就往下:

if (variant == RRVariant::FastOrMsgSend) {
        // These checks are only meaningful for objc_release()
        // They are here so that we avoid a re-load of the isa.
        if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
            ClearExclusive(&isa.bits);
            if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
                swiftRelease.load(memory_order_relaxed)((id)this);
                return true;
            }
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
            return true;
        }
    }

3另假、判斷是否是nonpointer,如果不是就返回怕犁,不需要release:

if (slowpath(!oldisa.nonpointer)) {
        // a Class is a Class forever, so we can perform this check once
        // outside of the CAS loop
        if (oldisa.getDecodedClass(false)->isMetaClass()) {
            ClearExclusive(&isa.bits);
            return false;
        }
    }

4边篮、判斷對(duì)象是否正在釋放开睡,如果是正在釋放就退出循環(huán)跳到deallocate:

       if (slowpath(newisa.isDeallocating())) {
            ClearExclusive(&isa.bits);
            if (sideTableLocked) {
                ASSERT(variant == RRVariant::Full);
                sidetable_unlock();
            }
            return false;
        }

如果是正在釋放則直接跳到deallocate:

 if (slowpath(newisa.isDeallocating()))
        goto deallocate;

5、引用計(jì)數(shù)extra_rc--苟耻,如果isa中的引用計(jì)數(shù)已經(jīng)減為0了篇恒,則跳轉(zhuǎn)到underflow:

uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
 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 (variant != RRVariant::Full) {
            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.
            oldisa = LoadExclusive(&isa.bits);
            goto retry;
        }

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

        bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there

        if (borrow.borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            bool didTransitionToDeallocating = false;
            newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
            newisa.has_sidetable_rc = !emptySideTable;

            bool stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);

            if (!stored && oldisa.nonpointer) {
                // 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.
                uintptr_t overflow;
                newisa.bits =
                    addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
                newisa.has_sidetable_rc = !emptySideTable;
                if (!overflow) {
                    stored = StoreReleaseExclusive(&isa.bits, &oldisa.bits, newisa.bits);
                    if (stored) {
                        didTransitionToDeallocating = newisa.isDeallocating();
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                ClearExclusive(&isa.bits);
                sidetable_addExtraRC_nolock(borrow.borrowed);
                oldisa = LoadExclusive(&isa.bits);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            if (emptySideTable)
                sidetable_clearExtraRC_nolock();

            if (!didTransitionToDeallocating) {
                if (slowpath(sideTableLocked)) sidetable_unlock();
                return false;
            }
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

在underflow流程中判斷之前是否有用于存儲(chǔ)引用計(jì)數(shù)的SideTable,如果有凶杖,從里面讀取引用計(jì)數(shù)胁艰,然后減1,然后重新把引用計(jì)數(shù)同步更新到isa指針(方便下次讀戎球稹)腾么,清除SideTable(清理內(nèi)存)。

6杈湾、如果引用計(jì)數(shù)為0就會(huì)調(diào)用dealloc

deallocate:
    // Really deallocate.

    ASSERT(newisa.isDeallocating());
    ASSERT(isa.isDeallocating());

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
dealloc源碼解析

dealloc底層通過objc_object::rootDealloc()方法實(shí)現(xiàn)解虱,其源碼:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer                     &&
                 !isa.weakly_referenced             &&
                 !isa.has_assoc                     &&
#if ISA_HAS_CXX_DTOR_BIT
                 !isa.has_cxx_dtor                  &&
#else
                 !isa.getClass(false)->hasCxxDtor() &&
#endif
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

1、這里我們看到它會(huì)判斷當(dāng)前對(duì)象是否有弱引用(weakly_referenced)漆撞、關(guān)聯(lián)對(duì)象(has_assoc)殴泰、C++析構(gòu)函數(shù)(has_cxx_dtor)、Sidetable(has_sidetable_rc)浮驳,如果沒有直接釋放free(this)悍汛;如果有,則調(diào)用object_dispose方法至会。object_dispose的流程如下:

object_dispose -> objc_destructInstance -> clearDeallocating -> clearDeallocating_slow- > free(obj);

2离咐、objc_destructInstances首先判斷有沒有C++析構(gòu)函數(shù)和關(guān)聯(lián)對(duì)象,有C++析構(gòu)函數(shù)就調(diào)用奉件,有關(guān)聯(lián)對(duì)象remove:

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, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

clearDeallocating方法中會(huì)判斷有沒有弱引用或者SideTable中是否有引用計(jì)數(shù)has_sidetable_rc宵蛀,如果有則調(diào)用clearDeallocating_slow處理(因?yàn)槿缫帽砗筒糠忠糜?jì)數(shù)表是存儲(chǔ)于SideTable中,所以它們被放在一個(gè)方法里處理):

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}
retainCount源碼解析

retainCount就是讀取isa和Sidetable(如果有)中的引用計(jì)數(shù)的和:

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    sidetable_unlock();
    return sidetable_retainCount();
}
SideTable表的作用

SideTable是個(gè)哈希表县貌,適用于存儲(chǔ)引用計(jì)數(shù)和弱引用表的术陶,它的表結(jié)構(gòu)如下:


SideTable.jpg

這里有個(gè)問題:引用計(jì)數(shù)為什么要使用SideTable? 因?yàn)橐糜?jì)數(shù)開始是存儲(chǔ)在isa指針中的,但是由于isa指針位數(shù)有限(64位)窃这,而分配給引用技術(shù)8位瞳别,如果引用計(jì)數(shù)溢出征候,那么就需要一個(gè)引用計(jì)數(shù)表來存儲(chǔ)多余的部分杭攻,SideTable是個(gè)哈希表,方便增刪改查疤坝。
那么這里又有一個(gè)問題:既然isa指針位數(shù)有限兆解,為什么不直接使用SideTable呢?主要是為了節(jié)省內(nèi)存和提高讀寫效率跑揉。首先isa存儲(chǔ)指針的初衷就是為了節(jié)省內(nèi)存锅睛、提高讀寫效率的埠巨,而且對(duì)于大部分對(duì)象來說8位(2^8個(gè))用于存儲(chǔ)引用計(jì)數(shù)也已經(jīng)足夠,只有少部分對(duì)象引用計(jì)數(shù)可能會(huì)超過现拒,而如果每個(gè)對(duì)象都使用SideTable表的話會(huì)影響效率辣垒,還浪費(fèi)內(nèi)存。

弱引用

弱引用不會(huì)增加對(duì)象的引用計(jì)數(shù)印蔬,但是它會(huì)有一個(gè)表來記錄引用變量勋桶,用于當(dāng)對(duì)象釋放的時(shí)候把這些變量指針置為nil,防止野指針侥猬。有前面可知弱引用表是存儲(chǔ)在SideTable中的(weak_table_t weak_table)例驹,下面是weak_table_t的數(shù)據(jù)結(jié)構(gòu):

/**
 * 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;
};

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

在對(duì)象釋放的時(shí)候會(huì)把關(guān)聯(lián)的弱引用表中的變量指針一個(gè)個(gè)刪除,并把指針指向nil:

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

自動(dòng)釋放池

關(guān)于自動(dòng)釋放池直接參考iOS自動(dòng)釋放池的底層原理退唠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鹃锈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瞧预,更是在濱河造成了極大的恐慌屎债,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垢油,死亡現(xiàn)場(chǎng)離奇詭異扔茅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)秸苗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門召娜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惊楼,你說我怎么就攤上這事玖瘸。” “怎么了檀咙?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵雅倒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我弧可,道長(zhǎng)蔑匣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任棕诵,我火速辦了婚禮裁良,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘校套。我一直安慰自己价脾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布笛匙。 她就那樣靜靜地躺著侨把,像睡著了一般犀变。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秋柄,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天获枝,我揣著相機(jī)與錄音,去河邊找鬼骇笔。 笑死映琳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜘拉。 我是一名探鬼主播萨西,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旭旭!你這毒婦竟也來了谎脯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤持寄,失蹤者是張志新(化名)和其女友劉穎源梭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稍味,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡废麻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了模庐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烛愧。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掂碱,靈堂內(nèi)的尸體忽然破棺而出怜姿,到底是詐尸還是另有隱情,我是刑警寧澤疼燥,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布沧卢,位于F島的核電站,受9級(jí)特大地震影響醉者,放射性物質(zhì)發(fā)生泄漏但狭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一撬即、第九天 我趴在偏房一處隱蔽的房頂上張望立磁。 院中可真熱鬧,春花似錦搞莺、人聲如沸息罗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迈喉。三九已至,卻和暖如春温圆,著一層夾襖步出監(jiān)牢的瞬間挨摸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工岁歉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留得运,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓锅移,卻偏偏與公主長(zhǎng)得像熔掺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子非剃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 從這篇文章開始探索iOS的內(nèi)存管理置逻,主要涉及的內(nèi)容有1. 內(nèi)存布局;2. 內(nèi)存管理方案:Tagged Pointe...
    風(fēng)緊扯呼閱讀 1,534評(píng)論 1 16
  • 一备绽、在 Obj-C 中券坞,如何檢測(cè)內(nèi)存泄漏?你知道哪些方式肺素? 目前我知道的方式有以下幾種 Memory Leaks ...
    maskerII閱讀 320評(píng)論 0 0
  • 一. 引用計(jì)數(shù) 1. 引用計(jì)數(shù)存儲(chǔ)在哪 我們都知道倍靡,調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1猴伶,調(diào)用release...
    Imkata閱讀 1,424評(píng)論 5 2
  • 本文中的源代碼來源:需要下載Runtime的源碼,官方的工程需要經(jīng)過大量調(diào)試才能使用塌西。這里有處理好的objc4-7...
    shen888閱讀 834評(píng)論 0 3
  • 1蜗顽、內(nèi)存布局 stack:方法調(diào)用 heap:通過alloc等分配對(duì)象 bss:未初始化的全局變量等。 data:...
    AKyS佐毅閱讀 1,589評(píng)論 0 19