【问题标题】:Append text or data to text file in Swift在 Swift 中将文本或数据附加到文本文件
【发布时间】:2015-02-04 06:47:30
【问题描述】:

我已经看过Read and write data from text file

我需要将数据(字符串)附加到文本文件的末尾。
一种明显的方法是从磁盘读取文件并将字符串附加到它的末尾并写回它,但它效率不高,特别是如果您正在处理大文件并且经常这样做。

所以问题是“如何将字符串附加到文本文件的末尾,而不读取文件并将整个内容写回”?

到目前为止我有:

    let dir:NSURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
    let fileurl =  dir.URLByAppendingPathComponent("log.txt")
    var err:NSError?
    // until we find a way to append stuff to files
    if let current_content_of_file = NSString(contentsOfURL: fileurl, encoding: NSUTF8StringEncoding, error: &err) {
        "\(current_content_of_file)\n\(NSDate()) -> \(object)".writeToURL(fileurl, atomically: true, encoding: NSUTF8StringEncoding, error: &err)
    }else {
        "\(NSDate()) -> \(object)".writeToURL(fileurl, atomically: true, encoding: NSUTF8StringEncoding, error: &err)
    }
    if err != nil{
        println("CANNOT LOG: \(err)")
    }

【问题讨论】:

  • @John 你是什么意思?

标签: swift append text-files


【解决方案1】:

你应该使用NSFileHandle,它可以seek to the end of the file

let dir:NSURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
let fileurl =  dir.URLByAppendingPathComponent("log.txt")

let string = "\(NSDate())\n"
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!

if NSFileManager.defaultManager().fileExistsAtPath(fileurl.path!) {
    var err:NSError?
    if let fileHandle = NSFileHandle(forWritingToURL: fileurl, error: &err) {
        fileHandle.seekToEndOfFile()
        fileHandle.writeData(data)
        fileHandle.closeFile()
    }
    else {
        println("Can't open fileHandle \(err)")
    }
}
else {
    var err:NSError?
    if !data.writeToURL(fileurl, options: .DataWritingAtomic, error: &err) {
        println("Can't write \(err)")
    }
}

【讨论】:

  • 如果您在 Xcode 中单击那些带有白点的红色小圆圈,您会惊讶于将其转换为 swift 3 是多么容易。
【解决方案2】:

这是 Swift 2 的版本,在 String 和 NSData 上使用扩展方法。

//: Playground - noun: a place where people can play

import UIKit

extension String {
    func appendLineToURL(fileURL: NSURL) throws {
        try self.stringByAppendingString("\n").appendToURL(fileURL)
    }

    func appendToURL(fileURL: NSURL) throws {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        try data.appendToURL(fileURL)
    }
}

extension NSData {
    func appendToURL(fileURL: NSURL) throws {
        if let fileHandle = try? NSFileHandle(forWritingToURL: fileURL) {
            defer {
                fileHandle.closeFile()
            }
            fileHandle.seekToEndOfFile()
            fileHandle.writeData(self)
        }
        else {
            try writeToURL(fileURL, options: .DataWritingAtomic)
        }
    }
}

// Test
do {
    let url = NSURL(fileURLWithPath: "test.log")
    try "Test \(NSDate())".appendLineToURL(url)
    let result = try String(contentsOfURL: url)
}
catch {
    print("Could not write to file")
}

【讨论】:

    【解决方案3】:

    这是 PointZeroTwo 在 Swift 3.0 中的答案的更新,附上一个简短的说明 - 在操场上使用简单的文件路径进行测试是可行的,但在我的实际应用程序中,我需要使用 .documentDirectory(或您选择的任何目录)来构建 URL用于阅读和写作 - 确保它在整个应用程序中保持一致):

    extension String {
        func appendLineToURL(fileURL: URL) throws {
             try (self + "\n").appendToURL(fileURL: fileURL)
         }
    
         func appendToURL(fileURL: URL) throws {
             let data = self.data(using: String.Encoding.utf8)!
             try data.append(fileURL: fileURL)
         }
     }
    
     extension Data {
         func append(fileURL: URL) throws {
             if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
                 defer {
                     fileHandle.closeFile()
                 }
                 fileHandle.seekToEndOfFile()
                 fileHandle.write(self)
             }
             else {
                 try write(to: fileURL, options: .atomic)
             }
         }
     }
     //test
     do {
         let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
         let url = dir.appendingPathComponent("logFile.txt")
         try "Test \(Date())".appendLineToURL(fileURL: url as URL)
         let result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
     }
     catch {
         print("Could not write to file")
     }
    

    感谢 PointZeroTwo。

    【讨论】:

      【解决方案4】:

      更新:我为此写了一篇博文,你可以找到here

      保持 Swifty,这是一个使用 FileWriter 协议和默认实现的示例(撰写本文时为 Swift 4.1):

      • 要使用此功能,请让您的实体(类、结构、枚举)符合此协议并调用 write 函数(仅供参考,它会抛出异常!)。
      • 写入文档目录。
      • 如果文件存在,将附加到文本文件中。
      • 如果文本文件不存在,将创建一个新文件。
      • 注意:这仅适用于文本。你可以做类似写/追加Data的事情。

        import Foundation
        
        enum FileWriteError: Error {
            case directoryDoesntExist
            case convertToDataIssue
        }
        
        protocol FileWriter {
            var fileName: String { get }
            func write(_ text: String) throws
        }
        
        extension FileWriter {
            var fileName: String { return "File.txt" }
        
            func write(_ text: String) throws {
                guard let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                    throw FileWriteError.directoryDoesntExist
                }
        
                let encoding = String.Encoding.utf8
        
                guard let data = text.data(using: encoding) else {
                    throw FileWriteError.convertToDataIssue
                }
        
                let fileUrl = dir.appendingPathComponent(fileName)
        
                if let fileHandle = FileHandle(forWritingAtPath: fileUrl.path) {
                    fileHandle.seekToEndOfFile()
                    fileHandle.write(data)
                } else {
                    try text.write(to: fileUrl, atomically: false, encoding: encoding)
                }
            }
        }
        

      【讨论】:

      • 我正在尝试使用它,但我不知道该怎么做...“让您的实体(类、结构、枚举)符合此协议并调用写入函数(仅供参考,它会抛出!)”。我实际上如何调用它来保存 VC 中文本视图中的文本?
      • 这将两个概念联系在一起:符合协议和使用协议扩展来提供默认功能。对于初学者,您的实体应符合协议(例如,MyClass 类:FileWriter)。现在,由于FileWriter 协议上的协议要求有一个默认实现的扩展,因此您的实体(本示例中的 MyClass)可以免费获得写入功能!因此,您可以在 MyClass 的实例上调用该函数。 (例如,让 myClassInstance = MyClass(); 试试!myClassInstance.write("hello"))。
      • 另外,如果您想通过示例获得更详细的解释,请查看我的博客文章,我在上面的答案中包含了一个链接:)。
      【解决方案5】:

      为了保持@PointZero 二的精神。 这是他的 Swift 4.1 代码的更新

      extension String {
          func appendLine(to url: URL) throws {
              try self.appending("\n").append(to: url)
          }
          func append(to url: URL) throws {
              let data = self.data(using: String.Encoding.utf8)
              try data?.append(to: url)
          }
      }
      
      extension Data {
          func append(to url: URL) throws {
              if let fileHandle = try? FileHandle(forWritingTo: url) {
                  defer {
                      fileHandle.closeFile()
                  }
                  fileHandle.seekToEndOfFile()
                  fileHandle.write(self)
              } else {
                  try write(to: url)
              }
          }
      }
      

      【讨论】:

        【解决方案6】:

        这是一种以更有效的方式更新文件的方法。

        let monkeyLine = "\nAdding a ? to the end of the file via FileHandle"
        
        if let fileUpdater = try? FileHandle(forUpdating: newFileUrl) {
        
            // Function which when called will cause all updates to start from end of the file
            fileUpdater.seekToEndOfFile()
        
            // Which lets the caller move editing to any position within the file by supplying an offset
            fileUpdater.write(monkeyLine.data(using: .utf8)!)
        
            // Once we convert our new content to data and write it, we close the file and that’s it!
            fileUpdater.closeFile()
        }
        

        【讨论】:

          【解决方案7】:

          一些已发布答案的变体,具有以下特征:

          • 基于 Swift 5
          • 可作为静态函数访问
          • 将新条目附加到文件末尾(如果存在)
          • 如果文件不存在,则创建文件
          • 不强制转换为 NS 对象(更快速)
          • 如果文本无法编码或路径不存在,则静默失败

            class Logger {
            
                static var logFile: URL? {
                    guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
                    let formatter = DateFormatter()
                    formatter.dateFormat = "dd-MM-yyyy"
                    let dateString = formatter.string(from: Date())
                    let fileName = "\(dateString).log"
                    return documentsDirectory.appendingPathComponent(fileName)
                }
            
                static func log(_ message: String) {
                    guard let logFile = logFile else {
                        return
                    }
            
                    let formatter = DateFormatter()
                    formatter.dateFormat = "HH:mm:ss"
                    let timestamp = formatter.string(from: Date())
                    guard let data = (timestamp + ": " + message + "\n").data(using: String.Encoding.utf8) else { return }
            
                    if FileManager.default.fileExists(atPath: logFile.path) {
                        if let fileHandle = try? FileHandle(forWritingTo: logFile) {
                            fileHandle.seekToEndOfFile()
                            fileHandle.write(data)
                            fileHandle.closeFile()
                        }
                    } else {
                        try? data.write(to: logFile, options: .atomicWrite)
                    }
                }
            }
            

          【讨论】:

          • 嗨@atineoSE 你有关于在 FileManager 上写入被弃用后使用什么的任何信息吗?看起来像 var writeabilityHandler: ((FileHandle) -> Void)?是异步版本
          • 效果很好。谢谢!
          【解决方案8】:

          所有答案(截至目前)为每个写入操作重新创建 FileHandle。这对于大多数应用程序来说可能没问题,但这也相当低效:每次创建 FileHandle 时都会进行系统调用,并访问文件系统。

          为避免多次创建文件句柄,请使用以下内容:

          final class FileHandleBuffer {
              let fileHandle: FileHandle
              let size: Int
              private var buffer: Data
          
              init(fileHandle: FileHandle, size: Int = 1024 * 1024) {
                  self.fileHandle = fileHandle
                  self.size = size
                  self.buffer = Data(capacity: size)
              }
          
              deinit { try! flush() }
          
              func flush() throws {
                  try fileHandle.write(contentsOf: buffer)
                  buffer = Data(capacity: size)
              }
          
              func write(_ data: Data) throws {
                  buffer.append(data)
                  if buffer.count > size {
                      try flush()
                  }
              }
          }
          
          // USAGE
          
          // Create the file if it does not yet exist
          FileManager.default.createFile(atPath: fileURL.path, contents: nil)
          
          let fileHandle = try FileHandle(forWritingTo: fileURL)
          
          // Seek will make sure to not overwrite the existing content
          // Skip the seek to overwrite the file
          try fileHandle.seekToEnd()
          
          
          let buffer = FileHandleBuffer(fileHandle: fileHandle)
          for i in 0..<count {
              let data = getData() // Your implementation
              try buffer.write(data)
              print(i)
          }
          
          
          

          【讨论】:

            猜你喜欢
            • 2016-08-27
            • 1970-01-01
            • 2016-10-21
            • 2011-11-16
            • 2017-03-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-01-24
            相关资源
            最近更新 更多