Swift 与 iOS 开发经验总结
原文由 Ellie Huxtable 于 发布,订阅该博客
首先声明一下,我绝不是什么专家。虽然我编程已经很多年了,但 Swift 和 iOS 对我来说还是全新的领域,而且我之前从未开发过移动应用 :) 以下就是我在开发 Pillion 首个 iOS 版本的过程中摸索和学到的一切!
Pillion 是一款帮你寻找摩托车停车位的应用——目前在全球收录了约 1 万个停车点,有的来自 OpenStreetMap,有的来自线上数据集,其余则由用户提供。除了停车,它还提供骑行记录功能
很多内容都是我在摸索阶段发现的,所以不少情况下我的做法并不是“正确”的方式。对于后来找到了更好做法的地方,我会把两种方式都写出来。
如果这篇文章反响不错,之后我可能会针对每个部分(尤其是 Mapbox)再写更详细的文章。
如果你发现任何错误,欢迎告诉我 😊
SwiftUI
我直接选用了 SwiftUI,也就是苹果随 iOS 13 推出的全新 UI 框架。需要注意的是,它只支持 iOS >= 13 的设备——不过这已经涵盖了 iPhone 6S 以后的所有机型。
由于我已经用了多年 React,SwiftUI 用起来感觉很舒服。虽然两者有所不同,但在组件/子组件/状态这些概念上却很自然。我甚至更喜欢 SwiftUI 的状态绑定方式,相比 React 的状态处理,代码看起来要简洁不少。
我没用过 UIKit,所以没太多可对比的,但就我目前看到的 SwiftUI 而言,它要简洁得多。
我在使用 SwiftUI 时也遇到了一些问题,似乎大多都和它还不够成熟有关——下面会具体讲到。
让人有点烦的是,我在 GitHub 上找到的那些好用的 UI 组件大多只支持 UIKit。也许以后我会尝试去封装一下,但 SwiftUI 的生态目前看起来还是有点单薄——这也正体现了它的不成熟。这点需要留意。
调试
Xcode 的调试器给了我一个惊喜!我平时用 Python 时习惯用 ipdb 调试,本以为代码跑在手机上又是编译型的,应该不会有那么灵活的调试体验,没想到却完全不是这样。
结果发现调试器可以轻松地查看变量值、执行 Swift 表达式,想做的都能做到!Tab 补全也很好用。为工具点赞 :)
偶尔会有点慢,不过这也在意料之中。
UI 线程相关
我的应用很大一部分逻辑就是向 API 发起请求,然后渲染结果。我发现如果尝试在后台线程(比如 HTTP 响应的异步回调)中更新 UI,就会报错。应该改用这种写法:
DispatchQueue.main.async {
// do ui
}这样就能非常轻松地避免阻塞 UI 线程 😁
这其实是我刚开始学 Swift 时就看到的技巧。后来我了解到,对于数据的获取和更新,更推荐的做法是在条件允许时使用带 Published 属性的 ObservableObjects!不过我还是把它留在这里,以备不时之需。我对目前项目中 HTTP 请求的处理方式还不太满意,未来很可能会重构这部分。
隐藏键盘
这只是个小细节,但我希望在某些交互时能隐藏键盘。比如,当搜索框处于焦点状态时,点击列表中的某一项就把键盘收起。
extension UIApplication {
func hideKeyboard() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}UIApplication.shared.hideKeyboard()Just
在 Swift 里发 HTTP 请求不算太难,但我已经习惯了 Python 里的 requests,所以想找个类似的库。
Just 看起来就很符合需求!它小巧、快速,用起来可以这样写:
Just.get("https://example.com") { r in
print(r.text)
}……非常方便!
它还允许你创建一个预设了请求头的 session,这对于需要鉴权的 HTTP 请求非常有用
Mapbox
在这个项目的大部分时间里,我都在使用 Mapbox Maps SDK for iOS。在 JavaScript 中使用 Mapbox 是一种享受,但坦白说 iOS 端就有些逊色了。定制起来有点别扭,文档也不够好。我经常能找到多种实现同一功能的方法,却几乎没有解释为什么要这么做。不过 Android 端在这方面似乎要好一些!
我并不是说它很差,只是相比 JS 版本——甚至跟我看到的 Android 版本相比——感觉还不太完善、有点半成品的意思。等我做完 Android 版再来做个对比。
还有一点是,有些文档更多是针对 UIKit 的——这也好理解,毕竟 SwiftUI 还很新!这点也需要注意。不过也有一些文档展示了如何为 SwiftUI 封装地图
Swift 类型检查
从 Rust 转过来,Swift 的类型检查器感觉有点……不够给力。偶尔它会完全无法推断出一个看似很明显的类型,或者耗时太长,然后提示开发者把代码拆一下。这算不上大问题,但确实有点烦人。不过听说这方面一直在持续改进!
有些情况下,它会在一个根本没问题的地方标错。把那段代码删掉,才会显示出真正的问题所在。我觉得这更多和 SwiftUI 的 DSL 有关。当 DSL 内部出现类型错误时,如果它无法推断类型,整个就会直接崩掉。我暂时找不到合适的例子,找到后会来更新。
在升级到 Catalina 和最新版 Xcode 后,我遇到了大量之前从未出现过的奇怪的 “ambiguous member” 错误。Swift 似乎很倾向于给出误导性的错误信息,这……有点奇怪。我从未在其他现代语言中遇到过这种情况,尤其这还是一家如此大的公司支持的语言。
后来我慢慢学会了写出 Swift 能理解的代码,尽管就我所知,我之前写的那些在语义上也是正确的。
SwiftFormat
我在其他项目中已经习惯使用格式化工具,所以在这里也找了一个。SwiftFormat 用起来不错
SwiftUI DSL 问题
在 SwiftUI 中,View 的 body 是用一种类似 DSL 的方式来声明的
var body: some View {
ComponentOne {
AnotherOne {
// so much nesting :O
}
}
}……这本身没什么问题。
然而,当涉及到条件渲染时,这个 DSL 似乎还不太完善。
if thing.ouch {
RenderMe(thing.ouch.thing)
}然而,像 if let 这样的写法,在本文撰写时还不支持
if let o = thing.ouch {
RenderMe(o.thing)
}虽然不是必需的,但如果能支持就好了!
DSL 还有一些其他零零碎碎的小问题需要解决,不过 SwiftUI 还很新/还处在 beta 阶段,所以我并不意外。除了这些问题,用起来还是很愉快的。能正常工作的时候,体验真的非常好!只是不正常的时候就…… ;)
Auth0
我目前使用的是 Auth0 Lock,支持 Google 和 Apple 的 OAuth 登录。实际上我现在已经换成了自己的方案,但这里的内容仍然有参考价值,所以保留了下来。
Apple 登录其实非常酷!用户可以选择使用 Apple 的转发邮箱来隐藏真实邮箱,还能用 Face ID 登录。
Lock 本身不是为 SwiftUI 设计的,不过封装起来相当容易:
struct Auth0Lock: UIViewControllerRepresentable {
typealias UIViewControllerType = LockViewController
var lock: Lock = Lock.passwordless().withOptions {
$0.passwordlessMethod = .magicLink
}
.onAuth { credentials in
print(credentials)
// save access token and use it to do stuff :)
}
.onError{ error in
print(error)
// you should probably do something other than just print errors
}
func makeUIViewController(context: UIViewControllerRepresentableContext<Auth0Lock>) -> LockViewController {
return lock.controller
}
func updateUIViewController(_ uiViewController: LockViewController, context: UIViewControllerRepresentableContext<Auth0Lock>) {
// there isn't really much updating I need to do atm
}
}Auth0 自带了一个凭证管理器,可以处理刷新令牌、请求新的访问令牌、保存和验证令牌等。所以要让用户保持登录状态,你其实不需要做太多工作!
顺便稍微解释一下我为什么弃用了 Auth0:我希望在鉴权处理上有更大的灵活性,而且后来我把后端重写成了 Django 应用——所以直接集成 Django 自带的功能就非常容易了。我切换到 Django 是为了更快地构建产品,而不是花时间去琢磨那些有趣的架构问题
Apple 开发者
为我的公司(Pillion Software Ltd)注册 Apple 开发者账号大概花了一到两周——在线填表很快,但审批花了一段时间,而且他们还给我打了电话。不过之后就一切顺利了!
我尝试用 Fastlane 来截图,但发现模拟器/测试跑得不太好(Mapbox 在 UI 测试中会崩溃),所以唯一可行的方法就是手动截几张。不太理想,我想以后再解决这个问题。迟早会去弄的。雪上加霜的是,我的笔记本又慢又旧(主要是内存不足),所以模拟器体验非常糟糕。
首次提交到 App Store 后,我被告知应用功能不够丰富。哎。研究了一番后发现,似乎任何可以做成网页应用的东西,苹果都不太愿意批准。于是我干脆把路线图上的一些功能提前实现了。
实际上,我有一个版本从点击“提交”到正式上线的整个审核流程只用了大约 7 分钟,让我印象非常深刻。
总结
回过头看,我觉得在沿用之前网页版的同一套技术之前,应该先多调研一下其他方案。也许 MapKit 就完全够用了,我也就不会用 Mapbox 了。
不过在这个过程中我确实学到了很多,所以我觉得也不会做太大改变。很快我就要开始做 Android 版了(而且我其实也还不会 Android 开发 ;P),到时候看看情况如何!可以期待一篇类似的文章。
如果你骑摩托车,并决定试试看,我很想听听你的想法——所以请随时联系我!
随机一篇博客
评论
登录后参与讨论