0


【iOS】MRC

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

文章目录


前言

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

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

内存分配管理

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

栈区:

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

栈区是一段连续的内存区域,由高地址向低地址存储,遵循先进后出(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函数时它才会报错,但是它此时并没有报错请添加图片描述
但是我们在使用它一次之后,就出现了应该出现的问题
请添加图片描述
可以理解为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"intmain(int argc,constchar* argv[]){@autoreleasepool {
        Person* p =[[Person alloc] init];
        Room* r =[[Room alloc] init];
        r.no =888;[r release];[p release];}return0;}#import<Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Room : NSObject
@propertyint 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
intmain(int argc,constchar* argv[]){@autoreleasepool {
        Person* p =[[Person alloc] init];
        Room* r =[[Room alloc] init];
        r.no =888;
        
        
        p.room = r;[r release];NSLog(@"------");[p release];}return0;}

@ 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]);

请添加图片描述

使用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

本文转载自: https://blog.csdn.net/weixin_61196797/article/details/129821812
版权归原作者 山河丘壑 所有, 如有侵权,请联系我们删除。

“【iOS】MRC”的评论:

还没有评论