摸鱼精选第 33 期

1. How to Automate Memory Leak Detection in Your Swift Code with XCTest

使用 XCTest 来自动化检测内存泄露。其实很简单,在测试用例结束的时候判断这个对象是否为 nil。

func test_start_doesNotCreateMemoryLeaks () {
    let spy = TimerSpy()
    let sut = PomodoroTimer(timer: spy, timeReceiver: { _ in })
    sut.start()
    addTeardownBlock {
        XCTAssertNil(sut, "potential memory leak on \(String(describing: sut)")
        XCTAssertNil(spy, "potential memory leak on \(String(describing: spy)")
    }
}

2. How async/await works internally in Swift

【深度好文】Swift async/await 的内部实现其实是封装了一个线程池来处理。

3. 最佳实践|如何使用 c++开发 redis module

Redis 在 5.0 版本开始支持以 module 插件的方式来扩展 redis 的能力,包括但不限于开发新的数据结构、实现命令监听和过滤、扩展新的网络服务等。可以说,module 的出现极大的扩展了 redis 的灵活性,也大大的降低了 redis 的开发难度。 目前为止,redis 社区已经涌现了很多 module,覆盖了不同领域,生态已经丰富起来了。它们之中大多都是使用 c 语言开发。但是,redis module 也支持使用其他语言开发,如 c++和 rust 等。本文将试着总结 Tair 用 c++开发 redis module 中遇到的一些问题并沉淀为最佳实践,希望对 redis module 的使用者和开发者带来一些帮助(部分最佳实践也适用于 c 和其他语言)。

4. 初见 Swift 宏

Swift 宏是 Swift 5.9 中引入的新特性,只支持 iOS 17+系统,用处不大。

5. Modern CSS Solutions for Old CSS Problems

使用现代 CSS 来处理老问题,在旧的 CSS 规范里需要处理某些繁琐的布局问题时,可能在现代 CSS 规范里只需要一个两行代码。

6. Prisma's Data Guide

Prisma 是一款 Node.js 的 TypeScript ORM 开源库,它在官网出了一系列数据库的基础教程。

7. iOS 点九图 NinePatch 解析

iOS 里面比较少用到点九图,主要方法是[image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];

8. Working With UIViewRepresentable

在 SwiftUI 中难免需要用到 UIKit 的一些视图,这时我们会使用 UIViewRepresentable 来封装一下。

但是在运行时经常会有这个警告:

Modifying state during view update, this will cause undefined behavior.

要避免这个警告,可以使用异步操作来刷新状态,比如在一个 MKMapView 的封装里面,

func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
    DispatchQueue.main.async {
        self.parent.position = mapView.centerCoordinate
    }
}
``

但是这样带来一个问题,无法拖动地图标志,`updateUIView(_:context:)` 方法不响应了,所以我们还需要判断一下位置状态,在需要的时候才刷新 UIView:

```swift
func updateUIView(_ view: MKMapView, context: Context) {
    context.coordinator.parent = self
    if view.centerCoordinate != position {
        view.centerCoordinate = position
    }
}

9. C++知识体系总结:语言核心与代码工程

本文梳理了 C++的知识体系,针对 C++的重点和难点做了细致说明,同时给出了可运行的源代码。

10. 深入理解 Flutter 图片加载原理 | 京东云技术团队

本文介绍了京东 APP 中 Flutter 探索遇到的问题以及图片的加载原理和使用过程中的一些技巧

11. Goroutine Leaks 引发的思考(Go 语言)

作者介绍了自己在实际业务中排查 Goroutine 的内存泄露的问题。

当发生 goroutine 数量激增的时候,我们的主要排查的思路一般都是先通过直接调用 runtime.NumGoroutine()( Go 的 runtime 包里面包含了一些与 Go 运行时系统交互的操作,比如控制 goroutine 的函数。NumGoroutine 函数就是查看 goroutines 运行数量)或者使用可视化的 goroutine 数量监控工具来查看 goroutine 数量,通过对比异常时间段前后 goroutine 的数量是不是持续不正常增长(业务不在高峰期的时候突然翻 N 倍被视为异常)来确认是否是 goroutine 泄漏,一旦确认,那么接下来就可通过找到 goroutine 泄漏的问题作为切入点(忘记设置默认的请求超时时间、跟 redis、sql 交互超时、向已关闭的通道发数据等等)从而找到根因。

12. Mastering Thread Safety in Swift With One Runtime Trick

作者介绍一个在官方文档里没有出现的公共接口_modify,可用多线程环境保证数据的安全性。

因为我们在使用wrapperProperty时,通常会使用类似@ThreadSafe 装饰变量,get/set时来保证数据的安全性。 比如:

@ThreadSafe
var x: UInt64 = 0

x += 1

但是遇到 x += 1 这种操作时,其实是 x = x + 1 ,先 getset。这两种操作都是独立的,但是可能在这两个操作中间另一个线程更新了 x 值。有经验的开发者会封装一个小工具来确保安全性:

public func write<V>(_ f: (inout T) -> V) -> V {
    self.lock.lock(); defer { self.lock.unlock() }
    return f(&self.value)
}

用的时候:

@ThreadSafe
var x: Int = 5

$x.write { $0 += 1 }

但这种方式还是不够优雅,人为干扰了正常的逻辑。下面是使用_modify的例子:

var wrappedValue: T {
    get { return value }
    _modify { yield &self.value }
}

_modify 在 Swift 标准库的Array 也有应用,是原子性的,并且性能比较好,不是 copy 模式。

yield 是 swift 5.6 的新特性,可参考Yielding accessors in Swift