美文网首页
iOS底层原理 - 内存管理 之 MRC

iOS底层原理 - 内存管理 之 MRC

作者: hazydream | 来源:发表于2019-07-22 09:50 被阅读0次

面试题引发的思考:

Q: 谈一谈对手动内存管理的理解?

  • 在iOS中,使用 引用计数 来管理OC对象的内存;
  • 一个新创建的OC对象引用计数默认是1;
    当引用计数减为0,OC对象就会销毁,释放其占用的内存空间;
  • 调用retain会让OC对象的引用计数+1;
    调用release会让OC对象的引用计数-1;

Q: 内存管理的经验总结:

  • 当调用allocnewcopymutableCopy方法返回了一个对象;
    在不需要这个对象时,要调用release或者autorelease来释放它;
  • 想拥有某个对象,就让它的引用计数+1;
    不想再拥有某个对象,就让它的引用计数-1;
  • 可以通过以下私有函数来查看自动释放池
    的情况extern void _objc_autoreleasePoolPrint(void);

Q: 关键字@synthesize@dynamic区别是什么?

  • @synthesize
    编译器期间,让编译器自动生成getter/setter方法;
    当有自定义的存或取方法时,自定义会屏蔽自动生成该方法。
  • @dynamic
    告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告;
    然后由自己实现存取方法。

0. 新版本iOS项目设置为MRC运行崩溃解决办法

Xcode -> Targets --> Build Settings --> 搜索“automatic reference counting”,MRC模式设置为“NO”,ARC模式设置为“YES”。

野指针访问

修改main函数:

main函数

1. 内存管理介绍

(1) 内存管理

iOS的内存管理一般指的是 OC对象 的内存管理:

  • 因为OC对象分配在 堆内存,堆内存需要程序员自己去动态分配和回收;
  • 基础数据类型(非OC对象)则分配在 栈内存 中,超过作用域就会由系统检测回收;
  • 如果我们在开发过程中,对内存管理得不到位,就有可能造成内存泄露。

(2) 内存管理种类

我们通常讲的内存管理分为两种:MRC 和 ARC

  • MRC(Manual Reference Counting):指的是手动内存管理,
    在开发过程中需要开发者手动去编写内存管理的代码;
  • ARC(Automatic Reference Counting):指的是自动内存管理,
    由LLVM编译器和OC运行时库生成相应内存管理的代码。

本文主要介绍关于MRC环境下的内存管理方法。


2. 代码分析

(1) 示例一

首先在“TARGETS”的“Build Setting”分类下搜索“Automatic Reference Counting”,选择“NO”,关闭ARC,将项目改成MRC手动内存管理。

创建一个Person类:

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject
@end

@implementation Person
- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end
1> 手动释放

运行以下程序,可以手动进行release操作:

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"%zd", person.retainCount);
        [person release];
    }
    return 0;
}

// 打印结果
Demo[1234:567890] 1
Demo[1234:567890] -[Person dealloc]

以上写法是MRC代码的标准写法,如果创建person对象之后,不执行[person release];语句,就会造成内存泄漏,因为该释放的对象person没有释放。

2> 自动释放

运行以下程序,通过autorelease自动进行release操作:

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 自动释放 - 在自动释放池结束的时候,会对调用autorelease的对象进行release操作
        Person *person = [[[Person alloc] init] autorelease];
        NSLog(@"start");
    }
    NSLog(@"end");
    return 0;
}

// 打印结果
Demo[1234:567890] start
Demo[1234:567890] -[Person dealloc]
Demo[1234:567890] end

自动释放:在自动释放池结束的时候,会对调用autorelease的对象进行release操作。


(2) 示例二

创建一个Dog类:

// TODO: -----------------  Dog类  -----------------
@interface Dog : NSObject
- (void)run;
@end

@implementation Dog
- (void)run {
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end

创建一个Person类:

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end

@implementation Person
- (void)setDog:(Dog *)dog {
    _dog = dog; // 将dog的内存地址赋值给成员变量_dog
}
- (Dog *)dog {
    return _dog;
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}
@end
1> 情况一:

运行以下程序:

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog = [[Dog alloc] init];

        Person *person = [[ Person alloc] init];
        [person setDog:dog];
        [[person dog] run];

        [dog release];
        [person release];
    }
    return 0;
}

// 打印结果
Demo[1234:567890] -[Dog run]
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]

按照顺序释放对象没有问题。

2> 情况二:

接下来运行以下程序:

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog = [[Dog alloc] init];

        Person *person = [[ Person alloc] init];
        [person setDog:dog];

        [dog release];
        [[person dog] run];
        [person release];
    }
    return 0;
}

// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Dog run]: message sent to deallocated instance 0x1007a73b0

程序崩溃,报错

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

坏地址访问,因为执行[[person dog] run];语句时dog对象已经销毁。

所以要保证person对象没被销毁,dog对象也不能被销毁。


(3) 代码优化版本一

0> 对Person类进行优化:
// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end

@implementation Person
- (void)setDog:(Dog *)dog {
    _dog = [dog retain]; // dog引用计数+1
}
- (Dog *)dog {
    return _dog;
}
- (void)dealloc {
    [_dog release];  // 释放_dog
    _dog = nil;
    
    NSLog(@"%s", __func__);
    // 子类先释放,然后释放父类
    [super dealloc];
}
@end

PersonsetDog:方法实现时,对dog进行引用计数+1;
Persondealloc方法实现时,对_dog进行释放,引用计数-1。

1> 情况一:
// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog = [[Dog alloc] init]; // dog引用计数:1
        
        Person *person = [[ Person alloc] init];
        [person setDog:dog]; // dog引用计数:2
        
        [dog release]; // dog引用计数:1
        
        [[person dog] run];
        
        [person release]; // dog引用计数:0
    }
    return 0;
}

// 打印结果
Demo[1234:567890] -[Dog run]
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]

对以上代码进行分析可知:
Person类的优化满足:person对象没被销毁,dog对象也不能被销毁的需求。

2> 情况二:
// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog1 = [[Dog alloc] init]; // dog1引用计数:1
        Dog *dog2 = [[Dog alloc] init]; // dog2引用计数:1
        
        Person *person = [[ Person alloc] init];
        [person setDog:dog1]; // dog1引用计数:2
        [person setDog:dog2]; // dog2引用计数:2
        
        [dog1 release]; // dog1引用计数:1
        [dog2 release]; // dog2引用计数:1
        [person release]; // dog2引用计数:0
    }
    return 0;
}

// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]

对以上代码进行分析可知:
Person类的优化不满足:person对象持有Dog类创建的多个dog对象情况下,person对象没被销毁,dog对象也不能被销毁的需求。


(4) 代码优化版本二

0> 对Person类进行优化:
// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end

@implementation Person
- (void)setDog:(Dog *)dog {
    [_dog release]; // 先释放以前的_dog
    _dog = [dog retain]; // dog引用计数+1
}
- (Dog *)dog {
    return _dog;
}
- (void)dealloc {
    [_dog release];  // 释放_dog
    _dog = nil;
    
    NSLog(@"%s", __func__);
    // 子类先释放,然后释放父类
    [super dealloc];
}
@end

PersonsetDog:方法实现时,先释放以前的_dog,然后对dog进行引用计数+1;
Persondealloc方法实现时,对_dog进行释放,引用计数-1。

1> 情况一:
// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog1 = [[Dog alloc] init]; // dog1引用计数:1
        Dog *dog2 = [[Dog alloc] init]; // dog2引用计数:1
        
        Person *person = [[ Person alloc] init];
        [person setDog:dog1]; // dog1引用计数:2
        [person setDog:dog2]; // dog2引用计数:2,dog1引用计数:1
        
        [dog1 release]; // dog1引用计数:0
        [dog2 release]; // dog2引用计数:1
        [person release]; // dog2引用计数:0
    }
    return 0;
}

// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]

对以上代码进行分析可知:
Person类的优化满足:person对象持有Dog类创建的多个dog对象情况下,person对象没被销毁,dog对象也不能被销毁的需求。

2> 情况二:
// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog = [[Dog alloc] init]; // dog引用计数:1
        
        Person *person = [[ Person alloc] init];
        [person setDog:dog]; // dog引用计数:2
        
        [dog release]; // dog引用计数:1
        
        [person setDog:dog]; // dog引用计数:0
        [person setDog:dog];
        
        [person release];
    }
    return 0;
}

程序崩溃,报错:

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

以上代码进行分析可知:
Person类的优化不满足:person对象持有同一个dog对象情况下,person对象没被销毁,dog对象也不能被销毁的需求。


(5) 代码优化版本三

0> 对Person类进行优化:
// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end

@implementation Person
- (void)setDog:(Dog *)dog {
    if (_dog != dog) { // dog与_dog不相等,进行操作
        [_dog release]; // 先释放以前的_dog
        _dog = [dog retain]; // dog引用计数+1
    }
}
- (Dog *)dog {
    return _dog;
}
- (void)dealloc {
//    [_dog release];  // 释放_dog
//    _dog = nil;
    self.dog = nil;
    
    NSLog(@"%s", __func__);
    // 子类先释放,然后释放父类
    [super dealloc];
}
@end

PersonsetDog:方法实现时,判断dog_dog不相等时,进行以下操作
:先释放以前的_dog,然后对dog进行引用计数+1;
Persondealloc方法实现时,对_dog进行释放,引用计数-1。

1> 情况一:
// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Dog *dog = [[Dog alloc] init]; // dog引用计数:1
        
        Person *person = [[ Person alloc] init];
        [person setDog:dog]; // dog引用计数:2
        
        [dog release]; // dog引用计数:1
        
        [person setDog:dog];
        [person setDog:dog];
        
        [person release]; // dog引用计数:0
    }
    return 0;
}

// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]

以上代码进行分析可知:
Person类的优化满足:person对象持有同一个dog对象情况下,person对象没被销毁,dog对象也不能被销毁的需求。


3. MRC下关键字使用

(1) 关键字@synthesize@dynamic

  • MRC下使用@property只会生成settergetter方法的声明;
  • 如果想生成_age成员变量和settergetter方法的实现还要使用@synthesize关键字。

以下是MRC环境下两个关键字的使用方法:

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject
// 自动生成属性的setter、getter声明
@property(nonatomic, assign) int age;
@property(nonatomic, retain) Dog *dog;
@end

@implementation Person
// 自动生成成员变量和属性的setter、getter实现
@synthesize age = _age;
@synthesize dog = _dog;
// 基础数据类型(非OC对象)则分配在栈内存中,超过作用域就会由系统检测回收。
- (void)setAge:(int)age {
    _age = age;
}
- (int)age {
    return _age;
}
// OC对象分配在堆内存,堆内存需要程序员自己去动态分配和回收。
- (void)setDog:(Dog *)dog {
    if (_dog != dog) {
        [_dog release];
        _dog = [dog retain];
    }
}
- (Dog *)dog {
    return _dog;
}
//  OC对象需要手动释放
- (void)dealloc {
    [_dog release];
    _dog = nil;
    [super dealloc];
}
@end
  • 使用assign修饰生成的setter方法没有内存管理相关的东西,所以assign一般用来修饰基本数据类型;
  • 使用retain修饰生成的setter方法有内存管理相关的东西,所以retain一般用来修饰对象类型。

(2) 简略写法

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject

@property(nonatomic, assign) int age;
@property(nonatomic, retain) Dog *dog;
// 工厂方法
+ (instancetype)person;
@end

@implementation Person
+ (instancetype)person {
    return [[[self alloc] init] autorelease];
}
- (void)dealloc {
    self.dog = nil;
    [super dealloc];
}
@end

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person person];
    }
    return 0;
}

MRC环境下,一般会给类添加一个工厂方法,可以自动释放对象,操作简单。


4. MRC下代码写法

@interface ViewController ()
@property(nonatomic, retain) NSMutableArray *array;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    // 写法一
    NSMutableArray *array = [[NSMutableArray alloc] init];
    self.array = array;
    [array release];
    
    // 写法二
    self.array = [[NSMutableArray alloc] init];
    [self.array release];
    
    // 写法三
    self.array = [[[NSMutableArray alloc] init] autorelease];
    
    // 写法四
    // Foundation框架,一般使用类方法创建对象,其内部调用autorelease
    self.array = [NSMutableArray array];
}
- (void)dealloc {
    self.array = nil;
    [super dealloc];
}
@end

相关文章

网友评论

      本文标题:iOS底层原理 - 内存管理 之 MRC

      本文链接:https://www.haomeiwen.com/subject/dhhilctx.html