0
点赞
收藏
分享

微信扫一扫

iOS摸鱼周报 第三十八期

迪莉娅1979 2022-03-15 阅读 35

iOS摸鱼周报 第三十八期_swift

本期概要


  • 话题:Xcode 13.2 和 iOS 15.2 的正式版值得关注的几项新功能。
  • Tips:内存相关的一些机制。
  • 面试模块:dealloc 在哪个线程执行;NSString *str = @"123" 这里的 str 和  "123" 分别存储在哪个区域。
  • 优秀博客:SwiftUI 相关的几篇优秀博客。
  • 学习资料:软件随想录,Joel Spolsky 的 Blog 纸质版文集。
  • 开发工具:Dropshelf,一款 Mac OS 下的拖拽效率应用。

本期话题

@zhangferry:12 月 13 号,Apple 发布了 Xcode 13.2 和 iOS 15.2 的正式版。其中有几项新功能值得关注。

Xcode 13.2[1]

编译系统和 Swift 编译器有了一个新模式可以充分利用 CPU 核心,以达到优化 Swift 项目的效果。该模式可选,可以执行如下命令打开:

defaults write com.apple.dt.XCBuild EnableSwiftBuildSystemIntegration 1

iOS 15.2[2]

是关于 StoreKit 的新特性:

  • StoreKit 中展示退款请求的弹窗可以在 Xcode 中进行测试了。UIKit 模式下可利用:​​beginRefundRequest(in:)​​​ 或者 ​​beginRefundRequest(for:in:)​​​ ,SwiftUI 下可利用 ​​refundRequestSheet(for:isPresented:onDismiss:)​​ 作为视图修饰器。

  • StoreKit 中管理订阅的弹窗也可以在 Xcode 中进行测试了。UIKit 模式下课利用 ​​showManageSubscriptions(in:)​​​ ,SwiftUI 下可利用  ​​manageSubscriptionsSheet(isPresented:)​​ 作为视图修饰器完成。
  • 新的 ​​SKTestSession.TimeRate​​ 值可用于 StoreKit Test 模块的自动化测试。

开发Tips

整理编辑:zhangferry

内存相关的一些机制

虚拟内存寻址

为了安全性,防止物理内存被篡写(还有其他很多优势),操作系统引入了虚拟内存机制,虚拟内存是对物理内存的映射,操作系统会为每个进程提供一个连续并且私有的虚拟内存空间。

实际的数据读写首先要通过虚拟地址找到对应的物理地址,这个过程就是 CPU 寻址,CPU 寻址由位于 CPU 的 MMU(Memory Management Unit 内存管理单元)负责。

为了便于管理,虚拟内存被分割为大小固定的虚拟页(Virtual Page, VP)。程序加载过程中,虚拟内存由磁盘到内存的映射是以页为单位进行处理的。每次映射完成都会对应一个关联的物理内存地址,为了管理这个映射关系出现了页表条目(Page Table Entry)PTE 的一个数据表。这个页表条目里有一个标记位比特位,0 表示还未加载到内存,1 表示已经加载到内存。当访问到 0 就出产生缺页(Page Fault),之后会填充数据到内存,并修改这个标记位。

这个 PTE 通常是位于高速缓存或者内存中的,但即使是高速缓存它相对于 CPU 的读取速度仍然是很慢的。后来又引入了后备缓冲器(Translation Lookaside Buffer,TLB),这个缓存器位于 CPU 内部,其存储了 PTE 内容。虽然它的存储空间较小,但因为在 CPU 内部访问很快,由局部性原理来说这个处理仍是非常值得的。

汇总一下寻址流程如下图所示:

iOS摸鱼周报 第三十八期_swift_02

内存不足的处理

在 Linux 中虚拟内存空间是大于实际物理内存地址的,这就会出现一个状况,当内存物理地址不够用时会发生什么?实际操作系统会将物理内存中的部分内容迁移到磁盘中,然后腾出地方给申请内存方使用,这个过程叫 Swap Out。当又要使用那部分内存时会触发 Swap Int,再移出部分内存,将需要的内容映射到空缺内存空间里。这个机制的好处是可以使用更大的内存地址,但坏处也很明显就是 Swap 会造成较大性能损耗。

iOS 处理机制

iOS 没有 Disk Swap 机制,因为其本身磁盘空间相对电脑是比较小的,而且频繁读取闪存会影响闪存寿命。基于此 iOS 设备可申请内存地址是有限度的,关于各个设备所能允许申请的最大内存,​Stack OverFlow[3]​ 有人做过测试:

device: (crash amount/total amount/percentage of total)

iPhone5: 645MB/1024MB/62%
iPhone5s: 646MB/1024MB/63%
iPhone6: 645MB/1024MB/62% (iOS 8.x)
iPhone6+: 645MB/1024MB/62% (iOS 8.x)
iPhone6s: 1396MB/2048MB/68% (iOS 9.2)
iPhone6s+: 1392MB/2048MB/68% (iOS 10.2.1)
iPhoneSE: 1395MB/2048MB/69% (iOS 9.3)
iPhone7: 1395/2048MB/68% (iOS 10.2)
iPhone7+: 2040MB/3072MB/66% (iOS 10.2.1)
iPhone8: 1364/1990MB/70% (iOS 12.1)
iPhone X: 1392/2785/50% (iOS 11.2.1)
iPhone XS: 2040/3754/54% (iOS 12.1)
iPhone XS Max: 2039/3735/55% (iOS 12.1)
iPhone XR: 1792/2813/63% (iOS 12.1)
iPhone 11: 2068/3844/54% (iOS 13.1.3)
iPhone 11 Pro Max: 2067/3740/55% (iOS 13.2.3)

以 iPhone 11 Pro Max 为例,应用可申请内存为 2067M,占系统内容的 55%,这已经是很高了。但即使这样,仍会出现内存过高的情况,iOS 系统的处理主要是清理 + 压缩这两个方案。

Clean Page & Dirty Page

iOS 将内存页分为 Clean Page 和 Dirty Page,Clean Page 一般是固定内容,可以被系统回收,需要时从磁盘再加载回来。

iOS摸鱼周报 第三十八期_ios_03

上图可以看出,写入数据前申请内存为 Clean 内存,使用的部分就变成了 Dirty 内存。

Compressed Memory

iOS 还有另一种机制是压缩内存(Compressed Memory),这也是一种 Swap 机制。举个例子,某个 Dictionary 使用了 3 个 Page 的内存,如果一段时间没有被访问同时内存吃紧,则系统会尝试对它进行压缩从 3 个 Page 压缩为 1 个 Page 从而释放出 2 个 Page 的内存。但是如果之后需要对它进行访问,则它占用的 Page 又会变为 3 个。

这部分内存可以被 Instrument 统计到,对应的就是 VM Tracker 里的 Swapped Size:

iOS摸鱼周报 第三十八期_iphone_04

参考:


  • jonyfang-iOS 内存相关梳理[4]
  • Reducing Your App's Memory Use[5]
  • 《深入理解计算机系统》

面试解析

整理编辑:zhangferry

dealloc 在哪个线程执行

在回答这个问题前需要了解 ​​dealloc​​​ 在什么时机调用,​​dealloc​​​ 是在对象最后一次 ​​release​​​ 操作的时候进行调用的,对应的源码在 ​​rootRelease​​​ 中,针对 ​​nonpointer​​ 和 SideTable 有两种释放的操作。

SideTable 管理的引用计数会调用 ​​sidetable_release​​:

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];

bool do_dealloc = false;

table.lock();
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_DEALLOCATING);
auto &refcnt = it.first->second;
if (it.second) {
do_dealloc = true;
} else if (refcnt < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
refcnt |= SIDE_TABLE_DEALLOCATING;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
// 可以释放的话,调用dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return do_dealloc;
}

对于 ​​nonpointer​​​ 指针管理的引用计数,会修改 ​​extra_rc​​​值,需要释放时在​​rootRelease​​方法的底部还是会调用:

if (do_dealloc  &&  performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}

这里可以看出 ​​dealloc​​ 的调用并没有设置线程,所以其执行会根据触发时所在的线程而定,就是说其即可以是子线程也可以是主线程。这个也可以很方便的验证。

NSString *str = @"123" 这里的 str 和  "123" 分别存储在哪个区域

可以先做一下测试:

NSString *str1 = @"123"; // __NSCFConstantString
NSLog(@"str1.class=%@, str1 = %p, *str1 = %p", str1.class, str1, &str1);
// str1.class=__NSCFConstantString, str1 = 0x1046b8110, *str1 = 0x7ffeeb54dc50

这时的 str1 类型是 ​​__NSCFConstantString​​​,str1 的内容地址较短,它代表的是常量区,指向该常量区的指针 ​​0x7ffeeb54dc50​​ 是在栈区的。

再看另外两种情况:

NSString *str2 = [NSString stringWithFormat:@"%@", @"123"];
NSLog(@"str2.class=%@, str2 = %p, *str2 = %p", str2.class, str2, &str2);
// str2.class=NSTaggedPointerString, str2 = 0xe7f1d0f8856c5253, *str2 = 0x7ffeeb54dc58

NSString *str3 = [NSString stringWithFormat:@"%@", @"iOS摸鱼周报"]; //
NSLog(@"str3.class=%@, str3 = %p, *str3 = %p", str3.class, str3, &str3);
// str3.class=__NSCFString, str3 = 0x600002ef8900, *str3 = 0x7ffeeb54dc30

这里的字符串类型为 ​​NSTaggedPointerString​​​ 和 ​​__NSCFString​​,他们的指针都是在栈区,这三个对象的指针还是连续的,内容部分,前者在指针里面,后者在堆区。(栈区地址比堆区地址更高)

这里再回顾下内存的分区情况,大多数情况我们只需关注进程的虚拟内存就可以了:

iOS摸鱼周报 第三十八期_iphone_05

优秀博客

整理编辑:东坡肘子、我是熊大

1、​SwiftUI 视图的生命周期研究[6]​ -- 来自:东坡肘子

@东坡肘子:在 UIKit(AppKit)的世界中,通过框架提供的大量钩子(例如 viewDidLoad、viewWillLayoutSubviews 等),开发者可以将自己的意志注入视图控制器生命周期的各个节点之中,宛如神明。在 SwiftUI 中,系统收回了上述的权利,开发者基本丧失了对视图生命周期的掌控。不少 SwiftUI 开发者都碰到过视图生命周期的行为超出预期的状况(例如视图多次构造、onAppear 无从控制等)。本文将作者对 SwiftUI 视图、SwiftUI 视图生命周期的理解和研究做以介绍,供大家一起探讨。

2、​探究视图树[7]​ -- 来自:Javier

@东坡肘子:大多 SwiftUI 的开发者都已经熟练掌握了如何从父视图向子视图传递数据的方法,但如何获取子视图的状态和变化对很多人仍然比较陌生。swiftui-lab 的 Javier 写了三篇文章详细介绍了如何通过 PreferenceKey、AnchorPreferences 等方式向上传递数据的手段。链接中提供的是 Swift 花园的中文译本。

3、​SwiftUI 中的 Text 插值和本地化[8]​ -- 来自:onevcat

@东坡肘子:Text 是 SwiftUI 中最简单和最常见的 View 了,相较 String,Text 提供了更加丰富的差值和本地化功能。本文不仅介绍了 Text 中关于差值和本地化的一些特色功能,并讲解了在 Text 中创建自定义差值的方法。

4、​TCA - SwiftUI 的救星?(一)[9]​ -- 来自博客:onevcat

@我是熊大:SwiftUI 似乎可以真正走向前台,成为开发利器,本文是 TCA - SwiftUI 系列的第一篇文章

5、​SwiftUI Tab Bar[10]​ -- 来自博客:objc.io

@我是熊大:当你信心满满的准备用 SwiftUI 开发 App 时,可能会被困在第一步:Tab Bar 样式怎么处理,本文将替你解惑。

学习资料

整理编辑:Mimosa

软件随想录

地址​:https://book.douban.com/subject/4163938/

Joel Spolsky 的 Blog 纸质版文集,中文版由阮一峰翻译。作者 Joel Spolsky 毕业于耶鲁大学,曾任微软公司 Excel 开发小组项目经理,现在自己创业做 CEO,同时也是 Stack Overflow 的合伙人。他在书中以诙谐幽默的笔触将自己在软件行业的亲身感悟娓娓道来,其中包含关于软件、人才、创业和管理的很多看法。需要提醒读者的是本书的大部分内容都写于 2004 年底左右,是一本老书了,但其中很多内容都值得细细品味。这边节选一些有意思的观点供没看过的读者过过瘾:


  1. 从数量上来说,优秀的人才很少,而且从不出现在招聘市场上。
  2. 我从来没有见过哪个能用 Scheme 语言、Haskell 语言、C 语言中的指针函数编程的人,竟然不能在两天里面学会 Java,并且写出的 Java 程序质量竟然不能胜过那些有 5 年 Java 编程经验的人士。
  3. 看东西的时候,你的视力只是在你的视野中很小一块区域是高分辨率的,而且视野中央还有相当大的一个盲点。但是,你依然想当然的认定你能够超清晰的看清视野中的每一个角落。
  4. 别担心所有工作都被印度人抢走。????

工具推荐

推荐来源:iOSleep

Dropshelf

地址​:https://pilotmoon.com/dropshelf/

软件状态​:之前付费但是目前下架了,可以使用上面链接免费使用。

软件介绍

Dropshelf 是一款 Mac OS 下的拖拽效率应用。它提供了一个可以吸附在屏幕边缘的 Dock,你可以拖拽任何东西「图片、文件、文字、链接...」暂存到 Dock 中,方便你在其他 App 中来使用。

iOS摸鱼周报 第三十八期_ios_06Dropshelf

关于我们


引用资料

[1]

Xcode 13.2: ​​​​​​

[2]

iOS 15.2: ​​​​​​

[3]

ios-app-maximum-memory-budget: ​​​​​​

[4]

jonyfang-iOS 内存相关梳理: ​​​​​​

[5]

Reducing Your App's Memory Use: ​​​​​​

[6]

@东坡肘子:SwiftUI 视图的生命周期研究: ​​​​​​

[7]

@Javier:探究视图树: ​weixin.qq.com/s/JMxJqCoho-LGJcLrNt9ibQ">​​https://mp.weixin.qq.com/s/JMxJqCoho-LGJcLrNt9ibQ​​

[8]

@onevcat:SwiftUI 中的 Text 插值和本地化: ​​​​​​

[9]

@onevcat:TCA - SwiftUI 的救星?(一): ​​​​​​

[10]

SwiftUI Tab Bar: ​​​​​​


举报

相关推荐

0 条评论