内存

深入理解内存管理(一)

2020-07-29  本文已影响0人  juriau

目录

一、内存五大分区

二、iOS内存管理

iOS的内存管理是基于引用计数实现的。在cocoa的术语中也叫做对象的所有权(ownership)

基本内存管理规则:

简单来说就是:

1.MRC

即手动引用计数管理,开发者需要手动管理对象的引用计数。MRC中对象内存管理操作对应的OC方法有四种:

2.ARC

即自动引用计数管理,开发者只需设置好对象的内存管理语义,编译器和runtime会负责对象的引用计数管理,比如说:

内存管理语义:

三、MRC细节

一个简单的例子说明上述规则:

{
    Person *aPerson = [[Person alloc] init];
    // ...
    NSString *name = aPerson.fullName;
    // ...
    [aPerson release];
}

1.存取方法

@interface Counter : NSObject
@property (nonatomic, retain) NSNumber *count;
@end;
- (NSNumber *)count {
    return _count;
}
- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    _count = newCount;
}

此方法将保留新值并释放旧值,然后更新实例变量,令其指向新值。

2.不要在初始化或dealloc方法中调用存取方法

初始化和dealloc方法中是唯一不应该使用存取方法的地方,使用一个number对 象来将counter初始化为0,你需要这样实现初始化方法:

- init {
    self = [super init];
    if (self) {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

由于Counter类有一个实例变量,你也必须实现dealloc方法。这个方法中必须释 放实例变量的所有权,并且,最后还要调用基类的dealloc方法:

- (void)dealloc {
    [_count release];
    [super dealloc];
}

四、ARC细节

1.strong(默认)

使用strong修饰的对象,在其作用域结束后 / 重新赋值 / 置为nil,会被release一次。

@interface HJPerson : NSObject
@end
@implementation HJPerson
- (void)dealloc{
    NSLog(@"%s", __func__);
}
@end

int main() {
    NSLog(@"111");
    {
        HJPerson* person = [[HJPerson alloc]init];
    }
    NSLog(@"222");
}

在作用域结束后,person对象会被release,然后触发dealloc。

2.weak

NSString* __weak string = [[NSString alloc] initWithFormat:@"my age = %d", 26];
NSLog(@"%@", string);

虽然 string是在初始化赋值之后使用,但是在赋值的时候并没有其它强引用指向字符串对象;因此字符串对象会马上释放掉。log语句显示 stirng的值为 null。

在MRC下的实现大概如下:

NSString* temp = [[NSString alloc] initWithFormat:@"my age = %d", 26];
NSString* __weak string = temp;
[temp release];

Q:把weak对象放到NSArray中引用计数会增加吗?

会!weak变量不增加原对象的引用计数,即编译器只是简单不会增加retain语句。但是weak变量指向的还是原对象,放到NSArray会对原对象retain。

3.autorelease

autorelease的核心作用:延迟release。通常用在作为函数返回值时。

+ (instancetype)person{
    HJPerson* p = [[HJPerson alloc]init];
    return p;
}

在ARC下,超过大括号后,p会被清除,HJPerson对象就因为没有强引用指向而被释放,所以编译器会加上autorelease,使其延迟释放。如下:

+ (instancetype)person{
    HJPerson* p = [[HJPerson alloc]init];
    return [p autorelease];
}

五、内存泄漏

iOS内存泄漏的核心问题是循环引用。使用ARC的垃圾回收机制,虽然可以帮助开发者在大部分情况下正确管理内存,但是对于循环引用束手无策。

循环引用导致内存泄漏的原因在于:

1.循环引用

循环引用有三种典型例子delegate、block、timer,它们的共性就是:对象内的某个属性再次对当前对象进行强引用,则会发生强引用

(1)delegate

演示:self.myView.delegate = self;
解决方法:使用weak修饰符

(2)block

演示:person.block = ^{NSLog(@"%d", person.age);};
解决方法:使用__weak声明的weakSelf

(3)timer

演示:self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];

注意weakSelf不能解决问题:就算是用weakSelf,timer内部还是会直接对weakSelf指向的对象强引用。就像把weak对象放进数组中,还是会对weak对象进行强引用。

解决方式1:使用block+weakSelf

__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    [weakSelf test];
}];

解决方式2:使用代理对象(NSProxy)

代理对象将消息转发给viewController。

2.野指针、僵尸对象

当一个对象被释放后,如果其指针没有置空,则这个指针就变成了野指针

野指针指向的就是僵尸对象

六、垃圾回收

1.引用计数算法

优点:是GC算法中最简单也最容易实现的。

缺点:

2.GC大一统理论

像标记清除和复制收集之类的算法是从根开始扫描以判断对象生死的算法,被称为跟踪回收(Tracing GC)。而引用计数算法则是当对象之间的引用关系发生变化时,通过对引用计数进行更新来判定对象生死。

2004年IBM研究中心发表了一篇论文,提出了一个理论:任何一种GC算法都是跟踪回收和引用计数两种方式的组合,两者的关系正如“物质”和“反物质”一样,是相互对立的。对其中一方进行改善的技术之中,必然存在对另一方进行改善的技术,而其结果只是两者的组合而已。

上一篇下一篇

猜你喜欢

热点阅读