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

Accepted Reply

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.

  • Thank you @robnotyou, I will give it a try. Have put a reminder to come back and mark as solved if I can't reproduce with this change.

  • Thank you for the suggestion, I switched to the ISO8601DateFormatter() but I guess it makes sense to make that a class variable or extension anyway as an improvement. I will continue testing to make sure my problem is solved.

Add a Comment

Replies

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.

  • Thank you @robnotyou, I will give it a try. Have put a reminder to come back and mark as solved if I can't reproduce with this change.

  • Thank you for the suggestion, I switched to the ISO8601DateFormatter() but I guess it makes sense to make that a class variable or extension anyway as an improvement. I will continue testing to make sure my problem is solved.

Add a Comment
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"

  • Oops, thanks Quinn!

  • Thanks for the reply @eskimo, it is very interesting that the order matters here. I will also review the system log link you sent. I also found the ISO8601DateFormatter and if I use that, I do not have to pass any parameters, the format is fine as is. In order to combine the two answers, is the recommendation still to just declare the formatter once on class level, like: static let formatter = ISO8601DateFormatter(); instead of let formatter = ISO8601DateFormatter(); inside the log function.

  • Thank you Quinn, really interesting that the order matters here. I also found the ISO8601DateFormatter() which does the job without this culprit. Thanks also for the tip on system logg, I do use it but only for the console stuff, maybe it can be improved.

Add a Comment