[iOS 응용 개발] 파일을 추상화하는 구조체 만들기(취합)

53380 단어 iOSSwiftXcodetech
안녕하세요.
Zenn에서는 제가 평소에 사용하던 iOS 애플리케이션에서 개발한 Tips 같은 것을 써냈으면 좋겠다고 생각했어요.
지난 8차례 동안 스위프트의 파일 조작을 간단하게 하기 위해 우리는'파일을 추상화하는 구조체'를 만들었다.
나는 지금 보도에 그 원본 코드 전체를 싣고 싶다.
복제해도 작동할 것 같아서요.
이 방법과 속성이 뭐였더라면 과거 기사를 참조하세요.

소스 코드


Xcode:12.4 Swift5
import UIKit

// MARK: ファイルを抽象化した構造体を作る(1)

struct File {
    let path: String
}

extension File {
    
    static let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(
        .documentDirectory,
        .userDomainMask,
        true
    ).first!
    
    static let libraryDirectoryPath = NSSearchPathForDirectoriesInDomains(
        .libraryDirectory,
        .userDomainMask,
        true
    ).first!
    
    static let temporaryDirectoryPath = NSTemporaryDirectory()
    
    static let mainBundlePath = Bundle.main.bundlePath
    
    static let documentDirectory = File(path: documentDirectoryPath)
    static let libraryDirectory = File(path: libraryDirectoryPath)
    static let temporaryDirectory = File(path: temporaryDirectoryPath)
    static let mainBundle = File(path: mainBundlePath)
}

extension File {
    
    func append(pathComponent: String) -> File {
        return File(path: (path as NSString).appendingPathComponent(pathComponent))
    }
    
    static func + (lhs: File, rhs: String) -> File {
        return lhs.append(pathComponent: rhs)
    }
}

extension File: Equatable {
    
    static func == (lhs: File, rhs: File) -> Bool {
        return lhs.path == rhs.path
    }
}

// MARK: ファイルを抽象化した構造体を作る(2)

extension File {
    
    var exists: Bool {
        return FileManager.default.fileExists(atPath: path)
    }
}

extension File {
    
    var parentDirectoryPath: String {
        if path == "/" { return "" }
        return (path as NSString).deletingLastPathComponent
    }
    
    var parentDirectory: File {
        return File(path: parentDirectoryPath)
    }
}

extension File {
    
    func makeDirectory() throws {
        if !exists {
            try FileManager.default.createDirectory(
                atPath: path,
                withIntermediateDirectories: true,
                attributes: nil
            )
        }
    }
}

// MARK: ファイルを抽象化した構造体を作る(3)

extension File {
    
    var url: URL {
        return URL(fileURLWithPath: path)
    }
    
    var data: Data? {
        return try? Data(contentsOf: url)
    }
}

extension File {
    
    func contents(encoding: String.Encoding) -> String? {
        guard let data = self.data else { return nil }
        return String(data: data, encoding: encoding)
    }
    
    var contents: String? {
        return contents(encoding: .utf8)
    }

    func write(contents: String, encoding: String.Encoding = .utf8) throws {
        try parentDirectory.makeDirectory()
        try contents.write(to: url, atomically: false, encoding: encoding)
    }
}

extension File {
    
    var image: UIImage? {
        guard let data = self.data else { return nil }
        return UIImage(data: data)
    }
    
    // JPEGで書き込む
    func write(imageAsJpeg image: UIImage, quality: CGFloat = 0.9) throws {
        guard let data = image.jpegData(compressionQuality: quality) else { return }
        try parentDirectory.makeDirectory()
        try data.write(to: url)
    }
    
    // PNGで書き込む
    func write(imageAsPng image: UIImage) throws {
        guard let data = image.pngData() else { return }
        try parentDirectory.makeDirectory()
        try data.write(to: url)
    }
}

// MARK: ファイルを抽象化した構造体を作る(4)

extension File {
    
    var name: String {
        return (path as NSString).lastPathComponent
    }
    
    var `extension`: String {
        let ext = (name as NSString).pathExtension
        return ext.isEmpty ? "" : ".\(ext)"
    }
    
    var extensionWithoutDot: String {
        let ext = (name as NSString).pathExtension
        return ext.isEmpty ? "" : "\(ext)"
    }
    
    var nameWithoutExtension: String {
        return (name as NSString).deletingPathExtension
    }
}

extension File {
    
    var isFile: Bool {
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) {
            return !isDirectory.boolValue
        }
        return false
    }
    
    var isDirectory: Bool {
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) {
            return isDirectory.boolValue
        }
        return false
    }

// こちらでも可
//  var isDirectory: Bool {
//      return exists && !isFile
//  }
}

// MARK: ファイルを抽象化した構造体を作る(5)

extension File {

    // 作成日時
    var creationDate: Date? {
        return attributes[.creationDate] as? Date
    }
    
    // 更新日時
    var modificationDate: Date? {
        return attributes[.modificationDate] as? Date
    }
    
    // ファイルサイズ
    var size: UInt64 {
        return attributes[.size] as? UInt64 ?? 0
    }
    
    private var attributes: [FileAttributeKey : Any] {
        let attr1 = (try? FileManager.default.attributesOfFileSystem(forPath: path)) ?? [:]
        let attr2 = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [:]
        return [attr1, attr2].reduce(into: [FileAttributeKey : Any](), { ret, attr in
            ret.merge(attr) { $1 }
        })
    }
}

// MARK: ファイルを抽象化した構造体を作る(6)

extension File {

    var files: [File] {
        return filesMap { self + $0 }
    }
    
    var filePaths: [String] {
        return filesMap { (self + $0).path }
    }
    
    var fileNames: [String] {
        return filesMap { $0 }
    }
    
    private func filesMap<T>(_ transform: (String) throws -> (T)) rethrows -> [T] {
        guard let fileNames = try? FileManager.default.contentsOfDirectory(atPath: path) else {
            return []
        }
        return try fileNames.map { try transform($0) }
	// ファイル名で並べたいときは下を使用する
	// return fileNames.sorted().map { try transform($0) }
    }
}

extension File: CustomStringConvertible {
    
    var description: String {
        let type = isDirectory ? "Dir" : "File"
        return "<\(type) \(name)>"
    }
}

// MARK: ファイルを抽象化した構造体を作る(7)

extension File {

    func delete() throws {
        try FileManager.default.removeItem(atPath: path)
    }
    
    func deleteAllChildren() throws {
        try files.forEach { file in
            try file.delete()
        }
    }
    
    func copy(to destination: File, force: Bool = true) throws {
        if force && destination.exists {
            try destination.delete()
        }
        try FileManager.default.copyItem(atPath: path, toPath: destination.path)
    }
    
     func move(to destination: File, force: Bool = true) throws {
        if force && destination.exists {
            try destination.delete()
        }
        try FileManager.default.moveItem(atPath: path, toPath: destination.path)
    }
    
    func rename(to name: String, force: Bool = true) throws -> File {
        let destination = File(path: parentDirectoryPath) + name
        try move(to: destination, force: force)
        return destination
    }
}

// MARK: ファイルを抽象化した構造体を作る(8)

extension File {
    
    func jsonDecoded<T>(_ type: T.Type) throws -> T? where T : Decodable {
        guard let data = self.data else { return nil }
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        return try decoder.decode(type, from: data)
    }
}

extension File {
    
    func jsonEncode<T>(_ value: T) throws -> Data where T : Encodable {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        encoder.keyEncodingStrategy = .convertToSnakeCase
        return try encoder.encode(value)
    }
    
    func writeEncodedJson<T>(_ value: T, encoding: String.Encoding = .utf8) throws where T : Encodable {
        let encoded = try jsonEncode(value)
        let jsonString = String(data: encoded, encoding: encoding) ?? ""
        try parentDirectory.makeDirectory()
        try jsonString.write(to: url, atomically: false, encoding: encoding)
    }
}
extension은 상세한 분류가 있지만 보도를 위한 것이니 적당히 하세요.

총결산


[iOS 응용 개발] 파일을 추상화하는 구조체 만들기(1)
  • 제작 File 구조체
  • 특정 디렉토리를 즉시 참조할 수 있음
  • 경로 문자열은 연산자+로 연결할 수 있음

  • 근거Equatable
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(2)
  • 파일(또는 디렉토리)의 존재를 확인할 수 있음
  • 현재 경로의 부모 디렉터리를 얻을 수 있음
  • 디렉토리를 실제로 생성하는 기능
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(3)
  • 간단한 파일 URL 확보
  • 파일 컨텐츠를 데이터로 간편하게 만들 수 있음
  • 텍스트 파일의 내용을 가져올 수 있음
  • UIImage를 통해 이미지 파일의 이미지를 가져올 수 있음
  • 텍스트 파일 쓰기 가능
  • 이미지 파일에 UIImage 내용을 쓸 수 있음
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(4)
  • 파일 이름과 확장자 또는 조합이 이미 있습니다.
  • File 구성체에서 실제로 존재하는 파일을 얻었습니까?
  • File fabric이 "실제 디렉토리인지 여부"를 성공적으로 획득했습니다.
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(5)
  • 파일의 속성 값(메타데이터)을 가져올 수 있습니다.
  • 속성 값을 숨기고 가져오면 창설 날짜, 업데이트 날짜와 크기를 얻을 수 있습니다.
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(6)
  • 디렉토리의 모든 파일(또는 디렉토리)을 가져올 수 있음
  • 고급 방법을 통해 간단히 구현할 수 있고 순환 횟수도 절약할 수 있다
  • 모든 것을 얻을 때 디버깅을 쉽게 볼 수 있도록 CustomStringConvertible
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(7)
  • 파일 삭제, 복사, 이동 가능
  • 이동을 적용하여 이름 바꾸기
  • [iOS 응용 개발] 파일을 추상화하는 구조체 만들기(8)
  • 사용Codable구조화된 데이터 처리
  • JSON 파일을 통해 Codable 데이터를 가져오고 저장할 수 있음
  • 데이터 생성과 파일 처리의 책임은 두 부분으로 나뉜다
  • 최후


    이번에 제작된 File構造体인데 직접 사용하기 편해서 소개해드렸어요.
    물론 다른 기능도 추가할 수 있고, 사용하지 않는 것도 삭제할 수 있다.
    "괜찮을지도 몰라요"라고 생각되면 사용해 보세요.
    그럼 안녕히 계세요.

    좋은 웹페이지 즐겨찾기