使用Chat GPT-4辅助开发的iOS应用开发经验 (#2)
我是如何连接后端并将数据保存到数据库中的
新手介绍
我是一名安卓开发者,从未接触过iOS,但是在Chat GPT的帮助下,我正在构建自己安卓应用Uncover的完整iOS版本。
这里是该系列介绍的链接 - https://medium.com/@andrzej.ryl/my-experience-building-an-ios-app-with-the-help-of-chat-gpt-4-intro-dc5e402a6ea8
这个故事将着重于后端连接和数据库处理。我最初的目标是打击我的后端,将数据保存在数据库中,然后使用它来基于这些数据进行初始导航。
上一次我完成了一个工作的闪屏界面,在2秒后导航到启动引导界面。看起来不错,但跟我在 Android 应用程序中实际使用的逻辑没有什么关系,所以是时候进行一些真正的编码了。
想法很简单 - 我们展示一个闪屏,打我的后端端点,下载一些初始数据(包括指定后端是否处于维护状态的信息),将其保存在数据库中,然后根据用户状态导航到7个屏幕/弹出窗口中的一个:-如果BE正在维护,则为后端维护屏幕-如果应用程序的版本不受支持,则需要应用程序更新弹出窗口-如果用户第一次使用应用程序,则为入门屏幕-如果用户未登录,则为登录屏幕-如果用户匿名登录并且他没有接受当前版本的使用条款,则为使用条款弹出窗口-如果出现任何问题,则为错误屏幕-否则为主屏幕
我以为我有一个简单的应用程序 ?
网络化
我开始用简单的信息在聊天中尝试连接到后台并检索一些数据。这次我做好了准备,并调整了提示,以立即跳过之前的问题。
Hi. Can you help me do a network call to my backend in Swift?
I am an experienced Android Developer so you can describe it to me
in reference to Retrofit library from Android.
I'm building iOS app in Swift and Swift UI targeting iOS 15.
If you don't know something, please ask or simply tell me. Don't lie
他的回复非常详细。他清楚地比较了 Retrofit 和 Swift 的 URLSession,向我解释了如何将对象序列化/反序列化为 JSON(在 iOS 世界中称为编码和解码),以及如何连接所有内容以获取我的响应。
我还要求他将这段代码实现到我的VIPER架构中,他为我准备好了一切。我甚至向他获取了一些信息,可以让我准备我的模型,以便它们在Swift中可读并与我的后端命名配合使用。以下是我得到的东西。
// Models
struct InitialDataReponse: Codable {
let isBeAvailable: Bool
let minAndroidAppVersion: Int
let currentTermsOfUseVersion: Int
let appVersions: [AppVersionResponse]
enum CodingKeys: String, CodingKey {
case isBeAvailable = "is_be_available"
case minAndroidAppVersion = "min_android_app_version"
case currentTermsOfUseVersion = "current_terms_of_use_version"
case appVersions = "app_versions"
}
}
struct AppVersionResponse: Codable {
let id: String
let name: String
let versionCode: Int
let priority: Int
let description: String
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case versionCode = "version_code"
case priority = "priority"
case description = "description"
}
}
// UncoverApi (something like Retrofit interface for all endpoints)
import Foundation
enum UncoverApi {
case initialData
var path: String {
switch self {
case .initialData:
return PATH_TO_ENDPOINT
}
}
var url: URL {
let backendURL = Config.shared.backendURL!
let urlString = backendURL + self.path
return URL(string: urlString)!
}
}
// UncoverServiceApi (something like built Retrofit instance)
import Foundation
class UncoverApiService {
func getInitialData(
completion: @escaping (Result<InitialDataReponse, Error>) -> Void
) {
fetchResponse(
from: UncoverApi.initialData.url.absoluteString,
completion: completion
)
}
}
他甚至很好心地帮我轻松地找出了我的错别字。我在我的AppVersionResponse模型中打错了一个字段,XCode给我返回了一个不太有用的错误信息“Type‘AppVersionResponse’ does not conform to protocol‘Decodable’”。我只是把我的模型复制到聊天里面,告诉他这个错误,他立刻告诉我一个字段错拼了? 我认为将Chat集成到XCode将成为我们iOS朋友的一大利器 ? Android Studio做得更好。
这次把所有东西都连起来非常容易。我只需告诉Chat我需要什么,他就毫不费力地说了出来,毫不犹豫。
I want to have a repository between interactor and apiservice.
This repository should call ApiService method, map model to domain one
and then return it through callback to interactor
这就是我得到的。
protocol Repository {
func getAppVersion(
completion: @escaping (Result<AppVersion, Error>) -> Void
)
}
class AppVersionRepository: Repository {
private let apiService: APIService
init(apiService: APIService) {
self.apiService = apiService
}
func getAppVersion(completion: @escaping (Result<AppVersion, Error>) -> Void) {
apiService.fetch(from: .appVersion) { (result: Result<AppVersionResponse, Error>) in
switch result {
case .success(let response):
let appVersion = self.mapToDomainModel(response: response)
completion(.success(appVersion))
case .failure(let error):
completion(.failure(error))
}
}
}
private func mapToDomainModel(response: AppVersionResponse) -> AppVersion {
return AppVersion(id: response.id, name: response.name, versionCode: response.versionCode, priority: response.priority, description: response.description)
}
}
正如你所看到的,Chat只是基本上准备了一个抓取app版本的逻辑,但将它更改为实际需要的内容非常简单。我将它分成了代码中的层和模块,就这样!我只需要基本测试一下即可。几秒钟后我在终端中看到了这个。
Received JSON:
{
"id":1,
"app_versions":[],
"is_be_available":true,
"min_android_app_version":0,
"current_terms_of_use_version":2
}
然后哇!我的应用程序刚刚打开了启动屏幕,调用了后端,并根据收到的数据决定导航到主屏幕。
这里要注意的一件重要事情是,在这个过程中,Chat又犯了一个错误。他最初提供了错误的代码来构建 100% 确定的绝对 URL。
var url: URL {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "yourapi.com"
urlComponents.path = self.path
return urlComponents.url!
}
但我必须承认,我只是复制了我看到的问题,他立即知道了问题所在并纠正了自己。
// Looking back at the code for the API enum, it seems I made an error.
// The url computed property is incorrectly defined.
// It creates a new URL instance each time it's called, rather
// than appending to a base URL.
// Here's how you can modify the API enum to correctly append the path
// to your base URL:
var url: URL {
let backendURL = Config.shared.backendURL!
let urlString = backendURL + self.path
return URL(string: urlString)!
}
它有用,但再次——他起初误导了我,所以他帮助的紧迫感仍比正确更重要,这可能非常令人恼火。
保存数据
完成这个简单的练习后,我想要深入探究。实际上,保存从网络中获取的数据,将其与本地数据混合(如应用程序打开计数器),并在我的代码中创建更高级的逻辑。
我打开了一个与我的人工智能朋友的单独聊天窗口(我已经学会了过长的对话会使他感到困惑),然后简单地询问了一下。
Hi. Can you help me with saving and retrieving data in Swift
from local storage? I am an experienced Android Developer so you
can describe it to me in reference to SharedPreferences library from Android.
I'm building iOS app in Swift and Swift UI targeting iOS 15.
If you don't know something, please ask or simply tell me.
Don't lie or try to guess. I want you to only show the code that you
are certain is going to work
他详细介绍了UserDefaults,这正是我所需要的-一种简单的测试键值存储。他警告我,这种方法仅用于存储非敏感数据,因为它很容易被攻击者读取。这是一个很好的补充。
关于我的最后一次询问,他基本上已经“找了律师”?他立即加入了这个特殊注释以保护他在“错误”情况下的权益。
This information is up-to-date and relevant to your target of iOS 15
as of my training cut-off in September 2021. If Apple has introduced
new APIs or libraries for managing local storage after that time,
I would recommend checking the latest documentation or resources online.
我想我不能真正指望他在发布任何代码之前就百分之百确定,因为即使是人类也不能达到百分之百的确定性。
然而,在那个简单的解释之后,我很容易地设置了整个存储/检索本地数据的“框架”,甚至为不同目的设置了单独的存储。这是一个简单的工作,但Chat帮助我解决了一些小问题。我不得不承认,编写这部分代码是一件非常容易的事情。
我上次在聊天中完成的有趣任务是解析iOS应用版本号。在Android中,我们有versionName和versionCode,但在iOS中,似乎只有versionName作为一个字符串。不过,我有一个基于版本号的逻辑,所以我向Chat请求解析它。他立刻明白我的意思,我认为这有点酷。
// ME
let's say the version is "1.2.0" - can I parse this to retrieve
major, minor and patch numbers as ints?
// CHAT
Yes, you can parse a version string to retrieve the major,
minor, and patch numbers as Int. Here's how you can do it in Swift:
let version = "1.2.0"
let versionComponents = version.split(separator: ".").compactMap { Int($0) }
if versionComponents.count == 3 {
let major = versionComponents[0]
let minor = versionComponents[1]
let patch = versionComponents[2]
print("Major: \(major), Minor: \(minor), Patch: \(patch)")
}
几分钟后,我几乎有了所有我需要的东西。
- 应用程序以闪屏开始。
- 它向后端发出一个调用请求
- 在本地存储中保存所需数据
- 增加应用打开计数器
- 检查来自服务器和本地保存数据的初始数据混合(版本号和应用打开计数)。
- 决定显示哪个屏幕
我跳过了登录信息,因为认证还没有设置好,但我会处理的。
#2摘要
我不得不说这次工作进展得很顺利。我不确定是因为我将聊天分成了两个部分(一个用于网络,一个用于本地数据),而不是在一个聊天中完成所有工作,还是因为任务变得更加简单了。尽管如此,这次我在很短的时间内取得了一些很酷的进展。我不得不说我开始有希望了 ?
我们会等到多久呢 ?
下一部分是UI设计…我会尝试构建完整的引导屏幕,添加一些简单的滑动效果。我们将会看到它的效果如何。