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

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

自动引用计数

自动引用计数原理

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

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

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

内存管理

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

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

自己生成的对象自己持有

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

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

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

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

objc 复制代码
id obj = [NSMutableArray array];
[obj retain];

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

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

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

objc 复制代码
id obj = [NSMutableArray array];
[obj release];

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

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

objc 复制代码
id obj = [[NSObject alloc] init];
[obj autorelease];

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

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

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

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

objc 复制代码
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的对象。

objc 复制代码
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类型和对象类型默认的所有权修饰符。

objc 复制代码
id obj = [[NSObject alloc] init];
id __strong obj1 = [[NSObject alloc] init];

上述两个代码是相同的。

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

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

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

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

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

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

赋值上管理

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

obj1持有对象A的强引用。

objc 复制代码
id __strong obj1 = [[NSObject alloc] init];//对象A

obj2持有对象B的强引用。

objc 复制代码
id __strong obj2 = [[NSObject alloc] init];//对象B

obj3不持有任何对象。

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

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

objc 复制代码
-(void)dealloc {
    NSLog(@"%@被释放", self.name);
}
管理方法参数的对象所有者
objc 复制代码
-(void)setObject:(id __strong)obj;
objc 复制代码
{
  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。

objc 复制代码
id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;

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

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

__weak修饰符

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

objc 复制代码
{
  //test1持有Classes对象A的强引用
  //test2持有Classes对象B的强引用
  id test1 = [[Classes alloc] init];//A
  id 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在超出作用域后依然不能自动释放,因为还互相强引用着,这样就发生了内存泄漏!

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

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

objc 复制代码
id test = [[Classes alloc] init];
[test setObject:test];

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

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

objc 复制代码
id __weak obj = [[NSObject alloc] init];

上述代码会引起警告:

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

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

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

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

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

objc 复制代码
@interface Classes : NSObject {
    id __weak obj_;
}

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

objc 复制代码
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也不能持有对象实例

objc 复制代码
id __unsafe_unretained obj = [[NSObject alloc] init];

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

objc 复制代码
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有效时
objc 复制代码
@autoreleasepool {
  id __autoreleasing obj = [[NSObject alloc] init];
}

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

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

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

objc 复制代码
@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中。

objc 复制代码
id __strong obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;

那么这是为什么呢?

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

autoreleasing隐式使用机制

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

objc 复制代码
- (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方法返回值的对象进行初始化处理并返回对象

正确的命名方法:

objc 复制代码
-(id)initWithObject;

错误的命名方法:

objc 复制代码
-(void)initThisObject;
  • 不要显示调用dealloc

dealloc方法适用的情况有:

  1. 对象的所有者不持有该对象,要被废弃时。
  1. 删除已注册的代理或观察者对象。
objc 复制代码
-(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转换
objc 复制代码
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)(obj);

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

  • __bridge_retained转换
objc 复制代码
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void*)obj;

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

  • __bridge_transfer转换
objc 复制代码
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:方法复制赋值源所生成的对象。

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

objc 复制代码
@interface Auto : NSObject {
    id obj;
}
@property(nonatomic, weak) id obj;

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

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

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

objc 复制代码
@synthesize obj = obj;

这时出现报错:

这是因为@synthesize强制让weak属性使用了强引用的ivar。

那么解决这个问题的办法有两种:

objc 复制代码
 @interface Auto : NSObject {
     id __weak obj;
 }
 @property(nonatomic, weak) id obj;
objc 复制代码
 @interface Auto : NSObject {
     id obj;
 }
 @property(nonatomic, strong) id obj;

数组

静态数组

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

objc 复制代码
{
  id obj[2];
  obj[0] = [[NSObject alloc] init];
  obj[1] = [NSMutableArray array];
}

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

动态数组

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

  • 声明方式

    • 需用指针显式指定修饰符:由于"id *类型"默认为"id__autoreleasing *类型",所以显式指定为_ _strong修饰符。

      objc 复制代码
      id __strong *array = nil;
    • 指定类名的形式

      objc 复制代码
      NSObject * __strong *array = nil;
  • 内存分配

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

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

objc 复制代码
NSInteger entries = 10;
id __strong *array = (id __strong*)calloc(entries, sizeof(id));

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

  • 内存释放

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

objc 复制代码
for (int i = 0; i < entries; i++) {
    array[i] = nil;
}
free(array);

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

  • 禁止操作

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

相关推荐
彭同学她同桌4 小时前
Mac-终端
开发语言·javascript·macos
周杰伦_Jay4 小时前
【Mac下通过Brew安装Ollama 】部署 DeepSeek 轻量模型(实测版)
人工智能·macos·数据挖掘·database·1024程序员节
游戏开发爱好者86 小时前
iOS 混淆工具链实战 多工具组合完成 IPA 混淆与加固 无源码混淆
android·ios·小程序·https·uni-app·iphone·webview
2501_9160088912 小时前
用多工具组合把 iOS 混淆做成可复用的工程能力(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
android·开发语言·ios·小程序·uni-app·iphone·swift
胎粉仔12 小时前
Swift 初阶 —— inout 参数 & 数据独占问题
开发语言·ios·swift·1024程序员节
williamdsy16 小时前
【清除 Mac DNS 缓存】Mac 电脑能访问外网却无法加载特定页面?你的 DNS 缓存“发霉”了!
macos·缓存
疯笔码良17 小时前
【IOS开发】SwiftUI + OpenCV实现图片的简单处理(一)
opencv·ios·swiftui
xingxing_F20 小时前
Topaz Video AI for Mac AI视频无损放大 视频画质增强
人工智能·macos·音视频