这两天又捡起来了之前开的一个老坑,继续完成 X 岛揭示板 的 iOS 客户端,而且刚刚完成了从 JSON 初始化版面列表的功能。 这部分感觉最难的还是上手 Alamofire
,因为它返回结果不像我平时做 Web 开发那样通过方法返回(也有可能是我没学到位),而是要把反序列化得到的对象传给一个回调方法。而这个思路的差异也导致我刚开始学的时候非常的痛苦,因为怎么也找不到我想要的那种返回方式。 我相信应该不止我一个人会遇到这种情况,所以打算在这里把完整的实现过程记录在这里,并希望后面有类似情况的同志能因为这篇文章而少掉几根头发。
定义 JSON 对应的结构体 X 岛揭示板的版面列表
API 会返回一个类似这样的 JSON:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [ { "id" : "4" , "sort" : "1" , "name" : "综合" , "status" : "n" , "forums" : [ { "id" : "-1" , "name" : "时间线" , "msg" : "这里是匿名版最新的串" } , { "id" : "23" , "fgroup" : "3" , "sort" : "12" , "name" : "暴雪游戏" , "showName" : "暴雪游戏" , "msg" : "•本版发文间隔为15秒。" , "interval" : "15" , "safe_mode" : "0" , "auto_delete" : "0" , "thread_count" : "72" , "permission_level" : "0" , "forum_fuse_id" : "0" , "createdAt" : "2012-05-25 21:21:21" , "updateAt" : "2015-04-21 12:30:39" , "status" : "n" } ] } ]
所以,我们可以创建一个这样的结构体来用来反序列化它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 struct ForumGroup : Codable , Identifiable { var id: String var sort: String var name: String var status: String var forums: [Forum ] private enum CodingKeys : String , CodingKey { case id case sort case name case status case forums } } struct Forum : Codable , Identifiable { var id: String = "" var fGroup: String ? = "" var sort: String ? = "" var name: String = "" var showName: String ? = "" var msg: String = "" var interval: String ? = "" var threadCount: String ? = "" var permissionLevel: String ? = "" var forumFuseId: String ? = "" var createdAt: String ? = "" var updateAt: String ? = "" var status: String ? = "" private enum CodingKeys : String , CodingKey { case id case fGroup case sort case name case showName case msg case interval case threadCount = "thread_count" case permissionLevel = "permission_level" case forumFuseId = "forum_fuse_id" case createdAt case updateAt case status } }
编写网络请求 创建一个新的 Swift 文件 AnoBbsApiClient
,编写如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 final class AnoBbsApiClient { private static let logger = LoggerHelper .getLoggerForNetworkRequest(name: "AnoBbsApiClient" ) public static func loadForumGroups ( completion :@escaping ([ForumGroup ]) -> Void , failure :@escaping (String ) -> Void ) { let url = URL (string: XdnmbAPI .GET_FORUM_LIST )! AF .request(url, method: .get, interceptor: .retryPolicy) { $0 .timeoutInterval = 10 } .cacheResponse(using: .cache) .validate() .responseDecodable(of: [ForumGroup ].self ) { response in switch response.result { case .success(let data): completion(data) case .failure(let error): failure(error.localizedDescription) } } } }
是的,就这几行代码,花了我大概一整天时间来学明白,定稿之前不知道来来回回试了多少遍。前面都很好懂,重点就是 responseDecodable
这个方法调用,of
参数指明我希望把返回的 JSON 反序列化成一个 ForumGroup
列表,后面的方法块中根据成功反序列化和发生任何错误的情况,分别调用 completion
和 failure
这两个回调方法。
渲染列表 现在回到展示版面的 ForumsView
,在 body
里面做如下实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 var body: some View { NavigationStack { List { ForEach ($forumGroups ) { $forumGroup in Section { ForEach (forumGroup.forums) { forum in NavigationLink (destination: CookieListView (globalState: globalState)) { if (forum.showName == nil || forum.showName! .isEmpty) { Text (forum.name) } else { Text (forum.showName! ) } } } } header: { Text (forumGroup.name) } } } } .onAppear { if (! isContentLoaded) { globalState.loadingStatus = String (localized: "msgLoadingForumList" ); shouldDisplayProgressView = true ; AnoBbsApiClient .loadForumGroups { forumGroups in self .forumGroups = forumGroups isContentLoaded = true shouldDisplayProgressView = false ; } failure: { error in showErrorToast(message: error) shouldDisplayProgressView = false ; } } } .toast(isPresenting: $isErrorToastShowing ) { AlertToast (type: .regular, title: errorMessage) } }
在这个 View 展示时,如果版面列表没有被加载,那么就调用刚刚写的 loadForumGroups
方法获取版面列表,后面的第一个代码块就是 completion
这个回调的实现,负责把 loadForumGroups
方法得到的结果传给一个 @State
变量 forumGroups
,以及标记内容已经成功载入,并隐藏载入提示的风火轮;第二个代码块是 failure
这个回调的实现,负责显示一个带有错误信息的 Toast
并隐藏风火轮。
在这个 View
的 NavigationStack
里面,就可以监听 forumGroups
这个 @State
变量,并用变量里面的内容来渲染整个列表了。最后,我们就可以得到这样一个结果: