《Effective Objective-C》阅读笔记(下)

目录

内存管理

理解引用计数

引用计数工作原理

自动释放池

保留环

以ARC简化引用计数

使用ARC时必须遵循的方法命名规则

变量的内存管理语义

ARC如何清理实例变量

在dealloc方法中只释放引用并解除监听

编写“异常安全代码”时留意内存管理问题

以弱引用避免保留环

以“自动释放池块”降低内存峰值

用“僵尸对象”调试内存管理问题

不要使用retainCount

块与大中枢派发

理解“块”这一概念

块的基础知识

块的内部结构

全局块、栈块及堆块

为常用的块类型创建typedef

用handler块降低代码分散程度

用块引用其所属对象时不要出现保留环

多用派发队列,少用同步锁

​编辑

多用GCD,少用performSelector系列方法

掌握GCD及操作队列的使用时机

通过Dispatch Group机制,根据系统资源状况来执行任务

使用dispatch_once来执行只需运行一次的线程安全代码

不要使用dispatch_get_current_queue

系统框架

熟悉系统框架

多用枚举块,少用for循环

对自定义其内存管理语义的collection使用无缝桥接

构建缓存时选用NSCache而非NSdictionary

精简initiakize与load的实现代码

别忘了NSTimer会保留其目标对象


内存管理

理解引用计数

OC使用引用计数来管理内存。

引用计数工作原理

NSObject协议声明了下面三个方法来操作计数器,以递增或递减其值:

retain 递增保留计数

release 递减保留计数

autorelease 待清理“自动释放池”,再递减保留计数

对象被创建后保留计数的变化过程如下图所示:

一般调用完release后都会清空指针,这就能保证不会出现可能指向无效对象的指针。

自动释放池

有时可以不调用release,改为调用autorelease,此方法会在稍后递减计数,通常是下一次“事件循环”时递减。

使用自动释放池autorelease可以延长对象生命期,使其在跨越方法调用边界后依然可以存活一段时间。

保留环

引用计数经常要注意的一个问题是“保留环”,“保留环”会导致内存泄漏,因为循环中的对象保留计数不会降为0,通常采用“若引用”来解决这一问题。

以ARC简化引用计数

ARC,即自动引用计数,所做的事情就是自动管理引用计数。由于ARC自动执行retain、release、autorelease等操作,所以不能调用上述方法

ARC在调用这些方法时,并不通过消息派发机制,而是直接调用其底层C语言版本,这样性能更好。

使用ARC时必须遵循的方法命名规则

若方法名以下列词语开头,则其返回的对象归调用者所有

若方法名不以上述四个词语开头,则表示其所返回的对象不归调用者所有。

变量的内存管理语义

在应用程序中,可用下列修饰符来改变局部变量与实例变量的语义:

通过__weak局部变量可以打破由块所引入的“保留环”。

ARC如何清理实例变量

ARC会借助OC特性来生成清理例程,来自动清理OC对象,所以ARC环境下通常无需再编写dealloc方法

不过如果有非OC的对象,那么仍然需要清理,dealloc方法可以这样写:

在dealloc方法中只释放引用并解除监听

对象在经历其生命期后,最终会为系统所回收,这时就要执行dealloc方法了。dealloc方法中要做的主要就是释放对象所拥有的引用。如果对象拥有其他非OC对象,那就要手工释放。

dealloc方法中还要把配置的观测行为清理掉,比如用NSNotificationCenter订阅的通知,dealloc可以这样写:

有一些开销较大或系统内稀缺的资源不在dealloc中释放,比如文件描述符、套接字、大块内存等。通常的做法是:实现另一个方法,当对象使用完后,就调用此方法。

这种对象所属的类,接口可以这样写:

#import <Foudation/Foudation.h>
@interface EOCServerConnection : NSObject
- (void)open:(NSString*)address;
- (void)close;
@end

该类与开发者的约定是:想打开连接,就调用“open:”方法,连接完毕就调用close方法。

close与dealloc方法可以这样写:

#import <Foudation/Foudation.h>
@interface EOCServerConnection : NSObject
- (void)open:(NSString*)address;
- (void)close;
@end

还要注意:dealloc中不要随便调用其他方法,执行异步任务的方法不应在dealloc里调用,只能在正常状态下执行的方法不应在dealloc里调用,属性的存取方法不应在dealloc里调用。

编写“异常安全代码”时留意内存管理问题

在try块中,如果先保留了某个对象,在释放它以前又抛出了异常,那么除非catch块能处理此问题,否则对象所占内存就将泄漏。

在手动管理引用计数时,要使用@finally块,并在块中释放对象,这样无论是否抛出异常,其中的代码都保证会运行且只运行一次。

当自动管理引用计数时,需要打开-fobjc-arc-exceptions标志,该标志默认关闭,只有在打开时,才会自动管理异常的引用计数,不过降低运行效率。

当遇到大量异常捕获操作时,就要考虑重构代码,用NSError式错误信息传递法来取代异常。

以弱引用避免保留环

最简单的保留环就是两个对象互相引用对方。

保留环会导致内存泄漏

如果只剩一个引用还指向保留环中的实例,而现在又把这个引用移除,那么整个保留环就泄漏了。

避免保留环最佳方式是弱引用,这种引用经常用来表示“非拥有关系”,将属性声明为unsafe_unretained或weak即可。

两者都会对属性弱引用,区别只是对象回收后的行为:

以“自动释放池块”降低内存峰值

嵌套自动释放池,可以控制应用的内存峰值,使其不致过高。

比如这段代码:

如果方法创建临时对象,那这些对象即便不再使用,也依然存活,直到系统将其释放并回收,意味着所有临时对象都要等for循环执行完才会释放。这会导致程序所占内存量持续上涨。

当循环长度取决于用户输入时更如此,比如:

这时,增加一个自动释放池,把循环内的代码包裹在“自动释放池块”中,那么循环中自动释放的对象就会放在这个池,内存峰值就会降低

用“僵尸对象”调试内存管理问题

Cocoa提供了“僵尸对象”这一功能,当功能启用后,系统会把所有已经回收的实例转化为“僵尸对象”,而不会真正回收它们。这种对象内存不可能遭到覆写,并且收到消息后会抛出异常,准确说明发送过来的消息,并描述回收之前的那个对象。僵尸对象是调试内存管理问题的最佳方式。

将NSZombieEnabled环境变量设为YES,即可开启此功能。开启功能后,系统会修改对象的isa指针,指向特殊的僵尸类,从而使对象变为僵尸对象,他会相应所有选择子,响应方式为:打印一条包含信息内容及其接收者的消息,然后终止应用程序

不要使用retainCount

ARC中已经废弃了retainCount这一方法,而在不启用ARC时,也不应该使用这一方法。

这个方法之所以无用,原因在于:它所返回的保留计数只是某个给定时间点上的值。

块与大中枢派发

理解“块”这一概念

块可以实现闭包

块的基础知识

块与函数类似,只不过是直接定义在另一个函数里的,和定义它的函数共享同一个范围内的东西,用符号“^”来表示,后面跟一对花括号。

块类型的语法结构如下:

块的强大之处是:在声明它的范围内,所有变量都可以为其所捕获,但是默认情况下,不可以在块里修改。

如果声明时加上__block修饰符,这样就可以在块内修改了。

如果块捕获了对象类型,就会自动保留它,就会导致一个问题,块本身也具有引用计数,这样就很容易导致循环引用。

块的内部结构

栈的内存布局如图:

这里invoke变量是个函数指针,指向块的实现代码,descriptor是指向结构体的指针,结构体中声明了块对象的总体大小,还声明了copy与dispose两个辅助函数对应的函数指针。

全局块、栈块及堆块

定义块时,所占内存区域是分配在栈中的,离开相应范围后,栈内存可能被覆写。

将块对象发送copy消息就可以拷贝,把块从栈复制到堆。此后再调用copy,只会递增块对象引用计数

除了栈块和堆块,还有全局块,这种块不会捕捉任何状态,也不需要,块所使用的内存区域在编译器就已经确定了。这种块可以声明在全局内存中,不需要在栈中创建。

全局块的拷贝是个空操作,它实际上相当于单例。

为常用的块类型创建typedef

块类型的语法结构如下:

为了简化,可以使用typedef为块起一个已读的别名,比如这样:

现在可以直接使用新类型了。

当块作为参数时,也可以简化,比如:

简化后:

使用类型定义还有个好处,就是在打算重构块时,会很方便,只要修改类型定义处即可

用handler块降低代码分散程度

为用户界面编码时,经常异步执行任务,如果使用委托模式,那么处理数据的代码总是在受委托者处实现,如下图:

而如果改用块来写的话,代码就会变得更加清晰。

如此一来,代码就变得好懂许多了。

除此之外,委托模式还有一个缺点:如果类要分别使用多个获取器下载不同数据,那就得在delegate回调方法里根据传入参数来切换。

如果使用块,就可以将实现代码与委托者内联在一起。

在设计API时,如果用到了handler块,那就可以增加一个参数,使调用者可以通过此参数来决定应该把块安排在哪个队列上执行

用块引用其所属对象时不要出现保留环

使用块很容易导致“保留环”

有两种常见情况。第一种如下例:

当使用block捕获self时,就可能造成这种情况,这时应该等闭包执行完后,再打破保留环

另一种情况是互相引用,这时常见的方法是不保留引用,设定一套机制,令获取器对象自己设法保持存活。

多用派发队列,少用同步锁

在GCD出现之前,有两种办法来实现同步机制,第一种是“同步块”(@synchronized),第二种是直接使用NSLock对象。这两种方法都有其缺陷,替代方案就是GCD。

有种简单而高效的办法可以代替同步块或锁对象,那就是使用“串行同步队列”。GCD在底层实现。可以做许多优化。

比如属性的存取:

将属性的访问操作都放在串行队列中,就可以实现同步。

如果把设置方法改为异步派发,就可以进一步优化

然而,获取方法与设置方法不能并发执行,但多个获取方法可以并发执行。将并发队列与栅栏块结合,可以进一步优化。

在队列中,栅栏块必须单独执行,不能与其他块并行。

实现代码也很简单:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
- (NSString*)someString {__block NSString *localSomeString;dispatch_sync(_syncQueue, ^{localSomeString = _someString;});return localSomeString;
}
​
- (void)setSomeString:(NSString*)someString{dispatch_barrier_async(_syncQueue, ^{_someString = someString;});
}

多用GCD,少用performSelector系列方法

使用performSelector方法,可以动态绑定消息,如下图:

但是这种方法不仅可能导致内存泄漏,还很难传达一些非对象类型参数。

避免这些限制和缺陷,方法就是使用块,而perform方法提供的那些线程功能,都可以通过在大中枢派发机制中使用块来执行,延后执行可以用dispatch_after来实现,在另一个线城上执行任务可以通过dispatch_sync及dispatch_async来实现

掌握GCD及操作队列的使用时机

有时使用NSOperationQueue要比使用GCD技术要更合适。

用NSOperationQueue类的“addOperationWithBlock:”方法搭配NSBlockOperation类使用操作队列,语法与GCD非常类似,但使用下来有以下好处:

通过Dispatch Group机制,根据系统资源状况来执行任务

Dispatch group是GCD的一项特性,能够把任务分组。

与dispatch group相关的有以下几个函数:

dispatch_group_tdispatch_group_create// 创建分组
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_bloack_t block);//管理分组
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);//管理分组

有两个方法可以用于等待dispatch group执行完毕:

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

二者的区别是:前者会阻塞当前线程,而后者只会在完成任务后进行通知。

未必一定要使用dispatch group,使用异步派发加单个队列也可以实现该效果。然而,使用dispatch group,GCD会根据系统资源状况来调度这些任务。若不采用group,则不仅性能有所下降,还需要额外代码来防止死锁

使用dispatch_once来执行只需运行一次的线程安全代码

在GCD中,有一种很好的方式实现单例,采用以下函数:

void dispatch_once(dispatch_once_t *token, dispatch_block_t block);

此函数接受一个参数,只要这个参数相同,那么这个块里的代码便只执行一次。

可以这样实现单例:

这个参数应该声明在static或global作用域中。

不要使用dispatch_get_current_queue

dispatch_get_current_queue这个函数已经被弃用了。使用这个函数时,容易造成死锁。并且这个函数的行为也总是与预计不符。因为队列存在一套层级体系。

所以使用该函数检查当前队列是否为执行同步派发所用的队列,并不总是奏效。这个函数所返回的不一定是API指定的那个,有可能是API内部的那个同步队列。

为了解决这个问题,最好的办法是通过GCD提供的功能来设定“队列特有数据”,通过队列特有数据来解决不可重入导致的死锁。

系统框架

熟悉系统框架

许多系统框架可以直接使用,最重要的是Foundation与CoreFoundation。

许多常见任务都能用框架来做,例如音频与视频处理、网络通信、数据管理等。

多用枚举块,少用for循环

遍历collection有四种方式,最基本的是for循环,其次是NSEnumerator类和for...in...快速便利法,最新、最先进的是“块枚举法”。

以数组为例,NSArray中定义了这个方法

- (void)enumerateObjectsUsingBlock:(void(^)id object, NSUInteger idx, BOOL *stop)block

通过这种方法遍历collection,可以直接从块里获取更多信息。遍历数组时可以知道当前下标,遍历字典可以同时获取键值,还可以修改块的方法签名以免进行类型转换操作

对自定义其内存管理语义的collection使用无缝桥接

使用无缝桥接技术,可以在Foudation框架中的OC对象与CoreFoudation框架中的C语言数据结构之间来回转换。

使用无缝桥接,可以将collection转换为具备特殊内存管理语义的collection。

比如字典的键为“拷贝”,而值为“保留”,这时就只能使用无缝桥接来改变语义。

CF框架中的可变字典叫CFMutableDictionary,可通过下列方法来制定键和值的内存管理语义:

构建缓存时选用NSCache而非NSdictionary

构建缓存时,应选用NSCache而非NSdictionary。

NSCache的优势在于:当系统资源快要耗尽时,它可以自动删减缓存

NSCache对象可以设置上限来限制缓存中的对象总个数和成本。

NSCache经常与NSData和NSPurgeableData搭配使用。将NSPurgeableData与NSCache搭配使用,可以实现自动清除数据的功能。

精简initiakize与load的实现代码

类的初始化有两个方法,分别是load和initialize

load方法的问题在于,执行时系统处于“脆弱状态”,在执行时必然执行超类的load方法,如果依赖程序库,那库里所有相关类的load方法必定会先执行,但是根据某个库,无法判定各个类的载入顺序。因此,在load方法里使用其他类是不安全的。

load方法务必实现精简一些,因为整个程序在执行load时都会阻塞。

initialize方法会在程序首次调用该类之前调用且只调用一次。它与load不同之处在于:

1.initiakize为惰性调用

2.执行initialize时,系统处于正常状态,也能确保一定是“线程安全的环境”中执行。

3.如果某个类为实现initialize,就会运行超类的实现代码。

initialize方法在实现时也尽量精简。

别忘了NSTimer会保留其目标对象

使用重复执行模式的计时器,很容易引入“保留环”

如果创建本类的实例,并调用startPolling方法,创建计时器时,由于目标对象是self,所以要保留此实例。然而,实例也保留了计时器。因此就产生了保留环。

这时可以拓展NSTimer的功能,用“块”来打破保留环。不过需要创建分类,将实现代码加入分类中。

- (void)startPolling{__weak EOCClass *weakSelf = self;_pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block: ^{EOCClass *strongSelf = weakSelf;[strongSelf p_doPoll];} repeats:YES];
}

这段代码先定义了一个弱引用,令其指向self,然后捕获这个引用,而不是原本的self变量,这样self就不会被计时器所保留块开始执行时,立即生成strong引用,以保证实例在执行期间持续存活。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/26279.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一、对iic类模块分析与使用

bmp280驱动代码 说明&#xff1a; 1、该模块用于获取气压&#xff0c;温度&#xff0c;海拔等数据。 vcc&#xff0c;gnd接电源 sda &#xff0c;scl 接iic通信引脚 2、该模块使用iic通信&#xff0c;通过iic发送请求相关类的寄存器值&#xff0c;芯片获取对应寄存器返回的数据…

辛格迪客户案例 | 祐儿医药科技GMP培训管理(TMS)项目

01 项目背景&#xff1a;顺应行业趋势&#xff0c;弥补管理短板 随着医药科技行业的快速发展&#xff0c;相关法规和标准不断更新&#xff0c;对企业的质量管理和人员培训提出了更高要求。祐儿医药科技有限公司&#xff08;以下简称“祐儿医药”&#xff09;作为一家专注于创新…

汽车低频发射天线介绍

汽车低频PKE天线是基于RFID技术的深度研究及产品开发应用的一种天线&#xff0c;在汽车的智能系统中发挥着重要作用&#xff0c;以下是关于它的详细介绍&#xff1a; 移动管家PKE低频天线结构与原理 结构&#xff1a;产品一般由一个高Q值磁棒天线和一个高压电容组成&#xff…

Java 大视界 -- Java 大数据分布式文件系统的性能调优实战(101)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

全国普通高等学校名单

全国普通高等学校名单 全国普通高等院校&#xff0c;简称“高校”&#xff0c;是指那些提供高等教育的学校&#xff0c;涵盖了大学、独立学院、高等专科学校以及高等职业学校等多种类型。这些机构通过国家普通高等教育招生考试&#xff0c;主要招收高中毕业生&#xff0c;并为…

Vue.js 学习笔记

文章目录 前言一、Vue.js 基础概念1.1 Vue.js 简介1.2 Vue.js 的特点1.3 Vue.js 基础示例 二、Vue.js 常用指令2.1 双向数据绑定&#xff08;v-model&#xff09;2.2 条件渲染&#xff08;v-if 和 v-show&#xff09;2.3 列表渲染&#xff08;v-for&#xff09;2.4 事件处理&am…

【Python】基础语法三

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解Python的函数、列表和数组。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff…

传奇3光通版手游行会战攻略:团队协作与战术布局详解

戳一戳&#xff1b;了解更多 在《传奇3光通版》手游中&#xff0c;行会战是玩家们展现团队协作与战术布局的重要舞台。下面&#xff0c;我们就来详细解析一下行会战中的团队协作与战术布局攻略。 一、团队协作 ​职业搭配 在行会战中&#xff0c;合理的职业搭配至关重要。一般…

unity学习56:旧版legacy和新版TMP文本输入框 InputField学习

目录 1 旧版文本输入框 legacy InputField 1.1 新建一个文本输入框 1.2 InputField 的子物体构成 1.3 input field的的component 1.4 input Field的属性 2 过渡 transition 3 控件导航 navigation 4 占位文本 placeholder 5 文本 text 5.1 文本内容&#xff0c;用户…

99分巧克力

99分巧克力 ⭐️难度&#xff1a;中等 &#x1f31f;考点&#xff1a;二分 2017省赛真题 &#x1f4d6; &#x1f4da; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();i…

Python 基础知识全面总结

Python 是一种广泛应用的编程语言&#xff0c;具有简洁、易读、功能强大等特点。本文将对 Python 的基础知识进行全面梳理&#xff0c;涵盖从入门必备知识到各类模块和编程概念等内容。 一、Python基础语法 &#xff08;一&#xff09;标识符 定义&#xff1a;用于给变量、函…

7.1.1 计算机网络的组成

文章目录 物理组成功能组成工作方式完整导图 物理组成 计算机网络是将分布在不同地域的计算机组织成系统&#xff0c;便于相互之间资源共享、传递信息。 计算机网络的物理组成包括硬件和软件。硬件中包含主机、前端处理器、连接设备、通信线路。软件中包含协议和应用软件。 功…

领域驱动设计:事件溯源架构简介

概述 事件溯源架构通常由3种应用设计模式组成,分别是:事件驱动(Event Driven),事件溯源(Event Source)、CQRS(读写分离)。这三种应用设计模式常见于领域驱动设计(DDD)中,但它们本身是一种应用设计的思想,不仅仅局限于DDD,每一种模式都可以单独拿出来使用。 E…

【AD】3-10 原理图PDF导出

文件—智能PDF 多页原理图导出 导出设置时选择工程&#xff0c;可自行选择导出一页或多页原理图&#xff0c;一般PCB不用导出

半导体制造工艺(二)光刻工艺—掩模版

在上文中我们已经简单概述了光刻工艺的大致流程。接下来将会介绍在光刻工艺中所需用到的必备材料以及设备。例如掩模版、光刻胶、匀胶机、光刻机等等。由于需要保持讲述工艺的完整性以及流畅&#xff0c;每一个都需要涉及&#xff0c;所以每次仅是侧重点不同。此篇主要讲述的是…

ubuntu服务器安装VASP.6.4.3

ubuntu服务器安装VASP.6.4.3 1 安装Intel OneAPI Base Toolkit和Intel OneAPI HPC Toolkit1.1 更新并安装环境变量1.2 下载Intel OneAPI Base Toolkit和Intel OneAPI HPC Toolkit安装包1.3 安装 Intel OneAPI Base Toolkit1.4 安装 Intel OneAPI HPC Toolkit1.5 添加并激活环境…

Autosar RTE配置-Port Update配置及使用-基于ETAS工具

文章目录 前言Autosar Rte中enableUpdate参数定义ETAS工具中的配置生成代码分析总结前言 在E2E校验中,需要对Counter进行自增,但每个报文周期不一样,导致自增的周期不一样。且Counter应该在收到报文之后才进行自增。基于这些需求,本文介绍使用RTE Port中的参数enableUpdat…

文字滚动效果组件和按钮组件

今天和大家分享一个vue中好用的组件&#xff0c;是我自己写的&#xff0c;大家也可以自己改&#xff0c;就是文字的循环滚动效果&#xff0c;如下图&#xff0c;文字会向左移动&#xff0c;结束之后也会有一个循环&#xff0c;还有一个按钮组件&#xff0c;基本框架写的差不多了…

VMWare虚拟机Ubuntu Desktop怎么共享文件夹

1、虚拟机设置 在菜单"管理"里面找到"虚拟机设置"菜单&#xff0c;然后从"硬件"切换到"选项"&#xff0c;点到"共享文件夹"&#xff0c;然后在右侧选中"总是启用"&#xff0c;然后添加一个共享文件夹。 2、在我的…

JAVA安全—手搓内存马

前言 最近在学这个内存马&#xff0c;就做一个记录&#xff0c;说实话这个内存马还是有点难度的。 什么是内存马 首先什么是内存马呢&#xff0c;顾名思义就是把木马打进内存中。传统的webshell一旦把文件删除就断开连接了&#xff0c;而Java内存马则不同&#xff0c;它将恶…