Objective-C高级编程读书笔记(一)

news/2024/7/20 13:21:32 标签: 移动开发, 内存管理, c/c++

自动引用计数

自动引用计数(ARC, Automatic Reference Counting), 是指内存管理中对引用采用自动计数的计数,让编译器来进行内存管理

内存管理/引用计数

思考方式

  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

这四种方式,对应成oc的方法的话,就是:

对象操作oc方法
生成并持有对象alloc/new/copy/mutableCopy等方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法

自己生成的对象,自己持有

使用以下名称的方法意味着自己生成的对象只有自己持有:alloc, new, copy, mutableCopy. e.g.:

id obj = [[NSObject alloc] init];
复制代码

NSObject通过alloc类方法自己生成并持有对象,并将指向此对象的指针赋值给变量obj(obj实际上是一个指针)

非自己生成的对象,自己也能持有

id obj = [NSMutableArray array];
复制代码

上面这段代码中,变量obj并不持有NSMutableArray生成的类对象(可以通过retain方法进行持有)

不在需要自己持有的对象时释放

释放采用release方法

id obj = [[NSObject alloc] init];
[obj release]; // 释放obj指向的对象,obj本身仍存在,但是其指向的对象已经被释放
复制代码

非自己持有的对象不能释放

alloc/retain/release/dealloc的实现

根据苹果的文档,可以看出alloc的实际实现是:

+ alloc
+ allocWithZone:
class_createInstance
calloc
复制代码

autorelease

具体使用方法:

  1. 生成并持有NSAutoreleasePool对象
  2. 调用已分配对象的autorelease实例方法
  3. 废弃NSAutoreleasePool对象

对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。需要注意的是,如果生成大量的autorelease对象,而NSAutoreleasePool对象又不废弃的话,大量对象得不到释放,就会出现内存不足的现象。

autorelease的实现如下:

class AutoreleasePoolPage {
  static inline void *push () {
    // 相当于生成或持有NSAutoreleasePool类对象
  }

  static inline void *pop() {
    // 相当于废弃NSAutoreleasePool类对象
    releaseAll();
  }

  static inline id autorelease(id obj) {
    // 相当于NSAutoreleasePool类的addObject类方法
    AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例
    autoreleasePoolPage->add(obj);
  }

  id *add(id obj) {
     // 将对象添加到内部数组
  }
  
  void releaseAll() {
    调用内部数组中对象的release实例方法
  }
}
复制代码

从源代码中我们可以看到,autorelease方法的实现是通过动态数组来进行自动释放池的对象的存储,在需要释放的时候废弃数组中的对象。

ARC规则

所有权修饰符

在ARC有效时,id类型的所有权修饰符有4种:__strong, __weak, __unsafe_unretained__autoreleasing

__strong

__strong是默认的修饰符。表示对对象的强引用,在其作用域被废弃的时候,随着强引用的失效,引用的对象会随之释放

__weak

__weak防止循环引用引起的内存泄漏。比方说

id obj1 = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
obj1.obj = obj2;
obj2.obj = obj1;

// or
obj1.obj = obj;
复制代码

上面代码中两个写法都会造成循环引用,因为两个对象之间有互相强引用,导致作用域被废弃时,两个对象之间得不到释放。通过__weak可避免这种情况。除此之外,在持有某对象的弱引用时,如果对象被废弃的话,弱引用将自动失效并且处于nil

__unsafe_unretained

__unsafe_unretained修饰的变量跟__strong__weak不同,是不属于编译器的内存管理对象。其变量既不持有强引用也不持有弱引用。

__autoreleasing

在ARC有效时,使用@autoreleasepool来替代NSAutoreleasePool类,用__autoreleasing替代autorelease方法。一般不会显式附加。对于非自己生成当持有的对象来说,编译器会自动检查方法名是否以alloc/new/copy/mutableCopy开头,如果不是的话会将对象注册到自动释放池当中。

使用__weak变量的时候,必定会访问到注册到自动释放池中的对象。

因为__weak只持有对象的弱引用,在访问引用对象的过程中,该对象随时有可能被废弃。但是只要将对象注册到自动释放池中的话,则能保证在作用域结束之前,对象一直存在。

可以使用_objc_autoreleasePoolPrint()方法调试自动释放池上的对象

规则

  • 不能使用retain/release/retainCount/autorelease: 内存管理是编译器的工作,没必要使用内存管理的方法
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理方法命名规则:alloc/new/copy/mutableCopy开头的方法在返回对象时,必须返回给调用方所应当持有的对象。
  • 不要显式调用dealloc
  • 使用@autoreleasepool替代NSAutoreleasePool
  • 不能使用NSZone:
  • 对象型变量不能作为C语言结构体的成员:即C语言中不能使用OC的对象
  • 显式转换idvoid *: 使用__bridge_retained__bridge_transfer(多用于OC对象和Core Foundation对象的转换)

数组

id __strong *array = nil;

id *类型默认为id __autoreleasing * 类型,因此需要显式指定为__strong。但是,这仅保证了__strong修饰的id类型变量被初始化为nil,并不保证id指针型变量被初始化为nil

ARC的实现

__strong

{
  id __strong obj = [[NSObject alloc] init];
}

// 编译器的模拟代码
id objc = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

{
  id __strong obj = [NSMutableArray array];
}

// 编译器的模拟代码
id objc = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj); // 用于最优化程序运行。用于自己持有对象的函数,但它返回的对象应为返回注册在自动释放池中的对象的方法或者是返回值
objc_release(obj);
复制代码

objc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue一般是配套使用的,通过这两个方法,可以获取到原本应注册到释放池中的对象,省略了注册和查找的步骤,实现优化处理。

__weak

  • 若使用__weak的变量所引用的对象被废弃的话,则将nil赋值给该变量
  • 使用__weak的变量,即是使用注册到autoreleasepool中的对象。
{
  id __weak obj1 = obj; // obj是__strong修饰的值
}

// 编译器模拟代码
id obj1;
objc_initWeak(&obj1, obj); // 初始化后调用objc_storeWeak函数
-> obj1 = 0; objc_storeWeak(&obj1, obj);
objc_destoryWeak(&obj1);

// 等价于
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
复制代码

objc_storeWeak将对象的地址作为key,将__weak修饰的变量地址作为value注册到weak表(一个哈希表,跟引用计数表相同)。

释放对象的步骤:

  1. objc_release
  2. 因为引用计数为0, 所以执行dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

在最后一个步骤中,会执行以下动作:

  1. 从weak表中获取废弃对象的地址为key的记录
  2. 将包含在记录中的所有__weak修饰的变量地址赋值为nil
  3. 从weak表中删除
  4. 从引用计数表中删除废弃对象地址为key的记录

由上可知,如果大量使用__weak修饰的变量的话,会消耗相应的CPU资源

下面验证下__weak修饰的变量即为自动释放池中的对象

{
  id __weak obj1 = obj;
  NSLog(@"%@", obj1);
}

// 编译器模拟代码
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1); // 取出__weak修饰符变量所引用的对象并retain
objc_autorelease(tmp); // 将对象注册到自动释放池
NSLog(@"%@", tmp);
objc_destoryWeak(&obj1);
复制代码

__autoreleasing

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

// 编译器模拟代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
复制代码

引用计数

使用_objc_rootRetainCount()方法来获取引用计数的数值(CFGetRetainCount((__bridge CFTypeRef)(obj))也可) 使用_objc_autoreleasePoolPrint()可打印自动释放池中的引用对象的状态:

// 需要通过extern声明
extern void _objc_autoreleasePoolPrint();
extern uintptr_t _objc_rootRetainCount(id obj);

int main(int argc, char * argv[]) {
    
    id obj = [[NSObject alloc] init];
    id obj1 = obj;
    NSLog(@"%ld", _objc_rootRetainCount(obj));
    
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        _objc_autoreleasePoolPrint();
        id __weak o = obj;
        NSLog(@"before using __weak: retain count = %d", _objc_rootRetainCount(obj));
        NSLog(@"class = %@", [o class]);
        NSLog(@"after using __weak: retain count = %d", _objc_rootRetainCount(obj));
        _objc_autoreleasePoolPrint();
    }
    return 0;
}
复制代码

输出如下:

2017-05-03 00:18:20.415 ObjectiveCDemo[7740:740389] 2
objc[7740]: ##############
objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0
objc[7740]: 0 releases pending.
objc[7740]: [0x1]  ................  PAGE (placeholder)
objc[7740]: [0x1]  ################  POOL (placeholder)
objc[7740]: ##############
2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] before using __weak: retain count = 1
2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] class = NSObject
2017-05-03 00:18:20.418 ObjectiveCDemo[7740:740389] after using __weak: retain count = 1
objc[7740]: ##############
objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0
objc[7740]: 1 releases pending.
objc[7740]: [0x7f82e2003000]  ................  PAGE  (hot) (cold)
objc[7740]: [0x7f82e2003038]  ################  POOL 0x7f82e2003038
objc[7740]: ##############

复制代码

转载于:https://juejin.im/post/5a30e2846fb9a0451a765ffe


http://www.niftyadmin.cn/n/1664002.html

相关文章

poj 2104 K-th Number 主席树+超级详细解释

poj 2104 K-th Number 主席树超级详细解释 传送门:K-th Number 题目大意:给出一段数列,让你求[L,R]区间内第几大的数字! 在这里先介绍一下主席树! 如果想了解什么是主席树,就先要知道线段树,主席…

【算法学习笔记】1:回溯法中子集树与排列树(装载/最大团/n皇后/旅行商)

之前一直分不清哪种递归属于回溯法,当然回溯法也不一定用递归来做,上了算法课有了一点感悟,记录一下。这四道题是算法的作业,在OJ上可以测试通过,感觉解空间这个概念真的是很帮助思考的一个东西。 解空间 解空间就是…

BZOJ - 2733: [HNOI2012]永无乡

传送门 对每个联通块建一棵线段树&#xff0c;并用并查集维护联通块的根。 先开始WA了几次&#xff0c;要注意每次操作都是对联通块的根进行的。 1 #include<cstdio>2 #include<iostream>3 using namespace std;4 inline char nc() {5 static char b[1<<…

【ML学习笔记】2:机器学习中的数学基础2(琴生不等式,概率公式,统计量)

琴生不等式 下凸函数的一个良好的性质就是满足琴生不等式&#xff0c;因为&#xff1a; 它的加权形式即琴生不等式&#xff1a; 如果将这些权都视为概率&#xff0c;它们加起来为1&#xff0c;那么还能写成数学期望的形式&#xff1a; f(E(x))<E(f(x)) 也就是说&am…

mac读写ntfs

2019独角兽企业重金招聘Python工程师标准>>> mountymacOS High Sierra上使用 ntfs-3g读取 NTFS 格式磁盘 Using-ntfs-3g-on-macOS-High-Sierrabrew cask install osxfuse brew install ntfs-3g挂载 yjd ntfs 格式硬盘&#xff0c;先在系统上弹出该硬盘&#xff0c;重…

【汇编学习笔记】5:多窗口协同输出

这次实验中使用了宏定义、BIOS功能调用等一些方便的功能。首先在一个大窗口中开出三个窗口&#xff0c;每次输入字符会在下面窗口和左边窗口中回显&#xff0c;按下->会调整到右边窗口&#xff0c;按下<-会调整到左边窗口。 当输出充满窗口一行时&#xff0c;光标调整至…

多媒体工具ffmpeg(音乐格式应用)

前言&#xff1a; 今天来了个新需求要将用户上传的高品质&#xff0c;无损格式的音乐转换为低码率的mp3格式。来达到试听时播放低品质mp3&#xff0c;下载时下载高品质的无损格式的目的。 目录&#xff1a; 安装ffmpeg再服务器中测试转码php中应用ffmpeg1. 安装ffmpeg FFmpeg是…

【ML学习笔记】3:机器学习中的数学基础3(特征值,特征向量,认识SVD)

矩阵乘以向量的几何意义 实际上也就是 所以&#xff0c;它还可以写成 那么把原来的矩阵按照列视图来看&#xff0c;也就是 而[x]和[y]作为1x1的矩阵&#xff0c;在刚刚那个式子里可以看成一个标量&#xff0c;也就变成了 所以矩阵乘以一个列向量&#xff0c;可以看…