本期概要

  • 论题:苹果春节发布会
  • Tips:如安在SwiftUI中显示二维码;怎么将 JSON 字典编码为 JSONEncoder
  • 面试模块:iOS 内存管理:Autorelease 细节速记
  • 优异博客:SwiftUI 进阶技巧
  • 学习材料:KKBOX iOS/Mac OS X 基礎開發教材
  • 开发东西:几款面向 iOS 开发的 UI 调试东西

本期论题

@zhangferry:苹果在北京时间的3月9号凌晨举行了春季发布会,本次发布会也是诚意满满,带来了许多惊喜。这最重要的便是 M1 Ultra 芯片,M1 Ultra 是两颗 M1 Max 的组合,但这也不是简略的拼接,而是得益于M1 Max 的躲藏特性:突破性的晶粒到晶粒技能,然后运用了一个叫做 UltraFusion 的封装架构将两颗 M1 Max 融合到一同,关于使用层来说它便是一颗完好的芯片。它具有这些东西:

  • 20 核中央处理器:16 个高功用中心 + 4 个高能效中心,用于CPU密布操作。

  • 64 核图形处理器,用于图形密布使命。

  • 32 个神经网络中心,用于机器学习。

  • 10 个多媒体处理引擎,用于提高视频的编解码才能,对 H264/HEVC/ProRes/ProRes RAW 处理有硬件层面的加快。

iOS 摸鱼周报 第四十六期

当然这么强壮的芯片要有一个产品运用,它便是 Mac Studio。这是一条新的产品线,它看上去像是 Mac Mini 「加厚」版,但得益于其强壮的功用,它的定位确是工作站。到目前为止 Mac 端的产品线基本都用上 M1 了,除了 Mac Pro。Mac Pro 之前的定位也是工作站,从「垃圾桶」进化到「行李箱」,它作为苹果功用的最强代表不断冷艳着咱们,但随着 M1 的到来,这两款产品都不香了,更不用说「行李箱」起售价就 47999。尽管 M1 Ultra 现已让咱们大呼苹果不讲武德了,但发布会结尾特意提了一下 Mac Pro,这很有理由信任作为功用天花板的 Mac Pro 的下一代才是真正的大杀器。

开发 Tips

如安在 SwiftUI 中显示二维码

收拾修改:FBY展菲

运用 CoreImage 生成二维码图画。

import SwiftUI
import CoreImage.CIFilterBuiltins
struct QRView: View {
    let qrCode: String
    @State private var image: UIImage?
    var body: some View {
        ZStack {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .interpolation(.none)
                    .frame(width: 210, height: 210)
            }
        }
        .onAppear {
            generateImage()
        }
    }
    private func generateImage() {
        guard image == nil else { return }
        let context = CIContext()
        let filter = CIFilter.qrCodeGenerator()
        filter.message = Data(qrCode.utf8)
        guard
            let outputImage = filter.outputImage,
            let cgImage = context.createCGImage(outputImage, from: outputImage.extent)
        else { return }
        self.image = UIImage(cgImage: cgimg)
    }
}

参阅:如安在 SwiftUI 中显示二维码 – Swift社区

怎么将 JSON 字典编码为 JSONEncoder

收拾修改:FBY展菲

JSONEncoder 处理类型安全,因而咱们需求为一切可能的类型声明枚举 JSONValue。咱们还需求一个自界说 initializer 来从 JSON 字典中初始化 JSONValue

import Foundation
enum JSONValue {
    case string(String)
    case int(Int)
    case double(Double)
    case bool(Bool)
    case object([String: JSONValue])
    case array([JSONValue])
}
extension JSONValue: Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let string): try container.encode(string)
        case .int(let int): try container.encode(int)
        case .double(let double): try container.encode(double)
        case .bool(let bool): try container.encode(bool)
        case .object(let object): try container.encode(object)
        case .array(let array): try container.encode(array)
        }
    }
}
extension JSONValue {
    init?(any: Any) {
        if let value = any as? String {
            self = .string(value)
        } else if let value = any as? Int {
            self = .int(value)
        } else if let value = any as? Double {
            self = .double(value)
        } else if let value = any as? Bool {
            self = .bool(value)
        } else if let json = any as? [String: Any] {
            var dict: [String: JSONValue] = [:]
            for (key, value) in json {
                dict[key] = JSONValue(any: value)
            }
            self = .object(dict)
        } else if let jsonArray = any as? [Any] {
            let array = jsonArray.compactMap { JSONValue(any: $0) }
            self = .array(array)
        } else {
            return nil
        }
    }
}
var dict: [String: Any] = [
    "anArray": [1, 2, 3],
    "anObject": [
        "key1": "value1",
        "key2": "value2"
    ],
    "aString": "hello world",
    "aDouble": 1.2,
    "aBool": true,
    "anInt": 12
]
let encoder = JSONEncoder()
let value = JSONValue(any: dict)
let data = try! encoder.encode(value)
print(String(data: data, encoding: .utf8))

参阅:怎么将 JSON 字典编码为 JSONEncoder – Swift社区

@zhangferry 补充:该计划首要考虑的是 Encodable 功用,假如增加对 Decodable 的支撑,就能完成完好的 Codable 功用。咱们能够给这个数据类型命名为 AnyCodable,这样关于某一不确定格局的字段(例如复合型的 Dictionary)就能够无缝支撑 Codable 了。

参阅:Github-AnyCodable

面试解析

收拾修改:Hello World

Autorelease 细节速记

本文内容是根据 Autorelease-818版别源码来剖析的, 假如你还未了解 Autorelease的原理,请先依照另一位修改的文章学习 AutoreleasePool。下面会介绍一些源码细节。

AutoreleasePool 数据结构

AutoreleasePool底层数据结构是根据 AutoreleasePoolPage, 实质上是个双向链表, 每一页的巨细为 4K,能够在 usr/include/mach/arm/vm_param.h文件中检查 PAGE_MIN_SIZE的值,

#define PAGE_MAX_SHIFT          14
#define PAGE_MAX_SIZE           (1 << PAGE_MAX_SHIFT)
#define PAGE_MIN_SHIFT          12
#define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    static size_t const SIZE =
        // `PROTECT_AUTORELEASEPOOL`默认是界说为 0 的,
    #if PROTECT_AUTORELEASEPOOL
            PAGE_MAX_SIZE;  // must be multiple of vm page size 有必要是 vm 页面巨细的倍数 界说为1<<14 = 4096K,正好是虚拟页巨细
    #else
            PAGE_MIN_SIZE;  // size and alignment, power of 2 巨细和对齐, 2的指数倍
    #endif
}

64 位系统下的存储优化

在最新的 818版别代码中,AutoreleasePoolPage::add()中对接连增加的相同目标存储方法做了优化,运用 LRU 算法结合新的AutoreleasePoolEntry 目标来兼并存储,简化后中心源码如下:

struct AutoreleasePoolPageData
    struct AutoreleasePoolEntry {
            uintptr_t ptr: 48;  // 关联的 autorelease 的目标
            uintptr_t count: 16; // 关联目标 push 的次数
            static const uintptr_t maxCount = 65535; // 2^16 - 1 能够存储的最大次数
        };
	// ...其他变量
}
id *add(id obj)
{
      // .. 准备工作
        for (uintptr_t offset = 0; offset < 4; offset++) {
             AutoreleasePoolEntry *offsetEntry = topEntry - offset;
             if (offsetEntry <= (AutoreleasePoolEntry*)begin() || *(id *)offsetEntry == POOL_BOUNDARY) {
                 break;
             }
             if (offsetEntry->ptr == (uintptr_t)obj && offsetEntry->count < AutoreleasePoolEntry::maxCount) {
                  if (offset > 0) {
                       AutoreleasePoolEntry found = *offsetEntry;
                       // 将offsetEntry + 1中
                       memmove(offsetEntry, offsetEntry + 1, offset * sizeof(*offsetEntry));
                       *topEntry = found;
                  }
                  topEntry->count++;
                  ret = (id *)topEntry;  // need to reset ret
                  goto done;
             }
        // 旧版别依次刺进目标的存储方法
}

假如运用 LRU 算法, 则刺进时从 next指针向上遍历最近的四个目标, 遍历中假如和当前目标匹配,则 Entry 实体记录的 count属性加一, 然后经过 memmove函数移动内存数据,将匹配的 Entry放到距离 next指针最近的位置,以完成 LRU的特征。假如只是单纯的兼并存储,则只匹配 next指针相邻的Entry,未匹配到则刺进

是否敞开兼并和 LRU环境变量为OBJC_DISABLE_AUTORELEASE_COALESCING & OBJC_DISABLE_AUTORELEASE_COALESCING_LRU

别的最好一同准备下缓存淘汰算法,因为假如面试中提到了 LRU,面试官很可能会延伸到缓存算法完成,比方 LFULRU

和线程以及 Runloop 的联络

AutoreleasePool和线程的直观联络:

  1. 数据结构中存储了和线程相关的成员变量 thread

  2. 在完成计划中运用了 TLS线程相关技能用来存储状况数据。例如 Hotpage以及 EMPTY_POOL_PLACEHOLDER等状况值。

  3. objc初始化时调用了 AutoreleasePoolPage::init(),该函数内部经过 pthread_key_init_np注册了回调函数 tls_dealloc,在线程毁掉时调用收拾 Autorelease相关内容。大致流程为:_pthread_exit => _pthread_tsd_cleanup => _pthread_tsd_cleanup_new => _pthread_tsd_cleanup_key => tls_dealloc。相关源码能够在 libpthread中检查。

    static void tls_dealloc(void *p)
    {
        if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
            // No objects or pool pages to clean up here.
            return;
        }
        // reinstate TLS value while we work
        setHotPage((AutoreleasePoolPage *)p);
        if (AutoreleasePoolPage *page = coldPage()) {
            if (!page->empty()) objc_autoreleasePoolPop(page->begin());  // pop all of the pools
            if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
                // pop() killed the pages already
            } else {
                page->kill();  // free all of the pages
            }
        }
        // clear TLS value so TLS destruction doesn't loop
        setHotPage(nil);
    }
    

    由以上流程可知,子线程处理 Autorelease 的机遇一般有两种:线程毁掉时 & 自界说 pool作用域退出时

    在主线程中因为敞开了 Runloop并且主动注册了两个回调,所以在每次 Runloop循环时都会去处理默认增加的 AutoreleasePool,该详细内容请参阅文章 AutoreleasePool,这也不做重复复述。

Autorelease ARC环境下根据 tls 的返回值优化计划以及失效场景

首要是经过嵌入 objc_autoreleaseReturnValue & objc_retainAutoreleasedReturnValue两个函数,根据 tls存储状况值完成优化。

优化思路归纳为:

  • objc_autoreleaseReturnValue 经过 __builtin_return_address()函数能够查找到函数返回后下一条指令的地址,判别是否为 mov x29, x29(arm64)从而决议是否进行优化,
  • 假如敞开优化会设置 tls存储状况值 1并直接返回目标,不然放入自动开释池走普通逻辑
  • objc_retainAutoreleasedReturnValue调用acceptOptimizedReturn校验 tls中的值是否为 1,为 1 表明启动优化直接返回目标, 不然走未优化逻辑先 retain 再放入 自动开释池

优化思路是根据 tls以及__builtin_return_address()完成的,以是否刺进无实际作用的汇编指令 mov x29, x29作为优化标识。 别的检查源码时需求留意 objc_retainAutoreleasedReturnValueobjc_retainAutoreleaseReturnValue的区别

ARC 下函数返回值是否一定会敞开优化呢,存在一种状况会破坏系统的优化逻辑,即 for或者while等场景。示例如下:

- (HWModel *)takeModel {
//    for (HWModel *model in self.models) {}
    HWModel *model = [HWModel new];
    return model;
}

假如打开注释代码,会导致返回的 model未优化,经过动态调试能够检查原因。

注释 for代码后跳转用的 b指令,所以 lr 寄存器存储的是调用方调用 takeModel函数后的指令地址

iOS 摸鱼周报 第四十六期

for 循环时,跳转到 objc_autoreleaseReturnValue的汇编指令是 bl

iOS 摸鱼周报 第四十六期

bl表明执行完函数后继续执行后续指令,后续汇编指令目的首要是为了检测是否存在函数调用栈溢出操作,详细解说能够参阅Revisit iOS Autorelease 二。这造成咱们上面提到的 __builtin_return_address()函数获取到的返回值下一条指令地址,并不是优化标识指令 mov x29 x29,而是检测代码指令,导致优化未敞开。

未敞开优化的影响是多做一次 retain操作和两次 autorelease操作, 笔者未测验出五子棋长辈遇到的 Autorelease 目标未开释的状况, 可能是后续 apple 现已优化过,假如读者有不同的结果,欢迎辅导

总结: 以上是笔者在收集面试题时关于 AutoreleasePool的一些扩展内容,再次着重需求精读AutoreleasePool,尤其需求把握 ARC 下手动处理的几种场景。期望各位能够对 Autorelease面试题一网打尽。

  • 内幕背面的Autorelease
  • AutoreleasePool
  • Revisit iOS Autorelease 一
  • Revisit iOS Autorelease 二
  • iOS13 一次Crash定位 – 被开释的NSURL.host

优异博客

转眼间 SwiftUI 已推出接近 3 年。越来越多的开发者测验运用 SwiftUI 来构建其使用。本期介绍的博文将更多地涉及 SwiftUI 的进阶技巧,协助开发者对 SwiftUI 有愈加深化的知道和了解。

收拾修改:东坡肘子

1、无法解说的 SwiftUI —— SwiftUI 的编程言语实质 — 来自:WeZZard

@东坡肘子:作者 WeZZard 从一个十分新颖的视点来看待、剖析 SwiftUI。经过一个斐波纳契数示例,来展示 SwiftUI 的图灵完好性,从而提出一个有趣的观点——SwiftUI 是一种编程言语,而不是 UI 结构。

2、SwiftUI 底层:可变视图 — 来自:The Moving Parts Team

@东坡肘子:本文介绍了一些 View 协议中没有公开的 API。经过运用这些 API,开发者能够编写出愈加强壮、灵敏,且与原生完成相似的容器,构建自己的布局逻辑。作者 Moving Parts 团队当前正在开发一个功用强壮的 SwiftUI 组件库。

3、了解 SwiftUI 怎么以及何时决议重绘视图 — 来自:Donny Wals

@东坡肘子:作者经过观察和实践,测验了解和总结 SwiftUI 中对视图重绘的规则。尽管该文没有给出内部完成的详细证明,但沿着作者的测验路径,读者仍然能够从中获取到适当宝贵的经验。

4、了解 SwiftUI 的 onChange — 来自:东坡肘子

@东坡肘子:onChange 是从 SwiftUI 2.0 后提供的功用,能够将其作为另一种驱动视图重绘的手段。本文将对 onChange 的特点、用法、留意事项以及替代计划做以详细介绍。结合上文「了解 SwiftUI 怎么以及何时决议重绘视图」以及「SwiftUI 视图的生命周期研究」一文,能够对视图的计算、布局、绘制有更深化的了解。

5、谁说咱们不能对 SwiftUI 视图进行单元测验? — 来自:Alexey Naumov

@东坡肘子:因为很难构建依靠和运转环境,对 SwiftUI 视图进行单元测验是好不容易的。Alexey Naumov 是著名的 SwiftUI 测验结构 ViewInspector 的作者,本文介绍了他在创立 ViewInspector 结构背面的故事,其间有关获取 SwiftUI 黑盒中隐秘的思路和途径十分值得学习。

6、高档 SwiftUI 动画 1-5 — 来自:Javier 中文版:Swift 君

@东坡肘子:仅需少量的代码,SwiftUI 即可为开发者完成适当优异的动画作用。但假如想创立愈加炫酷、灵敏、高效的动画则需求把握更多的常识和高档技巧。本系列文章已继续更新 2 年之久(SwiftUI 诞生至今不到 3 年),详细讲解了各种有关 SwiftUI 高档动画的内容。

学习材料

收拾修改:Mimosa

KKBOX iOS/Mac OS X 基礎開發教材

地址:zonble.gitbooks.io/kkbox-ios-d…

一份来自台湾 KKBOX 的 iOS/Mac OS 开发部门编写的新人学习材料。这份学习材料不算是从 0 到 1 的入门材料,阅览这份教材需求一些简略的基础,教材首要是在你现已会一些简略 OC 代码的基础上协助你深化探讨一些在代码中常见的小问题和小细节,也是对技能探索方向的一些指引和辅导。教材中的描述言语十分亲切不生硬,就像是有一位同龄人在你周围辅导你的代码有什么问题相同,阅览体会十分不错,尽管内容略有陈旧,但也值得新手开发者阅览一下。

东西引荐

收拾修改:CoderStar

本次引荐一系列关于 UI 调试的软件,包括电脑端以及 App 端两种类型;

电脑端

  • Reveal:经典UI调试软件,但需求付费;
  • Lookin:腾讯出品的一款免费好用的 iOS UI 调试软件;

App 端

  • FLEX:FLEX (Flipboard Explorer) 是一套用于 iOS 开发的使用内调试东西;
  • 啄幕鸟iOS开发东西:阿里出品的一套用于 iOS 开发的使用内调试东西;

其间 Reveal、Lookin、FLEX 都有对应的Tweak,有越狱设备的小伙伴能够玩一玩;

关于咱们

iOS 摸鱼周报,首要分享开发过程中遇到的经验教训、优质的博客、高质量的学习材料、实用的开发东西等。周报库房在这里:github.com/zhangferry/… ,假如你有好的的内容引荐能够经过 issue 的方法进行提交。别的也能够请求成为咱们的常驻修改,一同保护这份周报。另可关注公众号:iOS 生长之路,后台点击进群交流,联络咱们,获取更多内容。

往期引荐

iOS摸鱼周报 第四十五期

iOS摸鱼周报 第四十四期

iOS摸鱼周报 第四十三期

iOS摸鱼周报 第四十二期