From 7deb4fc5b4f71996821628fd308a1d2ab1345236 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 11 Apr 2020 15:33:39 -0400 Subject: [PATCH] Add LazilyDecoding for CoreData embedded objects --- Pachyderm/Model/Attachment.swift | 8 +-- Pachyderm/Model/Emoji.swift | 2 +- Pachyderm/Model/Mention.swift | 2 +- Tusker.xcodeproj/project.pbxproj | 3 + Tusker/CoreData/AccountMO.swift | 11 +++- Tusker/CoreData/StatusMO.swift | 13 ++++ .../Tusker.xcdatamodel/contents | 4 +- Tusker/LazilyDecoding.swift | 63 +++++++++++++++++++ 8 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 Tusker/LazilyDecoding.swift diff --git a/Pachyderm/Model/Attachment.swift b/Pachyderm/Model/Attachment.swift index 32bcee85..7a9fbbb2 100644 --- a/Pachyderm/Model/Attachment.swift +++ b/Pachyderm/Model/Attachment.swift @@ -8,7 +8,7 @@ import Foundation -public class Attachment: Decodable { +public class Attachment: Codable { public let id: String public let kind: Kind public let url: URL @@ -58,7 +58,7 @@ public class Attachment: Decodable { } extension Attachment { - public enum Kind: String, Decodable { + public enum Kind: String, Codable { case image case video case gifv @@ -68,7 +68,7 @@ extension Attachment { } extension Attachment { - public class Metadata: Decodable { + public class Metadata: Codable { public let length: String? public let duration: Float? public let audioEncoding: String? @@ -99,7 +99,7 @@ extension Attachment { } } - public class ImageMetadata: Decodable { + public class ImageMetadata: Codable { public let width: Int? public let height: Int? public let size: String? diff --git a/Pachyderm/Model/Emoji.swift b/Pachyderm/Model/Emoji.swift index bec0ea9d..a0a6d50f 100644 --- a/Pachyderm/Model/Emoji.swift +++ b/Pachyderm/Model/Emoji.swift @@ -8,7 +8,7 @@ import Foundation -public class Emoji: Decodable { +public class Emoji: Codable { public let shortcode: String public let url: URL public let staticURL: URL diff --git a/Pachyderm/Model/Mention.swift b/Pachyderm/Model/Mention.swift index ea086f34..2e60f4c6 100644 --- a/Pachyderm/Model/Mention.swift +++ b/Pachyderm/Model/Mention.swift @@ -8,7 +8,7 @@ import Foundation -public class Mention: Decodable { +public class Mention: Codable { public let url: URL public let username: String public let acct: String diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index b23acb78..4f42aa7c 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; }; D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; }; D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; }; + D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; }; D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; }; D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; }; D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; }; @@ -1236,6 +1237,7 @@ D6028B9A2150811100F223B9 /* MastodonCache.swift */, D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */, D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */, + D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */, D6F1F84E2193B9BE00F5FE67 /* Caching */, D6757A7A2157E00100721E32 /* XCallbackURL */, D62D241E217AA46B005076CC /* Shortcuts */, @@ -1664,6 +1666,7 @@ D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */, D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */, D60E2F272442372B005F8713 /* StatusMO.swift in Sources */, + D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */, D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */, D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */, D6A3BC852321F6C100FD64D5 /* AccountListTableViewController.swift in Sources */, diff --git a/Tusker/CoreData/AccountMO.swift b/Tusker/CoreData/AccountMO.swift index b46da8f9..74409cf8 100644 --- a/Tusker/CoreData/AccountMO.swift +++ b/Tusker/CoreData/AccountMO.swift @@ -9,6 +9,7 @@ import Foundation import CoreData +import Pachyderm @objc(AccountMO) public class AccountMO: NSManagedObject { @@ -22,8 +23,8 @@ public class AccountMO: NSManagedObject { @NSManaged public var bot: Bool @NSManaged public var createdAt: Date? @NSManaged public var displayName: String? - @NSManaged public var emojis: Data? - @NSManaged public var fields: Data? + @NSManaged public var emojisData: Data? + @NSManaged public var fieldsData: Data? @NSManaged public var followersCount: Int64 @NSManaged public var followingCount: Int64 @NSManaged public var header: URL? @@ -36,4 +37,10 @@ public class AccountMO: NSManagedObject { @NSManaged public var username: String? @NSManaged public var movedTo: AccountMO? + @LazilyDecoding(arrayFrom: \AccountMO.emojisData) + var emojis: [Emoji] + + @LazilyDecoding(arrayFrom: \AccountMO.fieldsData) + var fields: [Account.Field] + } diff --git a/Tusker/CoreData/StatusMO.swift b/Tusker/CoreData/StatusMO.swift index aacde0d5..34251ab5 100644 --- a/Tusker/CoreData/StatusMO.swift +++ b/Tusker/CoreData/StatusMO.swift @@ -9,6 +9,7 @@ import Foundation import CoreData +import Pachyderm @objc(StatusMO) public final class StatusMO: NSManagedObject { @@ -41,4 +42,16 @@ public final class StatusMO: NSManagedObject { @NSManaged public var inReplyToAccount: AccountMO? @NSManaged public var reblog: StatusMO? + @LazilyDecoding(arrayFrom: \StatusMO.attachmentsData) + var attachments: [Attachment] + + @LazilyDecoding(arrayFrom: \StatusMO.emojisData) + var emoji: [Emoji] + + @LazilyDecoding(arrayFrom: \StatusMO.hashtagsData) + var hashtags: [Hashtag] + + @LazilyDecoding(arrayFrom: \StatusMO.mentionsData) + var mentions: [Mention] + } diff --git a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents index 9bd9aa12..a4de90e0 100644 --- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents +++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents @@ -6,8 +6,8 @@ - - + + diff --git a/Tusker/LazilyDecoding.swift b/Tusker/LazilyDecoding.swift new file mode 100644 index 00000000..2930df58 --- /dev/null +++ b/Tusker/LazilyDecoding.swift @@ -0,0 +1,63 @@ +// +// LazyDecoding.swift +// Tusker +// +// Created by Shadowfacts on 4/11/20. +// Copyright © 2020 Shadowfacts. All rights reserved. +// + +import Foundation + +private let decoder = PropertyListDecoder() +private let encoder = PropertyListEncoder() + +@propertyWrapper +struct LazilyDecoding { + + private let keyPath: ReferenceWritableKeyPath + private let fallback: Value + private var value: Value? + + init(from keyPath: ReferenceWritableKeyPath, fallback: Value) { + self.keyPath = keyPath + self.fallback = fallback + } + + var wrappedValue: Value { + get { fatalError("called LazilyDecoding wrappedValue getter") } + set { fatalError("called LazilyDecoding wrappedValue setter") } + } + + static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath, storage storageKeyPath: ReferenceWritableKeyPath) -> Value { + get { + var wrapper = instance[keyPath: storageKeyPath] + if let value = wrapper.value { + return value + } else { + guard let data = instance[keyPath: wrapper.keyPath] else { return wrapper.fallback } + do { + let value = try decoder.decode(Value.self, from: data) + wrapper.value = value + instance[keyPath: storageKeyPath] = wrapper + return value + } catch { + return wrapper.fallback + } + } + } + set { + var wrapper = instance[keyPath: storageKeyPath] + wrapper.value = newValue + instance[keyPath: storageKeyPath] = wrapper + let newData = try? encoder.encode(newValue) + instance[keyPath: wrapper.keyPath] = newData + } + } + +} + +extension LazilyDecoding { + init(arrayFrom keyPath: ReferenceWritableKeyPath) { + self.init(from: keyPath, fallback: [] as! Value) + } +}