ios内存管理

ios采用引用计数管理对象的生命周期,开启指针优化后对象的引用计数器可能存在于isa结构体中,

  • CADisplayLink:类似于定时器,跟屏幕的刷新频率一致,也就是16ms一次,但是如果掉帧就不准了,用法:
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
    [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

注意点:会对target对象产生强引用,导致其无法释放。

  • NSTimer:定时器,多种用法,若使用以下用法也会对target产生强引用导致其无法释放。
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:true];

如何解决以上强引用带来的内存无法释放的问题?

中间人代理模式proxy,NSProxy专门同来解决代理问题,让代理对象对当前对象产生弱引用,CADisplayLink和NSTimer对代理对象产生强引用,这样定时器就不会对当前对象产生强引用了。proxy定义如下:

@interface TargetProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property(nonatomic, weak)id target;
@end
@implementation TargetProxy
+ (instancetype)proxyWithTarget:(id)target
{
    //NSProxy类是不需要init方法的
    TargetProxy *proxy = [self alloc];
    proxy.target = target;
    return proxy;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

proxy用法如下:

    //CADisplayLink代理用法
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[TargetProxy proxyWithTarget:self] selector:@selector(test)];
    [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    //NSTimer代理用法
    [NSTimer scheduledTimerWithTimeInterval:1 target:[TargetProxy proxyWithTarget:self] selector:@selector(test) userInfo:nil repeats:true];

NSTimer还可以使用代码库解决这个问题如下:

    __weak typeof(self) ws = self;
    [[NSTimer scheduledTimerWithTimeInterval:1
                                     repeats:YES
                                       block:^(NSTimer * _Nonnull timer) {
        [ws test];
    }] fire];
  • GCD定时器
    NSTimer定时器依赖runloop,如果runloop任务繁重可能导致定时器不准时。可以使用GCD定时器来解决此问题:
    dispatch_queue_t queue = dispatch_queue_create("timer_queue", DISPATCH_QUEUE_CONCURRENT);
    //创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //设置定时器时间
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(2 * NSEC_PER_SEC)), (uint64_t)(2 * NSEC_PER_SEC), 0);
    //设置回调
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"%s",__func__);
    });
    //启动定时器
    dispatch_resume(timer);
  • ios程序的内存布局
    地址从低到高依次是:保留区、代码段、数据段、堆区、栈区、内核区。需要注意的是堆区地址从小到大分配,栈区是从大到小分配。

  • tagged pointer技术
    此技术是从64位架构开始,用于优化NSNumber、NSString等ios小对象的内存问题。此技术之前ios中小对象存储也是跟其他类是一样的,动态分布内存、维护引用计数等。使用此技术后ios中的小对象使用tag+data的方式存储,也就是将对象的值直接存储在对象指针中,不会区堆区开辟内存空间了。当指针无法存储数据后才会动态分配内存来存储数据。objc_ msgSend能够识别指针类型是否是使用了此技术,若使用此技术会直接从指针中获取数据,节省了方法调用开销,大大提升了ios的运行效率。通过源码可以看到mac平台上指针地址最低位用来标记是否使用此技术,ios平台使用最高位(64bit)来标记。

  • 对象内存管理
    在iOS中,使用引用计数来管理OC对象的内存。一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它。想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1。copy不可变对象为浅拷贝,不会开辟新的内存空间;copy可变对象或者使用mutaleCopy都是深拷贝,深拷贝会开辟新的内存空间,生成新对象;对象的copy需要遵守NSCopy协议,使用copyWithZone方法进行copy。

可以通过申明以下函数来查看自动释放池中的内存情况

extern void _objc_autoreleasePoolPrint(void);
  • 对象的引用计数
    在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中
struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}

refcnts是一个存放着对象引用计数的散列表

  • dealloc流程
    dealloc-> _objc_rootDealloc-> rootDealloc-> object_dispose->objc_destructInstance
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;
}
  • 自动释放池
    自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage。调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。AutoreleasePoolPage数据结构:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic;
    __unsafe_unretained id *next;// 指向当前page的下一个存储对象
    pthread_t const thread;
    AutoreleasePoolPage * const parent;//指向上一个page,root节点为空nil
    AutoreleasePoolPage *child;//指向下一个page,最后一个节点为nil
    uint32_t const depth;
    uint32_t hiwat;
};

所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起。每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量(56字节),剩下的空间用来存放autorelease对象的地址(4040字节)。调用objc_autoreleasePoolPush方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址。调用objc_autoreleasePoolPop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY。id *next指向了下一个能存放autorelease对象地址的区域。

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

推荐阅读更多精彩内容

  • 一 iOS程序内存布局 二 Tagged Pointer内存地址优化 三 MRC概念讲解 四 引用计数的存储 五 ...
    当前明月阅读 959评论 0 2
  • CADisplayLink、NSTimer使用注意 CADisplayLink、NSTimer会对target产生...
    lieon阅读 335评论 0 4
  • 面试题 使用CADisplayLink、NSTimer有什么注意点? 介绍下内存的几大区域 讲一下你对 iOS 内...
    e297b14c9e53阅读 153评论 0 0
  • CADisplayLink、NSTimer 使用注意 CADisplayLink 和 NSTimer 会对 tar...
    valentizx阅读 728评论 1 3
  • ​ 前言:随着手机市场日新月异的更新,目前无论安卓手机还是iPhone手机的内存都越来越大,但是手机系统和App...
    阿饼six阅读 834评论 6 7