内存管理

内存布局

内存布局.png
  • stack(栈区): 方法调用
  • heap(堆区):通过alloc等分配的对象
  • bss:未初始化的全局变量或静态变量等。
  • data:已初始化的全局变量等。
  • text:程序的代码段

内存管理方案

iOS是如何对内存进行管理的?

  • TaggedPointer:对一些小对象如NSNumber

  • NONPOINTER_ISA: 对于64位架构下的应用程序

    在64位架构下,isa指针占用64位bit,实际有32位或者40位就够用了,剩余的实际上是浪费的,苹果为了提高内存利用率,在这些剩余的bit位当中,存储了一些关于内存管理的相关数据内容,所以称为非指针型的isa

  • 散列表

    散列表是一个复杂的数据结构,其中包含了应用计数表和弱引用计数表。

NONPOINTER_ISA结构

arm64架构

nonpointer_isa01.png
nonpointer_isa02.png
  • 第0号位是indexed的标志位,如果这个位置是0,代表的是我们使用的isa指针只是一个纯的isa指针,它里面的内容就直接代表了当前对象的类对象的地址;如果这个位置是1,就代表这个isa指针里面存储的不仅是他的类对象的地址,而且还有一些内存管理方面的数据。

  • 第1号位has_assoc是表示当前对象是否有关联对象,0没有,1有。

  • 第2位has_cxx_dtor,表示的是当前对象是否有使用到C++相关的一些代码,或者C++语言方面的一些内容。在ARC中也可以通过这个标志位,来表示有些对象是通过ARC来进行内存管理的。

  • 后面的3-35位shiftcls,表示当前对象的类对象指针地址。

  • 后面的6位是一个magic字段

  • 后面是一位weakly_referenced,标识这个对象是否有相应的弱引用指针。

  • deallocating,表示的是当前对象是否在进行dealloc操作

  • has_sidetable_rc,表示的是当前这个isa指针当中,如果所存储的引用计数已经达到了上限的话,需要外挂一个sidetable数据结构,去存储相关的引用计数内容(也就是散列表)

  • extra_rc额外的引用计数,当我们引用计数在一个很小的值得范围之内就会存到isa指针当中,而不是由单独的引用计数表去存他的引用计数。

散列表方式

SideTables()源码
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTables()结构
sideTable.png

side tables实际上是一个hash表,通过一个对象指针,找到他对应的引用计数表,或弱引用表。

Side Table

SideTable源码
struct SideTable {
    spinlock_t slock;
    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<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

SideTable结构

sideTable02.png

为什么不是一个side table?

one_side_table03.png

假如说只有一张side table,相当于我们在内存当中分配的所有对象的引用计数或者说弱引用存储都放在一张大表当中,这个时候如果我们要操作某一个对象的引用计数值进行修改,比如说进行加1或减1的操作的话,由于所有的对象可能是在不同的线程当中去分配创建的,包括调用他们的release,retain等方法,也可能是在不同的线程当中进行操作;这个时候对一种表进行操作的时候,需要进行加锁处理,才能保证对于数据的访问安全,在这个过程中就存在了一个效率问题。比如说用户的内存空间一共有4GB,那么可能分配出成千上百万个内存对象,如果说每一个对象在对他进行内存引用计数的改变的时候,都操作这张表很显然就会有效率的问题。如果说已经又一个对象在操作这张表,下一个对象就要等他操作完,把锁释放之后再进行操作,这效率就会太低了。

one_side_table04.png

系统为了解决效率问题,引入了分离锁的技术方案。我们可以把内存对象所对应的引用计数表,可以分拆成多个部分。比如说分拆成8个,需要对8个表分别加锁。当A和B同时进行引用计数操作的话可以进行并发操作,如果是一张表他们需要进行顺序操作。很明显分离锁可以提高访问效率。

怎样实现快速分流?

快速分流指通过一个对象的指针如何快速定位到它属于那张side table 表当中。

hash表.png

side tables的本质是一张Hash表。这张hash表当中,可能有64张具体的side table 存储不同对象的引用计数表和弱引用表。

自旋锁 Spinlock_t

  • Spinlock_t是"忙等"的锁。

    如果当前锁已被其他线程获取,那么当前线程会不断的探测这个锁是否有被释放,如果释放掉,自己第一时间去获取这个锁。所以说自旋锁是一种忙等的锁。获取不到锁的时候,他会他自己的线程阻塞休眠,然后等到其他线程释放这个锁的时候来唤醒当前线程。

  • 适用于轻量访问。

引用计数表RefcountMap

引用计数表实际上是一个hash表,我们可以通过指针来找到对应对象的引用天计数,这一过程实际上也是hash查找(使用hash查找是为了提高查找效率)。

refcount_map.png

插入和获取是通过同一个hash函数完成,避免了递归查找和for循环遍历

size_t内存分配

size_t.png
  • 第一个二进制位表示的是weakly_referenced,对象是否有弱引用,0没有,1有。
  • 第二位deallocating表示当前对象是否处于dealloc
  • 后面(RC)存储的是对象的实际引用计数值,在实际计算这个引用计数值,需要向右偏移两位,因为后面两位需要去掉。

弱引用表weak_table_t

weak_table_t实际上也是一个hash表.

weak_table_t.png

weak_entry_t实际上是一个结构体数组。结构体数组存储的是每一个的弱引用指针,也就是代码当中定义的__weak id obj,obj内存地址即指针就存储在weak_entry_t

MRC & ARC

MRC 手动引用计数

  • alloc: 用来分配一个对象的内存空间。
  • retain:对一个对象的引用计数加1;
  • release:对一个对象的引用计数减1;
  • retainCount:获取当前对象的引用计数值
  • autorelease:如果调用了一个对象的autorelease方法,当前这个对象会在autoreleasepool结束的时候,调用他的release操作进行引用计数减1.
  • dealloc:在MRC当中调用dealloc方法需要显式调用[super dealloc]来释放或废弃父类的相关成员变量。

ARC 自动引用计数

  • ARC是LLVM和Runtime协作来进行自动引用计数管理;
  • ARC中禁止手动调用retain,release,retainCount,dealloc,并且在ARC中可以重写某个对象的dealloc方法,但是不能再dealloc方法当中,显示调用[super dealloc]
  • ARC中新增了weak,strong属性关键字。

ARC实际是由编译期自动为我们插入retainrelease操作之外,还需要runtime的功能进行支持,然后由编译器和Runtime共同协作才能组成ARC的全部功能。

引用计数管理

实现原理分析

  • alloc
  • retain
  • release
  • retainCount
  • dealloc

alloc实现

  • 经过一系列调用,最终调用了C函数calloc
  • 此时并没有设置引用计数为1

retain实现

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
  //hash查找SideTable
    SideTable& table = SideTables()[this];
    
  //SideTable加锁
    table.lock();
  //hash查找引用计数值
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
      //引用计数加1
      //#define SIDE_TABLE_RC_ONE            (1UL<<2)
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
  1. 通过当前对象的指针this,经过hash函数的计算,可以快速的SideTables当中找到(hash查找)它对应的SideTable
  2. 然后在SideTable当中获取应用计数map这个成员变量,通过对象的指针this,在SideTable的引用计数表中获取(hash查找)当前当前对象的引用计数值。
  3. 经过一定的条件判断之后,引用计数加1。

引用计数加1,实际是加上了偏移量对应的操作,这个偏移量是4,反应出来的结果是加1,因为size_t64位,前两位不是存储引用计数,所以需要向左偏移两位操作1UL<<2

release实现

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
  //hash查找SideTable
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

  //加锁
    table.lock();
  //根据当前对象指针,访问table的应用计数表
    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)) {
      //引用计数减1操作
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}
  1. 通过当前对象的指针this,经过hash函数的计算,可以快速的SideTables当中找到(hash查找)它对应的SideTable
  2. 根据当前对象指针,访问table的应用计数表
  3. 找到对应的值进行引用计数减1操作

retainCount实现

uintptr_t
objc_object::sidetable_retainCount()
{
  //hash查找SideTable
    SideTable& table = SideTables()[this];
  //声明局部变量赋值为1
    size_t refcnt_result = 1;
    
  //加锁
    table.lock();
  //根据当前对象指针,访问table的应用计数表
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
      //查找结果向右位移2位,再加上局部变量的值
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}
  1. 通过当前对象的指针this,经过hash函数的计算,可以快速的SideTables当中找到(hash查找)它对应的SideTable

  2. 根据当前对象指针,访问table的应用计数表

  3. 找到对应的值向右位移2位,再加上局部变量的值1

    这就是alloc操作之后,引用计数没有变化,但retainCount获取的值是1的原因

dealloc实现源码

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

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

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

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

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

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

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.png
object_dispose()实现
object_dispose()实现.png
objc_destructInstance()实现
objc_destructInstance()实现.png
clearDeallocating()实现
clearDeallocating实现.png

弱引用管理

弱引用管理.png
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

添加weak变量的弱引用实现

weak调用栈.png

当一个对象被释放或者废弃之后,weak变量怎样处理的?

/** 
 * 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) {
        //referrers取到弱引用指针的所有对应的数组列表
        objc_object **referrer = referrers[I];
        if (referrer) {//如果referrer即弱引用指针存在
            if (*referrer == referent) { //如果弱引用指针对应的是被废弃的对象的话,就将指针置为nil
                *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);
}

当一个对象被dealloc之后,内部实现当中会去调用weak_clear_no_lock()函数,函数实现内部会根据弱引用指针查找弱引用表把当前对象相对应的弱引用拿出来,然后遍历数组的所有弱引用指针,分别置为nil

自动释放池

编译期会将代码块@autoreleasepool{}改写为:

  1. void *ctx = objc_autoreleasePoolPush();

  2. {}中的代码

  3. objc_autoreleasePoolPop(ctx);

    一次pop实际上相当于一次批量的pop操作

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

自动释放池的数据结构

  • 是以为节点通过双向链表的形式组合而成。(什么是自动释放池/自动释放池的数据结构是怎样的?)
  • 是和线程一一对应的

双向链表

双向链表.png

栈结构.png

AutoreleasePoolPage类源码

class AutoreleasePoolPage 
{
    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;//指向当前栈中下一个可填充的位置
    pthread_t const thread; //线程
    AutoreleasePoolPage * const parent;//双向链表的父指针
    AutoreleasePoolPage *child;//双向链表的子指针
    uint32_t const depth;
    uint32_t hiwat;

    // SIZE-sizeof(*this) bytes of contents follow

    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    static void operator delete(void * p) {
        return free(p);
    }
  
  .....
}

AutoreleasePoolPage结构

autoreleasePoolPage.png

AutoreleasePoolPage::push

autoreleasePoolPage_push.png

[obj autorelease]

obj_autorelease.png

AutoreleasePoolPage::pop

  • 根据传入的哨兵对象找到对应位置。
  • 给上次push操作之后添加的对象依次发送release消息。
  • 回退next指针到正确位置。
autoreleasePoolPage_pop01.png

autoreleasePoolPage_pop02.png

总结

  • 当每次runloop将要结束的时候调用AutoreleasePoolPage::pop()
  • 多层嵌套就是多次插入哨兵对象。
  • 在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool
- (void)viewDidLoad
{
  [super viewDidLoad];
  NSMutableArray *array = [NSMutableArray array];
  NSLog(@"%@",array);
}

上面array的内存在什么时候释放的?

当每次runloop将要结束的时候,都会对前一次创建的AutoreleasePool调用AutoreleasePoolPage::pop()操作,同时会push进来一个AutoreleasePool。所以array对象会在当前runloop就要结束的时候调用AutoreleasePoolPage::pop()方法,把对应的array对象,调用其release函数对其进行释放

AutoreleasePool的实现原理是怎样的?

是以为节点通过双向链表的形式组合而成的数据结构

AutoreleasePool为何可以嵌套使用?

多层嵌套就是多次插入哨兵对象。在我们每次创建代码块@autoreleasepool{},系统就会为我们进行哨兵对象的插入,完成新的AutoreleasePool的创建,实际上也是创建了一个AutoreleasePoolPage,假如当前AutoreleasePoolPage没有满的话,就不用创建AutoreleasePoolPage。所以新创建的AutoreleasePool底层就是插入一个哨兵对象,所以可以多层嵌套。

循环引用

三种循环引用

  • 自循环引用
  • 相互循环引用
  • 多循环引用

自循环引用

自循环引用.png

相互循环引用

相互循环引用.png

多循环引用

多循环引用.png

循环引用考点

  • 代理
  • Block
  • NSTimer
  • 大环引用

如何破除循环引用?

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

破除循环引用具体的解决方案都有哪些?

  • __weak破解
  • __block破解
  • __unsafe_unretained破解

__weak破解

weak破解.png

__block破解

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

__unsafe_unretained破解

  • 修饰对象不会增加其引用计数,避免了循环引用。
  • 如果被修饰对象在某一时机被释放,会产生悬垂指针!

循环引用示例

  • Block的使用示例。(参看Block的讲解)

  • NSTimer使用示例。

nstimer方案.png
#import "NSTimer+WeakTimer.h"

@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;

- (void)fire:(NSTimer *)timer;
@end

@implementation TimerWeakObject

- (void)fire:(NSTimer *)timer
{
    if (self.target) {
        if ([self.target respondsToSelector:self.selector]) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        }
    }
    else{
        [self.timer invalidate];
    }
}

@end

@implementation NSTimer (WeakTimer)

+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats
{
    TimerWeakObject *object = [[TimerWeakObject alloc] init];
    object.target = aTarget;
    object.selector = aSelector;
    object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    
    return object.timer;
}

@end

内存管理面试总结

  • 什么是ARC?
  • 为什么weak指针指向的对象在废弃之后会被自动置为nil?
  • 苹果是如何实现AutoreleasePool的?
  • 什么是循环引用?你遇到过哪些循环引用,是怎样解决的?
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 1、内存布局 stack:方法调用 heap:通过alloc等分配对象 bss:未初始化的全局变量等。 data:...
    AKyS佐毅阅读 1,589评论 0 19
  • 文章目录 一.内存管理准则 二.属性内存管理修饰符全解析 三.block中的weak和strong 四.weak是...
    YouKnowZrx阅读 1,047评论 5 10
  • 前言 从我开始学习iOS的时候,身边的朋友、网上的博客都告诉我iOS的内存管理是依靠引用计数的,然后说引用计数大于...
    盖世英雄_ix4n04阅读 544评论 0 1
  • 一.面试问题 使用CADisplayLink、NSTimer有什么注意点?循环引用、NSTimer定时器不准 介绍...
    蔚尼阅读 852评论 0 1
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,470评论 1 3