内存管理

OC源码:https://opensource.apple.com/tarballs/objc4/

定时器

  • CADisplayLink (调用频率和帧率保持一致 60fps)
  • NSTimer
    当定时器为类属性时 ,且定时器target为该类,则会造成循环引用

定时器循环引用解决方案
(1).NSTimer用block
(2).timer的target指向otherObject,otherObject的target弱引用VC

NSProxy

NSProxy没有继承自NSObject,没有init方法,这个类专门用来做消息转发,此时的消息机制是先查找自己类中是否实现,有实现调用,没有实现的话不经过父类消息查找和动态方法解析,直接进入消息转发
继承自NSProxy 的类的实例对象,在调用isKindOfClass和isMemberOfClass的方法时,直接进入消息转发,NSProxy该类的实例对象适合做中间对象


内存管理_第1张图片
image.png
GCD定时器

NSTimer依赖于Runloop,Runloop如果任务繁重的话,会导致NSTimer不准时,而GCD的定时器更准确一些

     //主队列 事件在主线程跑
//    dispatch_queue_t queue = dispatch_get_main_queue();
    //并发队列,串行队列,全局并发队列 事件在子线程跑
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //设置时间  参数 :timer 多久后开始  时间间隔  时间误差
    NSTimeInterval start = 2.0;
    NSTimeInterval interval = 1.0;
    //从现在开始
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //从2.0秒后开始
//    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC) , interval * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        //回调事件
        NSLog(@"hello");
    });  
    //定时器开始
    dispatch_resume(timer);
    //取消定时器
    dispatch_source_cancel(timer);

iOS程序的内存布局

从低地址到高地址排列

  • 从0地址开始有一段系统保留

  • 代码段(_TEXT)
    存放编译之后的代码

  • 数据段(_DATA)
    存放字符串常量
    已初始化的全局变量,静态变量等
    未初始化数据

  • 堆(heap)
    通过alloc,malloc,calloc等动态分配的空间,分配的内存空间地址越来越大

  • 栈(stack)
    函数调用开销,比如局部变量。分配的内存空间地址越来越小

  • 内核区

Tagged Pointer

  • 从64位开始,iOS引入Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
  • 在没有使用Tagged Pointer之前,NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
  • 使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag+Data ,也就是将数据直接存储在了指针中
  • 当指针不够存储数据时,才会使用动态分配堆内存的方式来存储数据
  • objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据
  • 判断一个指针是否为Tagged Pointer
    iOS平台,最高有效位为1
    Mac平台,最低有效位为1


    内存管理_第2张图片
    image.png

OC对象的内存管理

  • iOS中,使用引用计数来管理OC对象的内存
  • 一个新创建的OC对象的引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
  • 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
  • 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者auto release来释放它
Copy
  • iOS提供了两个拷贝方法
    copy 不可变拷贝
    mutableCopy 可变拷贝
    NSString* sss = @"abc";
    
    self.str = [sss copy];   //返回NSString
    self.str1 = [sss mutableCopy];   //返回NSMutableString
    
    sss = @"gfw";
   
    NSLog(@"%@ -- %@",self.str,self.str1);
    NSLog(@"%p -- %p",self.str,self.str1);
image.png
  • NSString字符串
    NSString* str = [NSString stringWithFormat:@"abc"];
    NSString* str1 = [str copy];  //浅拷贝
    NSMutableString* str2 = [str mutableCopy];  //深拷贝
image.png
    NSMutableString* str = [NSMutableString stringWithFormat:@"abc"];
    NSString* str1 = [str copy];  //深拷贝
    NSMutableString* str2 = [str mutableCopy];  //深拷贝
image.png
  • NSArray和NSDictionary同上


    image.png
  • copy修饰的属性

@property (nonatomic,copy)  NSMutableArray  *array;

copy不要修饰可变类型,因为copy修饰的属性的自动生成的set方法,会对可变参数进行copy操作,将不可变对象再赋值给成员变量

  • 自定义类的实例对象的copy
//
//  ViewController.h
//  内存管理-copy
//
//  Created by SivatiMac on 2018/12/9.
//  Copyright © 2018 SivatiMac. All rights reserved.
//

#import 
@interface ViewController : UIViewController 
@property (nonatomic,strong)  UIView*  cuntomView;
@property (nonatomic,assign)  int  age;
@end
//
//  ViewController.m
//  内存管理-copy
//
//  Created by SivatiMac on 2018/12/9.
//  Copyright © 2018 SivatiMac. All rights reserved.
//

#import "ViewController.h"
@implementation ViewController
-(id)copyWithZone:(NSZone *)zone{
    ViewController* vc = [[ViewController allocWithZone:zone] init];
    vc.cuntomView = [UIView new];
    vc.age = self.age;
    return vc;
}
@end
引用计数的存储

引用计数存储在对象的union isa中,如果isa中extra_rc不够存引用计数了,额外的引用计数就存在sidetable中
下面为查看objc4中的源码

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

    Class cls;
    uintptr_t bits;

#if SUPPORT_PACKED_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // nonpointer must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1;     // no r/r overrides
    // uintptr_t lock : 2;        // lock for atomic property, @synch
    // uintptr_t extraBytes : 1;  // allocated with extra bytes

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        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;    //sidetable中是否有引用计数
        uintptr_t extra_rc          : 19;   //extra_rc+1为引用计数
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
};
image.png
inline uintptr_t 
objc_object::rootRetainCount()
{
//如果是TaggedPointer对象,返回自己
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
//引用计数
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
//加上存在sidetable中额外的引用计数
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}
weak实现原理

SideTable& table = SideTables()[this];
该方法可以获取当前对象的SideTable,SideTable中存有该对象的弱引用表weak_table,weak_table是散列表,对象dealloc时,会遍历weak_table,将指向该对象的weak指针置为nil ,代码调用如下

  • dealloc详细流程
    查看objc4源码,对象在dealloc时候发生了什么事
// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

查看_objc_rootDealloc()

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

    obj->rootDealloc();
}

查看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());
/*
     如果没有被弱引用 weakly_referenced 
        没有关联对象 has_assoc
        没有c++析构函数 has_cxx_dtor
        sidetable中没有引用计数 has_sidetable_rc
*/
        free(this);   //直接释放
    } 
    else {
        //否则调用object_dispose
        object_dispose((id)this);
    }
}

查看object_dispose()

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

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

在free前会执行objc_destructInstance(obj)

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);
        //将指向当前对象的弱指针置为nil
        obj->clearDeallocating();
    }

    return obj;
}

查看clearDeallocating()

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.
        //isa非指针走这里
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

查看clearDeallocating_slow()

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

查看 weak_clear_no_lock(&table.weak_table, (id)this)

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) {
                //将指向该对象的弱指针置为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);
}
autorelease和autoreleasepool
  • autoreleasepool的主要底层数据结构是__AtAutoreleasePool和AutoreleasePoolPage
  • 调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
AutoreleasePoolPage
  • 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
  • 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
  • 一个AutoreleasePoolPage对象存满了,会创建一个新的AutoreleasePoolPage对象,APP运行可能存在多个AutoreleasePoolPage对象

*查看autoreleasePool信息的私有函数

//
//  main.m
//  Created by SivatiMac on 2018/12/9.
//  Copyright © 2018 SivatiMac. All rights reserved.
//

#import 
#import "AppDelegate.h"


extern void _objc_autoreleasePoolPrint(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        _objc_autoreleasePoolPrint();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

内存管理_第3张图片
image.png
分析过程

在终端用clang重写main.m后在main.cpp中查看
指令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
可以看到,autoreleasepool结构体对象创建的时候调用objc_autoreleasePoolPush(),销毁的时候调用objc_autoreleasePoolPop(atautoreleasepoolobj)

struct __AtAutoreleasePool {
//构造函数
  __AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析构函数
  ~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
  void * atautoreleasepoolobj;
};

在objc4源码NSObject.mm中查看

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

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

查看AutoreleasePoolPage::push()

 static inline void *push() 
    {
        id *dest;
        //第一次进来,如果没有pool配置
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

autoreleaseNewPage和autoreleaseFast方法都是生成AutoreleasePoolPage对象

  static __attribute__((noinline))
    id *autoreleaseNewPage(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page) return autoreleaseFullPage(obj, page);
        else return autoreleaseNoPage(obj);
    }
 static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }
查看AutoreleasePoolPage成员

class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}

查看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);
        //寻找或创建一个不满的page
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
       //设置这个不满的page为hotPage
        setHotPage(page);
       //将POOL_BOUNDARY加到page
        return page->add(obj);
    }

查看page->add(obj)

  id *add(id obj)
    {
        assert(!full());
        unprotect();
       //next为page最后一个成员变量的下一个地址
        id *ret = next;  // faster than `return next-1` because of aliasing
        //先将obj地址给next,再将next移到下一个
        *next++ = obj;
        protect();
        //返回的是next移动前的地址,第一次是POOL_BOUNDARY的地址
        return ret;
    }

push方法创建page到此结束,接下来查看对象的autorelease方法

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

------------------------

// Base autorelease implementation, ignoring overrides.
inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

------------------------

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

-------------------------

   static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
       //autoreleaseFast方法是将对象加到page中
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

接下来查看自动释放池释放时执行的objc_autoreleasePoolPop方法都干了什么

  ~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}

-------------------------

void
objc_autoreleasePoolPop(void *ctxt)
{
    //ctxt为传进来的atautoreleasepoolobj,也就是
    AutoreleasePoolPage::pop(ctxt);
}

查看AutoreleasePoolPage::pop(ctxt)

  static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        //token也就是POOL_BOUNDARY的地址
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();
        //release page中的对象
        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

查看releaseUntil(stop)

 void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {
               //release对象
                objc_release(obj);
            }
        }

        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            assert(page->empty());
        }
#endif
    }

autoreleasepool生命周期至此结束

runloop与autorelease

那么平时写的VC中的autorelease变量是什么时机被释放的呢

  • MRC 程序运行时runloop会一直处于唤醒或者休眠状态,而局部变量的释放时机是在所属的那次runloop循环中,runloop休眠之前会调用AutoreleasePoolPage的pop方法,在这个方法中release
  • ARC 由于编译器自动生成release代码,所以局部变量是出作用域之前立马释放

你可能感兴趣的:(内存管理)