iOS iPhone模拟器是否导致内存使用分析膨胀?

3
我正在尝试在我的应用程序中处理一个大文本文件。我知道在读取数据时要小心消耗的内存量。一旦读取了一段数据,应用程序就不需要保留这些数据。
感谢“Martin R”和他的帖子Read a file/URL line-by-line,为我启动了工作。
我正在尝试监控我的应用程序在读取大数据文件时的内存消耗,以确保它的行为符合预期。这就是我遇到问题的地方。
当我使用Xcode中的Command-I运行Instruments并监视分配时,我发现在读取文件时,应用程序会达到约15MB的高峰,然后再降回来。这是相当可重复的,误差在+/- 0.5MB之间。
当我使用Xcode中的Command-R运行应用程序,并让其完成对文件的读取,然后在Instruments中按下记录按钮时,内存消耗现在会急剧增加到约360MB。
因此,我进行内存分配测量的两种方式是: Profile: 1. Xcode Command-I. 2. Instruments Record Allocations. 观察到约15MB Simulate and Profile: 1. Xcode Command-R. 2. 让应用程序运行到“IDLE”状态。 3. Instruments Record. 观察到约360MB。
我一直在试图弄清楚几件事情。 Q1. 为什么会有这种差异?(这可能回答了我所有的问题)
Q2. 我是否有真正的问题,或者这只是因为调试代码被注释到模拟器上的原因?
Q3. 类似于Q2,如果我在真实设备上运行调试版本,它会有同样的问题吗?
Q4. 对于我的应用程序,当解析文件时,约15MB是可以接受的,但约360MB则不行。有没有其他方法可以在我的设备上继续进行调试,而不会遇到这个360MB的问题?
版本8.1(8B62) Sierra 2.7Ghz i5 MacBook Pro Circa 2015
附带示例代码。文件的前半部分只是从引用帖子中复制的代码,以方便读者使用。人们可以将此代码按原样在Xcode中运行。在底部是ViewController ViewDidLoad()方法,在那里“运行”事情。内存“膨胀”发生在“File opened”之后。
//
//

import UIKit

/* Originally from 
 * stackoverflow:
 * https://dev59.com/n2Af5IYBdhLWcg3wey3Z 
 * posted by Martin R.
 * Much thanks!
*/
class StreamReader  {

  let encoding : String.Encoding
  let chunkSize : Int
  var fileHandle : FileHandle!
  let delimData : Data
  var buffer : Data
  var atEof : Bool

  init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
        chunkSize: Int = 4096) {

    guard let fileHandle = FileHandle(forReadingAtPath: path),
      let delimData = delimiter.data(using: encoding) else {
        return nil
    }
    self.encoding = encoding
    self.chunkSize = chunkSize
    self.fileHandle = fileHandle
    self.delimData = delimData
    self.buffer = Data(capacity: chunkSize)
    self.atEof = false
  }

  deinit {
    self.close()
  }

  /// Return next line, or nil on EOF.
  func nextLine() -> String? {
    precondition(fileHandle != nil, "Attempt to read from closed file")

    // Read data chunks from file until a line delimiter is found:
    while !atEof {
      if let range = buffer.range(of: delimData) {
        // Convert complete line (excluding the delimiter) to a string:
        let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
        // Remove line (and the delimiter) from the buffer:
        buffer.removeSubrange(0..<range.upperBound)
        return line
      }
      let tmpData = fileHandle.readData(ofLength: chunkSize)
      if tmpData.count > 0 {
        buffer.append(tmpData)
      } else {
        // EOF or read error.
        atEof = true
        if buffer.count > 0 {
          // Buffer contains last line in file (not terminated by delimiter).
          let line = String(data: buffer as Data, encoding: encoding)
          buffer.count = 0
          return line
        }
      }
    }
    return nil
  }

  /// Start reading from the beginning of file.
  func rewind() -> Void {
    fileHandle.seek(toFileOffset: 0)
    buffer.count = 0
    atEof = false
  }

  /// Close the underlying file. No reading must be done after calling this method.
  func close() -> Void {
    fileHandle?.closeFile()
    fileHandle = nil
  }
}

extension StreamReader : Sequence {
  func makeIterator() -> AnyIterator<String> {
    return AnyIterator {
      return self.nextLine()
    }
  }
}



class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let path2WordList = Bundle.main.path(forResource: "large_text_file", ofType: "txt")
    var wordCnt: Int = 0

    if nil != path2WordList {
      if let aStreamReader = StreamReader(path: path2WordList!) {
        defer {  aStreamReader.close() }
        print("File openned")

        /* Read and discard */
        while aStreamReader.nextLine() != nil {
          wordCnt += 1
        }

      } // if let ...
    } // if nil ...

    print ("Final wordCnt := \(wordCnt)")
  } // viewDidLoad


  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }


}

顺便提一下,每当你想使用仪器进行测试时,请尝试使用真实设备,因为MacBook Pro的模拟器可能会表现得比真实设备更好。请参见此视频 - mfaani
作为一句附言,每当你想使用仪器进行测试时,尽量使用真实设备,因为Macbook Pro的模拟器可能会表现得比真实设备更加优越,甚至是极其糟糕。将其视为布局的方便工具,而不要期望太多。 - Confused
1个回答

1

当使用长时间运行的while循环时,我遇到过类似于这样的问题。问题在于任何分配到当前自动释放池的内容都不会在循环退出之前被释放。

为了避免这种情况,您可以将您的 while 循环的内容包装在 autoreleasepool(invoking:)中。这将导致您的每个循环迭代具有自己的自动释放池,并在每次迭代时进行释放。

示例如下:

/// Return next line, or nil on EOF.
func nextLine() -> String? {
  precondition(fileHandle != nil, "Attempt to read from closed file")

  var result: String? = nil

  // Read data chunks from file until a line delimiter is found:
  while !atEof, result == nil {
    result = autoreleasepool {
      if let range = buffer.range(of: delimData) {
        // Convert complete line (excluding the delimiter) to a string:
        let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
        // Remove line (and the delimiter) from the buffer:
        buffer.removeSubrange(0..<range.upperBound)
        return line
      }
      let tmpData = fileHandle.readData(ofLength: chunkSize)
      if tmpData.count > 0 {
        buffer.append(tmpData)
      } else {
        // EOF or read error.
        atEof = true
        if buffer.count > 0 {
          // Buffer contains last line in file (not terminated by delimiter).
          let line = String(data: buffer as Data, encoding: encoding)
          buffer.count = 0
          return line
        }
      }
      return nil
    }
  }
  return result
}

关于您的内存增长是否是调试环境的副作用,很难说。但无论如何,防范这种增长可能是明智的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接