iOS开发:String扩展为何在AppDelegate方法前执行?
didFinishLaunchingWithOptions执行的原因与解决办法 嘿,这个问题我做多语言适配的时候也踩过一模一样的坑!咱们先把原因说清楚,再给你几个可行的解决方案。
为什么Extension的代码执行更早?
iOS App的启动流程是有固定顺序的:
系统先加载App的基础资源(比如Storyboard/XIB、全局变量、静态属性)→ 初始化界面控件 → 然后才会调用
AppDelegate的didFinishLaunchingWithOptions方法
你写的String extension的localized属性,应该是被界面上的控件直接调用了(比如Storyboard里的UILabel的text绑定了"some_key".localized)。这些控件的初始化是在didFinishLaunchingWithOptions之前完成的,所以当localized去UserDefaults里取值时,你还没在didFinishLaunchingWithOptions里把selectedLocale存进去,自然就拿到nil了。
看你的代码也能印证这一点:你在didFinishLaunchingWithOptions里才把Singleton.sharedInstance.currentLocale存到UserDefaults,但界面控件早就已经调用localized去取值了。
解决方案
1. 给Extension加默认值兜底
先在localized属性里判断,如果UserDefaults里取不到值,就用系统当前Locale或者预设的默认值,避免nil的情况:
extension String { var localized: String { // 先从UserDefaults取,取不到就用系统Locale判断,默认给fr let locale = UserDefaults.standard.value(forKey: USER_DEFAULT.selectedLocale) as? String ?? { let currentCollator = NSLocale.current.collatorIdentifier return currentCollator?.contains("en") ?? false ? "en" : "fr" }() if let path = Bundle.main.path(forResource: locale, ofType: "lproj") { let bundle = Bundle(path: path)! return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "") } return self } }
2. 启动完成后刷新界面
虽然默认值能解决nil的问题,但你在didFinishLaunchingWithOptions里设置的正确Locale还没生效,这时候需要通知所有界面控件重新加载本地化文本:
- 在
AppDelegate的didFinishLaunchingWithOptions最后添加通知:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // 你的原有代码... UserDefaults.standard.setValue(Singleton.sharedInstance.currentLocale, forKey: USER_DEFAULT.selectedLocale) // 发送通知,告诉界面刷新本地化内容 NotificationCenter.default.post(name: NSNotification.Name("LocaleDidSet"), object: nil) return true }
- 在每个ViewController里监听通知,刷新控件:
class YourViewController: UIViewController { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() // 监听通知 NotificationCenter.default.addObserver(self, selector: #selector(refreshLocalizedContent), name: NSNotification.Name("LocaleDidSet"), object: nil) // 初始化时先加载一次 refreshLocalizedContent() } @objc private func refreshLocalizedContent() { titleLabel.text = "title_key".localized descLabel.text = "desc_key".localized // 其他需要本地化的控件都在这里重新赋值 } deinit { // 记得移除观察者 NotificationCenter.default.removeObserver(self) } }
3. 纯代码界面用懒加载
如果你的界面是纯代码创建的,把控件改成懒加载属性,这样控件的text赋值会在didFinishLaunchingWithOptions之后执行:
class YourViewController: UIViewController { lazy var titleLabel: UILabel = { let label = UILabel() label.text = "title_key".localized // 这里的localized会在控件第一次被访问时调用,也就是didFinishLaunchingWithOptions之后 // 其他布局设置... return label }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(titleLabel) // 布局代码... } }
这样调整后,就能保证localized属性能拿到正确的Locale值了。
内容的提问来源于stack exchange,提问作者sanjaykmwt

