簡體   English   中英

結構的異步閉包遞歸

[英]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
    }
}

順便一提,

  • 調度組不能是一個屬性,而必須是一個本地變量(尤其是當您似乎遞歸調用此方法時;);
  • 您有多個執行路徑,您不會離開該組(如果datanilreply.repliesIDsnil或 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM