NSDateFormatter dealloc EXC_BAD_ACCESS

Hi,

I have a log to file function that as part of the logline inserts the current datetime as below:

class func logToFile(message: String, logInfo: LogInfo = appLogInfo, type: OSLogType = .default) {
     
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
    formatter.timeZone = TimeZone(secondsFromGMT: TimeZone.current.secondsFromGMT())
    formatter.locale = Locale(identifier: "en_US_POSIX")
    let localDate = formatter.string(from: Date())
     
    let logMessage = "\(localDate) [\(logInfo.category)] \(message)\n"
     
    let documentDirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
     
    let filePath = "\(documentDirPath)/\(logFileName)"
     
    if let fileHandle = FileHandle.init(forWritingAtPath: filePath), let data = logMessage.data(using: .utf8) {
      fileHandle.seekToEndOfFile()
      fileHandle.write(data)
    }
  }

and it is always called from a serial queue:

Log.serialQueue.async {
      logToFile(message: message, logInfo: logInfo, type: type)
    }

Lately however, I am getting some crashes and I managed to catch one in the debugger. It happens when deallocating the local DateFormatter when exiting the logToFile function.

Xcode stops at the end of the function:

And the thread indicates that it is inside NSDateFormatter dealloc

and clicking on the ici::Dataformat destructor, the assembly code shows this:

And in the inspector, we can see that localDate is half-baked, it only contains the year and day:

I have found some posts on DateFormatter not being thread safe but that was way in the past. In my case I only want to convert a Date() to String.

Something is not working as expected and suggestions on how to improve it would be very welcome.

Thanks in advance

Answered by robnotyou in 716578022

DateFormatter is now threadsafe, but creating one is quite expensive.
You certainly shouldn't be creating one every time you call logToFile!

So you might try using an extension, like this:

extension DateFormatter {
    static let myFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
        formatter.timeZone = TimeZone(secondsFromGMT: TimeZone.current.secondsFromGMT())
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

(Give it a more meaningful name, of course)
Then in your logToFile, you can use it like this:

	let localDate = DateFormatter.myFormatter.string(from: Date())

This will be more efficient, and should fix your issue.

Accepted Answer

DateFormatter is now threadsafe, but creating one is quite expensive.
You certainly shouldn't be creating one every time you call logToFile!

So you might try using an extension, like this:

extension DateFormatter {
    static let myFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
        formatter.timeZone = TimeZone(secondsFromGMT: TimeZone.current.secondsFromGMT())
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

(Give it a more meaningful name, of course)
Then in your logToFile, you can use it like this:

	let localDate = DateFormatter.myFormatter.string(from: Date())

This will be more efficient, and should fix your issue.

formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
…
formatter.locale = Locale(identifier: "en_US_POSIX")

This is the wrong order. You need to pin the locale before applying the format.


Doing your own logging by writing data to a file is not something I generally recommend. In the majority of cases you’re better off logging to the system log. For hints and tips on that subject, see Your Friend the System Log.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

NSDateFormatter dealloc EXC_BAD_ACCESS
 
 
Q