📜  iOS-内存管理(1)

📅  最后修改于: 2023-12-03 15:31:26.343000             🧑  作者: Mango

iOS内存管理

iOS内存管理是开发iOS应用程序时必不可少的一部分。iOS设备上的应用程序每时每刻都在使用内存,因此需要确保内存管理良好。本文将介绍iOS内存管理的基本概念,如何避免内存泄漏和如何使用ARC(自动引用计数)进行内存管理。

内存管理基础

在了解如何进行内存管理之前,首先需要了解一些基础概念,这些概念在iOS的内存管理中是至关重要的。

堆和栈

堆和栈都是计算机内存中的两个基本概念。在iOS的内存管理中,堆和栈都占据着重要的位置。

是一块用于存储动态分配的内存的区域。通常情况下,在堆上分配的内存需手动释放,否则会导致内存泄漏。在iOS中,使用allocinit方法分配内存时,所分配的内存就位于堆中。

是一块用于存储局部变量和函数调用信息的内存区域。栈基本上是一个先进后出的数据结构。当函数被调用时,在栈上分配了一块存储空间,当函数退出时,该存储空间即被回收。在iOS中,栈一般用于存储函数调用所使用的参数和局部变量。

引用计数

引用计数是iOS内存管理中的一种技术,用于跟踪每个对象被引用的次数。当对象被创建时,引用计数为1。当对象被引用或复制时,其引用计数会增加。当对象不再被引用或使用时,其引用计数会减少。当某个对象的引用计数变成了0,这个对象就被标记为不再需要,系统会释放它所占用的内存。

在iOS中,可以手动调用一些方法来增加和减少对象的引用计数。例如,retain方法会增加对象的引用计数,而release方法会减少对象的引用计数。要注意的是,手动管理引用计数需要非常小心、细心,否则容易导致内存泄漏或者野指针问题。

自动引用计数

自动引用计数(Automatic Reference Counting,ARC)是iOS5引入的一种新特性,它使用编译器技术自动管理对象的引用计数。ARC会在编译时自动插入合适的retainrelease代码,使得开发者无需手动管理引用计数,从而避免了内存泄漏和野指针问题。

ARC的使用非常简单,只需要在Xcode中打开ARC选项,编译器就会自动为你插入相应的代码。要注意的是,虽然ARC很方便,但是适当的内存管理还是非常重要的,需要时刻关注内存使用情况,避免出现内存占用过高的情况。

内存泄漏

内存泄漏指的是程序中存在一些无用的对象,但是这些对象的引用计数依然不为0,从而导致这些对象占用的内存无法被释放,从而引起内存占用过高的问题。内存泄漏是每一个iOS开发者都需要避免的问题。

内存泄漏可能出现在很多地方,比如未释放的对象、循环引用、过度使用单例等。下面是一些常见的内存泄漏情况以及如何避免它们。

未释放的对象

未释放的对象是内存泄漏最常见的情况之一。当我们使用alloccopyretain等方法创建了一个对象后,需要在不再需要该对象时手动调用release方法释放对象占用的内存。

// 内存泄漏示例 
- (void)someMethod {
    NSString *str = [[NSString alloc] initWithFormat:@"%@", @"Hello World"];
    // Do something with str
    // 这里忘记释放对象str,导致内存泄漏
}
// 解决内存泄漏 
- (void)someMethod {
    NSString *str = [[NSString alloc] initWithFormat:@"%@", @"Hello World"];
    // Do something with str
    [str release]; // 释放对象str占用的内存
}
循环引用

循环引用指的是两个或多个对象彼此持有对方的引用,导致它们的引用计数不为0,无法释放内存。循环引用最常见的情况是在 UIViewController 和 UIView 之间的循环引用。

// 循环引用示例 
@interface MyViewController : UIViewController
@property (nonatomic, strong) MyView *myView;
@end

@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.myView = [[MyView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.myView];
    self.myView.delegate = self;//导致了循环引用
}
@end

@interface MyView : UIView
@property (nonatomic, weak) MyViewController *delegate;
@end

@implementation MyView
@end
// 解决循环引用 
@interface MyViewController : UIViewController
@end

@implementation MyViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    MyView *myView = [[MyView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:myView];
    myView.delegate = self;
}
@end

@interface MyView : UIView
@property (nonatomic, weak) MyViewController *delegate;
@end

@implementation MyView
@end

在上面的代码中,我们将 MyViewController 类中的 myView 属性声明为弱引用(weak),这样即使 MyView 持有了 MyViewController 的引用,由于 MyViewController 引用的是 MyView,MyView 又没持有 MyViewController 的引用,两个对象的引用计数都会变为 0,从而避免了循环引用。

过度使用单例

单例是一种经典的设计模式,它能够确保全局只有唯一一个实例对象。但是过度使用单例,可能会导致内存占用过高的问题。

如果单例创建在较早的时候,并且在开发过程中一直存在着,就会一直占用内存。因此,如果单例在不使用的时候可以释放掉,就应该尽早释放。如果是在 UIViewController 中使用的单例,那就在 dealloc 方法中释放相应的单例对象。

使用Block

在使用Block的时候,需要特别注意不要捕获self,不然也会导致循环引用的问题。解决方式是增加弱引用(weak)的self。

// 使用Block 
- (void)someMethod {
    [myObj doSomethingWithCompletion:^(NSArray *results) {
        [self processData:results];
    }];
}
// 解决循环引用 
- (void)someMethod {
    __weak typeof(self) weakSelf = self;
    [myObj doSomethingWithCompletion:^(NSArray *results) {
        [weakSelf processData:results];
    }];
}
ARC使用

ARC是自动引用计数,一种编译期间自动插入 retain 和 release 方法的技术,因此避免了手动管理引用计数的问题。

ARC支持所有的Objective-C对象,包括Core Foundation对象,不支持用于C基本类型的指针。

使用ARC之后,你就不需要主动调用retainrelease等方法来处理对象的引用计数了,但是ARC依然需要用到autorelease pool,并且需要处理循环引用。

默认情况下Xcode创建的工程都是使用ARC的。

引用计数规则

使用ARC后,你需要了解对象引用计数的规则是什么。当对象不会再被引用时,ARC会自动将其释放。以下是自动释放管理的几条引用计数规则:

  • 一个新分配的对象的引用计数为1,即使没有显式地调用retain等方法。
  • 通过copy方法分配一个对象的时候,其引用计数为1。
  • 所有的__autorelease pool语句结束后,处于自动释放池中的对象引用计数会减少1,如果引用计数为0,ARC会自动释放该对象。
  • 当一个对象没有任何强引用时,ARC会立刻释放该对象。
内存管理(ARC下)

由于ARC不需要开发者手动进行retain和release特别是 dealloc等释放工作,但开发者仍然需要时刻关注内存的占用情况,作出适当的内存管理,以避免内存泄漏。

以下是一些ARC开发中的内存管理技巧:

  • 延迟实例化: 无需在对象创建时就分配存储空间,如果对象不需要被创建,则不需要分配内存。如下是一种典型的延迟实例化方式
- (MyObject *)myObject {
    if (!_myObject) {
        _myObject = [[MyObject alloc] init];
    }
    return _myObject;
}

在这个例子中,只有当需要使用myObject实例的时候,才会初始化该实例。

  • 使用局部变量来限定对象的生命周期: 如果一个对象只有在方法内被使用,那么就将其定义为局部变量来限定其生命周期。例如:
- (void)someMethod {
    NSMutableArray *array = [[NSMutableArray alloc] init]; // 定义为局部变量
    // Do something with array
    // 离开作用域后,对象会自动释放
}
  • 使用weak引用来避免循环引用: weak引用会自动将引用对象的引用计数减少1,所以当一个对象不再有强引用时,对象就会被直接释放。例如:
@property (nonatomic, weak) MyClass *myObject; // 声明一个弱引用

在以上的例子中,如果 Myclass 的引用计数变成了 0,myObject 就会被自动置为 nil

  • 避免使用单例: 单例通常会一直存在于内存中,这会占用大量的内存,并导致性能下降。如果一个单例在应用程序的整个生命周期内都需要存在,就不需要担心内存占用和性能的问题。但是如果一个单例可以在某个时刻被释放掉,就需要根据应用程序的需要进行优化。

  • 使用@autoreleasepool语句来释放过多占用空间的对象

总结

iOS内存管理并不是一项简单的任务。需要时刻关注内存的使用情况,找出内存泄漏的问题等等。本文介绍了iOS内存管理的一些基础概念,如堆和栈,引用计数以及ARC等,并提供了一些避免内存泄漏和进行内存管理的技巧。对iOS开发者而言,适当的内存管理不仅能够提高应用程序的性能,同时也能够避免不必要的错误和问题。