刷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开发中的错误我基本都踩过,有的还踩了不止一次。开发中你还踩过哪些坑?
