Objective-C 内存管理 | 青训营笔记

116 阅读4分钟

这是我参与「第四届青训营」笔记创作活动的的第3天

1 Stack , Heap

1.1 栈 Stack

遵循FILO原则

是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。

image.png

1.2 堆 Heap

  • 与Heap数据结构无关
  • 手动分配
  • 向高地址扩展
  • 存储数组和对象, 线程共享

2 C和C++内存管理

C:用 malloc & free 动态分配内存 C++:

2.1 new & delete

会调用构造函数和析构函数进行初始化与清理,可自动计算类型的大小,管理内存失败会抛出异常。

2.2 Smart Pointer 智能指针

  • 保留 -> 和 * 运算符
  • auto_pt & unique_ptr

    • 基于排他所有权模式:两个指针不能指向同一个资源
    • 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值 shared_ptr
  • 可以复制赋值操作

    • 对象+引用计数

2.3 Reference Counting 引用计数

  • 记录了当前内存资源到底有多少指针在引用(可以访问这个资源)
  • 当新增加一个可以访问这个资源的引用,计数器会加1(e.g. Copy),反之会减去1
  • 当引用计数= 0时, 对象会被销毁

3 iOS内存管理

核心:管理(强)引用计数 (Reference Counting),只用考虑 ARC

  • 我们需要管理什么?

    • 继承 NSObject 对象会分配在堆里面
    • 任何继承 NSObject 对象都需要进行内存管理
  • 为什么要管理内存?

    • 多个对象之间相互强引用,导致不能释放,无法让系统回收
    • 如果一个程序占用内存过多,系统可能会强制关闭程序,造成crash
    • 如果提前释放指针,会导致野指针,也会造成crash
  • 持有 = retain = 引用计数 +1

  • 释放 = release = 引用计数 -1

3.1 Autorelease-Pool 自动释放池

对象会放到自动释放池,统一释放

autorelease:延迟释放某个对象

3.2 ARC

  • retain/release都不用考虑

    • 只需要初始化的时候 [NSObject alloc] init]
    • 可以自定义 dealloc
  • 只要有一个强指针在内存指向对象,对象就不能释放

    • ARC 销毁时机是强引用的个数 = 0
    • 而不是引用计数 = 0
  • 默认所有对象变量的指针都是强指针

    • 弱指针需显式声明
__weak Person *p2 = [Person new];

4 Practical Memory Management

4.1 @property 修饰符

  • strong: 拥有属性对象,强持有,只有OC对象才能使用该属性
  • weak: 不拥有属性对象,弱持有,只有OC对象才能使用该属性
  • assign: 指定 setter 使用简单赋值,一般用于基本类型/NSInteger/CGRect
@property (nonatomic, strong) People *people; 
@property (nonatomic, copy) NSString *name; 
@property (nonatomic, weak) id<PersonDelegate> delegate; 
@property (nonatomic, assign) int age;

4.2 循环引用

Delegate 与弱引用

@property (nonatomic, weak) id<PersonDelegate> delegate;

正常代码:

    Person *person1 = [[Person alloc] init];
    Dog * dog1 = [Dog new];
    Friend *f1 = [Friend new];

    person1.dog = dog1;
    person1.delegate = f1;
    f1.myFriend = person1;

循环引用代码:(永远有强指针指向,无法释放)

@property (nonatomic, strong) id<PersonDelegate> delegate;

5 浅拷贝,深拷贝

  • 浅拷贝:直接将一个引用数据类型的地址,赋值给另一个变量存储的,两个变量存储的是相同的内存地址,一个操作,另一个也会改变
  • 深拷贝:只复制引用数据类型中的数值而不是复制地址

image.png

image.png 不可变对象

  • copy 【深拷贝】
  • mutableCopy【深拷贝】

可变对象

  • copy 【深拷贝】
  • mutableCopy 【深拷贝】

5.1 可变与不可变

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) NSString *strongName;

@end

NSString

NSString *stringA = @"stringA";
NSLog(@"本身string的地址:%p",stringA);
Person *person1 = [[Person alloc] init];

person1.name = stringA;
NSLog(@"copy string的地址:%p",person1.name);
person1.strongName = stringA;
NSLog(@"strong string的地址:%p",person1.strongName);

image.png

  • copy:浅拷贝

  • strong : 持有stringA

NSMutableString

可变对象可以直接赋值给不可变对象属性

NSMutableString *stringM = [[NSMutableString alloc] initWithString:@"stringM"];
NSLog(@"本身mutablString的地址:%@",stringM);
Person *person1 = [[Person alloc] init];

person1.name = stringM;
NSLog(@"copy string的地址:%p",person1.name);
person1.strongName = stringM;
NSLog(@"strong string的地址:%p",person1.strongName);

image.png

  • copy:深拷贝

  • strong : 持有stringA

6 野指针与Crash

当所指向的对象被提前释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址, 若操作系统将这部分已经释放的内存重新分配给另外一个进程, 而原来的程序重新引用现在的野指针,则将产生无法预料的后果。 因为此时野指针所指向的内存现在包含的已经完全是不同的数据。

7 小结

这节课讲了比较重要的Objective-C 内存管理,其中循环引用与深拷贝浅拷贝,可变与不可变较为重要,在开发过程中需要注意。

8 引用参考

Objective-C 内存管理