内存管理(二) ARC

内存管理(二) ARC

上篇我们介绍了MRC,本篇我们介绍下ARC

ARC概述

ARC是一种编译器功能,它通过LLVM编译器和Runtime协作来进行自动管理内存。LLVM编译器会在编译时在合适的地方为 OC 对象插入retain、release和autorelease代码来自动管理对象的内存,从而彻底解放程序员。

ARC不能解决的问题

  • Block等引发的循环引用问题(更多循环引用看我这篇文章)
  • 底层 Core Foundation 对象需要手动管理

所有权修饰符

__strong
__weak
__unsafe_unretained
__autoreleasing

在对象变量的声明中使用所有权修饰符时,正确的格式为:

 ClassName * 所有权 varName;

例如:

Person * __weak yang
Person * __unsafe_unretained yang

其它格式在技术上是不正确的,但编译器会 “原谅”。也就是说,以上才是标准写法。

__strong

默认修饰符,_strong 修饰符表示对对象的“强引用”,只要有强指针指向对象,对象就会保持存活。

id strongObj = [[NSObject alloc] init];
id __strong strongObj = [[NSObject alloc] init];

与之相反 如果没有强引用指针指向对象,对象就会死亡。
或者可以这么理解 最少有一个强引用指针指向对象,对象才不会死亡。

__weak

__weak 修饰符表示对对象的“弱引用” 不影响对象的释放

__weak经常会这么用

 id __strong strongObj = [[NSObject alloc] init];
 id __weak weakObj = strongObj;

在解释这段代码前,我们先看一个极端的特例,能加深我们对ARC 强持有对象规则的理解

__weak极端例子

- (void)viewDidLoad {
    [super viewDidLoad];
    id __weak weakObj = [[NSObject alloc] init];
    NSLog(@"%@", weakObj);
}

编译器警告
Assigning retained object to weak variable; object will be released after assignment

输出

weakObj===(null)

单纯地使用__weak修饰符修饰变量,编译器会给出警告,因为NSObject的实例创建出来没有强引用,就会立即释放,ARC环境中一个对象必须有一个强引用指针指向它来保证自己存活。 这里没有强引用指针所以对象被创建出来之后就销毁了
而_weak修饰的指针 weakObj,会在所指向的NSObject对象被释放后,自动指向nil,所以打印为nil

汇编分析

    0x10f5ade30 <+16>:  movq   0x75a9(%rip), %rdi        ; (void *)0x00007fff80030660: NSObject
    // 创建NSObject 对象
    0x10f5ade37 <+23>:  callq  0x10f5ae402               ; symbol stub for: objc_alloc_init
    0x10f5ade3c <+28>:  leaq   -0x18(%rbp), %rcx
    0x10f5ade40 <+32>:  movq   %rcx, %rdi
    0x10f5ade43 <+35>:  movq   %rax, %rsi
    0x10f5ade46 <+38>:  movq   %rax, -0x30(%rbp)
    0x10f5ade4a <+42>:  movq   %rcx, -0x38(%rbp)
    
    // 创建weak指针变量
    0x10f5ade4e <+46>:  callq  0x10f5ae420               ; symbol stub for: objc_initWeak
    0x10f5ade53 <+51>:  movq   0x21b6(%rip), %rcx        ; (void *)0x00007fff20191530: objc_release
    0x10f5ade5a <+58>:  movq   -0x30(%rbp), %rdi
    0x10f5ade5e <+62>:  movq   %rax, -0x40(%rbp)
    0x10f5ade62 <+66>:  callq  *%rcx
    0x10f5ade64 <+68>:  movq   -0x38(%rbp), %rdi
->  0x10f5ade68 <+72>:  callq  0x10f5ae426               ; symbol stub for: objc_loadWeakRetained
    0x10f5ade6d <+77>:  movq   %rax, %rcx
    0x10f5ade70 <+80>:  leaq   0x21a9(%rip), %rdi        ; @"weakObj===%@"
    0x10f5ade77 <+87>:  xorl   %edx, %edx
    0x10f5ade79 <+89>:  movq   %rax, %rsi
    0x10f5ade7c <+92>:  movb   %dl, %al
    0x10f5ade7e <+94>:  movq   %rcx, -0x48(%rbp)
    0x10f5ade82 <+98>:  callq  0x10f5ae3e4               ; symbol stub for: NSLog
    0x10f5ade87 <+103>: jmp    0x10f5ade8c               ; <+108> at ViewController.m
    0x10f5ade8c <+108>: movq   -0x48(%rbp), %rdi
    // 没有强引用指针能让对象存活 所以对象销毁
    0x10f5ade90 <+112>: callq  *0x217a(%rip)             ; (void *)0x00007fff20191530: objc_release
    0x10f5ade96 <+118>: leaq   -0x18(%rbp), %rdi
    
    // 对象被销毁之后 objc_destroyWeak被调用 销毁weak指针 weak设置为nil 
    0x10f5ade9a <+122>: callq  0x10f5ae41a               ; symbol stub for: objc_destroyWeak

正常的weak使用

    id __strong strongObj = [[NSObject alloc] init];
    id __weak weakObj = strongObj;

NSObject的实例已有强引用,再赋值给__weak修饰的变量就不会有警告了

__weak弱引用不影响对象的释放和废弃,若某对象被废弃,则此弱引用将自动失效且处于nil

- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[Person alloc] init];
    id __weak obj1 = obj;
    NSLog(@"---释放Person实例--");
    [obj release];
}

输出

(lldb) p obj1
(Person *) $0 = 0x0000600001ecc320
2021-12-02 22:54:46.242685+0800 05_内存[47385:318004] ---释放Person实例--
2021-12-02 22:54:48.929859+0800 05_内存[47385:318004] -[Person dealloc]
(lldb) po obj1 
 nil
(lldb) 

循环引用

我们经常使用weak解决循环引用,但是在block内部不允许使用弱指针—>访问成员变量

代码如下

@interface Person : NSObject {
    @public NSString *_name;
}
@property(nonatomic, copy) void(^block)(void);
@end

@implementation Person
- (void)test {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        // 报错 error  Use of undeclared identifier 'strongSelf'
        NSLog(@"-------%@", strongSelf->_name);
    };
}

- (void)dealloc {
    NSLog(@"%s",__func__);
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *yang = [[Person alloc] init];
    [yang test];
}

输出

// 报错
Use of undeclared identifier 'strongSelf'

我们可以加上__strong

@interface Person : NSObject {
    @public NSString *_name;
}
@property(nonatomic, copy) void(^block)(void);
@end

@implementation Person
- (void)test {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        // ARC下不允许使用弱指针—>访问成员变量  需要加上 __strong
        __strong typeof(weakSelf) strongSelf = weakSelf;
        // 加上之后可以正常访问了  其实就是骗编译器通过
        NSLog(@"-------%@", strongSelf->_name);
    };
}

- (void)dealloc {
    NSLog(@"%s",__func__);
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *yang = [[Person alloc] init];
    [yang test];
}
@end

Clang

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  // 内部还是 __weak 
  Person *const __weak weakSelf;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

内部还是 __weak

__unsafe_unretained

不安全且不会持有对象,附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象
对比__weak “不会持有对象” 这一特点使它和__weak的作用相似,可以防止循环引用
“不安全“ 这一特点是它和__weak的区别,那么它不安全在哪呢?

__unsafe

代码

// 注意一下代码崩溃 Thread 1: EXC_BAD_ACCESS
- (void)viewDidLoad {
    [super viewDidLoad];
    id __weak weakObj = nil;
    id __unsafe_unretained unsafeUnretainedObj = nil;
    {
        id __strong strongObj = [[NSObject alloc] init];
        weakObj = strongObj;
        unsafeUnretainedObj = strongObj;
        NSLog(@"strongObj:%@", strongObj);
        NSLog(@"weakObj:%@", weakObj);
        NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj);
    }
    // 出作用域NSObject实例释放  释放时遍历对象weak表 把weak类型指针变量设置为nil
    NSLog(@"-----obj dealloc-----");
    NSLog(@"weakObj:%@", weakObj);  // 访问nil
	// Crash 
    NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj);  // 访问不确定空间、释放地址
    // 如果你能确定NSObject对象一定存在 那么用unsafeUnretainedObj性能更快。不确定时候 使用weak
}

分析

  • 出作用域NSObject实例释放 释放时遍历对象weak表 把weak类型指针变量设置为nil
  • 访问不确定空间、释放地址,出现unsafeUnretainedObj野指针,程序Crash
  • 如果你能确定NSObject对象一定存在 那么用unsafeUnretainedObj性能更快。不确定时候 使用weak

问题

既然 __weak 更安全,那么为什么已经有了 __weak 还要保留 __unsafe_unretained ?

  • __weak仅在ARC中才能使用,而MRC只能使用__unsafe_unretained
  • __weak对性能会有一定的消耗,当一个对象dealloc时,需要遍历对象的weak表,把表里的所有weak指针变量值置为nil,指向对象的weak指针越多,性能消耗就越多。所以__unsafe_unretained比__weak快。当明确知道对象的生命周期时,选择__unsafe_unretained会有一些性能提升。
    比如,MyViewController 持有 MyView,MyView 需要调用 MyViewController 的接口。MyView 中就可以存储__unsafe_unretained MyViewController *_viewController

__autoreleasing

用附有 _autoreleasing修饰符的变量替代 autorelease 方法

二级指针类型的默认修饰符

__autoreleasing 是二级指针类型的默认修饰符

请添加图片描述
声明一个参数为NSError **的方法,但不指定其所有权修饰符, 调用该方法,发现智能提示中的参数NSError **附有__autoreleasing修饰符

只能修饰自动变量

__autoreleasing修饰符时,必须注意对象变量要为自动变量(局部变量 函数参数),否则编译不通过

请添加图片描述

属性修饰符

按照属性特质进行区分

原子性

atomic原子性访问
nonatomic非原子性访问

读/写权限

readwrite读写
readonly只读

内存管理

assign “纯量类型”
retain:“拥有关系”(owning relationship)
strong:“拥有关系”(owning relationship)
weak:  “非拥有关系”(nonowning relationship)
copy:  “拷贝”
unsafe_unretained:“不安全非拥有”

方法名

getter=XXX:指定“获取方法”的方法名
setter=XXX:指定“设置方法”的方法名

默认值

引用类型:@property (atomic,readWrite,strong) UIView *view;
基本数据类型:@property (atomic,readWrite,assign) int a;

对照表

assign:__unsafe_unretained

  • 他不能在对象被释放后自动将引用设置为nil 只能用于基本数据类型

retain:__strong

  • retain也会增加引用计数

strong:__strong

  • 可以用在ARC上对属性进行修饰,作为强引用

copy:__strong

  • copy的所有权也是__strong,所以也会进行强引用
  • 区别在于会重新开辟一个空间,指向该引用,新对象引用+1,原来的对象并不会引用+1

weak:__weak

  • 在ARC中使用,作为弱引用。

unsafe_unretained

  • 该引用不对对象保持强引用,并在对象被释放后不会置为nil

问题

用assign修饰“对象类型”(object type)会如何?

会报warning⚠️,当指向对象被释放掉后,再使用该属性会crash。

用strong/weak/copy 修饰“纯量类型”(scalar type)时会如何?

​会报Error❗️,这些修饰符只能用来修饰“对象类型”(object type)。

weak和assign的区别?

assign变量在指向变量释放后不会置为nil,再使用会crash。而weak会置为nil。

weak和strong的区别?

​当一个对象还有strong类型的指针指向时,不会被释放。若仅有weak类型的指针指向时,会被释放。

NSString和NSArray和NSDictionary 用copy还是strong修饰?

都属于“容器类型”(collection)的对象,用copy修饰表示不希望值跟随外部改变,用strong修饰会跟随指向内存地址的内存的改变而改变。
copy的所有权也是__strong,所以也会进行强引用
区别在于会重新开辟一个空间,指向该引用,新对象引用+1,原来的对象并不会引用+1

​NSMutableArray用copy修饰,会怎如何?

变成不可变数组,进行可变操作时会crash​崩溃的原因 看我这篇文章,深浅copy

​xib或storyboard拖的控件为什么是weak?

因为xib或storyboard对该控件已经有一个强引用了,而拖出来的属性应该跟这个控件保持相同的生命周期,所以应该用weak修饰。

autoreleasepool

自动释放池

创建

ARC下只能使用@autoreleasepool,用 @autoreleasepool 块替代 NSAutoreleasePool 类

@autoreleasepool { 
	id __autoreleasing obj = [[NSObject alloc] init];
}

MRC下使用autoreleasepool

@autoreleasepool {
    id obj = [[NSObject alloc] init];
    [obj autorelease];
}

MRC下使用NSAutoreleasePool

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

[pool release] 和 [pool drain]

释放NSAutoreleasePool对象,使用[pool release]与[pool drain]的区别
Objective-C 语言本身是支持 GC 机制的,但有平台局限性,iOS 开发用的是 RC 机制
在 iOS 的 RC 环境下[pool release]和[pool drain]效果一样,但在 GC 环境下drain会触发 GC 而release不做任何操作。使用[pool drain]更佳,一是它的功能对系统兼容性更强,二是这样可以跟普通对象的release区别开。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值