長話短說,我必須comments按照以下結構實作帶有嵌套注釋(1 級)的視圖控制器:
-- Comment
-- reply to comment
-- reply to comment
-- Comment
-- Comment
我曾詢問后端是否可以在內部提供可選的子評論陣列 ( let children[Comment]?),因此,當我洗掉集合視圖單元格中的父項時,我不必等待后端重新加載資料,從當前資料源。
相反,后端提出了這個 json,如果id和root_comment_id相同,您可以在其中了解哪個是父級。如果不是,則所有后續評論都屬于第一個物件。??
{
"result": [
{
"id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"post_id": "03486c50-6a4a-48a3-a68e-374cf42686d8",
"poster": {
"id": "5b52c4ed-bd21-49fe-9439-8722e4223d50",
"username": "foobar",
"fullname": "foo bar",
"avatar_url": null
},
"body": "Nice Post",
"root_comment_id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"to_poster": null,
"created_at": "2021-11-05T14:38:15.000Z"
},
{
"id": "43fb2e48-2aae-4b01-bafa-456b927444d5",
"post_id": "03486c50-6a4a-48a3-a68e-374cf42686d8",
"poster": {
"id": "5b52c4ed-bd21-49fe-9439-8722e4223d50",
"username": "foobar",
"fullname": "foo bar",
"avatar_url": null
},
"body": "Yes I like it too",
"root_comment_id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"to_poster": null,
"created_at": "2021-11-05T14:38:46.000Z"
},
{
"id": "11a4c5d6-9db8-47c1-a472-947d8d1ac81a",
"post_id": "03486c50-6a4a-48a3-a68e-374cf42686d8",
"poster": {
"id": "5b52c4ed-bd21-49fe-9439-8722e4223d50",
"username": "foobar",
"fullname": "foo bar",
"avatar_url": null
},
"body": "Awesome!",
"root_comment_id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"to_poster": {
"id": "788343e4-3695-44e5-bda4-9b0593b7a496",
"username": "matthewzorpas",
"fullname": "Matthew Zorpas",
"avatar_url": null
},
"created_at": "2021-11-05T14:39:45.000Z"
}
],
"statusCode": 200
}
這對我來說沒有意義,但由于他們不會改變它,我必須映射這個 json 并制作我自己的模型,它看起來像這樣:
struct CommentResult: Codable {
let result: [Comment]
let statusCode: Int
}
// MARK: - Result
struct Comment: Codable {
let commentID, postID: String
let poster: Poster
let body: String?
let rootCommentID: String
let toPoster: Poster?
let createdAt: Date
let children:[Comment]? // I would like to add this and append children with a root_id == to the parent id
enum CodingKeys: String, CodingKey {
case commentID = "id"
case postID = "post_id"
case poster
case body
case rootCommentID = "root_comment_id"
case toPoster = "to_poster"
case createdAt = "created_at"
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.commentID = try container.decode(String.self, forKey: .commentID)
self.postID = try container.decode(String.self, forKey: .postID)
self.poster = try container.decode(Poster.self, forKey: .poster)
self.toPoster = try container.decodeIfPresent(Poster.self, forKey: .toPoster)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
let dateString = try container.decode(String.self, forKey: .createdAt)
self.createdAt = dateFormatter.date(from: dateString) ?? Date()
self.body = try container.decodeIfPresent(String.self, forKey: .body)
self.rootCommentID = try container.decode(String.self, forKey: .rootCommentID)
}
}
我知道我可能必須使用,filter但我一直在努力尋找不增加時間復雜度的正確方法。
所以 json 看起來像這樣:
{
"result": [
{
"id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"post_id": "03486c50-6a4a-48a3-a68e-374cf42686d8",
"poster": {
"id": "5b52c4ed-bd21-49fe-9439-8722e4223d50",
"username": "foobar",
"fullname": "foo bar",
"avatar_url": null
},
"body": "Nice Post",
"root_comment_id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"to_poster": null,
"created_at": "2021-11-05T14:38:15.000Z",
"children": [ {
"id": "43fb2e48-2aae-4b01-bafa-456b927444d5",
"post_id": "03486c50-6a4a-48a3-a68e-374cf42686d8",
"poster": {
"id": "5b52c4ed-bd21-49fe-9439-8722e4223d50",
"username": "foobar",
"fullname": "foo bar",
"avatar_url": null
},
"body": "Yes I like it too",
"root_comment_id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"to_poster": null,
"created_at": "2021-11-05T14:38:46.000Z"
},
{
"id": "11a4c5d6-9db8-47c1-a472-947d8d1ac81a",
"post_id": "03486c50-6a4a-48a3-a68e-374cf42686d8",
"poster": {
"id": "5b52c4ed-bd21-49fe-9439-8722e4223d50",
"username": "foobar",
"fullname": "foo bar",
"avatar_url": null
},
"body": "Awesome!",
"root_comment_id": "bedcab34-f6b7-44a9-ab81-6d443ada580e",
"to_poster": {
"id": "788343e4-3695-44e5-bda4-9b0593b7a496",
"username": "matthewzorpas",
"fullname": "Matthew Zorpas",
"avatar_url": null
},
"created_at": "2021-11-05T14:39:45.000Z"
}
]
}
],
"statusCode": 200
}
uj5u.com熱心網友回復:
如果您嘗試實作自定義解碼演算法,您將遇到非常艱難的時期。我會保留評論并將評論映射到一組評論。在這個例子中,我添加了一些方便的方法和一個名為CommentCluster. 另請注意,Comment為了簡潔起見,我縮短了定義。
struct Comment: Codable {
let commentID: String
let postID: String
let body: String
let rootCommentID: String
let createdAt: Date
}
extension Comment: Equatable {
static func == (lhs: Comment, rhs: Comment) -> Bool {
lhs.commentID == rhs.commentID
}
}
struct CommentCluster {
let root: Comment
var children: [Comment]
}
extension Comment {
var isRoot: Bool { rootCommentID == commentID }
func isChild(of parent: Comment) -> Bool {
rootCommentID == parent.commentID
}
}
這將有助于除錯:
extension Comment: CustomStringConvertible {
var description: String {
"\(commentID)"
}
}
extension CommentCluster: CustomStringConvertible {
var description: String {
"root=\(root), children=\(children)"
}
}
在這個例子中,我假設評論是按原樣解碼的。用您的解碼注釋替換硬編碼注釋。
class CommentChildrenTests: XCTestCase {
let comments: [Comment] = [
.init(commentID: "Comment - 0", postID: "Post 0", body: "Comment - 0", rootCommentID: "Comment - 0", createdAt: .now 0.0),
.init(commentID: "Comment - 1", postID: "Post 0", body: "Comment - 1", rootCommentID: "Comment - 1", createdAt: .now 1.0),
.init(commentID: "Comment - 2", postID: "Post 0", body: "Comment - 2", rootCommentID: "Comment - 2", createdAt: .now 2.0),
.init(commentID: "Comment - 3", postID: "Post 0", body: "Comment - 3", rootCommentID: "Comment - 3", createdAt: .now 3.0),
.init(commentID: "Comment - 2-0", postID: "Post 0", body: "Comment - 2-0", rootCommentID: "Comment - 2", createdAt: .now 4.0),
.init(commentID: "Comment - 2-1", postID: "Post 0", body: "Comment - 2-1", rootCommentID: "Comment - 2", createdAt: .now 5.0),
.init(commentID: "Comment - 3-0", postID: "Post 0", body: "Comment - 3-0", rootCommentID: "Comment - 3", createdAt: .now 6.0),
.init(commentID: "Comment - 3-1", postID: "Post 0", body: "Comment - 3-1", rootCommentID: "Comment - 3", createdAt: .now 7.0),
]
func testIt() throws {
let comments = comments
.shuffled()
.sorted(by: { lhs, rhs in
lhs.createdAt < rhs.createdAt
})
let rootComments = comments
.filter { $0.isRoot }
let clusters = rootComments
.map { root -> CommentCluster in
let children = comments
.filter { $0 != root && $0.isChild(of: root) }
return CommentCluster(root: root, children: children)
}
print("=============")
for cluster in clusters {
print(cluster)
}
print("=============")
}
}
正如您在示例中看到的那樣,我正在洗牌和排序。您沒有提到 JSON 中注釋的順序。因此,您可能需要也可能不需要排序。我只是想確保評論和回復按順序列出。這可能很重要。
另外,我注意到您正在使用自定義 CodingKeys 和 DateFormatter。如果您JSONDecoder正確設定了屬性,則這不是必需的。退房keyDecodingStrategy和dateDecodingStrategy上JSONDecoder
如果您的團隊對此類解決方案的性能不滿意,請推回后端團隊。他們應該以對您的移動應用程式有用的格式為您提供資料。
uj5u.com熱心網友回復:
你是對的,JSON 應該采用不同的格式,但這并不總是可行的,這就是 DTO 模式存在的原因。
在我的解決方案中,我們將使用符合后端結構的 CommentDTO 物件和描述應用程式使用的物件的另一個 Comment 物件。
第一步是創建 CommentDTO 結構:
// MARK: - CommentDTO
struct CommentDTO: Codable {
let id, postID: String
let poster: Poster
let body, rootCommentID: String?
let toPoster: Poster?
let createdAt: String
enum CodingKeys: String, CodingKey {
case id
case postID = "post_id"
case poster, body
case rootCommentID = "root_comment_id"
case toPoster = "to_poster"
case createdAt = "created_at"
}
}
// MARK: - Poster
struct Poster: Codable {
let id, username, fullname: String
let avatarURL: String?
enum CodingKeys: String, CodingKey {
case id, username, fullname
case avatarURL = "avatar_url"
}
}
typealias CommentDTOS = [CommentDTO]
第二步是創建 Comment 結構,在這種情況下,我更喜歡使用“Class”而不是“struct”來添加對每個評論 Root 和 Children 的直接參考。如果您使用“struct”,您可以只擁有 Children 參考,因為 struct 不能具有遞回包含它的存盤屬性。
class Comment {
let id, postID : String
let poster : Poster
let body : String?
let toPoster : Poster?
let createdAt : String
let rootComment : Comment?
var childrenComments : [Comment]?
init(commentDto : CommentDTO)
{
self.id = commentDto.id
self.postID = commentDto.postID
self.poster = commentDto.poster
self.body = commentDto.body
self.toPoster = commentDto.toPoster
self.createdAt = commentDto.createdAt
self.rootComment = nil
self.childrenComments = nil
}
init(commentDto : CommentDTO, root : Comment)
{
self.id = commentDto.id
self.postID = commentDto.postID
self.poster = commentDto.poster
self.body = commentDto.body
self.toPoster = commentDto.toPoster
self.createdAt = commentDto.createdAt
self.rootComment = root
self.childrenComments = nil
}
func addChild(comment : Comment)
{
if self.childrenComments == nil
{
self.childrenComments = []
}
self.childrenComments?.append(comment)
}
}
該演算法非常簡單,并像您說的那樣使用過濾器。現在我們有一個 CommentDTO 陣列,我們將選擇 id 等于 rootCommentID 的第一個評論,構造一個 CommentObject 并將其放入 Map 以在 O(1) 時間內執行一些操作。在我們只選擇不是根的元素之后,我們將它們添加到我們的結構中。
if let url = Bundle.main.url(forResource: "Comments", withExtension: "json") {
do {
//1 : Reading data from JSON and put in an Array of CommentDTO
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let commentDTOS = try decoder.decode(CommentDTOS.self, from: data)
//2 : Create a Map that contain only Comment Rootes for doing some operation in O(1)
var commentMap : [String : Comment] = [:]
//3 : Using Filter for select only roots
let roots = commentDTOS.filter { $0.id == $0.rootCommentID }
//4 : Create a Root Comment and put it in a Map
roots.forEach {
let comment = Comment(commentDto: $0)
commentMap[$0.id] = comment
}
//5 : Using Filter for select only children
let children = commentDTOS.filter { $0.id != $0.rootCommentID }
children.forEach {
//6 : Retrieve root from Map
let root = commentMap[$0.rootCommentID!]
if let root = root
{
//7 : Create comment and update Root Children list
let comment = Comment(commentDto: $0, root: root)
root.addChild(comment: comment)
}
}
let output = Array(commentMap.values) as [Comment]
} catch {
print("error:\(error)")
}
}
'output' 是一個只包含根的 Comment 陣列,你可以通過它們的參考訪問子元素。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/361634.html
