【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重新分配内存块,这会导致对象被错误保留或重复释放。

相关推荐
kkk_皮蛋21 小时前
在移动端使用 WebRTC (Android/iOS)
android·ios·webrtc
Swift社区1 天前
如何在 SwiftUI 中对 CoreImage 滤镜做实时预览
ios·swiftui·cocoa
李玮豪Jimmy1 天前
MacOS 上安装 JDK 并实现多版本灵活切换
macos
mixboot1 天前
VoxCPM 1.5.0 macOS
macos·voxcpm
甜可儿1 天前
mac环境安装不同版本maven
java·macos·maven
bl4ckpe4ch1 天前
从零开始Mac OS 开荒整理笔记
笔记·macos·开荒
ipad协议开发1 天前
企业微信iPad协议的开发进程
ios·企业微信·ipad
七月巫山晴1 天前
【iOS】OC中的一些宏
前端·ios·objective-c
初级代码游戏1 天前
iOS开发 SwiftUI Text的基本用法
ios·swiftui·swift
TheNextByte12 天前
如何安全有效地清除iPad数据以便出售?
安全·ios·ipad