Swift:FileManager+Extension

JKSwiftExtension,测试用例在 FileManagerExtensionViewController 里面
目录:

  • 1、沙盒路径的获取
    • 获取Home的完整路径名
    • 获取Documnets的完整路径名
    • 获取Library的完整路径名
    • 获取/Library/Cache的完整路径名
    • 获取Library/Preferences的完整路径名
    • 获取Tmp的完整路径名
  • 2、文件以及文件夹的操作 扩展
    • 创建文件夹(蓝色的,文件夹和文件是不一样的)
    • 删除文件夹
    • 创建文件
    • 删除文件
    • 读取文件内容
    • 把文字,图片,数组,字典写入文件
    • 从文件 读取 文字,图片,数组,字典
    • 拷贝(文件夹/文件)的内容 到另外一个(文件夹/文件),新的(文件夹/文件)如果存在就先删除再 拷贝
    • 移动(文件夹/文件)的内容 到另外一个(文件夹/文件),新的(文件夹/文件)如果存在就先删除再 移动
    • 判断 (文件夹/文件) 是否存在
    • 获取 (文件夹/文件) 的前一个路径
    • 判断目录是否可读
    • 判断目录是否可写
    • 根据文件路径获取文件扩展类型
    • 根据文件路径获取文件名称,是否需要后缀
    • 对指定路径执行浅搜索,返回指定目录路径下的文件、子目录及符号链接的列表(只寻找一层)
    • 深度遍历,会递归遍历子文件夹(包括符号链接,所以要求性能的话用enumeratorAtPath)
    • 深度遍历,会递归遍历子文件夹(但不会递归符号链接)
    • 计算单个 (文件夹/文件) 的大小,单位为字节 (没有进行转换的)
    • 计算 (文件夹/文件) 的大小(转换过的)
    • 获取(文件夹/文件)属性集合
一、沙盒路径的获取
// MARK:- 一、沙盒路径的获取
/*
 - 1、Home(应用程序包)目录
 - 整个应用程序各文档所在的目录,包含了所有的资源文件和可执行文件
 - 2、Documents
 - 保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录
 - 需要保存由"应用程序本身"产生的文件或者数据,例如: 游戏进度,涂鸦软件的绘图
 - 目录中的文件会被自动保存在 iCloud
 - 注意: 不要保存从网络上下载的文件,否则会无法上架!
 - 3、Library
 - 3.1、Library/Cache
 - 保存应用运行时生成的需要持久化的数据,iTunes同步设备时不备份该目录。一般存放体积大、不需要备份的非重要数据
 - 保存临时文件,"后续需要使用",例如: 缓存的图片,离线数据(地图数据)
 - 系统不会清理 cache 目录中的文件
 - 就要求程序开发时, "必须提供 cache 目录的清理解决方案"
 - 3.2、Library/Preference
 - 保存应用的所有偏好设置,IOS的Settings应用会在该目录中查找应用的设置信息。iTunes
 - 用户偏好,使用 NSUserDefault 直接读写!
 - 如果想要数据及时写入硬盘,还需要调用一个同步方法
 - 4、tmp
 - 保存临时文件,"后续不需要使用"
 - tmp 目录中的文件,系统会自动被清空
 - 重新启动手机, tmp 目录会被清空
 - 系统磁盘空间不足时,系统也会自动清理
 - 保存应用运行时所需要的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行,系统也可能会清除该目录下的文件,iTunes不会同步备份该目录
 */

public extension FileManager {
    // MARK: 1.1、获取Home的完整路径名
    /// 获取Home的完整路径名
    /// - Returns: Home的完整路径名
    static func homeDirectory() -> String {
        //获取程序的Home目录
        let homeDirectory = NSHomeDirectory()
        return homeDirectory
    }

    // MARK: 1.2、获取Documnets的完整路径名
    /// 获取Documnets的完整路径名
    /// - Returns: Documnets的完整路径名
    static func DocumnetsDirectory() -> String {
        //获取程序的documentPaths目录
        //方法1
        // let documentPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
        // let documnetPath = documentPaths[0]
    
       //方法2
        let ducumentPath = NSHomeDirectory() + "/Documents"
        return ducumentPath
    }

    // MARK: 1.3、获取Library的完整路径名
    /**
     这个目录下有两个子目录:Caches 和 Preferences
     Library/Preferences目录,包含应用程序的偏好设置文件。不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好。
     Library/Caches目录,主要存放缓存文件,iTunes不会备份此目录,此目录下文件不会再应用退出时删除
     */
    /// 获取Library的完整路径名
    /// - Returns: Library的完整路径名
    static func LibraryDirectory() -> String {
        //获取程序的documentPaths目录
        //Library目录-方法1
        // let libraryPaths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
        // let libraryPath = libraryPaths[0]
        //
        // Library目录-方法2
        let libraryPath = NSHomeDirectory() + "/Library"
        return libraryPath
    }

    // MARK: 1.4、获取/Library/Caches的完整路径名
    /// 获取/Library/Caches的完整路径名
    /// - Returns: /Library/Caches的完整路径名
    static func CachesDirectory() -> String {
        //获取程序的/Library/Caches目录
        let cachesPath = NSHomeDirectory() + "/Library/Caches"
        return cachesPath
    }

    // MARK: 1.5、获取Library/Preferences的完整路径名
    /// 获取Library/Preferences的完整路径名
    /// - Returns: Library/Preferences的完整路径名
    static func PreferencesDirectory() -> String {
        //Library/Preferences目录-方法2
        let preferencesPath = NSHomeDirectory() + "/Library/Preferences"
        return preferencesPath
    }

    // MARK: 1.6、获取Tmp的完整路径名
    /// 获取Tmp的完整路径名,用于存放临时文件,保存应用程序再次启动过程中不需要的信息,重启后清空
    /// - Returns: Tmp的完整路径名
    static func TmpDirectory() -> String {
        //方法1
        //let tmpDir = NSTemporaryDirectory()
        //方法2
        let tmpDir = NSHomeDirectory() + "/tmp"
        return tmpDir
    }
}
二、文件以及文件夹的操作 扩展
// MARK:- 二、文件以及文件夹的操作 扩展
public extension FileManager {
    // MARK: 文件写入的类型
    /// 文件写入的类型
    enum FileWriteType {
        case TextType
        case ImageType
        case ArrayType
        case DictionaryType
    }
    // MARK: 移动或者拷贝的类型
    /// 移动或者拷贝的类型
    enum MoveOrCopyType {
        case file
        case directory
    }
    /// 文件管理器
    static var fileManager: FileManager {
        return FileManager.default
    }

    // MARK: 2.1、创建文件夹(蓝色的,文件夹和文件是不一样的)
    /// 创建文件夹(蓝色的,文件夹和文件是不一样的)
    /// - Parameter folderName: 文件夹的名字
    /// - Returns: 返回创建的 创建文件夹路径
    @discardableResult
    static func createFolder(folderPath: String) -> (isSuccess: Bool, error: String) {
        if !judgeFileOrFolderExists(filePath: folderPath) {
            return (true, "")
        }
        // 不存在的路径才会创建
        do {
            // withIntermediateDirectories为ture表示路径中间如果有不存在的文件夹都会创建
            try fileManager.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
            return (true, "")
        } catch _ {
            return (false, "创建失败")
        }
    }

    // MARK: 2.2、删除文件夹
    /// 删除文件夹
    /// - Parameter folderPath: 文件的路径
    @discardableResult
    static func removefolder(folderPath: String) -> (isSuccess: Bool, error: String) {
        let filePath = "\(folderPath)"
        guard judgeFileOrFolderExists(filePath: filePath) else {
            // 不存在就不做什么操作了
            return (true, "")
       }
        // 文件存在进行删除
        do {
            try fileManager.removeItem(atPath: filePath)
            return (true, "")
        } catch _ {
            return (false, "删除失败")
        }
    }

    // MARK: 2.3、创建文件
    /// 创建文件
    /// - Parameter filePath: 文件路径
    /// - Returns: 返回创建的结果 和 路径
    @discardableResult
    static func createFile(filePath: String) -> (isSuccess: Bool, error: String) {
        guard judgeFileOrFolderExists(filePath: filePath) else {
            // 不存在的文件路径才会创建
            // withIntermediateDirectories 为 ture 表示路径中间如果有不存在的文件夹都会创建
            let createSuccess = fileManager.createFile(atPath: filePath, contents: nil, attributes: nil)
            return (createSuccess, "")
        }
        return (true, "")
    }    

    // MARK: 2.4、删除文件
    /// 删除文件
    /// - Parameter filePath: 文件路径
    @discardableResult
    static func removefile(filePath: String) -> (isSuccess: Bool, error: String) {
        guard judgeFileOrFolderExists(filePath: filePath) else {
            // 不存在的文件路径就不需要要移除
            return (true, "")
        }
        // 移除文件
        do {
            try fileManager.removeItem(atPath: filePath)
            return (true, "")
        } catch _ {
            return (false, "移除文件失败")
        }
    }

    // MARK: 2.5、读取文件内容
    /// 读取文件内容
    /// - Parameter filePath: 文件路径
    /// - Returns: 文件内容
    @discardableResult
    static func readfile(filePath: String) -> String? {
        guard judgeFileOrFolderExists(filePath: filePath) else {
            // 不存在的文件路径就不需要要移除
            return nil
        }
        let data = fileManager.contents(atPath: filePath)
        return String(data: data!, encoding: String.Encoding.utf8)
    }

    // MARK: 2.6、把文字,图片,数组,字典写入文件
    /// 把文字,图片,数组,字典写入文件
    /// - Parameters:
    ///   - writeType: 写入类型
    ///   - content: 写入内容
    ///   - writePath: 写入路径
    /// - Returns: 写入的结果
    @discardableResult
    static func writeToFile(writeType: FileWriteType, content: Any, writePath: String) -> (isSuccess: Bool, error: String) {
        guard judgeFileOrFolderExists(filePath: writePath) else {
            // 不存在的文件路径
            return (false, "不存在的文件路径")
        }
        // 1、文字,2、图片,3、数组,4、字典写入文件
        switch writeType {
        case .TextType:
            let info = "\(content)"
            do {
                try info.write(toFile: writePath, atomically: true, encoding: String.Encoding.utf8)
                return (true, "")
            } catch _ {
                return (false, "写入失败")
            }
        case .ImageType:
            let data = content as! Data
            do {
                try data.write(to: URL(fileURLWithPath: writePath))
                return (true, "")
            } catch _ {
                return (false, "写入失败")
            }
        case .ArrayType:
            let array = content as! NSArray
            let result = array.write(toFile: writePath, atomically: true)
            if result {
                return (true, "")
            } else {
                return (false, "写入失败")
            }
        case .DictionaryType:
            let result = (content as! NSDictionary).write(toFile: writePath, atomically: true)
            if result {
                return (true, "")
            } else {
                return (false, "写入失败")
            }
        }
    }

    // MARK: 2.7、从文件 读取 文字,图片,数组,字典
    /// 从文件 读取 文字,图片,数组,字典
    /// - Parameters:
    ///   - readType: 读取的类型
    ///   - readPath: 读取文件路径
    /// - Returns: 返回读取的内容
    @discardableResult
    static func readFromFile(readType: FileWriteType, readPath: String) -> (isSuccess: Bool, content: Any?, error: String) {
        guard judgeFileOrFolderExists(filePath: readPath),  let readHandler =  FileHandle(forReadingAtPath: readPath) else {
            // 不存在的文件路径
            return (false, nil, "不存在的文件路径")
        }
        let data = readHandler.readDataToEndOfFile()
        // 1、文字,2、图片,3、数组,4、字典
        switch readType {
        case .TextType:
            let readString = String(data: data, encoding: String.Encoding.utf8)
            return (true, readString, "")
        case .ImageType:
            let image = UIImage(data: data)
            return (true, image, "")
        case .ArrayType:
            guard let readString = String(data: data, encoding: String.Encoding.utf8) else {
                return (false, nil, "读取内容失败")
            }
            return (true, readString.jsonStringToArray(), "")
        case .DictionaryType:
            guard let readString = String(data: data, encoding: String.Encoding.utf8) else {
                return (false, nil, "读取内容失败")
            }
            return (true, readString.jsonStringToDictionary(), "")
        }
    }

    // MARK: 2.8、拷贝(文件夹/文件)的内容 到另外一个(文件夹/文件),新的(文件夹/文件)如果存在就先删除再 拷贝
    /**
     几个小注意点:
     1、目标路径,要带上文件夹名称,而不能只写父路径
     2、如果是覆盖拷贝,就是说目标路径已存在此文件夹,我们必须先删除,否则提示make directory error(当然这里最好做一个容错处理,比如拷贝前先转移到其他路径,如果失败,再拿回来)
     */
    /// 拷贝(文件夹/文件)的内容 到另外一个(文件夹/文件),新的(文件夹/文件)如果存在就先删除再 拷贝
    /// - Parameters:
    ///   - fromeFile: 拷贝的(文件夹/文件)路径
    ///   - toFile: 拷贝后的(文件夹/文件)路径
    ///   - isOverwrite: 当要拷贝到的(文件夹/文件)路径存在,会拷贝失败,这里传入是否覆盖
    /// - Returns: 拷贝的结果
    @discardableResult
    static func copyFile(type: MoveOrCopyType, fromeFilePath: String, toFilePath: String, isOverwrite: Bool = true) -> (isSuccess: Bool, error: String) {
        // 1、先判断被拷贝路径是否存在
        guard judgeFileOrFolderExists(filePath: fromeFilePath) else {
            return (false, "被拷贝的(文件夹/文件)路径不存在")
        }
        // 2、判断拷贝后的文件路径的前一个文件夹路径是否存在,不存在就进行创建
        let toFileFolderPath = directoryAtPath(path: toFilePath)
        if !judgeFileOrFolderExists(filePath: toFileFolderPath), type == .file ? !createFile(filePath: toFilePath).isSuccess : !createFolder(folderPath: toFileFolderPath).isSuccess {
            return (false, "拷贝后路径前一个文件夹不存在")
        }
        // 3、如果被拷贝的(文件夹/文件)已存在,先删除,否则拷贝不了
        if isOverwrite, judgeFileOrFolderExists(filePath: toFilePath) {
            do {
                try fileManager.removeItem(atPath: toFilePath)
            } catch _ {
                return (false, "拷贝失败")
            }
        }
        // 4、拷贝(文件夹/文件)
        do {
            try fileManager.copyItem(atPath: fromeFilePath, toPath: toFilePath)
        } catch _ {
            return (false, "拷贝失败")
        }
        return (true, "success")
    }

    // MARK: 2.9、移动(文件夹/文件)的内容 到另外一个(文件夹/文件),新的(文件夹/文件)如果存在就先删除再 移动
    /// 移动(文件夹/文件)的内容 到另外一个(文件夹/文件),新的(文件夹/文件)如果存在就先删除再 移动
    /// - Parameters:
    ///   - fromeFile: 被移动的文件路径
    ///   - toFile: 移动后的文件路径
    @discardableResult
    static func moveFile(type: MoveOrCopyType, fromeFilePath: String, toFilePath: String, isOverwrite: Bool = true) -> (isSuccess: Bool, error: String) {
        // 1、先判断被拷贝路径是否存在
        guard judgeFileOrFolderExists(filePath: fromeFilePath) else {
            return (false, "被移动的(文件夹/文件)路径不存在")
        }
        // 2、判断拷贝后的文件路径的前一个文件夹路径是否存在,不存在就进行创建
        let toFileFolderPath = directoryAtPath(path: toFilePath)
        if !judgeFileOrFolderExists(filePath: toFileFolderPath), type == .file ? !createFile(filePath: toFilePath).isSuccess : !createFolder(folderPath: toFileFolderPath).isSuccess {
            return (false, "移动后路径前一个文件夹不存在")
        }
        // 3、如果被移动的(文件夹/文件)已存在,先删除,否则拷贝不了
        if isOverwrite, judgeFileOrFolderExists(filePath: toFilePath) {
            do {
                try fileManager.removeItem(atPath: toFilePath)
            } catch _ {
                return (false, "移动失败")
            }
        }
        // 4、移动(文件夹/文件)
        do {
            try fileManager.moveItem(atPath: fromeFilePath, toPath: toFilePath)
        } catch _ {
            return (false, "移动失败")
        }
        return (true, "success")
    }

    // MARK: 2.10、判断 (文件夹/文件) 是否存在
    /** 判断文件或文件夹是否存在*/
    static func judgeFileOrFolderExists(filePath: String) -> Bool {
        let exist = fileManager.fileExists(atPath: filePath)
        // 查看文件夹是否存在,如果存在就直接读取,不存在就直接反空
        guard exist else {
            return false
        }
        return true
    }

    // MARK: 2.11、获取 (文件夹/文件) 的前一个路径
    /// 获取 (文件夹/文件) 的前一个路径
    /// - Parameter path: (文件夹/文件) 的路径
    /// - Returns: (文件夹/文件) 的前一个路径
    static func directoryAtPath(path: String) -> String {
        return (path as NSString).deletingLastPathComponent
    }

    // MARK: 2.12、判断目录是否可读
    static func judegeIsReadableFile(path: String) -> Bool {
        return fileManager.isReadableFile(atPath: path)
    }

    // MARK: 2.13、判断目录是否可写
    static func judegeIsWritableFile(path: String) -> Bool {
        return fileManager.isReadableFile(atPath: path)
    }

    // MARK: 2.14、根据文件路径获取文件扩展类型
    /// 根据文件路径获取文件扩展类型
    /// - Parameter path: 文件路径
    /// - Returns: 文件扩展类型
    static func fileSuffixAtPath(path: String) -> String {
        return (path as NSString).pathExtension
    }

    // MARK: 2.15、根据文件路径获取文件名称,是否需要后缀
    /// 根据文件路径获取文件名称,是否需要后缀
    /// - Parameters:
    ///   - path: 文件路径
    ///   - suffix: 是否需要后缀,默认需要
    /// - Returns: 文件名称
    static func fileName(path: String, suffix: Bool = true) -> String {
        let fileName = (path as NSString).lastPathComponent
        guard suffix else {
            // 删除后缀
            return (fileName as NSString).deletingPathExtension
        }
        return fileName
    }

    // MARK: 2.16、对指定路径执行浅搜索,返回指定目录路径下的文件、子目录及符号链接的列表(只寻找一层)
    /// 对指定路径执行浅搜索,返回指定目录路径下的文件、子目录及符号链接的列表(只寻找一层)
    /// - Parameter folderPath: 建搜索的lujing
    /// - Returns: 指定目录路径下的文件、子目录及符号链接的列表
    static func shallowSearchAllFiles(folderPath: String) -> Array<String>? {
        do {
            let contentsOfDirectoryArray = try fileManager.contentsOfDirectory(atPath: folderPath)
            return contentsOfDirectoryArray
        } catch _ {
            return nil
        }
    }

    // MARK: 2.17、深度遍历,会递归遍历子文件夹(包括符号链接,所以要求性能的话用enumeratorAtPath)
    /**深度遍历,会递归遍历子文件夹(包括符号链接,所以要求性能的话用enumeratorAtPath)*/
    static func getAllFileNames(folderPath: String) -> Array<String>? {
        // 查看文件夹是否存在,如果存在就直接读取,不存在就直接反空
        if (judgeFileOrFolderExists(filePath: folderPath)) {
            guard let subPaths = fileManager.subpaths(atPath: folderPath) else {
                return nil
            }
            return subPaths
        } else {
            return nil
        }
    }

    // MARK: 2.18、深度遍历,会递归遍历子文件夹(但不会递归符号链接)
    /** 对指定路径深度遍历,会递归遍历子文件夹(但不会递归符号链接))*/
    static func deepSearchAllFiles(folderPath: String) -> Array<Any>? {
        // 查看文件夹是否存在,如果存在就直接读取,不存在就直接反空
        if (judgeFileOrFolderExists(filePath: folderPath)) {
            guard let contentsOfPathArray = fileManager.enumerator(atPath: folderPath) else {
                return nil
            }
            return contentsOfPathArray.allObjects
        }else{
            return nil
        }
    }    

    // MARK: 2.19、计算单个 (文件夹/文件) 的大小,单位为字节(bytes) (没有进行转换的)
    /// 计算单个 (文件夹/文件) 的大小,单位为字节 (没有进行转换的)
    /// - Parameter filePath: (文件夹/文件) 路径
    /// - Returns: 单个文件或文件夹的大小
    static func fileOrDirectorySingleSize(filePath: String) -> UInt64 {
        // 1、先判断文件路径是否存在
        guard judgeFileOrFolderExists(filePath: filePath) else {
            return 0
        }
        // 2、读取文件大小
        do {
            let fileAttributes = try fileManager.attributesOfItem(atPath: filePath)
            guard let fileSizeValue = fileAttributes[FileAttributeKey.size] as? UInt64 else {
                return 0
            }
            return fileSizeValue
        } catch {
            return 0
        }
    }

    //MARK: 2.20、计算 (文件夹/文件) 的大小(转换过的)
    /// 计算 (文件夹/文件) 的大小
    /// - Parameter path: (文件夹/文件) 的路径
    /// - Returns: (文件夹/文件) 的大小
    static func fileOrDirectorySize(path: String) -> String {
        if path.count == 0, !fileManager.fileExists(atPath: path) {
            return "0MB"
        }
        // (文件夹/文件) 的实际大小
        var fileSize: UInt64 = 0
        do {
            let files = try fileManager.contentsOfDirectory(atPath: path)
            for file in files {
                let path = path + "/\(file)"
                fileSize = fileSize + fileOrDirectorySingleSize(filePath: path)
            }
        } catch {
            fileSize = fileSize + fileOrDirectorySingleSize(filePath: path)
        }
        // 转换后的大小 ["bytes", "KB", "MB", "GB", "TB", "PB",  "EB",  "ZB", "YB"]
        return covertUInt64ToString(with: fileSize)
    }

    // MARK: 2.21、获取(文件夹/文件)属性集合
    ///  获取(文件夹/文件)属性集合
    /// - Parameter path: (文件夹/文件)路径
    /// - Returns: (文件夹/文件)属性集合
    @discardableResult
    static func fileAttributes(path: String) -> ([FileAttributeKey : Any]?) {
        do {
            let attributes = try fileManager.attributesOfItem(atPath: path)
            /*
            print("创建时间:\(attributes[FileAttributeKey.creationDate]!)")
            print("修改时间:\(attributes[FileAttributeKey.modificationDate]!)")
            print("文件大小:\(attributes[FileAttributeKey.size]!)")
            */
            return attributes
        } catch _ {
            return nil
        }
        // key的列表如:
        /*
        public static let type:
        public static let size:
        public static let modificationDate:
        public static let referenceCount:
        public static let deviceIdentifier:
        public static let ownerAccountName:
        public static let groupOwnerAccountName:
        public static let posixPermissions:
        public static let systemNumber:
        public static let systemFileNumber:
        public static let extensionHidden:
        public static let hfsCreatorCode:
        public static let hfsTypeCode:
        public static let immutable:
        public static let appendOnly:
        public static let creationDate:
        public static let ownerAccountID:
        public static let groupOwnerAccountID:
        public static let busy:
        @available(iOS 4.0, *)
        public static let protectionKey:
        public static let systemSize:
        public static let systemFreeSize:
        public static let systemNodes:
        public static let systemFreeNodes:
        */
    }
}

// MARK:- fileprivate
extension FileManager {

    // MARK: 计算文件大小:UInt64 -> String
    /// 计算文件大小:UInt64 -> String
    /// - Parameter size: 文件的大小
    /// - Returns: 转换后的文件大小
    fileprivate static func covertUInt64ToString(with size: UInt64) -> String {
        var convertedValue: Double = Double(size)
        var multiplyFactor = 0
        let tokens = ["bytes", "KB", "MB", "GB", "TB", "PB",  "EB",  "ZB", "YB"]
        while convertedValue > 1024 {
            convertedValue /= 1024
            multiplyFactor += 1
        }
        return String(format: "%4.2f %@", convertedValue, tokens[multiplyFactor])
    }
}

推荐阅读更多精彩内容