普通视图

发现新文章,点击刷新页面。
今天 — 2025年6月27日掘金 iOS

包瘦身之未引用图片资源扫描工具

2025年6月26日 18:33
包瘦身之未引用图片资源扫描工具”主要是指一种用于检测软件包(如移动应用、网页项目、游戏资源包等)中未被实际引用或使用的图片资源的工具。其目的是帮助开发者清理冗余图片文件,从而减小包体积,提高加载速度!
昨天 — 2025年6月26日掘金 iOS

Swift 新并发模型中 isolated 和 nonisolated 关键字的含义看这篇就懂了!

2025年6月26日 11:30

在这里插入图片描述

概览

在 Swift 新 async/await 并发模型中,我们可以利用 Actor 来避免并发同步时的数据竞争,并从语义上简化代码。

Actor 伴随着两个独特关键字:isolatednonisolated,弄懂它们的含义、合理合规的使用它们是完美实现同步的必要条件。

那么小伙伴们真的搞清楚它们了吗?

在本篇博文中,您将学到如下内容:

  1. isolated 关键字
  2. nonisolated 关键字
  3. 没有被 async 修饰的方法也可以被异步等待!

闲言少叙,让我们即刻启航!

Let‘s go!!!;)


isolated 关键字

Actor 从本质上来说就是一个同步器,它必须严格限制单个实例执行上下文以满足同步的语义。

这意味着在 Actor 中,所有可变属性、计算属性以及方法等默认都是被隔离执行的。

actor Foo {
    let name: String
    let age: Int
    var luck = 0
    
    init(name: String, age: Int, luck: Int = 0) {
        self.name = name
        self.age = age
        self.luck = luck
    }
    
    func incLuck() {
        luck += 1
    }
    
    var fullDesc: String {
        "\(name)[\(age)] luck is *\(luck)*"
    }
}

如上代码所示,Foo 中的 luck 可变属性、incLuck 方法以及 fullDesc 计算属性默认都被打上了 isolated 烙印。大家可以想象它们前面都隐式被 isolated 关键字修饰着,但这不能写出来,如果写出来就会报错:

在这里插入图片描述

在实际访问或调用这些属性或方法时,必须使用 await 关键字:

Task {
    let foo = Foo(name: "hopy", age: 11)
    await foo.incLuck()
    print(await foo.luck)
    print(await foo.fullDesc)
}

正是 await 关键字为 Foo 实例内容的同步创造了隔离条件,以摧枯拉朽之势将数据竞争覆巢毁卵。

nonisolated 关键字

但是在有些情况下 isolated 未免有些“防御过度”了。

比如,如果我们希望 Foo 支持 CustomStringConvertible 协议,那么势必需要实现 description 属性:

extension Foo: CustomStringConvertible {
    var description: String {
        "\(name)[\(age)]"
    }
}

如果大家像上面这样写,那将会妥妥的报错:

在这里插入图片描述

因为 description 作为计算属性放在 Actor 中,其本身默认处在“隔离”状态,而 CustomStringConvertible 对应的 description 实现必须是“非隔离”状态!

大家可以这样理解:我们不能异步调用 foo.description!

extension Foo: CustomStringConvertible {
    /*
    var description: String {
        "\(name)[\(age)]"
    }*/
    
    var fakeDescription: String {
        "\(name)[\(age)]"
    }
}

Task {
    let foo = Foo(name: "hopy", age: 11)
    // foo.description 不能异步执行!!!
    print(await foo.fakeDescription)
}

大家或许注意到,在 Foo#description 中,我们只使用了 Foo 中的只读属性。因为 Actor 中只读属性都是 nonisolated 隐式修饰,所以这时我们可以显式用 nonisolated 关键字修饰 description 属性,向 Swift 表明无需考虑 Foo#description 计算属性内部的同步问题,因为里面没有任何可变的内容:

extension Foo: CustomStringConvertible {
    nonisolated var description: String {
        "\(name)[\(age)]"
    }
}

Task {
    let foo = Foo(name: "hopy", age: 11)
    print(foo)
}

但是,如果 nonisolated 修饰的计算属性中含有可变(isolated)内容,还是会让编译器“怨声载道”:

在这里插入图片描述

没有被 async 修饰的方法也可以被异步等待!

最后,我们再介绍 isolated 关键字一个非常有用的使用场景。

考虑下面的 incLuck() 全局函数,它负责递增传入 Foo 实例的 luck 值,由于 Actor 同步保护“魔法”的存在,它必须是一个异步函数:

func incLuck(_ foo: Foo) async {
    await foo.incLuck()
}

不过,如果我们能够保证 incLuck() 方法传入 Foo 实参的“隔离性”,则可以直接访问其内部的“隔离”(可变)属性!

如何保证呢?

很简单,使用 isolated 关键字:

func incLuck2(_ foo: isolated Foo) {
    foo.luck += 1
}

看到了吗? luck 是 Foo 内部的“隔离”属性,但我们竟然可以在外部对其进行修改,是不是很神奇呢?

这里,虽然 incLuck2() 未用 async 修饰,但它仍是一个异步方法,我称之为全局“隐式异步”方法:

Task {
    let foo = Foo(name: "hopy", age: 11)
    await incLuck(foo)
    await incLuck2(foo)
}

虽然 foo 是一个 Actor 实例,它包含一些外部无法直接查看的“隔离”内容,但我们仍然可以使用一些调试手段探查其内部,比如 dump 方法:

Task {
    let foo = Foo(name: "hopy", age: 11)
    await incLuck(foo)
    await incLuck2(foo)
    dump(foo)
}

输出如下:

over
hopy[11]
▿ hopy[11] #0
  - $defaultActor: (Opaque Value)
  - name: "hopy"
  - age: 11
  - luck: 2

通过 dump() 方法输出可以看到,foo 的 luck 值被正确增加了 2 次,棒棒哒!!!💯

总结

在本篇博文中,我们通过几个通俗易懂的例子让小伙伴们轻松了解到 Swift 新 async/await 并发模型中 isolated 与 nonisolated 关键字的精髓,并对它们做了进一步的深入拓展。

感谢观赏,再会!8-)

❌
❌