SwiftUI:硬件

原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、存储 Memory
    • 1、设备自身的存储方式
    • 2、代码在内存中的存储
    • 3、区分 Struct 与 Class 用法上的差异
  • 二、CPU 的运作方式
    • 1、中央处理器
    • 2、中央调度系统
  • Demo
  • 参考文献

一、存储 Memory

1、设备自身的存储方式

电子设备,无论是身形较小的 Apple Watch、主流的 iPhone、还是大屏的 Mac, 都具备两种硬件来存储信息,这两种硬件一个叫做硬盘、另一个叫做内存。如下图所示,硬盘负责永久存储信息,内存用于临时存储信息。

一般来说,当用户下载你的应用程序后,应用程序中所包含的所有代码、设计素材等都会被存放在设备的硬盘中。这些信息不会因为设备开关机而丢失,所以硬盘这种存储介质也被大家称作永久存储 Permanent Storage

与之相对应的,内存中所存放的信息,会在设备关机后彻底丢失,因此也被叫做临时存储 Temporary Storage。与用户硬盘的的较大空间不同,内存的空间则相对较小,以 iPhone 12 Pro 为例,其硬盘的大小可能为 512 GB,而内存则仅有 6 GB。

内存的空间有限,因此仅被用来临时存放正在运行的应用。比如你的应用叫「睡眠助手」,只有当用户启动该应用时,系统才会将「睡眠助手」应用的全部信息从硬盘挪到内存中。若用户同时在几个应用程序中来回切换,操作系统会尽可能地让这些应用都放在内存中。在察觉到内存即将耗尽时,操作系统会自动将用户最早使用的应用移出内存。


2、代码在内存中的存储

进一步讲,我们学习的所有 Swift 代码,比如整数 Int、浮点数 Double、类 Class、数组 Array、结构 Struct 等,实际都运行在设备的内存中。你可以将内存想象为线性排列且数量众多的小格子,我们所编写的代码便存储在格子中。

对于Swift 中的绝大多数类型,如 IntDoubleBoolEnumStructArrayDictionary,其使用内存的方式都很直接,就是直接将信息存储在下图的小格子中。对于这些类型的常量或变量来说,其指代便是内存中的信息本身,因此以上类型也被称作值类型 Value Type。比如下图中的常量 number,实际存储的信息便是内存格子中的值 12345。

值类型

然而对于某些特殊的类型,如 Class 的常量或变量,它使用小格子的方法有些区别。假如我们把内存中的小格子当作一栋公寓楼并为其标上门牌号 A - JClass 的常量或变量存储的不是小格子中的信息的值,而是信息所对应值的门牌号,因此 Class 也被称为引用类型 Reference Type。比如下图中的类实体 civicCar,当把值 Car(brand: "Honda") 放在小格子中后,civicCar 存储的信息实际上是门牌号 G

引用类型

总的来说,在 Swift 编程语言中,你看到的绝大多数类型都是值类型。如 Struct 的实体实际存储的是内存中对应的值。与之相反,引用类型如 Class,其实体所存储的是内存中值所对应的门牌号。


3、区分 Struct 与 Class 用法上的差异

上文中,你了解到了 structclass 分别作为值类型和引用类型在存储逻辑上的差异。本小节中,我将使用一个车辆颜色的案例,让你看看这两种存储方式在实际使用中的差异。

如下所示,用 structclass 分别创建新类型车辆颜色 CarColor,其只存储一个字符串颜色 color。为帮助你区分二者,我在命名上标注出了其归属,其中 struct 创建的新类型叫做 CarColorByStructclass 创建的新类型叫做 CarColorByClassclass 要求我们必须提供初始化器,因此下图使用 init(color:) 写明,而 struct 默认自带这个初始化器,因此没有写。总的来说,下面的两个类型除 structclass 关键词不同外完全一致。

struct CarColorByStruct
{
    var color: String
}

class CarColorByClass
{
    var color: String
    
    init(color: String) { self.color = color}
}
Struct 版本

首先我们先来看值类型 struct 实体的表现。如下所示,创建了一个新实体 carOne,并将其颜色赋值为黑色。之后创建一个新变量 carTwo,并将 carOne 的值赋给 carTwo。最后将 carTwo 的颜色更改为白色 carTwo.color = "白色",因 carTwocarOne 这两个变量分别代表内存中的两个格子,互不干扰,因此修改完 carTwo 颜色为白色后,carOne 的颜色仍旧为黑色,没有被改变。

// carOne 赋值为黑色
var carOne = CarColorByStruct(color: "黑色")
print("carOne 所具备的颜色是: \(carOne.color)")

// 将 carOne 赋值给 carTwo,并修改 carTwo 颜色
var carTwo = carOne
carTwo.color = "白色"

// 修改后,carOne 颜色没有被改变,仍为黑色
print("carOne 所具备的颜色是: \(carOne.color)")

输出结果为:

carOne 所具备的颜色是: 黑色
carOne 所具备的颜色是: 黑色

用内存的格子来阐释上述 struct 代码的过程如下图所示。第一步创建了 carOne,其格子中的值为黑色。第二步将 carOne 的值赋给新变量 carTwocarTwo 拥有了自己的格子,值与 carOne 相同。第三步将 carTwo 的颜色修改,对 carOne 没有影响。

Struct 版本
Class 版本

与上文中完全相同的流程,若采用引用类型的 class 实体,效果如下。在下面中,创建了一个新实体 carThree,并将其颜色设置为黑色。然后将 carThree 的值赋值给 carFour,仅修改 carFour 的颜色为白色后,你会发现 carThree 的颜色也被修改。这是因为 carThreecarFour 存储的是内存中的同一个门牌号。

// carThree 赋值为黑色
var carThree = CarColorByClass(color: "黑色")
print("carThree 所具备的颜色是: \(carThree.color)")

// 将 carFour 赋值给 carThree,并修改 carFour 颜色
var carFour = carThree
carFour.color = "白色"

// 修改后,carThree 被改变,变为白色
print("carThree 所具备的颜色是: \(carThree.color)")

输出结果为:

carThree 所具备的颜色是: 黑色
carThree 所具备的颜色是: 白色

用内存的格子来阐释上述 class 代码的过程如下图所示。第一步创建了 carThree,其格子中放置了颜色为黑色。第二步将 carThree 赋值给新变量 carFour,此时被赋值的并非 carThree 的值,而是 carThree 所指代的门牌号 E。第三步将 carFour 的颜色修改,carThree 的值也同时发生改变。这是因为本质上来说,carThreecarFour 都指的是门牌号 E,修改的是门牌号 E 对应的同一个格子的内容。

Class 版本

上文所述的门牌号,用计算机学科的专业术语来讲,叫做指针 Pointer。鉴于引用类型对比指针的特殊性,Swift 专门给了特殊的运算符用于对比指针。在 Swift 语言中,符号 ==!= 用于判定运算符两边的值是否相同,符号 ===!== 用于判断运算符两边的指针是否相同。如下所示,carThreecarFour 指针的指向位置相同。

print("是否指向同一个门牌号:\(carThree === carFour)")

输出结果为:

是否指向同一个门牌号:true

什么时候选用 struct 或 class?

对于 Apple 官方框架来说,适用于反复使用的框架一般定义为 class。如 Message UI 框架,其用途是提供一个发送短信或邮件的窗口,定义为 class 的好处便是整个应用程序中只需要使用一个该 class 的实体 instance,程序中用到的所有实体 instance 都可以指向该门牌号,从而避免重复占用过多内存而导致资源浪费。

Message UI

同样是 Apple 的官方框架,不适合反复使用的实体常被定义为 struct,如 SwiftUI 框架的文本 Text。对于 UI 视图Text 来说,应用界面中的文本很少相同,且不存在一个文本继承另一个文本的情况,无需用到继承的属性,因此定义为 struct 更合理。

在书写你的应用的过程中,Apple 官方文档的建议是当需要创建一个新的自定义类别时,可以先将其定义为 struct。只有你需要用到 class 继承的特性,或者是作为引用类型的特性时,再将其关键词换为 class


二、CPU 的运作方式

1、中央处理器

CPU 也叫中央处理器,是 A 系列芯片 Soc 上负责逻辑运算的重要硬件,我们写的代码主要通过设备中的 CPU 来运行。设备中的 CPU 通常又进一步分为性能核心和低功耗核心,操作系统会根据应用的实际需求来自动决定其运行场所,以达到最优的能耗表现。以下图中 iPhone 12 上的 A14 芯片为例,它包含 2 个高性能核心,以及 4 个低功耗核心。

A14 芯片

2、中央调度系统

你可以将我们的应用程序想象成无数个需要完成的子任务,这些任务由操作系统的工作人员,也叫做 GCD 来负责分配。GCD 的全称是 Grand Central Dispatch,翻译过来是中央调度系统,其任务便是将代码自动在恰当的时机分配给 CPU 中的不同核心来处理。

通常来说,为避免打乱应用程序的运行逻辑,GCD 会让所有代码在按其编写的先后顺序 Serial 运行。虽然没有明说,但目前为止,我们所写的所有代码都会被 GCD 运行在「主队列 main」中。其使用方法是 DispatchQueue.main.async {},如下所示。

DispatchQueue.main.async
{
    print("默认情况下,即使不写明,所有代码都运行在这里")
}

将以上信息用图表的方式展示出来,主队列有点像银行排着的长队,队伍中的每个人分别肩负着不同任务,这些任务可能是你的一个个函数,也可能是等着要求 CPU 将其存放在内存中的常量与变量等等。随着时间的推移,GCD 这个工作人员会让队伍中的每个人依次在 CPU 那里办理业务。

主队列

这种方法看似非常完美,但还存在一些问题。依旧以银行为例,有时候会有顾客办一个特别冗长的贷款业务,这个业务可能一办就是几个小时,以至于后面排队的人都被它堵在后面了。比如下图中的序号 5,办理的就是这种贷款业务,把后面要办理业务的 6,7,8,9 全都堵住了。

特别冗长的贷款业务

一个人办复杂业务卡住所有人的情况在应用程序中也时有发生。比如你在制作一款图片处理应用,你想给图片加上一个复杂的滤镜,而滤镜运算需要时间,因此就会堵住后面的人办事。比如你在制作一款需要读取网站文章的客户端,发送网络请求的过程可能会由于网速过慢,而等待很久才能完成。

更可怕的是,应用代码中的所有 UI 代码也在主队列中运行,如果主队列被某个任务堵住了,应用程序就好像卡死了一样。这种卡死并非是你的应用罢工了,而是某个任务运行太慢而卡住了主队列,导致后续 UI 代码无法运行。然而用户无法得知你的应用程序是真卡死了还是只是在运行较慢的任务,若他们看见应用程序按钮没反应了,就会下意识地认为它坏了。

为解决某个复杂业务堵住主队列的问题,GCD 给出的方法是加开银行窗口。这种加开窗口的办法与顺序 Serial 运行相反,也叫做并发运行 Concurrent,它指的是在你的明确指示下,让复杂任务到别的窗口运行,不要卡在主队伍中。比如让办理贷款业务的客户去专门的大客户柜台办理。在 Swift 中,默认提供的并发队列叫做全局队列 Global。如下图中的复杂任务 A 和复杂任务 B,分别交给了两个新窗口「全局队列」负责处理。

把任务从主队列分配给全局队列很可能只是因为它需要的时间更长而已,不一定意味着该任务不着急执行。就像银行中提供加急业务一样,我们不能只是单纯地把任务分配出去,还需要考虑任务的重要性。负责告知全局队列中任务优先级的参数是 Quality of Service,简称 QOS。全局队列的优先级 QOS 共有 4 种,如下图所示。

QOS

QOS 优先级从最着急要到慢慢来的排序下:.userInteractive (UI).userInitiated (用户发起的任务).utility (杂项).background (后台)

如果你希望分配到全局队列的任务与 UI 相关,应将 QOS 设置为 UI 级,也就是 userInteractive。此时 GCD 会将任务分配到高性能核心上尽快完成。反之若该项任务不急着要,比如只是在例行备份一些资料,也可以把 QOS 设置为后台级 .background 慢慢备份,执行代码如下。这时候 GCD 分工时就会看到开发者不急着要这里面的结果,因此若需要省电时则这部分内容可以放在低功耗核心上运算。若有其它更紧要的任务则可以把低优先级的任务先缓一缓,紧着最着急的来。

DispatchQueue.global(qos: .background).async
{
    print("执行复杂任务")
}

Demo

Demo在我的Github上,欢迎下载。
SwiftUIDemo

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容