Swift:FileManager+Extension

作者: IIronMan | 来源:发表于2020-11-30 22:11 被阅读0次

    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])
        }
    }
    

    相关文章

      网友评论

        本文标题:Swift:FileManager+Extension

        本文链接:https://www.haomeiwen.com/subject/gnvdwktx.html