【iOS】MRC

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 内存分配管理
    • 栈区:
    • 堆区:
    • 常量区
    • 静态区
    • 代码区
  • 如何查看一个对象是在堆区/栈区
  • MRC
    • 手动设置MRC环境
    • 空指针
    • 野指针
  • 多个对象内存管理思想
    • @ property参数
  • 自动释放池
      • 使用autorelease的好处
      • autorelease的原理实质上是
      • 自动释放池的嵌套使用
      • autorelease错误用法
      • 避免循环引用


前言

提示:这里可以添加本文要记录的大概内容:


提示:以下是本篇文章正文内容,下面案例可供参考

内存分配管理

内存分为五个区。
栈区(系统管理的地方)
堆区(程序员控制的地方)
常量区(全局去)
静态区和代码区

栈区:

由编译器自动分配并释放,存放函数的参数值,基本类型的变量和对象引用类型,方法调用的实参也是保存在栈区。
可以把栈区看作一个临时寄存,交换的内存区
栈是系统数据结构,对应的线程/进程是唯一的
优点是快速高效,缺点是有限制,数据操作不灵活

栈区是一段连续的内存区域,由高地址向低地址存储,遵循先进后出(FILO)原则,一般在运行时进行分配,内存空间由系统管理,变量过了作用域范围后就会自动释放。参数,函数,局部变量都存符在栈区。参数入栈是从前往后入栈,而结构体是从后往前入栈。

  • 栈对应的线程/进程是唯一的
  • 快速高效,缺点是有限制,数据不灵活
  • 栈空间分静态分配和动态分配两种
  • 静态分配是编译器完成的,如自动变量(auto)的分配
  • 动态分配由alloc函数完成
  • 栈的动态分配无需释放(是自动的),没有释放函数
  • 栈的动态分配不被鼓励;i

堆区:

由程序员分配和释放,构造的对象和数组如果不释放,可能造成内存泄漏,程序结束时可能会由系统回收。
比如通过new,alloc,malloc,realloc分配的内存块就存符在堆区
堆向高地址扩展的数据结构,是不连续的内存区域。堆中的所有东西都是匿名的,这样不能按名字访问,只能通过指针访问。
对于堆来讲,频繁的new/delete会造成内存空间的不连续性,使程序效率降低
堆向高地址扩展的数据结构,是不连续的内存区域。程序员负责在何时释放内存,在ARC中,计数器为0的时候,在当次的runLoop结束后,释放掉内存。堆区中的所有东西都是匿名的,这样奴能按照名字放完,只能通过指针访问。

  • 灵活方便,数据适应面广泛,效率有些降低
  • 堆是函数库内部数据结构,不一定唯一
  • 不同堆分配的内存无法互相操作
  • 堆空间的分配是动态的

常量区

文字常量区:该区是编译时分配的内存空间,在程序运行过程中,此内存中的数据一直存在。程序结束后由系统释放。
存放常量:整形,字符型,浮点型,字符串

静态区

全局区(静态区)(static):全局变量和静态全局变量的存储是放在一起的,初始化的全局变量和静态全局变量存放在一块区域,为初始化的全局变量和静态变量在相邻的另一块地址,程序结束后由系统释放。
全局区又可分为为初始化全局区(BSS段)和初始化全局区(DATA段)

代码区

程序代码区:用来存放函数的二进制代码
代码段需要防止在运行时被非法修改,所以只允许读取操作,不允许写入操作

如何查看一个对象是在堆区/栈区

如果初始化方法以new,alloc,retain,copy开头都是在堆区,被引用计数管理的方法也是在堆区
常量会在栈区
如果是在方法执行的过程中,定义在本地的原生类型(或者值类型)。那么它肯定是在栈上,上函数执行结束时直接销毁,而其他的引用类型(或者oc中的interface)都是在堆上创建的,由ARC负责清理

MRC

手动设置MRC环境

target->Build Settings

空指针

空指针指没有指向存储空间的指针(存的是nil)
给空指针发消息是没有反应的

野指针

只要一个对象被释放了,我们称这个对象为【僵尸对象(不能在使用的对象)】
当一个指针指向一个僵尸对象(不能在使用的对象),我们就称这个指针为【野指针】
只要给一个野指针发送消息就会报错

**但是我在这样运行时并没有报错,按道理来说在第二次调用release函数时它才会报错,但是它此时并没有报错
但是我们在使用它一次之后,就出现了应该出现的问题
【iOS】MRC_第1张图片
可以理解为release一次之后,编译器意识不到这个对象引用计数已经变为0了,我们再次使用这个对象时,编译器才能意识到它变为了0 ,然后报错。

NSObject *p = [[NSObject alloc] init]; // 执行完引用计数为 1。
[p release]; // 执行完引用计数为 0,实例对象被释放。
p = nil; // 此时,p 变为了空指针。
[p release]; // 再给空指针 p 发送消息就不会报错了。
[p release];

多个对象内存管理思想

多个对象是通过setter方法产生联系的。内存管理方法也是在setter方法中,dealloc方法中实现的。

#import <Foundation/Foundation.h>
#import "Room.h"
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* p = [[Person alloc] init];
        Room* r = [[Room alloc] init];
        r.no = 888;
        [r release];
        [p release];
    }
    return 0;
    

}



#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Room : NSObject
@property int no;
@end

NS_ASSUME_NONNULL_END



//

#import "Room.h"

@implementation Room

@end




#import <Foundation/Foundation.h>
#import "Room.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject {
    Room* room;
}
- (void)setRoom : (Room*) room;
- (Room*)room;
@end

NS_ASSUME_NONNULL_END




#import "Person.h"

@implementation Person

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* p = [[Person alloc] init];
        Room* r = [[Room alloc] init];
        r.no = 888;
        
        
        p.room = r;
        [r release];
        NSLog(@"------");
        [p release];
    }
    return 0;
    
}

@ property参数

在成员变量前加上@property,系统就会自动帮我门生成基本的setter/getter方法,但是不会生成内存管理相关代码

@property (nonatomic) int val;

如果在@property后价上assign,系统也不会帮我们生成setter方法内存管理的代码,仅仅生成普通的getter/setter方法,默认什么都不写就是assign

@property (nonatomic, aassign) int val;

如果在property后比加上retain,系统就会帮我们生成getter/setter方法内存管理的代码,但是仍然需要我们自己重写delloc方法

@property (nonatomic, retain) Room* room;

自动释放池

当我们不在使用一个对象的时候应该将其空间释放,为了解决如何释放空间的问题,Objective-C提供了autorelease方法

  • autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,将自动释放池销毁时,会对池子里的所有对象做一次release操作
  • 只是发送release消息,如果当时的引用计数仍然不为0,则该对象仍然不会被释放。
  • autoreleae方法会返回对象本身,且调用完autorelease方法后,对象的计数器不变
Person* p = [Person new];
p = [p autorelease];
NSLog(@"%lu", [p retainCount]);

【iOS】MRC_第2张图片

使用autorelease的好处

  • 不用在关心对象的释放时间
  • 不用在关心什么时候调用release方法

autorelease的原理实质上是

autorelease实际上只是把对releae的调用延迟了,对于每一个autoreleasepool中,该pool被释放时,pool中的所有对象都会被调用release方法

NSAutoreleasePool* autoreleasePool = [[NSAutoreleasePool alloc] init];
Person* p = [[[Person alloc] init] autorelease];
[autoreleasePool drain];

不是说放到自动释放池中的代码都会加入到自动释放池

@autoreleasepool {
    // 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
    Person *p = [[Person alloc] init];
    [p run];
}

在自动释放池的外部发送autorelease不会被加入到自动释放池中
autorelease是一个方法,只有在自动自动释放池中调用才有效

@autoreleasepool {
}
// 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
Person *p = [[[Person alloc] init] autorelease];
[p run];
 
// 正确写法
@autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
 }
 
// 正确写法
Person *p = [[Person alloc] init];
@autoreleasepool {
    [p autorelease];
}

自动释放池的嵌套使用

自动释放池是以栈的形式存在
由于栈只有一个入口,所以调用autorelease会将对象放到栈顶的自动释放池
栈顶就是离调用autorelease方法最近的自动释放池

@autoreleasepool { // 栈底自动释放池
    @autoreleasepool {
        @autoreleasepool { // 栈顶自动释放池
            Person *p = [[[Person alloc] init] autorelease];
        }
        Person *p = [[[Person alloc] init] autorelease];
    }
}

自动释放池中不应该放占用内存较大的对象
尽量避免对大内存使用该方法,对于这种延迟释放机制,尽量少用
不要把大量循环操作放到同一个@autoreleasepool之内,会造成内存峰值上升

// 内存暴涨
@autoreleasepool {
    for (int i = 0; i < 99999; ++i) {
        Person *p = [[[Person alloc] init] autorelease];
    }
}

// 内存不会暴涨
for (int i = 0; i < 99999; ++i) {
    @autoreleasepool {
        Person *p = [[[Person alloc] init] autorelease];
    }
}

autorelease错误用法

@autoreleasepool {
 // 错误写法, 过度释放
    Person *p = [[[[Person alloc] init] autorelease] autorelease];
 }

@autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
    [p release]; // 错误写法, 过度释放
}

避免循环引用

  • 不要让Aretain B,BretainA
  • 让其中一方不要做retain操作即可
  • 当两端相互引用时,一端用retain,一端用assign

你可能感兴趣的:(ios)