mac-cain13 / R.swift

Strong typed, autocompleted resources like images, fonts and segues in Swift projects
MIT License
9.49k stars 763 forks source link

Fix memory leak caused by NSLocalizedString #891

Open fthdgn opened 5 months ago

fthdgn commented 5 months ago

I detected very high memory usage on some screens of my app. I investigated the problem and found that the problem is NSLocalizedString(_:tableName:bundle:value:comment:) calls on R.swift.

This PR replaces NSLocalizedString(_:tableName:bundle:value:comment:) calls with String.init(localized:table:bundle:locale:comment:) on supported platforms.

Benchmarks

Steps

import UIKit

class ViewController: UIViewController {
    private let label: UILabel = .init()

    private var timer: Timer?
    private var stopTimer: Timer?

    override func viewDidLoad() {
        super.viewDidLoad()
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        timer = .scheduledTimer(withTimeInterval: 0.001, repeats: true, block: { [weak self] _ in
            let date = Date.now.ISO8601Format(.init(includingFractionalSeconds: true))

            //let myValue = R.string.myStrings.my_key()

            //let myValue = NSLocalizedString("my_key", tableName: "MyStrings", comment: "")

            let myValue = String(localized: .init("my_key"), table: "MyStrings")

            self?.label.text = myValue + ": " + date
        })
        stopTimer = .scheduledTimer(withTimeInterval: 30, repeats: false, block: { [weak self] _ in
            self?.timer?.invalidate()
        })
    }
}

Results

SwiftUI

import SwiftUI

struct ContentView: View {
    @State private var currentDate = Date.now
    let timer = Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()

    var body: some View {
        VStack {
            Text("my_key", tableName: "MyStrings")
            //Text(NSLocalizedString("my_key", tableName: "MyStrings", comment: ""))
            //Text(R.string.myStrings.my_key)
            Text(currentDate.ISO8601Format(.init(includingFractionalSeconds: true)))
        }
        .padding()
        .onReceive(timer) { input in
            currentDate = input
        }
    }
}

SwiftUI has similiar results.

tomlokhorst commented 5 months ago

Thanks, nice find!

Looking at the diff, it appears the default value behaviour differs from the original and the two new branches.

fthdgn commented 5 months ago

String.init one does not have any value parameter. I am not sure purpose of that parameter. On my cases it unused. I will try to find a case where its value is essantial.

fthdgn commented 5 months ago

I found the usa case of value parameter. It is used when a language file does not provide translation for a key.

I used String.init(localized:defaultValue:table:bundle:locale:comment:) function.