在iOS上修剪日志文件开头行的快速方法

在iOS上修剪日志文件开头行的快速方法,ios,swift,logging,file-io,Ios,Swift,Logging,File Io,我正在寻找一种快速、优化的方法来修剪iOS上的日志文件。我想指定日志文件的最大行数(例如10000行)。在文本文件末尾追加新行似乎相对简单。但是,我还没有找到一种在文件开头修剪线条的快速方法。这是我想出的(慢)代码 guard let fileURL = self.fileURL else { return } guard let path = fileURL.path else { return } guard let

我正在寻找一种快速、优化的方法来修剪iOS上的日志文件。我想指定日志文件的最大行数(例如10000行)。在文本文件末尾追加新行似乎相对简单。但是,我还没有找到一种在文件开头修剪线条的快速方法。这是我想出的(慢)代码

    guard let fileURL = self.fileURL else {
        return
    }

    guard let path = fileURL.path else {
        return
    }

    guard let fileHandle = NSFileHandle(forUpdatingAtPath: path) else {
        return
    }

    fileHandle.seekToEndOfFile()
    fileHandle.writeData(message.dataUsingEncoding(NSUTF8StringEncoding)!)
    fileHandle.writeData("\n".dataUsingEncoding(NSUTF8StringEncoding)!)

    currentLineCount += 1

    // TODO: This could probably use some major optimization
    if currentLineCount >= maxLineCount {
        if let fileString = try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding) {
            var lines = fileString.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
            lines.removeFirst()
            let newData = lines.joinWithSeparator("\n")
            fileHandle.seekToFileOffset(0)
            fileHandle.writeData(newData.dataUsingEncoding(NSUTF8StringEncoding)!)
        }
    }

    fileHandle.closeFile()

不要将新行写入日志,然后再处理附加内容,而是一步完成写入和修剪:

let fileString = (try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding)) as NSString? ?? ""
var lines = fileString.characters.split("\n").map{String($0)}
lines.append(message)
// this also more generic as it will remove any number of extra lines
lines.removeFirst(max(currentLineCount - maxLineCount), 0))
let newLogContents = lines.joinWithSeparator("\n")
(newLogContents as NSString).writeToURL(fileURL, atomically: true, encoding: NSUTF8StringEncoding)

你的问题有两个方面首先,您的代码删除单个 日志文件中的行。因此,一旦达到极限,每一个新的 日志消息导致读取、缩短和重新写入整个文件

使用“高水位线”和“低水位线”会更有效。例如,如果希望保留最后10.000行, 让日志文件增长到15000行,然后再截断 它可以连接到10000行。这减少了“修剪动作”的数量 相当多

第二部分是关于截断本身。您的代码将加载 文件转换为
NSString
,这需要转换UTF-8数据 转换为Unicode字符 (如果日志文件中有一个无效字节,则失败)。 然后将字符串拆分为一个数组,删除一个数组元素, 数组再次连接到字符串,然后写回 将Unicode字符转换为UTF-8的文件

我还没有做过性能测试,但我可以想象它可能是 仅对二进制数据操作更快,无需转换为
NSString
Array
和back。下面是一个可能的实现 从文件开头删除给定数量的行:

func removeLinesFromFile(fileURL: NSURL, numLines: Int) {

    do {
        let data = try NSData(contentsOfURL: fileURL, options: .DataReadingMappedIfSafe)
        let nl = "\n".dataUsingEncoding(NSUTF8StringEncoding)!

        var lineNo = 0
        var pos = 0
        while lineNo < numLines {
            // Find next newline character:
            let range = data.rangeOfData(nl, options: [], range: NSMakeRange(pos, data.length - pos))
            if range.location == NSNotFound {
                return // File has less than `numLines` lines.
            }
            lineNo++
            pos = range.location + range.length
        }

        // Now `pos` is the position where line number `numLines` begins.
        let trimmedData = data.subdataWithRange(NSMakeRange(pos, data.length - pos))
        trimmedData.writeToURL(fileURL, atomically: true)

    } catch let error as NSError {
        print(error.localizedDescription)
    }
}
func removeLinesFromFile(文件URL:NSURL,numLines:Int){
做{
let data=try NSData(contentsofull:fileURL,选项:.DataReadingMappedIfSafe)
让nl=“\n”.dataUsingEncoding(NSUTF8StringEncoding)!
变量lineNo=0
var pos=0
而lineNo
我已将Martin R答案更新为Swift 3,并对其进行了更改,以便我们可以传递要保留的行数,而不是要删除的行数:

func removeLinesFromFile(fileURL: URL, linesToKeep numLines: Int) {

    do {
        let data = try Data(contentsOf: fileURL, options: .dataReadingMapped)
        let nl = "\n".data(using: String.Encoding.utf8)!

        var lineNo = 0
        var pos = data.count-1
        while lineNo <= numLines {
            // Find next newline character:
            guard let range = data.range(of: nl, options: [ .backwards ], in: 0..<pos) else {
                return // File has less than `numLines` lines.
            }
            lineNo += 1
            pos = range.lowerBound
        }

        let trimmedData = data.subdata(in: pos..<data.count)
        try trimmedData.write(to: fileURL)

    } catch let error as NSError {
        print(error.localizedDescription)
    }
}
func removeLinesFromFile(fileURL:URL,linesToKeep numLines:Int){
做{
let data=try data(contentsOf:fileURL,options:.dataReadingMapped)
让nl=“\n”。数据(使用:String.Encoding.utf8)!
变量lineNo=0
var pos=data.count-1

虽然lineNo我肯定有一些不错的iOS第三方日志解决方案,但我正在寻找一个更明确的解决方案,我可以添加到我自己的自定义日志代码中。看看CocoaLumberJack的代码。它已经满足了你的需要。你确定吗?看看CocoaLumberJack,我看到了滚动日志文件的选项,但实际上没有看到它修剪日志文件。现在我不确定。我认为最大大小确保删除了旧行。但可能它只是创建了一个新文件。这对于大文件来说是个坏主意。您不希望一次将整个文件存储在内存中。我同意您的看法,但是如果询问者想要问题中所述的行为,这是一种方法。对于
maxLi的小值neCount
(甚至10000)内存使用应该是可以的,当然,考虑到我们并没有所有10k长的线:)