Swift开发避坑指南,7个Swift开发中易犯的错误

刷Medium时看到一篇讲述Swift开发踩坑的文章,看完发现里面提到的错误自己居然全都犯过。有些坑看起来只是小问题,但项目规模变大之后,排查起来会耗费大量精力。整理了7个开发中最常见的坑,看看你有没有遇到过。

1. if let套娃,代码越写越丑

刚学习Swift的时候,会特别喜欢用if let包裹所有可选值相关代码,觉得这样的写法很稳妥。

if let user = user {
    if let name = user.name {
        print("Hello, \(name)")
    }
}

嵌套两三层的情况下代码还能看,一旦嵌套五六层以上,代码的可读性就会变得极差。

其实可以用可选链简化代码:

if let name = user?.name {
    print("Hello, \(name)")
}

或者用三元运算符让写法更简洁:

print("Hello, \(user?.name ?? "Guest")")

如果是在函数中,推荐使用guard let,能让代码提前退出,逻辑也会更清晰:

func getUser(user: User?) {
    guard let user = user else { return }
    guard let name = user.name else { return }
    print("The name is \(name)")
}

2. 闭包里忘了weak self

这个坑踩过的开发者非常多。

在闭包里直接引用self,会形成循环引用,导致对应的对象永远无法被释放。

class UserViewModel {
    func loadUser() {
        api.fetchUser { data in
            self.updateProfileData(with: data) // 内存泄漏!
        }
    }
}

在闭包参数前加个[weak self]就能解决这个问题:

api.fetchUser { [weak self] data in
    self?.updateProfileData(with: data)
}

这样页面返回时,相关对象就能正常释放了。这个写法一定要养成习惯,闭包里用到self时,先想清楚要不要加weak。

3. struct和class傻傻分不清

struct是值类型,class是引用类型。很多开发者觉得搞混这两个概念没关系,但实际开发中,这种混淆会引发一些难以排查的bug。

struct User {
    var name: String
}
var user1 = User(name: "张三")
var user2 = user1
user2.name = "李四"
print(user1.name) // 张三,没变

struct进行赋值操作时会完成数据复制,修改user2的内容不会对user1产生影响。换成class的话,两个变量会指向同一个对象,修改一个会影响另一个。

简单的记忆方法:

struct:适合定义数据模型、轻量数据相关的结构

class:适合需要共享状态、用到继承特性的开发场景

4. @StateObject和@ObservedObject用反了

这个坑的印象特别深,当时开发天气App,页面状态怎么都不更新,折腾了一整天才找到问题。

struct WeatherView: View {
    @ObservedObject var viewModel = ProfileViewModel() // 错的
}

使用@ObservedObject的情况下,视图一旦刷新,对应的ViewModel就会重新创建,正在进行的API请求也会被中断。

正确的写法是使用@StateObject

@StateObject var viewModel = ProfileViewModel() // 对的

简单的记忆方法:

@StateObject:用于当前View创建并拥有的对象

@ObservedObject:用于从外部传递进来的对象

5. 后台线程刷UI,程序直接崩溃

Swift的并发语法用起来确实很方便,但有一个核心原则,UI更新操作必须在主线程执行。

DispatchQueue.global().async {
    self.text = "Hello Swift :)" // 会崩
}

把UI更新代码切回主线程执行就可以:

DispatchQueue.main.async {
    self.text = "Hello Swift :)"
}

如果使用async/await的并发写法,代码可以这样写:

Task { @MainActor in
    text = "Hello Swift :)"
}

6. Model没加Equatable,View疯狂重绘

开发SwiftUI时遇到View莫名其妙一直重绘的情况?原因大概率是自定义的Model没有实现Equatable协议。

struct Profile {
    var id: Int
    var name: String
}

这样的写法下,SwiftUI无法判断数据是否发生变化,为了保证页面展示准确,会选择每次都对View进行重绘。

给Model加上Equatable协议即可:

struct Profile: Equatable {
    var id: Int
    var name: String
}

实现协议后,编译器会自动对数据进行比较,只有数据发生变化时,View才会重绘,能大幅提升页面性能。

7. List里的Model没加Identifiable

和上面的问题原理一致,List中使用的Model如果没有唯一标识,SwiftUI就无法区分列表中的各个元素。

struct User {
    let name: String
}

给Model加上Identifiable协议:

struct User: Identifiable {
    let id = UUID()
    let name: String
}

也可以在使用List时手动指定唯一标识:

List(users, id: \.name) { user in
    Text(user.name)
}

不过还是建议直接让Model遵循Identifiable协议,这样写起来更省事。

这些Swift开发中的错误我基本都踩过,有的还踩了不止一次。开发中你还踩过哪些坑?

我的笔记