Adding randomness to your iOS app

Photo by Riho Kroll on Unsplash

Adding randomness to your iOS app

Learn how to generate random numbers in your Swift source code

This is the central illusion in life: that randomness is a risk, that it is a bad thing.

Why?

In many cases, when developing mobile applications or games, we have a need to add unpredictable behavior to the program logic. Random numbers can be used to simulate complex processes, implement cryptographic protocols, design advanced algorithms and data structures, or to select a random track from a list of tracks in a music application. Due to the wide range of random number applications, I decided to write a post about the API that Swift provides to developers.

Before Swift 4.2

For many years, in order to generate random numbers, iOS developers have used the C functions provided via the API of the BSD library. These are the legacy that came from C to Objective-C and then from Objective-C to Swift.

arc4random

import UIKit

let width = arc4random() % 400
let height = arc4random() % 300

let view = UIView(frame: CGRect(x: 0, y: 0, width: Int(width), height: Int(height)))

The function simply returns the pseudo-random numbers in the range of 0 to 2³²-1. Despite its simplicity, the function has a serious drawback  -  it leads to modulo bias (i.e. some numbers are generated with higher probability).

arc4random_uniform

import UIKit

let width = arc4random_uniform(400)
let height = arc4random_uniform(300)

let view = UIView(frame: CGRect(x: 0, y: 0, width: Int(width), height: Int(height)))

The function returns a uniformly distributed random number less than upper_bound. The usage of this function is recommended over arc4random since it avoids the modulo bias problem. Thus, it was the best choice for iOS developers before Apple introduced the newest Swift API.

The modern way

In Swift 4.2 a unified and secure random API has been released for all platforms. This means C APIs are no longer needed when someone wants to generate random numbers in Swift. In addition, all of the bottlenecks that the old way offers (e.g. modulo bias) are now completely avoided.

Swift basic value types

import UIKit

// Bool
let flippedHeads = Bool.random()

if flippedHeads {
    print("Heads, you win!")
} else {
    print("Tails, let's play again!")
}

// Int
let diceRoll = Int.random(in: 1...6)

switch diceRoll {
case 1, 2:
    print("Dude, try again :)")
case 3, 4:
    print("Keep going!")
case 5, 6:
    print("Perfect!")
default:
    break
}

// Float
let randomColorAlphaComponent = Float.random(in: 0...1)
let color = UIColor.black.withAlphaComponent(CGFloat(randomColorAlphaComponent))

// Double
let score = Double.random(in: 0..<100)

if score > 50.0 {
    print("Your score is better than average!")
} else {
    print("Keep going!")
}

For basic Swift value types, such as Bool, Int, Float or Double, the new API introduces a method called random(), which accepts a range of values (except for Bool) and generates a random number from the range.

Under the hood, these methods have a SystemRandomNumberGenerator which is based on the cryptographically secure randomizer that is built into the Swift language.

The new API is easy-to-use and, at the same time, guarantees the security and uniformity for all platforms.

Bonus: Collections

For Swift Collection Types (e.g. Array, Dictionary, Set) the new random API also introduces three methods: randomElement(), shuffle() and shuffled().

import Foundation

let playlist = [
    "Poker Face - Lady Gaga",
    "Audio - Diplo",
    "Anvil - Lorn",
    "Go Fuck Yourself - Two Feet",
    "Phenomenal - Eminem",
    "Baby's On Fire - Die Antwoord"
]

let currentSong = playlist.randomElement()!
let shuffledPlaylist = playlist.shuffled()

print("Now playing: \(currentSong)")
print("Playlist is shuffled: \(shuffledPlaylist)")

The randomElement() method returns a value that is contained in a collection. The method returns an Optional type in order to prevent the “Index out of range” error if the collection is empty.

The shuffle() method can be called on a mutable collection, and it shuffles elements in-place, while the shuffled() method returns a new copy of a collection with shuffled elements.

The implementation of these two methods relies on the aforementioned random() method.

Applications

There are a lot of things that can be built on top of the new random Swift API.

UIColor Extension

import UIKit

extension UIColor {
    static func random() -> UIColor {
        return UIColor(red: CGFloat(Float.random(in: 0...1)),
                       green: CGFloat(Float.random(in: 0...1)),
                       blue: CGFloat(Float.random(in: 0...1)),
                       alpha: 1.0)
    }
}

let color = UIColor.random()

Here is the UIColor extension which encapsulates a static method that generates a random color. It uses the Float.random(in:) method to generate RGB channel values.

Password Suggestion

import Foundation

func suggestPassword(length: Int) -> String {
    let characters = [
        UnicodeScalar("a").value...UnicodeScalar("z").value,
        UnicodeScalar("A").value...UnicodeScalar("Z").value,
        UnicodeScalar("0").value...UnicodeScalar("9").value
    ].joined()

    var result = ""

    for _ in 0 ..< length {
        let randomCharacter = characters.randomElement()!
        let unicodeScalar = UnicodeScalar(randomCharacter)!
        result += String(unicodeScalar)
    }

    return result
}

let password = suggestPassword(length: 16)

In this example, a password is created by the iterative process of taking a random element from a set of characters.

The Takeaway

  • In your apps prefer using the Int.random(in:) method rather than the arc4random() function. Use similar methods to generate Bool, Float and Double values.
  • The newest Swift API simplifies the work with randomness and collection types by exposing randomElement(), shuffle() and shuffled() methods.
  • Feel free to build your own complex systems on top of the Swift API as we did to generate a random color or a new password.

Reference

https://www.linkedin.com/in/yarspirin/