当前位置: 首页 > news >正文

【iOS】自动引用计数(一)

【iOS】自动引用计数(一)

  • 自动引用计数
    • 自动引用计数原理
    • 内存管理
      • 自己生成的对象自己持有
      • 非自己生成的对象自己也能持有
      • 不需要自己持有的对象时释放
      • 无法释放非自己持有的对象
    • alloc/retain/release/dealloc实现
    • autorelease
      • 认识autorelease
      • 使用autorelease
      • 实现autorelease
  • ARC规则
    • 所有权修饰符
      • __strong修饰符
        • 管理成员变量的对象所有者
          • 作用域中管理
          • 赋值上管理
        • 管理方法参数的对象所有者
      • __weak修饰符
      • __unsafe_unretained修饰符
      • __autoreleasing修饰符
        • __autoreleasing使用机制
        • __autoreleasing隐式使用机制__
    • 规则
    • 属性
    • 数组
      • 静态数组
      • 动态数组

自动引用计数

自动引用计数原理

Objective-C中的内存管理就是引用计数,而自动引用计数则是指内存管理中对引用采取自动计数的技术。自动引用计数技术用于管理对象的引用计数,也就是对象被引用的次数。

当一个对象的引用计数大于0时,表示该对象被持有,不可被释放;当某个指针不再指向该对象时,引用计数减1;当对象的引用计数变为0时,系统将销毁对象,回收内存。

在这里插入图片描述

正如苹果的官方说明,在新一代Apple LLVM编译器中,编译器将自动进行内存管理

内存管理

Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责,这里先使用一个图直观的感受如何使用引用计数管理对象的内存:

在这里插入图片描述

简而言之,引用计数仅仅只是“生成”、“持有”、“释放”、“废弃”这四个词的操作:

在这里插入图片描述

自己生成的对象自己持有

编程人员自己生成对象的方法有:

  • alloc类方法:指向生成并持有对象的指针被赋给变量。
  • new类方法:与alloc类方法完全一致。
  • copy方法:基于NSCopying方法约定,由实现 copyWithZone: 方法生成并持有不可变对象的副本。
  • mutableCopying方法:基于NSMutableCopying方法约定,由实现 mutableCopyWithZone: 方法生成并持有可变对象的副本。

非自己生成的对象自己也能持有

上述方法外的方法取得的对象,编程人员自己不是该对象的持有者,变量可以使用retain方法来持有对象。

id obj = [NSMutableArray array];
[obj retain];

不过现在的 Objective-C 已经引入了自动引用计数(ARC)。在启动ARC的情况下,我们已不再需要手动管理内存,不在使用 retain 和 release 方法了,因此我们输入时是找不到这个方法的。

不需要自己持有的对象时释放

自己持有的对象,一旦不再需要,持有者有义务使用 release 方法释放该对象。

id obj = [NSMutableArray array];
[obj release];

对象一经释放绝对不可访问。因为ARC的启动,release 方法我们同样找不到。

但是,我们想使得获取的对象存在,但自己不持有,需要使用autorelease方法。autorelease方法使得对象在超出指定的生存范围时能够自动并正确地释放。

id obj = [[NSObject alloc] init];
[obj autorelease];

这里我们通过一个图直观地对比一下 release 和 autorelease 方法的区别:

在这里插入图片描述

无法释放非自己持有的对象

对于持有者是自己的对象,在不需要时需要将其释放。而除此之外的对象绝不能释放,倘若在程序中释放了非自己所持有的对象就会造成崩溃

例如:释放之后再次释放已非自己持有的对象

id obj = [[NSObject alloc] init];
[obj autorelease];
[obj autorelease];

alloc/retain/release/dealloc实现

介于包含NSObject类的Foudation框架并没有公开,部分源代码没有公开。为此,我们首先使用开源软件GNUstep来说明。

  1. alloc
    我们直接来看一下去掉NSZone后简化了的源代码:
    在这里插入图片描述
    alloc类方法用struct obj_layout中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置0后返回。

在这里插入图片描述

  1. retain
    这里先认识一下retainCount,通过retainCount实例方法可获得对象的引用计数。

执行alloc后的对象的retainCount是“1”。因为分配是全部置0,所以retained为0。由NSExtraRefCount(self) + 1得出retainCount为1。
在这里插入图片描述
由对象寻址找到对象内存头部,从而访问其中的retained变量。在这里插入图片描述
我们再通过源代码就看出retain方法会使retained变量加1在这里插入图片描述这里虽然写入了retained变量超出最大值时发生异常的代码,但实际上只运行了使retained加1的retained++的代码。

  1. release
  • 当retained变量大于0时,release实例方法使retained变量减1
    在这里插入图片描述
  • 当retained变量等于0时调用dealloc实例方法,废弃对象

在这里插入图片描述
值得注意的是:上述代码仅废弃由alloc分配的内存块

总结一下:

  • 在Objective-C的对象中存有引用计数这一整数值。
  • 调用alloc或retain方法后,引用计数值加1。
  • 调用release后,引用计数值减1。
  • 引用计数值为0时,调用dealloc方法废弃对象。

苹果是采用散列表(引用计数表)来管理引用计数的。

autorelease

认识autorelease

首先复习一下C语言的自动变量。当程序执行时,若某自动变量超出其作用域,该自动变量将被自动废弃。

在这里插入图片描述

autorelease会像C语言的自动变量那样来对待对象实例。当超出变量作用域时,对象实例的release实例方法被调用。初次之外,与C语言的自动变量不同的是,编程人员可以设定变量的作用域。

使用autorelease

autorelease具体使用方法:

  • 生成并持有NSAutoreleasePool对象。
  • 调用已分配对象的autorelease实例对象。
  • 废弃NSAutoreleasePool对象。

这里首先认识一下NSAutoreleasePool对象的生存周期。
在这里插入图片描述

NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
在这里插入图片描述

在Cocoa框架中,Cocoa程序都有一个主事件循环(RunLoop),程序会一直在循环处理事件,而不是一运行就退出。在每次循环中,系统就会自动管理(创建、使用、销毁)一个自动释放池,以释放autorelease的对象。
在这里插入图片描述

但在大量产生autorelease的对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。

例如读入大量图像的同时改变其尺寸,这种情况下会大量产生autorelease的对象,外面只有一个大的autoreleasepool,那么在循环中产生的所有对象要等到循环结束后才释放,这样每张图片的内存都暂时保留在内存里,很容易爆内存。

在这里插入图片描述

在此情况下,有必要在适当的地方生成、持有或废弃NSAutoreleasePool对象。在这个例子中,也就是在循环中手动创建小的@autoreleasepool块。
在这里插入图片描述

这样。每次循环结束时,池子就被清空,该释放的对象马上释放,内存使用量会保持稳定。

另外,Cocoa框架中也有很多类方法用于返回autorelease的对象。

id array = [NSMutableArray arrayWithCapacity:1];

这个代码等同于源代码:

在这里插入图片描述

实现autorelease

autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法,addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法。

如果嵌套生成或持有NSAutoreleasePool对象,调用NSObject类的autorelease实例方法时,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组里。

追加一个问题:如果autorelease了NSAutoreleasePool对象会如何?

答案是发生异常。这是因为无论调用哪一个对象的autorelease实例方法,实际上调用的都是NSObject类的autorelease实例方法,但是对于NSAutoreleasePool类,autorelease实例方法已被该类重载,也就是说,NSAutoreleasePool本身就是管理autorelease对象的池子,而现在它要把自己放进自己管理的池子里,这样就会造成严重的内存错误或死循环,因此会报错。

ARC规则

所有权修饰符

ARC有效时,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。

所有权修饰符:用于修饰指针类型的关键字,用来告诉编译器对象的内存所有权、引用关系和生命周期管理方式。

所有权修饰符共4种:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

__strong修饰符

是id类型和对象类型默认的所有权修饰符。

id obj = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];

上述两个代码是相同的。

管理成员变量的对象所有者
作用域中管理

__strong修饰符表示对对象的强引用。持有强引用的变量在超出其作用域时被废弃,随着强引用的实效,引用的对象会随之释放。

  • 自己生成并持有的对象
{//因为变量是强引用,所以自己持有对象id __strong obj = [[NSObject alloc] init];
}

因为变量obj超出其作用域,强引用失效,所以自动释放自己持有的对象,对象的所有者不存在,因此废弃对象。

  • 非自己生成并持有的对象
{//强引用,自己持有id __strong obj = [NSMutableArray array];
}
//超出作用域,强引用实效,自动释放自己持有的对象

与自己生成并持有的对象的生命周期是一样明确的。

赋值上管理

附有__strong修饰符的变量之间还可以相互赋值,下面具体展示:

obj1持有对象A的强引用。

id __strong obj1 = [[NSObject alloc] init];//对象A

obj2持有对象B的强引用。

id __strong obj2 = [[NSObject alloc] init];//对象B

obj3不持有任何对象。

id __strong obj3 = nil;
obj1 = obj2;
obj3 = obj1;
  • obj1持有obj2赋值的对象的B的强引用,因为obj1被赋值,所以原先持有的对对象A得强引用实效。对象A的所有者不存在,因此废弃对象A。此时,持有对象B的强引用的变量为obj1和obj2。
  • obj3持有obj1赋值的对象的B的强引用。此时,持有对象B的强引用的变量为obj1、obj2和obj3。
obj2 = nil;
obj1 = nil;
obj3 = nil;
  • 因为nil被赋予了obj2,所以对对象B的强引用实效。此时,持有对象B强引用的变量为obj1和obj3。
  • 因为nil被赋予了obj1,所以对对象B的强引用实效。此时,持有对象B强引用的变量为obj3。
  • 因为nil被赋予了obj3,所以对对象B的强引用实效。此时,对象B的所有者不存在,因此废弃对象B。

这里我们使用dealloc函数来查看一下对象被持有和释放的时机:

-(void)dealloc {NSLog(@"%@被释放", self.name);
}

在这里插入图片描述

管理方法参数的对象所有者
-(void)setObject:(id __strong)obj;
{id __strong test = [[Classes alloc] init];[test setObject:[[NSObject alloc] init]];
}

test持有Classes对象的强引用,同时Classes对象的obj成员持有NSObject对象的强引用。

在上述代码中,因为test变量超出其作用域,强引用实效,所以自动释放Classes对象。Classes对象的所有者不存在,因此废弃该对象。与此同时,Classes对象的obj成员也被废弃,NSObject对象的强引用实效,自动释放NSObject对象。同样,NSObject对象的所有者不存在,因此也废弃了该对象。

另外,_ _ strong 、_ _ weak和_ _autoreleasing一起时可以保证将附有这些修饰符的自动变量初始化为nil。

id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;

在这里插入图片描述

这样,就可以通过__strong修饰符,不必再次输入retain或者release,且满足了“引用计数式内存管理的思考方式”。

id类型和对象类型的所有权修饰符默认为__strong修饰符。

__weak修饰符

__strong修饰符不能解决引用计数式内存管理中“循环引用”的问题。

在这里插入图片描述

{//test1持有Classes对象A的强引用//test2持有Classes对象B的强引用id test1 = [[Classes alloc] init];//Aid test2 = [[Classes alloc] init];//B[test1 setObject:test2];//A.obj_ = test2//Classes对象A的obj_成员变量持有Classes对象B的强引用,此时持有Classes对象B的强引用的变量为对象A的obj_和test2[test2 setObject:test1];//B.obj_ = test1//Classes对象B的obj_成员变量持有Classes对象A的强引用,此时持有Classes对象B的强引用的变量为对象B的obj_和test1
}

因为test1变量超出其作用域,强引用实效,所以自动释放Classes对象A。

因为test2变量超出其作用域,强引用实效,所以自动释放Classes对象B。

此时,持有Classes对象A的强引用的变量为Classes对象B的obj_。

此时,持有Classes对象B的强引用的变量为Classes对象A的obj_。

也就是,A和B在超出作用域后依然不能自动释放,因为还互相强引用着,这样就发生了内存泄漏!

内存泄漏:应当废弃的对象在超出其生存周期后继续存在。

对自身强引用时也会发生循环引用。

id test = [[Classes alloc] init];
[test setObject:test];

在这里插入图片描述

那么怎样才能避免循环引用呢?这时候就需要__weak修饰符了。

弱引用不能持有对象实例。

id __weak obj = [[NSObject alloc] init];

上述代码会引起警告:
在这里插入图片描述

这是因为代码将自己生成并持有的对象赋值给附有__weak修饰符的变量obj,也就是变量obj持有对持有对象的弱引用。因此,为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会立即释放,编译器就会给出警告。

下面代码(将对象先赋值给附有_ _strong修饰符的变量后再赋值给附有 _ _weak修饰符的变量)可以解决这个问题:

id __strong obj1 = [[NSObject alloc] init];
//因为obj1变量为强引用,所以自己持有对象
id __weak obj2 = obj1;
//obj2变量持有生成对象的弱引用

超出obj1变量作用域,强引用实效,自动释放自己持有的对象。对象所有者不存在,废弃该对象。因为弱引用的变量不持有对象,所以超出变量作用域时,对象即被释放

再看上面“循环引用”的问题,这时我们将成员变量弱引用,就可以避免该问题了。

@interface Classes : NSObject {id __weak obj_;
}

在这里插入图片描述

__weak修饰符的另一个优点:在持有某对象的弱引用时,若该对象被废弃,弱引用将自动失效且处于nil被赋值的状态(空弱引用)。

在这里插入图片描述

id __weak obj1 = nil;
{id __strong obj2 = [[NSObject alloc] init];obj1 = obj2;NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);

将obj2指向的对象赋给obj1,obj1是弱引用,不增加引用计数,只弱引用,因此作用域中obj1变量持有弱引用的对象。

obj2变量超出作用域后,强引用实效,自动释放自己持有的对象,废弃对象。同时,obj1持有该对象的弱引用也实效,nil赋值给obj1,因此输出nil。

在这里插入图片描述

__unsafe_unretained修饰符

不安全的所有权修饰符。附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

与附有_ _weak修饰符的变量一样,附有 __unsafe_unretained修饰符的变量因为自己生成并持有的对象不能继续为自己所有,所以生成的对象会被立即释放。

__unsafe_unretained也不能持有对象实例

id __unsafe_unretained obj = [[NSObject alloc] init];

在这里插入图片描述

那么,我们对比一下区别:

id __unsafe_unretained obj1 = nil;
{id __strong obj2 = [[NSObject alloc] init];obj1 = obj2;NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);

在这里插入图片描述

我们可以看到输出结果已经不一样了,第一次可以输出,而第二次出现了典型的“访问已释放对象”“的报错。

对比_ _weak,附有__unsafe_unretained的对象是一个不安全的弱引用。它不会增加引用计数,也不会在对象销毁时自动置为nil

因此,在作用域中和__weak无异,正常输出持有的对象,而超出作用域后,对象销毁且不会自动置为nil,因此程序崩溃,且此时的obj1就是悬垂指针。

悬垂指针:指向一块已经被释放或无效的内存的指针。

换句话说,这个指针曾经指向一个合法的对象,但那个对象后来被销毁了,指针变量本身还保留着那块旧地址。程序若再访问,就会访问到不可用的内存区域,导致程序崩溃。

__autoreleasing修饰符

__autoreleasing使用机制

实际上,不能使用autorelease方法,也不能使用NSAutoreleasePool类。虽然autorelease无法直接使用,但实际上,ARC有效时autorelease功能使起作用的。

  • ARC无效时

在这里插入图片描述

  • ARC有效时
@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];
}

ARC有效和无效时,有一部分是等价的:

  1. 指定@autoreleasepool块来代替无效时NSAutoreleasePool类对象的生成、持有及废弃。
  2. 对象赋值给附有__autoreleasing修饰符的变量等价于无效时调用对象的autorelease方法。

如果方法名以alloc、new、copy、mutableCopy开头,那么返回的对象不会自动加入autoreleasepool,反之,以array、dictionary、stringWithFormat:等命名的返回的对象会被子哦那个加入。因此同_ _strong修饰符一样,不用显式的使用__autoreleasing修饰符也可以。

@autoreleasepool {id __strong obj = [NSMutableArray array];
}

因为obj强引用,自己持有对象。并且由编译器判断方法名后自动注册到autoreleasepool。然而超出作用域时,强引用实效,自动释放自己持有的对象,同时,随着@autoreleasepool块的结束,注册到autoreleasepool中的所有对象(包含obj)也自动释放。

这样,不使用__autoreleasing修饰符也能使对象注册到autoreleasepool。

那么,如果不在@autoreleasepool块中,不显式使用__autoreleasing也会自动注册吗?

答案是会的。前面我们说到每个线程的 RunLoop在每一轮事件循环中,系统都会自动创建并销毁一个 autoreleasepool。因此即使没有显式写@autoreleasepool代码块,系统也会在每次事件循环自动帮助我们创建一个隐式的@autoreleasepool代码块。因此,@autoreleasepool块中,不显式使用__autoreleasing也会自动注册。不过,区别在于显式autoreleasepool中生成的对象会在离开块时立即释放,而不在显式autoreleasepool中的对象会在当前事件循环结束后才释放

访问带有__weak修饰符的对象,系统实际上会将被访问的对象注册到autoreleasepool中。

id __strong obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;

在这里插入图片描述

那么这是为什么呢?

这是因为__weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃。此时,如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。

autoreleasing隐式使用机制

id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符。因此一下代码等价:

- (BOOL) performOperationWithError:(NSError **)error;
- (BOOL) performOperationWithError:(NSError *_autoreleasing *)error;

作为alloc、new、copy、mutableCopy方法返回值取得的对象是自己生成并持有的,其他情况下是取得的是非自己生成并持有的对象,会被注册到autoreleasepool。因此,使用__autoreleasing修饰符的变量作为对象取得参数,与除alloc、new、copy、mutableCopy外其他方法的返回值取得对象完全一样,都会注册到autoreleasepool,并取得非自己生成并持有的对象。

总结一下:

  • alloc、new、copy、mutableCopy:自己持有,不注册到autoreleasepool
  • 其他工厂方法:非自己持有,注册到autoreleasepool
  • autoreleasing 参数:非自己持有,注册到autoreleasepool

规则

具体的ARC规则有:

  • 不能使用retain、release、retainCount、autorelease

内存管理是编译器的工作,因此不必使用内存管理方法。如若使用,将产生报错:
在这里插入图片描述

总之,只能在ARC无效且手动进行内存管理时使用retain、release、retainCount、autorelease方法

  • 不能使用NSAllocateObject、NSDeallocateObject

一般通过NSObject类的alloc类方法来生成并持有Objective-C对象。若使用,同样会产生报错。

  • 遵守内存管理的方法命名规则

在ARC无效时,以alloc、new、copy、mutableCopy开始的方法在返回对象时,必须返回给调用方所应当持有的对象。ARC有效时是在此基础上追加一条init。

以init开始的方法规则更加严格,该方法必须是实例方法,并且必须返回对象。返回的对象应为id类型或该方法声明类的对象类型,或是该类的超类型(父类)或子类型(子类)。该返回对象并不注册到autoreleasepool上,基本上只是对alloc方法返回值的对象进行初始化处理并返回对象

正确的命名方法:

-(id)initWithObject;

错误的命名方法:

-(void)initThisObject;
  • 不要显示调用dealloc

dealloc方法适用的情况有:

  1. 对象的所有者不持有该对象,要被废弃时。

在这里插入图片描述

  1. 删除已注册的代理或观察者对象。
-(void)dealloc {[[NSNotificationCenter defaultCenter] removeObserver:self];
}

ARC会自动对此进行处理,因此在ARC有效时会遵循无法显式调用dealloc这一规则。若使用,同样会引起编译错误。

  • 使用@autoreleasepool块代替NSAutoreleasepool

使用NSAutoreleasepool类会引起编译器报错。

  • 不能使用区域(NSZone)

NSZone是早期Objective-C中的一种内存分配优化机制,是为了让开发者能够把不用类型的对象分配在不同的内存区域中,以便优化内存管理或调试。

现代Objective-C宏定义#define __OBJC2__表示当前编译环境使用现代运行时:

  1. 所有对象分配都统一由malloc管理。
  2. 内存区域概念已完全废弃。
  3. allocWithZone:仍然存在,但zone参数被忽略。
  4. alloc方法现在直接调用allocWithZone:nil。
  • 对象型变量不能作为C语言结构体的成员
  • 显式转换"id"和"void"

在这里插入图片描述

id型或对象型变量赋值给void*或者逆向赋值时都需要进行特定的转换。解决这个问题可以使用“__bridge转换“。

__bridge转换有三种桥接修饰符:

  • __bridge转换
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)(obj);

只做类型转换,不改变所有权,不改变引用计数,两个指针指向同一内存。安全性与__unsafe_unretained修饰符相近,甚至更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

  • __bridge_retained转换
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void*)obj;

与retain类似,从ARC管理被retain到Core Foundation,并增加引用计数。

  • __bridge_transfer转换
void *p = (__bridge_retained void*) [[NSObject alloc] init];
(void)(__bridge_transfer id)p;

与release类似,把Core Foundation转换到ARC,把所有权交给ARC管理。

Objective-C对象 & Core Foundation对象:
Core Foundation对象主要用于C语言编写的Core Foundation框架中,并使用引用计数的对象。

Core Foundation对象与Objective-C对象区别很小,不同之处只在于由哪一个框架生成。Foundation框架的API生成并持有的对象可以用Core Foundation框架的API释放,反之也可以。

属性

当ARC有效时,Objective-C类的属性也会发生变化。

在这里插入图片描述

以上,只有copy属性不是简单的赋值,它赋值的是通过NSCopying接口的copyWithZone:方法复制赋值源所生成的对象。

不过,在声明类成员变量时,如果同属性声明中的属性不一致就会引起编译错误。

@interface Auto : NSObject {id obj;
}
@property(nonatomic, weak) id obj;

我们尝试跑这个代码,发现可以正常运行。这是为什么呢?

这是因为我们现在使用了新版 Xcode(Clang ≥ 8),该编译器不会强制使用我们声明的ivar,而是为属性自动生成一个实例变量_obj。这样的情况下,@property使用_obj,而我们定义的成员变量obj只是一个普通指针,没有被使用,因此编译器不会报冲突。那么哪种情况下会报错呢?

当我们显式使用@synthesize,并让属性和我们定义的ivar绑定时。

@synthesize obj = obj;

这时出现报错:
在这里插入图片描述

这是因为@synthesize强制让weak属性使用了强引用的ivar。
那么解决这个问题的办法有两种:

 @interface Auto : NSObject {id __weak obj;}@property(nonatomic, weak) id obj;
 @interface Auto : NSObject {id obj;}@property(nonatomic, strong) id obj;

数组

静态数组

静态数组除 __unsafe_unretained 外,__strong__weak__autoreleasing 修饰的数组元素会被自动初始化为nil。

{id obj[2];obj[0] = [[NSObject alloc] init];obj[1] = [NSMutableArray array];
}

数组超出其变量作用域时,数组中各个附有__strong修饰符的变量也随之实效,其强引用消失,所赋值的对象也随之释放。

动态数组

将附有__strong修饰符的变量作为动态数组来使用时,需要手动管理内存。必须遵守以下事项:

  • 声明方式
    • 需用指针显式指定修饰符:由于“id *类型“默认为“id__autoreleasing *类型“,所以显式指定为_ _strong修饰符。
      id __strong *array = nil;
      
    • 指定类名的形式
      NSObject * __strong *array = nil;
      
  • 内存分配

必须使用calloc函数分配内存。因calloc会将内存初始化为0,满足__strong变量使用前需初始化为nil的要求。那么为什么不使用malloc呢?

这是因为使用malloc函数分配的内存区域没有被初始化为0,因此nil会被赋值给__strong修饰符的并被赋值了随机地址的变量中,从而释放一个不存在的对象。

NSInteger entries = 10;
id __strong *array = (id __strong*)calloc(entries, sizeof(id));

若用malloc分配后,需要使用memset等函数将内存填充为0,禁止直接遍历数组给元素赋值为nil。

  • 内存释放

不能直接使用free释放数组内存,需将所有元素赋值为nil,即让元素对对象的强引用实效,释放对象,再调用free释放内存块。否则会内存泄漏。

for (int i = 0; i < entries; i++) {array[i] = nil;
}
free(array);

使用memset等函数填充0也无法释放对象,会导致内存泄漏。

  • 禁止操作

禁止使用memcpy拷贝数组元素,realloc重新分配内存块,这会导致对象被错误保留或重复释放。

http://www.dtcms.com/a/531983.html

相关文章:

  • 有什么免费的网站wordpress 采集器
  • 网站建设创新成果网站营销如何做
  • 设计模式-享元模式(Flyweight)
  • TMS320C6000 VLIW架构并行编程实战:加速AI边缘计算推理性能
  • 算法学习记录08——并归的应用(LeetCode[315])
  • 【机器人学中的状态估计】3.6.6 习题证明
  • Kafka生产者详解(下):数据去重(幂等性)与数据有序
  • Data Ingestion: Architectural Patterns
  • 网站建设心得体会范文郑州男科医院排行哪家最好
  • 【datawhale秋训营】动手开发RAG系统(应急安全方向) TASK02
  • 怎么搜索整个网站内容网站怎么做成app
  • Python3 集合
  • 九冶建设有限公司官方网站sem优化怎么做
  • MATLAB基于灰靶决策模型的高校信息化设备供应商选择研究
  • java类与对象
  • AI 应用层革命(一)——软件的终结与智能体的崛起
  • Linux Crontab命令详解:轻松设置周期性定时任务
  • beef-xss网页无法访问
  • JavaEE初阶——多线程(3)线程安全
  • AI 开发告别 “孤岛”:MCP + 火山引擎
  • 做网站怎么开发程序建设网站改版
  • 招生管理平台需求分析文档
  • 设计模式-代理模式(Proxy)
  • Apache IoTDB(8):时间序列管理——从创建到分析的实战指南
  • IntelliJ IDEA 四种项目构建:从普通 Java 到 Maven Web 项目
  • 深入浅出数据结构:堆的起源、核心价值与实战应用
  • 智能行李架:快速找到最佳行李位
  • ArcGIS如何根据属性字段符号化面要素
  • 洛阳企业网站建设深圳网站建设系统
  • 面试题-React