iOS之内存管理

1.ARC、MRC

ARC:automatic reference counting 自引用用计数
MRC:manual reference counting 手动引用计数

MRC

在2011年、IOS5之前,iOS的开发只支持MRC模式。
MRC的六个特有方法:

  • alloc
  • retain
  • release
  • retainCount
  • autorelease :[[[NSObject alloc] init] autorelease];在AutoreleasePool结束的时候会自动release对象
  • dealloc

使用new、alloc、copy和mutableCopy产生的对象,在以后不用的时候,也需要release:

# 注意 initWithFormat的字符串一定要长,如果字符串短系统会采用taggepointer优化,引用计数为-1
# str1的引用计数为1
 id str1 = [[NSString alloc] initWithFormat:@"asfdasasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfasdf"]; 
# str1、str2的引用计数为均为2  因为是不可变对象的拷贝,所以str2和str1指向同一块内存空间,即[str1 copy]相当于retain操作,引用计数+1
 NSString *str2 = [str1 copy];
# str3的引用计数为1,因为产生的是可变对象,所以相当于str3指向一个新的内存空间
 NSString *str3 = [str1 mutableCopy];
    
 [str3 release];
 [str2 release];
 [str1 release];

当时每当一个新的指针引用了一块堆空间(也就是对象),
就必须手动的把此块堆空间内的 retainCount + 1。

Person* p = [Person new];//默认就是1 ,所以这里的p不需要手动操作。
Person* p2 = p;
[p2 retain];//将retainCount的值+1;

当p2指针不使用此堆空间了。要手动把 retainCount 值 - 1

[p2 release];

p不用了,也需要release

[p release];

手动计数器使用规则:
谁申请(retain),谁释放(release)

关于内存释放的本质:
当一块内存释放的时候,本质上只是给这部分字节打了标签。并没有把字节里的二进制数据全部清成0或者1.

什么是僵尸对象?
堆空间已经被标记清空,能被其他数据使用。但此时此刻,新的二进制数据还没有进来。
我们此时用一个指针指向已经标记释放了的堆空间。这个就叫僵尸对象和野指针。

ARC
  • ARC是编译器和runtime共同作用的结果
  • ARC中禁用MRC的六个方法
  • ARC中新增weakstrong关键字

2.AutoreleasePool

iOS系统针对不同场景下提供的内存方案:

  • TaggedPointer:对于NSNumber、NSString、NSDate等对象,可以直接从指针提取数据内容,而不需要使用指针访问内存再提取内容
  • NONPOINTER_ISA: 64位架构下,ISA指针占64bite位,实际使用中不需要这么多,苹果就在剩余的ISA比特位中存储了一些内存管理的相关信息
  • 散列表 :包括引用计数表和弱引用计数表

在使用@autoreleasePool {}后,编译器会将其改写为:

//@autoreleasePool {
  创建了一个autoreleasePoolPage对象  push进一个标记 然后把数据依次放autoreleasePoolPage对象中

  {代码}

  依次释放掉表中的数据,直到碰到标记位停止
//}
  • Autoreleasepool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组成
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • iOS里的TaggedPointer不适用autorelesepool
  • NSAutoreleasePool可以创建一个autorelease pool,但该对象本身也需要被释放 drain
  • 在ARC下,应当使用@autoreleasepool{}
  • 对于不同线程,应当创建自己的autorelease pool。如果应用长期存在,应该定期drain和创建新的autorelease pool。
  • runloop 与 AutoreleasePool 是协同合作关系
  • AutoreleasePool 与 runloop 与线程是一一对应的关系
  • AutoreleasePool 在 runloop 在开始时被push,在runloop休眠时(beforewaiting状态)pop

思考 使用autorelease的对象什么时候会被释放?

  • 如果在autoreleasepool中,是在autoreleasepool 执行到后括号的时候释放
    NSLog(@"1");
    @autoreleasepool {
        NSObject *str1 = [[NSObject alloc] init];
    }  # 此时释放
 
    NSLog(@"2");
  • 如果没有单独放到autoreleasepool中的时候,是在runloop即将休眠的时候统一释放,因为程序入口main函数是放在autoreleasepool中:

3.循环引用

循环引用分类:

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

对象的强持有变量指向自身,就会造成自循环引用

相互循环引用
多循环引用
如何破除循环引用?
  • 使用__weak

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

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


CADisplayLink、NSTimer的循环引用问题:

CADisplayLink、NSTimer会对target产生强引用,如果target也对他们进行了强引用,就会出现循环引用问题

解决方案1:使用中间人
NSTimer的循环引用问题解决.png

通过创建一个中间对象,令中间对象持有两个弱引用变量分别是原对象和NSTimer,NSTimer的回调是在中间对象中实现的。在中间对象实现的NSTimer的回调方法中,对中间对象持有的weak弱引用target值的判断,如果当前target值存在,则把NSTimer的回调给原对象,如果值为nil,则把NSTimer设为无效即可解除当前runloop对NSTimer的强引用和NSTimer对中间对象的强引用。

解决方案2:使用动态消息解析

ViewController中:

@interface ViewController (){
    NSTimer *_timer;
}
- (void)viewDidLoad {
   [super viewDidLoad];
   _timer = [NSTimer scheduledTimerWithTimeInterval: 1 target: [TimerMiddleware initWithTarget: self] selector: @selector(sayhaha) userInfo: nil repeats: YES];
}
- (void)dealloc {
    [_timer invalidate];
    _timer = nil;
}

新建一个消息解析类:

@interface TimerMiddleware : NSObject
+ (instancetype)initWithTarget:(id)target;
@property (nonatomic,   weak) NSObject *target;
@end

@implementation TimerMiddleware

+ (instancetype)initWithTarget:(id)target {
    TimerMiddleware *mid = [TimerMiddleware new];
    mid.target = target;
    return mid;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}
@end
解决方案3:使用NSProxy转发消息

NSProxy是跟NSObjec一个级别的基类,用来设计做消息转发的。
NSProxy是抽象类,使用时候我们需要使用其子类
NSProxy不会跟NSObject类一样去父类搜索方法实现,会直接进入消息转发流程

@interface MyProxy : NSProxy

+ (instancetype)proxyWithTarget:(id)target;
@property (nonatomic,   weak) NSObject *target;

@end

@implementation MyProxy

+ (instancetype)proxyWithTarget:(id)target {
    MyProxy *proxy = [MyProxy alloc];
    proxy.target = target;
    return proxy;
}

# NSProxy接收到消息会自动进入到调用这个方法 进入消息转发流程
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel  {
    return [self.target methodSignatureForSelector: sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget: self.target];
}
@end

Demo详见:https://www.jianshu.com/p/9c91ee60b0dc


面试总结:

1.什么是ARC?
ARC是由LLVM和runtime共同协作来为我们实现自动引用计数管理

2.为什么weak指针指向的对象在被废弃之后会被自动置为nil?
当对象被废弃之后,dealloc的内部实现当中会调用清除弱引用的一个方法。然后在清楚弱引用的方法当中,会通过哈希算法来查找被废弃对象在弱引用表当中的位置,来提取所对应的弱引用指针的列表数组,然后进行for循环遍历,把每一个weak指针都置为nil

3.苹果是如何实现AutoreleasePool的?
AutoreleasePool是以栈为节点,以双向链表形式合成的一个数据结构

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

推荐阅读更多精彩内容