肇鑫的技术博客

业精于勤,荒于嬉

@State在初始化函数中的特殊性

原来Swift的Sheet里还不能这么做中,提到也可以使用初始化函数。但是我在修改使用为初始化函数之后,代码运行却不符合预期。于是我询问AI:

使用初始化函数的方式之后,始终显示的是xtTransUnits.isEmpty == true的分支,这是为什么呢?

#AI回答
问题出在初始化器的实现方式上。当前的实现使用了立即执行的闭包来设置 xtTransUnits,但因为 @State 属性在初始化器中的特殊性,这种方式并不会正确地设置初始值。

修改建议如下:

struct AITranslationView: View {
    // ... 其他属性 ...
    
    init(trees: [Tree<IDTree>]) {
        self.trees = trees
        let allUnits = Xliff_Tool.getXTTransUnits(from: trees)
        _xtTransUnits = State(initialValue: allUnits.filter { $0.target == nil || $0.target?.isEmpty == true })
    }
}

关键修改点:

  1. 使用 _xtTransUnits = State(initialValue: ...) 来正确初始化 @State 属性
  2. 移除了立即执行闭包,改为直接赋值
  3. 保持过滤逻辑不变

这样修改后,xtTransUnits 将被正确初始化,视图也会显示正确的分支。

原来Swift的Sheet里还不能这么做

今天在调试一个应用崩溃的问题的时候,发现有个sheet弹出后,被反复执行了多次。于是询问AI怎么回事。

原始的代码是这样的

.sheet(isPresented: $showAITranslationGuideView) {
  let xtTransUnits: [XTTransUnit] = {
    let xtTransUnits = Xliff_Tool.getXTTransUnits(from: trees)
    return xtTransUnits.filter { $0.target == nil || $0.target?.isEmpty == true }
  }()

  AITranslationView(xtTransUnits: xtTransUnits)
}

询问AI啥原因,AI是这么回答的。

.sheet(isPresented: $showAITranslationGuideView)为什么会多次执行?

在 .sheet(isPresented: $showAITranslationGuideView) 代码块中,每次 showAITranslationGuideView 状态变化时都会重新执行闭包内的代码。这里的问题是 xtTransUnits 的计算被放在了视图构建的闭包中,而不是在实际需要时才执行。

建议将这些逻辑移到 AITranslationView 的初始化或 onAppear 中。以下是修改建议:

.sheet(isPresented: $showAITranslationGuideView) {
    AITranslationView(trees: trees)
}

然后在 AITranslationView 中处理数据:

struct AITranslationView: View {
    let trees: [Tree<IDTree>]
    @State private var xtTransUnits: [XTTransUnit] = []
    
    var body: some View {
        // 你的视图内容
        .onAppear {
            xtTransUnits = getXTTransUnits(from: trees)
                .filter { $0.target == nil || $0.target?.isEmpty == true }
        }
    }
}

这样可以确保计算只在实际需要时执行一次。

需要注意的是,AI的解释是对的,但是不是真正的原因。但是解法又同样是对的。只要移出这个初始化的代码之后,的确就不会重复计算了。

我认为真正原因是这样的。像.sheet(isPresented: $showAITranslationGuideView)的sheet,是在View初始化时就会被生成的。它是不变的。这就是为什么还有一个同名的但是使用Optional的sheet,那个才是动态的。但是我这里面的代码,在sheet使用前重新进行了计算。这导致原本应该只能是静态的sheet,变成了动态的,从而导致它从新被加载,而这种情形,其实是为定义的,因为这里不应该使用动态的。这个才是出错的真正原因。

苹果的老六行为

苹果有时候还是很老六的。比如在系统声音设置中,有一个声音叫“Heroine”,也就是女英雄。

lao6

那么如果你想在自己的应用中使用它,可以通过NSSound调用

class Foo {
    private var sound: NSSound?
    
    func playSound() {
        sound = NSSound(named: "Heroine")
        sound?.play()
    }
}

但是这个声音应用运行时并不会发出。这是因为传统上,这段声音的名字叫“Hero”,大概是出于平权的考虑,苹果将它改名为“Heroine”了。但是程序代码里的,没改,还是叫“Hero”。所以,想在自己的应用里使用它,必须使用

 sound = NSSound(named: "Hero")

这可是连文档里都没有的。只能自己搜索加猜测才能摸索到。

苹果还真是个老六。如果你真想平权,还想保证兼容性。那你让"Hero"和"Heroine"可以同时使用就可以了啊。这么做很难吗?