Skip to content

IterableLogUtil.formatDate() creates a new DateFormatter on every call, causing app hangs #1006

@nicoleta-apetrei

Description

@nicoleta-apetrei

✍️ Issue Description

IterableLogUtil.formatDate() allocates a new DateFormatter instance on every invocation. DateFormatter (NSDateFormatter) is well-documented as expensive to create on iOS. When multiple SDK log calls fire in quick succession on the main thread (e.g. during foreground transitions), this causes fully-blocked app hangs of 2-3+ seconds.
Additionally, the expensive formatting happens before the log level is checked in the delegate, so even apps that set DefaultLogDelegate(minLogLevel: .error) pay the full cost for every ITBInfo() and ITBDebug() call internally.

Affected code

swift-sdk/Internal/Utilities/IterableLogUtil.swift, lines 52-56:

private static func formatDate(date: Date) -> String {
    let formatter = DateFormatter()          // ← new allocation every call
    formatter.dateFormat = "HH:mm:ss.SSSS"
    return formatter.string(from: date)
}

And the call site in log() at line 19-20:

func log(level: LogLevel, message: String?, file: String, method: String, line: Int) {
    let logMessage = IterableLogUtil.formatLogMessage(...)  // ← formats BEFORE delegate check
    logDelegate.log(level: level, message: logMessage)       // ← level filtered here, too late
}

Stack trace (from Sentry crash reporting)

-[NSDateFormatter stringForObjectValue:]
IterableLogUtil.formatDate (IterableLogUtil.swift)
IterableLogUtil.formatLogMessage (IterableLogUtil.swift)
  (×6 similar frames)
UIApplicationMain
main (main.swift)

App Hang Fully Blocked: 2.4–3.2 seconds. Occurs during foreground transition after returning from an external login flow.

Suggested fix

Two changes would resolve this:

  1. Cache the DateFormatter as a static property:
private static let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "HH:mm:ss.SSSS"
    return formatter
}()

private static func formatDate(date: Date) -> String {
    dateFormatter.string(from: date)
}
  1. Check the log level before formatting (optional but recommended):

Ideally the delegate protocol could be extended with a shouldLog(level:) method, or the level filtering could move into IterableLogUtil.log() itself before formatting. This would avoid the cost of formatLogMessage() entirely for filtered-out messages.

This issue was investigated with the help of Claude (Anthropic).


👤 Iterable orgId: Wolt

📦 Iterable SDK version: 6.6.5 (also verified the code is unchanged in 6.6.7)

📲 iOS SDK version: From iOS 16 to iOS 26.x


⚠️ Beta Software Notice

Important: Our team does not provide support for issues encountered on beta or pre-release versions of operating systems, development tools, or other software. Please verify that the issue occurs on stable, officially released software before submitting this report. Thank you for your understanding.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions