【问题标题】:Runtime error String(format: ) when passing less variadic parameters传递较少可变参数时的运行时错误字符串(格式:)
【发布时间】:2021-09-24 09:42:09
【问题描述】:

最近在firebase crashlytics中我们发现了以下问题:

let a = "1-> %@" // Strings are located in Localized.strings, we already know them
let b = "1-> %@, 2-> %@" 

let aStr = String(format: a, "hello") // Works fine
let bStr = String(format: b, "hello") // Run time error on certain machine (M1, physical device)

看起来当将较少的可变参数传递给String(format:) 时,您会在 M1 和物理设备上遇到运行时崩溃。

我想知道是否有任何方法可以避免来自 localised.strings 的字符串出现这些运行时错误。

最好在编译时捕捉到这一点。 您将如何面对这个问题?

【问题讨论】:

  • 由于格式来自localized.strings,您应该更改代码示例以使其看起来尽可能接近您的实际代码(即localizedStringWithFormatNSLocalizedString 和所有这些好东西)。我认为没有办法单独使用编译器来检查这一点,但是编译器 + 像 plutil -lint 这样的工具 + 一些自定义脚本可能会提供解决方案。
  • @SergeyKalinichenko 我基本上在想代码生成可能是一个解决方案。对于每个格式字符串,它将生成一个具有确切数量参数的函数(避免使用可变参数)。但是我们遇到了不同语言的问题。但是我们可以为不同的语言创建不同的方法。
  • 我认为布拉德利的回答为您提供了一个很好的解决方案模板。也许您会在这里和那里对其进行优化,但是构建自己的功能以“领先”本地化过程的想法是正确的想法。也许您可以推送更多参数而不是断言,以显示问号来代替未知参数,但总体方法对我来说看起来很可靠。
  • 这是不使用String(format:) 的一个很好的理由。据我所知,在现代 Swift 中,没有真正的理由来称呼它。
  • @matt 那么应该用什么代替呢?

标签: ios swift string-formatting apple-m1


【解决方案1】:

没有为格式字符串提供足够的参数将导致未定义的行为(它可能会崩溃,也可能不会 - 没有办法知道)。 这是一个编程错误,但由于它们的动态特性,您目前不会收到编译时警告。

但是,如果没有提供足够的格式参数,我们可以编写一个小帮助程序确定性地崩溃。 请注意,这不是一个完美的实现,可能需要根据您的要求进行调整。

extension String {

    // https://en.wikipedia.org/wiki/Printf_format_string
    private static var formatCharacters: Set<Character> {
        "@diufFeEgGxXoscpaAn".reduce(into: []) { $0.insert($1) }
    }

    static func safely(format template: String, _ params: CVarArg...) -> String {
        var potentialPattern = false
        var patternCount = 0
        for char in template {
            switch char {
            case "%":
                // %% or %
                potentialPattern.toggle()
            case let x where formatCharacters.contains(x) && potentialPattern:
                patternCount += 1
                potentialPattern = false
            case let x where !x.isWhitespace:
                potentialPattern = false
            default:
                break
            }
        }
        assert(
            patternCount <= params.count,
            "Not enough parameters passed to format String. Found \(params.count), expected at least \(patternCount)."
        )
        return String(format: template, arguments: params)
    }
    
}

如果没有提供足够的参数,我们就会崩溃:

let f = String.safely(format: "%@ %@", "nice")
// Assertion failed: Not enough parameters passed to format String. Found 1, expected at least 2.

【讨论】:

  • 我实际上是在谈论来自localized.strings 的字符串。所以我们已经认识他们了。确定动态字符串不能在编译时检查!
  • @KwnstantinosNikoloutsos 啊,我明白了,看看我更新的答案。
猜你喜欢
  • 1970-01-01
  • 2015-09-14
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-17
相关资源
最近更新 更多