Использование шаблонов для загрузки всех файлов json типа, хранящихся локально - PullRequest
0 голосов
/ 30 января 2020

Я пытаюсь создать стандартную c постоянную подпрограмму для личного пакета Swift, который я могу использовать в своих собственных проектах.

Идея:

  • Я могу накормить save(instance:) метод, и он
    • создаст новую папку в папке документов с именем типа экземпляра, который я передаю (т. Е. Если я передаю экземпляр Contact, папка будет называться Contact
    • сохранить экземпляр как json
  • , чтобы загрузить все экземпляры определенного типа c, я хочу использовать метод loadAll(of type:), который
    • декодирует все файлы в этой папке, если она существует
    • возвращает массив этих экземпляров

Вот класс сохраняемости пакетов:

public class ATPersistLocally {

    public static let shared = ATPersistLocally()

    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()

    private let fileManager: FileManager = {
        return FileManager.default
    }()

    private let docPath: URL = {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        guard let docPath = urls.first else { fatalError() }
        ATLogger.shared.logToConsole(message: "Doc path is: \(docPath)", type: .debug)
        return docPath
    }()

    //MARK: - Loading
    public func loadAll<T: Codable & Identifiable>(of type: T) -> [T] {
        var result = [T]()

        let typeName = getTypeName(of: type) //custom helper method, see below

        let pathFolder = docPath.appendingPathComponent(String(describing: typeName))
        if fileManager.fileExists(atPath: pathFolder.path) {
            var urls = [URL]()
            do {
                urls = try fileManager.contentsOfDirectory(at: pathFolder, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
            } catch let error {
                ATLogger.shared.logToConsole(message: "Could not load content of directory: \(error.localizedDescription)", type: .error)
            }

            for url in urls {
                if let data = fileManager.contents(atPath: url.path) {
                    do {
                        let instance = try decoder.decode(T.self, from: data)
                        result.append(instance)
                    } catch let error {
                        ATLogger.shared.logToConsole(message: "Could not decode data: \(error.localizedDescription)", type: .error)
                    }
                }
            }
        }

        return result
    }

    //MARK: - Saving
    public func save<T: Codable & Identifiable>(instance: T) {
        let typeName = getTypeName(of: instance)

        let pathFolder = docPath.appendingPathComponent(String(describing: typeName))
        if !fileManager.fileExists(atPath: pathFolder.absoluteString) {
            do {
                try fileManager.createDirectory(atPath: pathFolder.relativePath, withIntermediateDirectories: true)
            } catch let error {
                ATLogger.shared.logToConsole(message: "Could not create folder for type \(typeName): \(error.localizedDescription)", type: .error)
            }
        }

        let path = pathFolder.appendingPathComponent(instance.id as! String)

        do {
            let data = try encoder.encode(instance)
            do {
                try data.write(to: URL(fileURLWithPath: path.relativePath))
                ATLogger.shared.logToConsole(message: "Saved instance of \(typeName)", type: .debug)
            } catch let error {
                ATLogger.shared.logToConsole(message: "Could not save data: \(error.localizedDescription)", type: .error)
            }
        } catch let error {
            ATLogger.shared.logToConsole(message: "Could not encode data: \(error.localizedDescription)", type: .error)
        }
    }

    //MARK: - Helper Functions
    private func getTypeName<T: Codable & Identifiable>(of type: T) -> T.Type {
        return T.self
    }
}

Хотя эта компиляция и сохранение уже работает нормально, у меня есть проблема с загрузкой.

Вот пример структуры (которая должна быть Codable & Identifiable, если я правильно понимаю), что я пытаюсь использовать:

import Foundation

struct Contact: Codable, Identifiable {
    let id = UUID()
    let contactTitle: String
    var name: String? = nil
    var firstname: String? = nil
    var address: String? = nil
    var zip: String? = nil
    var city: String? = nil
}

А вот вызов загрузки, который я пытаюсь сделать e:

var contacts = ATPersistLocally.shared.loadAll(of: Contact.self)

Ошибка, которую я получаю во время сборки: Argument type 'Contact.Type' does not conform to expected type 'Decodable'.

Я также попытался вызвать contacts = ATPersistLocally.shared.loadAll(of: Contact), что приводит к точно так же сообщению об ошибке.

Очевидно, что я неправильно понимаю кое-что, касающееся концепции генериков здесь - поэтому любой намек в правильном направлении будет высоко оценен!

Ответы [ 2 ]

2 голосов
/ 30 января 2020

Тип параметра type в loadAll должен быть T.Type

public func loadAll<T: Codable & Identifiable>(of type: T.Type) -> [T] { ...

Это делает метод getTypeName устаревшим, поскольку вы можете написать

let pathFolder = docPath.appendingPathComponent(String(describing: T.self))

Примечание:

Экземпляр error неявно доступен в блоке catch, let error является избыточным.

0 голосов
/ 30 января 2020

Я уже принял ответ Вадиана как правильный ответ, поэтому вся заслуга go для него.

Я также хочу предоставить весь класс ATPersistLocally, который работает для меня сейчас, надеясь, что он может помочь и другим:

import Foundation

public class ATPersistLocally {

    public static let shared = ATPersistLocally()

    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()

    private let fileManager: FileManager = {
        return FileManager.default
    }()

    private let docPath: URL = {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        guard let docPath = urls.first else { fatalError() }
        ATLogger.shared.logToConsole(message: "Doc path is: \(docPath)", type: .debug)
        return docPath
    }()

    //MARK: - Loading
    public func loadAll<T: Codable & Identifiable>(of type: T.Type) -> [T] {
        var result = [T]()

        let pathFolder = docPath.appendingPathComponent(String(describing: type))
        if fileManager.fileExists(atPath: pathFolder.path) {
            var urls = [URL]()
            do {
                urls = try fileManager.contentsOfDirectory(at: pathFolder, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
            } catch {
                ATLogger.shared.logToConsole(message: "Could not load content of directory: \(error.localizedDescription)", type: .error)
            }

            for url in urls {
                if let data = fileManager.contents(atPath: url.path) {
                    do {
                        let instance = try decoder.decode(T.self, from: data)
                        result.append(instance)
                    } catch {
                        ATLogger.shared.logToConsole(message: "Could not decode data: \(error.localizedDescription)", type: .error)
                    }
                }
            }
        }
        return result
    }

    //MARK: - Saving
    public func save<T: Codable & Identifiable>(instance: T) {
        let typeName = getTypeName(of: instance)

        let pathFolder = docPath.appendingPathComponent(String(describing: typeName))
        if !fileManager.fileExists(atPath: pathFolder.absoluteString) {
            do {
                try fileManager.createDirectory(atPath: pathFolder.relativePath, withIntermediateDirectories: true)
            } catch {
                ATLogger.shared.logToConsole(message: "Could not create folder for type \(typeName): \(error.localizedDescription)", type: .error)
            }
        }

        let path = pathFolder.appendingPathComponent(String(describing: instance.id))

        do {
            let data = try encoder.encode(instance)
            do {
                try data.write(to: URL(fileURLWithPath: path.relativePath))
                ATLogger.shared.logToConsole(message: "Saved instance of \(typeName)", type: .debug)
            } catch {
                ATLogger.shared.logToConsole(message: "Could not save data: \(error.localizedDescription)", type: .error)
            }
        } catch let error {
            ATLogger.shared.logToConsole(message: "Could not encode data: \(error.localizedDescription)", type: .error)
        }
    }

    //MARK: - Helper Functions
    private func getTypeName<T: Codable & Identifiable>(of type: T) -> T.Type {
        return T.self
    }
}

В проектах, где я хочу использовать эту логику c, я импортирую свой пакет через SPM и проверяю, соответствует ли структура для сохранения / загрузки Codable и Identifiable.

Имейте в виду, что для Identifiable требуется iOS13, поскольку она была добавлена ​​в стандартную библиотеку только в Swift 5.1.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...