美文网首页
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