iOS基础

iOS学习-内存管理

2023-03-31  本文已影响0人  麻辣香锅加特辣

iOS内存管理是iOS开发中非常重要的一个话题,本文将深入探讨iOS内存管理的基本原理和相关知识点,包括strong和weak、ARC自动引用计数、内存泄漏以及相关面试问题。希望本文能够帮助iOS开发人员更好地理解和掌握iOS内存管理。

一、引用计数

iOS通过引用计数来进行内存管理,每个对象都有一个引用计数器(retain count),用于记录有多少个指针指向该对象。当对象的引用计数器为0时,表示该对象不再被使用,可以被释放。引用计数器的增减通过retain和release方法来完成。

retain方法会使对象的引用计数器加1,表示有一个新的指针指向了该对象;release方法会使对象的引用计数器减1,表示一个指针不再指向该对象。当对象的引用计数器为0时,会自动调用dealloc方法来释放对象的内存空间。

二、MRC和ARC

在Objective-C中,我们可以使用MRC(手动引用计数)或者ARC(自动引用计数)两种方式来管理内存。下面分别介绍一下这两种方式下的内存管理:

MRC下的内存管理:

在MRC下,我们需要手动管理对象的引用计数,当对象不再被使用时,我们需要将其引用计数减1,当引用计数为0时,对象会被自动释放。

例如,在MRC下,我们可以通过retain方法增加对象的引用计数,release方法减少对象的引用计数,autorelease方法将对象放入自动释放池中等方式来管理对象的引用计数。

ARC下的内存管理:

在ARC下,我们不需要手动管理对象的引用计数,系统会自动在合适的时机对对象进行引用计数的增减和释放操作,从而避免了手动管理引用计数的繁琐和出错。同时,ARC也提供了一些关键字和修饰符,如strong、weak、__unsafe_unretained、__autoreleasing等,用于更精细地控制对象的生命周期。
例如,在ARC下,我们可以使用strong和weak关键字来声明属性,使用__autoreleasing关键字来修饰方法返回值,使用__bridge关键字来进行类型转换等方式来管理对象的引用计数。

三、强引用和弱引用

在iOS中,强引用(strong)和弱引用(weak)是最常见的两种引用类型。

强引用会使对象的引用计数器加1,表示该对象被强引用,不会被释放,直到所有的强引用都被移除。一般情况下,我们使用强引用来持有自己创建的对象,例如在一个类中定义一个属性,这个属性应该是强引用类型。

弱引用不会使对象的引用计数器加1,表示该对象被弱引用,不会阻止对象的释放。当对象的引用计数器为0时,弱引用会被自动设置为nil。一般情况下,我们使用弱引用来解决循环引用问题,例如在两个类之间互相引用时,可以使用弱引用来避免循环引用。

四、内存泄漏

内存泄漏是指在程序中分配了内存空间,但在程序结束时没有正确释放该内存空间,导致内存泄漏。在iOS开发中,内存泄漏是一个非常严重的问题,会导致应用程序出现卡顿、崩溃等问题。

内存泄漏的主要原因是由于程序中存在循环引用,即两个对象互相引用,但都没有被其他对象所引用,导致它们之间的引用计数器无法自动减少到0,导致内存无法释放。因此,我们需要避免循环引用的产生,可以使用弱引用、解除引用或Block的方式来避免循环引用。

五、ARC下的内存管理实践

在ARC下,由于不需要手动管理引用计数,我们需要注意以下几个方面来保证内存的正常管理。

避免循环引用

循环引用是ARC下的一个常见问题,解决循环引用的方法通常有两种:

(1)使用weak关键字

当我们在一个类中需要引用另一个类的实例时,可以使用weak关键字来避免循环引用。

例如,一个ViewController中需要引用一个Model的实例时,可以将Model的实例定义为weak属性:

@property (nonatomic, weak) Model *model;

这样,当ViewController的实例被释放时,model的引用计数器会自动减1,如果没有其他强引用对象指向它,model会被自动释放。

(2)使用__block解除引用

在Block中,我们也需要避免循环引用的产生。一种常见的方法是使用解除引用(__block)关键字。

例如,当我们在一个Block中需要引用一个对象时,可以将该对象定义为__block类型:

__block Model *model = self.model;
[self doSomething:^{
    [model doSomething];
}];

这样,在Block中对model进行操作时,不会使model的引用计数器加1,也不会产生循环引用。

注意Block中的循环引用

在使用Block时,需要特别注意循环引用的问题。如果Block中引用了该Block所在的对象,就会产生循环引用的问题。

例如,下面的代码中,Block引用了self,导致self无法释放,从而产生了内存泄漏的问题。

[self doSomething:^{
    [self doSomethingElse];
}];

解决这个问题的方法通常是使用弱引用或者解除引用。例如,将self定义为weak属性:

__weak typeof(self) weakSelf = self;
[self doSomething:^{
    [weakSelf doSomethingElse];
}];
避免Block中使用循环引用的对象

在Block中,我们需要避免使用循环引用的对象,以免产生内存泄漏的问题。

例如,下面的代码中,self引用了timer,timer引用了Block,block引用了self,就会产生循环引用的问题。

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    [self doSomething];
}];

解决这个问题的方法通常是使用弱引用或者解除引用。例如,将self定义为weak属性,或者使用__block关键字来解除引用。

避免使用retainCount

在ARC下,我们不再需要手动管理对象的引用计数,因此也就不需要使用retainCount方法来获取对象的引用计数。

retainCount方法并不总是返回准确的引用计数,因此在ARC下使用retainCount方法并不可靠,可能会导致错误的结果。

避免使用__unsafe_unretained

在ARC下,我们应该尽量避免使用__unsafe_unretained关键字来声明属性,因为它不会自动将对象置为nil,容易产生野指针的问题。
例如,下面的代码

@property (nonatomic, unsafe_unretained) Model *model;

解决这个问题的方法通常是使用weak关键字来声明属性。

六、isa指针优化

在Objective-C中,每个对象都有一个isa指针,指向它所属所属的类对象。在MRC下,isa指针是指向Class结构体的指针,而在ARC下,isa指针是指向objc_class结构体的指针。

为了优化内存使用和提高程序的性能,iOS系统对isa指针进行了优化。具体来说,iOS系统使用了两种类型的isa指针,分别是普通指针和优化指针。

普通指针:

普通指针是最基本的isa指针类型,它直接指向类对象的地址。在普通指针下,当我们调用对象的方法时,系统会根据isa指针找到对象所属的类对象,并在类对象中查找方法的实现。

优化指针:

在iOS 2.0之前,isa指针是直接存储在对象中的,这样会占用大量的内存。在iOS 2.0之后,isa指针行了优化,变成了一个联合体(union)结构,将64位的内存空间用来存储地址、引用计数以及一些标记数据。

以ARM64为例,其中的33位存储具体的地址值,19位存储引用计数,而剩下的存储了更多的信息,如是否有weak指针,是否开启指针优化、是否有C++或Objc的析构器等等。

这种方式发挥了联合体的优势节省了比较多的内存空间,可以有效地降低内存的使用,尤其是在使用大量对象时。然而,由于位域的使用是由编译器自动完成的,因此有时会出现一些不可预测的问题,例如位域的长度不够等。因此,在进行性能优化时,需要谨慎使用isa指针优化,避免出现问题。

七、面试问题

下面列举一些常见的面试问题:

请介绍一下iOS中的内存管理方式,包括ARC和MRC两种方式。
请解释一下strong和weak关键字的区别和作用。
请介绍一下weak关键字的实现原理。
请解释一下isa指针的优化方式。
请解释一下循环引用的问题,以及如何避免循环引用。
请介绍一下iOS中的内存泄漏问题,以及如何避免内存泄漏。
请介绍一下autorelease池的作用和实现原理。
怎么样进行内存泄漏的排查和解决?
对于大内存的处理和优化,你有哪些思路和方法?
对于内存问题的优化,你有哪些实际的经验和技巧?

总结

在iOS开发中,内存管理是一个非常重要的话题,无论是手动管理引用计数,还是使用ARC自动管理引用计数,都需要我们注意以下几个方面来保证内存的正常管理:

了解对象的引用计数和引用计数的变化方式;
避免产生循环引用,可以使用weak关键字、解除引用或Block的方式来避免循环引用;
在ARC下,注意Block中的循环引用问题,避免Block中使用循环引用的对象,避免在Block中使用强引用;
避免使用retainCount方法,避免使用__unsafe_unretained关键字来声明属性。
以上是iOS内存管理的一些实践经验,希望能对你有所帮助。在iOS开发中,合理地管理内存是一项重要的技能,希望你能够在实践中逐渐掌握这项技能。

上一篇下一篇

猜你喜欢

热点阅读