arc4random_uniform 被记录为“加密伪随机数生成器”,因此它应该可以用于此目的。不要将 RC4 的安全问题与arc4random 混淆。有关详细信息,请参阅Zaph's answer。 (我之前对此进行过研究,我记得arc4random 与其他方法一样安全,但我更信任 Zaph,而不是我自己的记忆。)
也就是说,如果你很紧张,你想使用的工具是 SecRandomCopyBytes(或者你可以从 /dev/random 中读取,这正是 SecRandomCopyBytes 按规范所做的)。
从SecRandomCopyBytes 获取随机值比应有的困难,但不是太难。以下是您以高度通用的方式执行此操作的方法(Swift 3):
extension Integer {
static func makeRandom() -> Self {
var result: Self = 0
withUnsafeMutablePointer(to: &result) { resultPtr in
resultPtr.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout<Self>.size) { bytePtr in
SecRandomCopyBytes(nil, MemoryLayout<Self>.size, bytePtr)
}
}
return result
}
}
这适用于任何Integer。基本上我们将一堆随机字节解释为Integer。 (顺便说一句,这种方法对浮点值几乎没有效果。你可以这样做,但你会发现并非所有位模式实际上都是浮点中的“数字”。所以它有点复杂。)
现在您希望在不引入偏差的情况下将这些值置于一个范围内。只是说x % limit 创建modulo bias。不要那样做。正确的方法是做arc4random_uniform 所做的事情。是open source,你可以去看看。在 Swift 中应用相同的方法如下所示:
extension Int {
static func makeRandom(betweenZeroAnd limit: Int) -> Int {
assert(limit > 0)
// Convert our range from [0, Int.max) to [Int.max % limit, Int.max)
// This way, when we later % limit, there will be no bias
let minValue = Int.max % limit
var value = 0
// Keep guessing until we're in the range.
// In theory this could loop forever. It won't. A couple of times at worst
// (mostly because we'll pick some negatives that we'll throw away)
repeat {
value = makeRandom()
} while value < minValue
return value % limit
}
}
我们无法在Integer 上构建它,因为Integer 上没有.max 属性。
在 Swift 4 中,这一切都被 FixedWidthInteger 清理了,我们可以让它更通用:
extension FixedWidthInteger {
static func makeRandom() -> Self {
var result: Self = 0
withUnsafeMutablePointer(to: &result) { resultPtr in
resultPtr.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout<Self>.size) { bytePtr in
SecRandomCopyBytes(nil, MemoryLayout<Self>.size, bytePtr)
}
}
return result
}
static func makeRandom(betweenZeroAnd limit: Self) -> Self {
assert(limit > 0)
// Convert our range from [0, Int.max) to [Int.max % limit, Int.max)
// This way, when we later % limit, there will be no bias
let minValue = Self.max % limit
var value: Self = 0
// Keep guessing until we're in the range.
// In theory this could loop forever. It won't. A couple of times at worst
// (mostly because we'll pick some negatives that we'll throw away)
repeat {
value = makeRandom()
} while value < minValue
return value % limit
}
}