Build Status Codebeat Status Version: 1.9.1 Swift: 5.0 Platforms: iOS | tvOS | macOS | Linux License: MIT
PayPal: Donate GitHub: Become a sponsor Patreon: Become a patron

安装使用捐赠问题反馈贡献许可

CSVImporter

轻松逐行导入CSV文件。

原理

你可能会问:“为什么又一个CSVImporter?” 你可能会说:“已经有 SwiftCSVCSwiftV 了”。 事实是,这些框架对于较小的CSV文件效果很好。但是,一旦你有一个非常大的CSV文件(或者可能有一个,因为你允许用户导入他想要的任何CSV文件),那么这些解决方案可能会给你的某些用户带来延迟和内存问题

另一方面,CSVImporter 可以异步工作(防止延迟),并且逐行读取CSV文件,而不是将整个字符串加载到内存中(防止内存问题)。 最重要的是,它易于使用,并提供漂亮的回调来指示失败、进度、完成,甚至在你需要时进行数据映射

安装

目前,推荐的安装此库的方式是通过 macOS 上的 Carthage 或 Linux 上的 Swift Package ManagerCocoapods 也可以工作,但未经过测试。

当然,你也可以通过下载或使用 git 子模块手动将此框架包含到你的项目中。

使用

请查看 UsageExamples.playground 和 Tests/CSVImporterTests/CSVImporterSpec.swift 文件,以获取提供的完整功能列表。从 .xcworkspace 中打开 Playground 才能使其正常工作。

基本 CSV 导入

首先创建一个 CSVImporter 的实例,并指定 CSV 中一行数据应具有的类型。 默认数据类型是一个 String 对象数组,如下所示

let path = "path/to/your/CSV/file"
let importer = CSVImporter<[String]>(path: path)
importer.startImportingRecords { $0 }.onFinish { importedRecords in
    for record in importedRecords {
        // record is of type [String] and contains all data in a line
    }
}

请注意,在创建 CSVImporter 对象时,你可以在路径旁边指定一个替代分隔符。 如果你不指定任何分隔符,则默认分隔符为 ,

异步和回调

CSVImporter 默认情况下以异步方式工作,因此不会阻塞主线程。 正如你所见,onFinish 方法在完成后调用,以便使用结果。 还有 onFail 用于失败情况(例如,当给定的路径不包含 CSV 文件时),onProgress 会定期调用并提供已处理的行数(例如,用于进度指示器)。 你可以按如下方式链接它们

importer.startImportingRecords { $0 }.onFail {

    print("The CSV file couldn't be read.")

}.onProgress { importedDataLinesCount in

    print("\(importedDataLinesCount) lines were already imported.")

}.onFinish { importedRecords in

    print("Did finish import with \(importedRecords.count) records.")

}

默认情况下,真正的导入工作在 .utility 全局后台队列中完成,回调在 main 队列中调用。 这样,繁重的工作以异步方式完成,但回调允许你更新你的 UI。 如果你需要不同的行为,你可以在创建 CSVImporter 对象时自定义队列,如下所示

let path = "path/to/your/CSV/file"
let importer = CSVImporter<[String]>(path: path, workQosClass: .background, callbacksQosClass: .utility)

同步导入

如果你知道你的文件足够小,或者阻塞 UI 没有问题,你也可以使用同步导入方法来导入你的数据。 只需调用 importRecords 而不是 startImportingRecords,你将直接收到最终结果(与使用 startImportingRecordsonFinish 闭包中的内容相同)

let importedRecords = importer.importRecords { $0 }

请注意,此方法没有任何选项可以获得关于进度或失败的通知 - 你只能获得结果。 检查结果数组是否为空以识别潜在的失败。

简单的数据映射

如上所述,默认类型是 [String],但你可以提供你喜欢的任何类型。 例如,假设你有一个这样的类

class Student {
  let firstName: String, lastName: String
  init(firstName: String, lastName: String) {
    self.firstName = firstName
    self.lastName = lastName
  }
}

并且你的 CSV 文件看起来像这样

Harry,Potter
Hermione,Granger
Ron,Weasley

然后你可以指定一个映射器作为闭包,而不是上面示例中的 { $0 },像这样

let path = "path/to/Hogwarts/students"
let importer = CSVImporter<Student>(path: path)
importer.startImportingRecords { recordValues -> Student in

    return Student(firstName: recordValues[0], lastName: recordValues[1])

}.onFinish { importedRecords in

    for student in importedRecords {
        // Now importedRecords is an array of Students
    }

}

支持 Header 结构

最后但并非最不重要的是,一些 CSV 文件在第一行中指定了数据的结构,如下所示

firstName,lastName
Harry,Potter
Hermione,Granger
Ron,Weasley

在这种情况下,CSVImporter 可以自动将每个记录作为字典提供,如下所示

let path = "path/to/Hogwarts/students"
let importer = CSVImporter<[String: String]>(path: path)
importer.startImportingRecords(structure: { (headerValues) -> Void in

    print(headerValues) // => ["firstName", "lastName"]

}) { $0 }.onFinish { importedRecords in

    for record in importedRecords {
        print(record) // => e.g. ["firstName": "Harry", "lastName": "Potter"]
        print(record["firstName"]) // prints "Harry" on first, "Hermione" on second run
        print(record["lastName"]) // prints "Potter" on first, "Granger" on second run
    }

}

注意:如果记录的值计数与第一行的值计数不匹配,则该记录将被忽略。

捐赠

BartyCrouch 由 Cihat Gündüz 在业余时间为你带来。 如果你想感谢我并支持这个项目的开发,请PayPal 上进行小额捐赠。 如果你也喜欢我的其他 开源贡献文章,请考虑通过成为 GitHub 上的赞助者Patreon 上的赞助人来激励我。

非常感谢任何捐赠,这真的很有帮助! 💯

贡献

请参阅文件 CONTRIBUTING.md

许可

此库在 MIT 许可证下发布。 有关详细信息,请参阅 LICENSE。