JohnSundell/Files 这个文件处理库,封装的挺好
本文看一下其源代码
设计
磁盘上的分为两种,文件和文件夹
public enum LocationKind {
case file
case folder
}
文件和文件夹的共性是,他有一个路径,
操作他们,都要 FileManager
将文件和文件夹的共性,封装为 Storage
增删改查的具体实现,交给这个类
public final class Storage{
fileprivate private(set) var path: String
private let fileManager: FileManager
}
将文件和文件夹,抽象为 Location
通用的增删改查方法,放在 Location
里面。
Location 分为两种,使用枚举 LocationKind 区分类型
具体信息,记录在 Storage
里面
public protocol Location: Equatable, CustomStringConvertible {
static var kind: LocationKind { get }
var storage: Storage<Self> { get }
init(storage: Storage<Self>)
}
将文件,封装为 File
文件的读写,放在这个结构体中
public struct File: Location {
public let storage: Storage<File>
public init(storage: Storage<File>) {
self.storage = storage
}
}
将文件夹,封装为 Folder
处理子文件夹,根目录等
public struct Folder: Location {
public let storage: Storage<Folder>
public init(storage: Storage<Folder>) {
self.storage = storage
}
}
功能: 怎样实现 ls
文件 list
1, 实例化文件夹
Location 协议有一个扩展方法,
拿文件夹路径去实例化
// 里面调用 `Folder ` 遵守 `Location` 协议,实现的 `init(storage: Storage<Self>)`
public extension Location {
init(path: String) throws {
try self.init(storage: Storage(
path: path,
fileManager: .default
))
}
}
2, 获取文件目录
里面调用 storage
的创建子列表的方法
public extension Folder {
var files: ChildSequence<File> {
return storage.makeChildSequence()
}
}
创建子列表的方法,实现
一环套一环,层层封装,
调用了一个定义在 Folder
里面的类 ChildSequence
private extension Storage where LocationType == Folder {
func makeChildSequence<T: Location>() -> Folder.ChildSequence<T> {
return Folder.ChildSequence(
folder: Folder(storage: self),
fileManager: fileManager,
isRecursive: false,
includeHidden: false
)
}
}
Folder
里面的类 ChildSequence
路径信息在 folder
里面,
有两个可选的属性,
isRecursive
, 要不要递归,查看所有层次的文件夹,
includeHidden
, 要不要查看隐藏的文件和文件夹
默认从上到下排序
struct ChildSequence<Child: Location>: Sequence {
fileprivate let folder: Folder
fileprivate let fileManager: FileManager
fileprivate var isRecursive: Bool
fileprivate var includeHidden: Bool
public func makeIterator() -> ChildIterator<Child> {
return ChildIterator(
folder: folder,
fileManager: fileManager,
isRecursive: isRecursive,
includeHidden: includeHidden,
reverseTopLevelTraversal: false
)
}
}
到底了,最后一层
有文件夹路径,
有操作方式,要不要看里面文件夹的信息,要不要看隐藏的
剩下的,就好办了
如果不用递归获取,就比较简单
loadItemNames()
方法中,获取这一层的文件名
有一个索引 index
记录,遍历到了,换下一个
struct ChildIterator<Child: Location>: IteratorProtocol {
private let folder: Folder
private let fileManager: FileManager
private let isRecursive: Bool
private let includeHidden: Bool
private let reverseTopLevelTraversal: Bool
private lazy var itemNames = loadItemNames()
private var index = 0
private var nestedIterators = [ChildIterator<Child>]()
// 初始化,获取信息
fileprivate init(folder: Folder,
fileManager: FileManager,
isRecursive: Bool,
includeHidden: Bool,
reverseTopLevelTraversal: Bool) {
self.folder = folder
self.fileManager = fileManager
self.isRecursive = isRecursive
self.includeHidden = includeHidden
self.reverseTopLevelTraversal = reverseTopLevelTraversal
}
// 遵守 IteratorProtocol 协议,需要实现 next() 方法
public mutating func next() -> Child? {
guard index < itemNames.count else {
guard var nested = nestedIterators.first else {
return nil
}
guard let child = nested.next() else {
nestedIterators.removeFirst()
return next()
}
nestedIterators[0] = nested
return child
}
let name = itemNames[index]
index += 1
if !includeHidden {
guard !name.hasPrefix(".") else { return next() }
}
let childPath = folder.path + name.removingPrefix("/")
let childStorage = try? Storage<Child>(path: childPath, fileManager: fileManager)
let child = childStorage.map(Child.init)
if isRecursive {
let childFolder = (child as? Folder) ?? (try? Folder(
storage: Storage(path: childPath, fileManager: fileManager)
))
if let childFolder = childFolder {
let nested = ChildIterator(
folder: childFolder,
fileManager: fileManager,
isRecursive: true,
includeHidden: includeHidden,
reverseTopLevelTraversal: false
)
nestedIterators.append(nested)
}
}
return child ?? next()
}
private func loadItemNames() -> [String] {
let contents = try? fileManager.contentsOfDirectory(atPath: folder.path)
let names = contents?.sorted() ?? []
return reverseTopLevelTraversal ? names.reversed() : names
}
}
应用:文件夹内查重复文件
Mac 访达中,重复文件命名方式是,
扩展前,文件名后 + (1)
默认排序,重复文件在前面,原文件在后面
找到重复文件,使用一个指针保存 var current: File? = nil
再看后一个文件
let path = "\(NSHomeDirectory())/Downloads"
let folder = try Folder(path: path)
var current: File? = nil
for file in folder.files {
if let c = current{
if c.sheerName.contains(file.sheerName){
print(c.name)
print(file.name)
// 查看文件大小
do {
let attr = try FileManager.default.attributesOfItem(atPath: c.path)
let fileSize = attr[FileAttributeKey.size] as! UInt64
print("fileSize: \(fileSize.sheerSize)")
} catch { fatalError()}
print("----\n")
}
current = nil
}
if file.name.contains("(1"){
current = file
}
}