Swift5:将复杂对象保存到文件存储

Swift 5: saving complex objects to file storage

我正在做一项学校作业,为此我必须在 Swift 中开发一个 iOS 应用程序 5. 该应用程序需要使用 Web 服务(一个 web-API ) 和文件存储或用户默认值。

我选择开发一个“QR 码管理器”,用户可以在其中通过设置一些设计参数为 URL 创建 QR 码,然后将其发送到生成器 API . API(根据 OK 请求)returns 指定格式的图像(在我的例子中是 PNG)。

我有一个 class 和 URL 以及 QR 码的所有设计属性,其中还将包含图像本身。请参阅下面的 class.

代码段
public class QRCode {
    
    var bsId : Int?
    var url : String?
    var name: String?
    var frame: Frame?
    var logo: QrCodeLogo?
    var marker: Marker?
    var color : String?
    var bgColor : String?
    var image : Data?
    
    
    init(data: [String:String]) {
        self.url = data["url"]
        self.frame = Frame.allCases.first(where: { [=11=].description == data["frame"] })
        self.logo = QrCodeLogo.allCases.first(where: { [=11=].description == data["logo"] })
        self.marker = Marker.allCases.first(where: { [=11=].description == data["marker"] })
        self.bgColor = data["backGroundColor"]
        self.color = data["colorLight"]
    }
    
    init(json: String) {
        // todo
    }
}

extension QRCode {
    func toDict() -> [String:Any] {
        var dict = [String:Any]();
        let otherSelf = Mirror(reflecting: self);
        for child in otherSelf.children {
            if let key = child.label {
                dict[key] = child.value;
            }
        }
        return dict;
    }
}

为了便于开发,所有属性都可以为 null,class 将在我成功实现所有内容后进一步重构。

我尝试了在 Internet 上找到的各种方法,其中一种可以在 class 扩展中找到。函数 toDict() 将对象属性及其值转换为类型 [String:Any]Dictionary 对象。但是,我读到当 Any 数据类型被编码然后解码时,Swift 无法确定解码数据应该是哪种复杂数据类型,有效地使数据变得无意义或无法使用。

我发现的另一种方法是通过扩展 class 中的 Codable-协议。然而,据我所知,Codable 只接受原始数据类型。

请在下面找到我当前编写的文件存储处理代码。还没有完成,但我觉得这是一个好的开始,可能会对这个问题有所帮助。


class StorageManager {
    
    fileprivate let filemanager: FileManager = FileManager.default;
    
    fileprivate func filePath(forKey key: String) -> URL? {
        guard let docURL = filemanager.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else {
            return nil;
        }
        
        return docURL.appendingPathComponent(key);
    }
    
    func writeToStorage(identifier: String, data: QRCode) -> Void {
        guard let path = filePath(forKey: identifier) else {
            throw ApplicationErrors.runtimeError("Something went wrong writing the file to storage");
        }
        
        let dict = data.toDict();
        // TODO:: Implement
    }
    
    func readFromStorage(identifier: String) -> Any {
        // TODO:: Implement
        return 0;
    }
    
    func readAllFromStorage() throws -> [URL] {
        let docsURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0];
        
        do {
            let fileURLs = try filemanager.contentsOfDirectory(at: docsURL, includingPropertiesForKeys: nil);
            
            return fileURLs;
        } catch {
            throw ApplicationErrors.runtimeError("Something went wrong retrieving the files from \(docsURL.path): \(error.localizedDescription)");
        }
    }
}

我是 Swift 的新手,我 运行 卡在了文件存储上。有什么方法可以将此 class 的实例存储在文件存储中,以便在我检索数据时可以重新实例化此 class?

提前致谢!如有任何问题,请随时提出。

编辑

根据 matt 的评论,请在下面找到 MarkerFrameQrCodeLogo 枚举的代码片段。 Frame 枚举:


public enum Frame: String, CaseIterable {
    case noFrame
    case bottomFrame
    case bottomTooltip
    case topHeader
    static var count: Int { return 4 }
    
    var description: String {
        switch self {
        case .noFrame:
            return "no-frame"
        case .bottomFrame:
            return "bottom-frame"
        case .bottomTooltip:
            return "bottom-tooltip"
        case .topHeader:
            return "top-header"
        }
    }
}

QrCodeLogo枚举:

public enum QrCodeLogo: String, CaseIterable {
    case noLogo
    case scanMe
    case scanMeSquare
    static var count: Int { return 3 }
    
    var description: String {
        switch self {
        case .noLogo:
            return "no-logo"
        case .scanMe:
            return "scan-me"
        case .scanMeSquare:
            return "scan-me-square"
        }
    }
}

Marker枚举:

public enum Marker: String, CaseIterable {
    case version1
    case version2
    case version3
    case version4
    case version5
    case version6
    case version7
    case version8
    case version9
    case version10
    case version11
    case version12
    case version13
    case version15
    case version16
    static var count: Int { return 15 }
    
    var description: String {
        switch self {
        case .version1:
            return "version1"
        case .version2:
            return "version2"
        case .version3:
            return "version3"
        case .version4:
            return "version4"
        case .version5:
            return "version5"
        case .version6:
            return "version6"
        case .version7:
            return "version7"
        case .version8:
            return "version8"
        case .version9:
            return "version9"
        case .version10:
            return "version10"
        case .version11:
            return "version11"
        case .version12:
            return "version12"
        case .version13:
            return "version13"
        case .version15:
            return "version15"
        case .version16:
            return "version16"
        }
    }
}

以上所有枚举都包含我使用的 API 的有效设计选项。它们用作输入限制,以防止发生“无效参数”错误。

希望这能解决问题。

再次感谢!

一个类型可以符合 Codable,前提是它的所有属性都符合 Codable。除了枚举之外,您的所有属性 do 都符合 Codable,如果您声明它们符合,它们将符合 Codable。因此,您的类型的这个简单草图可以编译:

public enum Frame: String, Codable {
    case noFrame
    case bottomFrame
    case bottomTooltip
    case topHeader
}
public enum QrCodeLogo: String, Codable {
    case noLogo
    case scanMe
    case scanMeSquare
}
public enum Marker: String, Codable {
    case version1
    case version2
    case version3
    case version4
    case version5
    case version6
    case version7
    case version8
    case version9
    case version10
    case version11
    case version12
    case version13
    case version15
    case version16
}
public class QRCode : Codable {
    var bsId : Int?
    var url : String?
    var name: String?
    var frame: Frame?
    var logo: QrCodeLogo?
    var marker: Marker?
    var color : String?
    var bgColor : String?
    var image : Data?
}

关于您的代码,还有很多其他方面可以改进。 您不需要 CaseIterable 或 description 任何东西。现在您的类型是 Codable,您可以使用 it 直接自动从 JSON 检索值。如果您的枚举案例的名称与相应的 JSON 键不匹配,只需制作一个 CodingKey 嵌套枚举作为桥梁。

换句话说,成为 Codable 使您的类型 both 可直接从 JSON and 序列化到磁盘。