[英]Asynchronous closure recursion for structs
我正在為 Hacker News 編寫一個網絡客戶端。 我正在使用他們的官方 API 。
我無法修改我的網絡客戶端以使用結構而不是故事評論的類。 它適用於類,尤其是異步遞歸閉包。
這是我的數據 model。
class Comment: Item {
var replies: [Comment?]?
let id: Int
let isDeleted: Bool?
let parent: Int
let repliesIDs: [Int]?
let text: String?
let time: Date
let type: ItemType
let username: String?
enum CodingKeys: String, CodingKey {
case isDeleted = "deleted"
case id
case parent
case repliesIDs = "kids"
case text
case time
case type
case username = "by"
}
}
這是我的網絡客戶端的示例。
class NetworkClient {
// ...
// Top Level Comments
func fetchComments(for story: Story, completionHandler: @escaping ([Comment]) -> Void) {
var comments = [Comment?](repeating: nil, count: story.comments!.count)
for (commentIndex, topLevelCommentID) in story.comments!.enumerated() {
let topLevelCommentURL = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(topLevelCommentID).json")!
dispatchGroup.enter()
URLSession.shared.dataTask(with: topLevelCommentURL) { (data, urlResponse, error) in
guard let data = data else {
print("Invalid top level comment data.")
return
}
do {
let comment = try self.jsonDecoder.decode(Comment.self, from: data)
comments[commentIndex] = comment
if comment.repliesIDs != nil {
self.fetchReplies(for: comment) { replies in
comment.replies = replies
}
}
self.dispatchGroup.leave()
} catch {
print("There was a problem decoding top level comment JSON.")
print(error)
print(error.localizedDescription)
}
}.resume()
}
dispatchGroup.notify(queue: .global(qos: .userInitiated)) {
completionHandler(comments.compactMap { $0 })
}
}
// Recursive method
private func fetchReplies(for comment: Comment, completionHandler: @escaping ([Comment?]) -> Void) {
var replies = [Comment?](repeating: nil, count: comment.repliesIDs!.count)
for (replyIndex, replyID) in comment.repliesIDs!.enumerated() {
let replyURL = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(replyID).json")!
dispatchGroup.enter()
URLSession.shared.dataTask(with: replyURL) { (data, _, _) in
guard let data = data else { return }
do {
let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply
if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
reply.replies = replies
}
}
self.dispatchGroup.leave()
} catch {
print(error)
}
}.resume()
}
dispatchGroup.notify(queue: .global(qos: .userInitiated)) {
completionHandler(replies)
}
}
}
您像這樣調用網絡客戶端來獲取特定故事的評論樹。
var comments = [Comment]()
let networkClient = NetworkClient()
networkClient.fetchStories(from: selectedStory) { commentTree in
// ...
comments = commentTree
// ...
}
將注釋 class 數據 model 切換到 struct 不能很好地與異步閉包遞歸配合使用。 它適用於類,因為引用了類而復制了結構,這會導致一些問題。
如何調整我的網絡客戶端以使用結構? 有沒有辦法將我的方法重寫為一個方法而不是兩個? 一種方法是針對頂級(根)評論,而另一種是針對每個頂級(根)評論回復的遞歸。
考慮這個代碼塊
let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply
if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
reply.replies = replies
}
}
如果Comment
是一個結構,這將獲取reply
,將其副本添加到replies
數組,然后,在fetchReplies
中,您正在改變原始reply
(您必須將其從let
更改為var
才能編譯),不是數組中的副本。
因此,您可能希望在fetchReplies
閉包中引用replies[replyIndex]
,例如:
let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply
if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
replies[replyIndex].replies = replies
}
}
順便一提,
data
為nil
或reply.repliesIDs
為nil
或 JSON 解析失敗); 和reply.repliesIDs
不是nil
,則必須將leave()
調用移動到該完成處理程序閉包中)。我還沒有測試過,但我建議如下:
private func fetchReplies(for comment: Comment, completionHandler: @escaping ([Comment?]) -> Void) {
var replies = [Comment?](repeating: nil, count: comment.repliesIDs!.count)
let group = DispatchGroup() // local var
for (replyIndex, replyID) in comment.repliesIDs!.enumerated() {
let replyURL = URL(string: "https://hacker-news.firebaseio.com/v0/item/\(replyID).json")!
group.enter()
URLSession.shared.dataTask(with: replyURL) { data, _, _ in
guard let data = data else {
group.leave() // leave on failure, too
return
}
do {
let reply = try self.jsonDecoder.decode(Comment.self, from: data)
replies[replyIndex] = reply
if reply.repliesIDs != nil {
self.fetchReplies(for: reply) { replies in
replies[replyIndex].replies = replies
group.leave() // if reply.replieIDs was not nil, we must not `leave` until this is done
}
} else {
group.leave() // leave if reply.repliesIDs was nil
}
} catch {
group.leave() // leave on failure, too
print(error)
}
}.resume()
}
dispatchGroup.notify(queue: .main) { // do this on main to avoid synchronization headaches
completionHandler(replies)
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.