我意识到Swift书籍提供了一个随机数生成器的实现。复制和粘贴这个实现是最佳实践吗?或者有没有这样的库,我们现在就可以使用?


当前回答

Swift 4 + 2。

Xcode 10附带的Swift 4.2为许多数据类型引入了新的易于使用的随机函数。

只需在数值类型上调用random()方法。

let randomInt = Int.random(in: 0..<6)
let randomDouble = Double.random(in: 2.71828...3.14159)
let randomBool = Bool.random()

其他回答

对于0到n-1之间的随机整数,使用arc4random_uniform(n)。

let diceRoll = Int(arc4random_uniform(6) + 1)

将结果转换为Int,这样你就不必显式地将你的vars类型为UInt32(这看起来不像swifty)。

Swift 4.2编辑

从Swift 4.2开始,你可以使用Swift自己的本地函数,而不是使用导入的C函数arc4random_uniform()。

// Generates integers starting with 0 up to, and including, 10
Int.random(in: 0 ... 10)

你也可以使用random(in:)来获取其他原始值的随机值;比如Int型,Double型,Float型,甚至Bool型。

Swift版本< 4.2

该方法将在给定的最小值和最大值之间生成一个随机Int值

func randomInt(min: Int, max: Int) -> Int {
    return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}

我想补充现有的答案,在Swift书中的随机数生成器的例子是一个线性同余生成器(LCG),这是一个非常有限的一个,不应该除了必须平凡的例子,其中随机性的质量根本不重要。LCG永远不应该用于加密目的。

Arc4random()要好得多,可以用于大多数目的,但不应该用于加密目的。

如果您想要保证加密安全的内容,请使用SecCopyRandomBytes()。请注意,如果您将随机数生成器构建到某个东西中,其他人可能最终(错误地)将其用于加密目的(例如密码、密钥或盐生成),那么无论如何您都应该考虑使用SecCopyRandomBytes(),即使您的需要并不完全需要这样做。

从Swift 4.2开始

有一组新的api:

let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomDouble = Double.random(in: 1 ... 10)

所有数字类型现在都有random(in:)方法,该方法接受range。 它返回一个在该范围内均匀分布的数字。


博士TL;

那么,“好”的旧方法有什么错呢?

你必须使用导入的C api(它们在不同平台之间是不同的)。 而且……

如果我告诉你随机并不是那么随机呢?

如果您使用arc4random()(计算余数),如arc4random() % aNumber,结果不是均匀分布在0和aNumber之间。有一个问题叫做模偏倚。

模的偏见

通常,该函数生成一个0到MAX之间的随机数(取决于类型等)。举个简单的例子,假设最大的数字是7,你关心的是一个范围为0 ..< 2(或者间隔[0,3),如果你喜欢的话)。

单个数字的概率为:

0: 3/8 = 37.5% 1: 3/8 = 37.5% 2: 2/8 = 25%

换句话说,你更有可能得到0或1而不是2。 当然,请记住,这是非常简化的,MAX数字要高得多,使其更加“公平”。

Swift 4.2中的SE-0202 -随机统一解决了这个问题

下面的代码将产生一个0到255之间的安全随机数:

extension UInt8 {
  public static var random: UInt8 {
    var number: UInt8 = 0
    _ = SecRandomCopyBytes(kSecRandomDefault, 1, &number)
    return number
  }
}

你这样称呼它:

print(UInt8.random)

对于更大的数字,它会变得更复杂。 这是我能想到的最好的:

extension UInt16 {
  public static var random: UInt16 {
    let count = Int(UInt8.random % 2) + 1
    var numbers = [UInt8](repeating: 0, count: 2)
    _ = SecRandomCopyBytes(kSecRandomDefault, count, &numbers)
    return numbers.reversed().reduce(0) { $0 << 8 + UInt16($1) }
  }
}

extension UInt32 {
  public static var random: UInt32 {
    let count = Int(UInt8.random % 4) + 1
    var numbers = [UInt8](repeating: 0, count: 4)
    _ = SecRandomCopyBytes(kSecRandomDefault, count, &numbers)
    return numbers.reversed().reduce(0) { $0 << 8 + UInt32($1) }
  }
}

这些方法使用一个额外的随机数来确定有多少uint8将被用于创建随机数。最后一行将[UInt8]转换为UInt16或UInt32。

我不知道后两个是否还算真正的随机,但你可以根据自己的喜好进行调整:)