Skip to content

Instantly share code, notes, and snippets.

@ehuynh
Last active February 23, 2018 08:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ehuynh/203ee3e2532652503ac8 to your computer and use it in GitHub Desktop.
Save ehuynh/203ee3e2532652503ac8 to your computer and use it in GitHub Desktop.
Swift Cheat Sheet

Basic

Integers

Int

In most cases, you don't need to pick a specific size of integer to use in your code. Swift provides an integer type, Int, which has the same size as the current platform's native word size:

  • On a 32bit platform, Int is the same size as Int32
  • On a 64bit platform, Int is the same size as Int64
let meaningOfLife = 42 // meaningOfLife is inferred to be of type Int

Use the Int type for all general-purpose integer constants and variables in your code, event if they are known to be non-negative. Using the default integer type in everyday situations means that integer constrants and variables are immediately interoperable in your code and will match the inferred type for integeral literal values without the need of conversion.

Floating-Point Numbers

Swift provides two signed floating point number types:

  • Double represents a 64-bit floating point number
  • Float represents a 32-bit floating point number

Swift always choses Double (rather than Float) when inferring the type of floating-point numbers

let anotherPi = 3.13159 // anotherPi is inferred to be of type Double

Type Aliases

Type aliases are useful when you want to refer to an existing type by a name that is contextually more appropriate

typealias AudioSample = UInt16

Tuples

Tuples group multiple values into a single compound value.

if you only need some of the tuple's value, ignore parts of the tuple with an underscore (_) when you decompose the tuple:

let http404Error = (404, "Not Found")
let (justTheStatusCode, _) = http404Error

Alternatively, access the individual element values ina tuple using index numbers starting at zero:

println("The status code is \(http404Error.0)")
println("The status message is \(http404Error.1)")

You can nume the individual eleemnts in a tuple when the tuple is defined:

let http200Status = (statusCode: 200, description: "OK")

If you name the elements in a tuple, you can use the leemnt names to access the values of those elements:

println("The status code is \(http200Status.statusCode)")
println("The status message is \(http200Status.description)")

Control Flow

For Loops

Two for loops in swift

  • The for-in loop performs a set of statements for each item in a sequence
  • The for loop performs a set of statements until a specific condition is met, typically by incrementing a counter each time the loop ends

For-in

for index in 1...5 {
	println("\(index) times 5 is \(index * 5)")
}

if you don't need each value from a sequence, you can ignore the values by using an underscore in place of a variable name:

for _ in 1...5 {
	...
}

You can also iternate over a dictionary

for (key, value) in dictionary {
	...
}

You can also iternate a string

for character in "Hello" {
	...
}	

For

for var index = 0; index < 10 ; index++ {
	...
}

Switch

Switch statements do not "fall through" to the next case in Swift, avoiding common C errors caused by missing break statements

Every switch statement must be exhaustive. That is, every possible value of the type being considered must be matched by one of the switch cases. If it is not appropriate to provide a swtich case for every possible value, you can define a default catch-all case to cover any values that are not addressed explicity. This catch-all case is indicated by the keyword default, and must always appear last.

let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
	println("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "s", "t", "v", "w", "x", "y", "z":
 	println("\(someCharacter) is a consonant")
 default:
 	println("\(someCharacter) is not a vowel or a consonant")
}

Functions

Function syntax

func helloWorld() {
    println("hello, world!")
}

func add(a: Int, b: Int) -> Int {
    return a + b
}

Calling functions

You can call a function without parameter names

func add(a: Int, b: Int) -> Int {
     return a + b
}

add(1,2)

If there is a external parameter name, then it must be used

func add(a: Int, toNum b: Int) -> Int {
    return a + b
}

add(1, toNum: 2)

If you want the external and internal parameter names to be the same, you don’t have to write out the parameter name twice, just add a # in front of the parameter name as a shortcut

func add(a: Int, #b: Int) -> Int {
    return a + b
}

add(1, b: 2)

Calling methods

Calling methods are a bit different. By default swift makes the internal parameter names the external parameter names. The excpetion in the default case is the first parameter where no external parameter name is used.

Hence when you call a method, you need to provide the external parameter name for every parameter except the first. Because of this you typically want to encode the first param name into the method name.

class Foo {
    
    func helloWorldWithName(name: String, surname: String, age: Int) {
        println("Hello \(name) \(surname). You're \(age).")
    }
}

let foo = Foo()
foo.helloWorldWithName("John", surname: "Citizen", age: 42)

If you want the external name to be excluded then just add _ infront of the internal parameter name

func helloWorldWithName(name: String, _ surname: String, _ age: Int) {
   println("Hello \(name) \(surname). You're \(age).")
}

First Class Citizens

Since functions are first class citizens, you can return functions from functions

func incrementor(amount: Int) -> ((Int) -> Int) {
    func adder(num: Int) -> Int {
        return num + amount
    }
    
    return adder
}

var adder = incrementor(2)
adder(4) // -> 6

You can also pass functions as arguments

func add(adder: (Int) -> Int, num: Int) -> Int {
    return adder(num)
}

add(adder, num: 10) // -> 12

Retain Cycles

Swift provides two ways to resolve strong reference cycles when you work with properties of class type: weak references and unowned references.

Weak and unowned references enable one instance in a reference cycle to refer to the other instance without keeping a strong hold on it.

  • Use a weak reference whenever it is valid for that reference to become nil at some point during its lifetime.
  • Conversely, use an unowned reference when you know that the reference will never be nil once it has been set during initialization.

Note

  • Weak references must be declared as variables, to indicate that their value can change at runtime. A weak reference cannot be declared as a constant.
  • Because weak references are allowed to have “no value”, you must declare every weak reference as having an optional type.
  • ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated.
  • An unowned reference is assumed to always have a value. Because of this, an unowned reference is always defined as a nonoptional type.
  • If you try to access an unowned reference after the instance that it references is deallocated, you will trigger a runtime error.
class Foo {    
    weak var delegate: Bar?
}

Closures

You can write a closure without a name by surrounding code with braces ({}). Use in to separate the arguments from the return type.

add({
    (num: Int) -> Int in
    return num + 2
    },
    num: 10) // -> 12

if the function is rewritten to use a trailing closure

func add2(num: Int, adder: (Int) -> Int) -> Int {
    return adder(num)
}

you can pass the trailing closure after the parentheses

add2(10) { (num: Int) -> Int in
    return num + 2
}

If the closure is the only argument to a function, then you can omit the parentheses all together

[3,1,2].sort {(num1: Int, num2: Int) -> Bool in
    return num1 < num2
} // [1,2,3]

Single statement closures can omit the return keyword as they implicitly return the value of their only statement

[3,1,2].sort {(num1: Int, num2: Int) -> Bool in
    num1 < num2
} // [1,2,3]

When the closure's type is already known then you can omit the type for parameters

[3,1,2].sort {(num1, num2) -> Bool in
    num1 < num2
} // [1,2,3]

You can also refer to arguments by numbers to make the closure shorter

[3,1,2].sort { $0 < $1 } // [1,2,3]

Dealing with retain cycles

You avoid retain cycles in closures by defining a capture list as part of the closure's definition. The capture list defines the rules to use when capturing one or more reference types within the closure's body.

var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}
  • Define a catpure in aclosure as an unowned reference when the closure and the instance it captures will always refere to each other, and will always be deallocated at the same time. If the capture reference will never become nil, it should always be captured as an unowned reference, rather than a weak reference.
  • Conversely, define a capture as a weak reference when the capture reference may become nil at some point in the feature. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocted

Type Casting

Type cast operator is as.

  • as is used for guareented conversion. E.g for upcasting
  • as! is used for forced conversions. E.g. for downcasting. Will raise an runtime error if downcast fails
  • as? is used for optional conversions. Will return nil if it fails.
class Animal {}
class Dog: Animal {}

let a: Animal = Dog()
a as Dog		// now raises the error:  "'Animal is not convertible to 'Dog';
				// ... did you mean to use 'as!' to force downcast?"

a as! Dog		// forced downcast is allowed

let d = Dog()
d as Animal		// upcast succeeds

let animal: Animal = Cat()
animal as? Dog	// evaluates to nil
animal as! Dog	// triggers a runtime error

Arrays

Type type of Swift array is written in full as Array<SomeType>, where SomeType is the type of values the array is allowed to store. You can also write the type of an array in shorthand form as [SomeType]

var strArray01 = [String]() // empty string array
strArray01.append("hello") // appending a string
strArray01 += ["world"] // another way to append
strArray01 += ["foo", "bar"] // concat a array of strings

let strArray02 = ["hello", "world"] // initialize array with strings

let strArray03 = strArray01 + strArray02 // create new array from existing arrays

Dictionary

var dict01 = [Int: String]() // empty dictionary
dict01[1] = "One" // add to dictionary
dict01 = [:] // clear dictionary

let dict02 = [1: "One", 2: "Two"] // initialize dictionary with content

Access Control

Access Levels

  • public access enables entities to be used anywhere
  • internal access enables entitles to be used within their defining module
  • private access restricts use to their own defining source file

Default access Levels

All entities in your code have a default access level of internal.

The access control level of a type also affects the default access level of that type's members(its properties methods, initializers, and subscripts).

  • If you define a type's access level as private, the default access level of its members will also be private.
  • If you define a type's access level as public or internal, the default access level of the type's members will be internal

Optionals

Optional chaining

When working with optional values, you can write ? before operations like

  • methods
  • properties
  • subscripting

If the value before ? is nil, everything after the ? is ignored and the value of the whole expression is nil. Otherwise, the optional value is unwrapped, and everything after the ? acts on the unwrapped value. In both cases, the value of the whole expression is an optional value

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

Nil Coalescing Operator

The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression b must match the type that is stored inside a. It is a shorthand for below

a != nil ? a! : b

Optional binding

You use optional binding to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable. Optional binding can be used with if and while statements to check for a value inside an optional, and to extract that value into a constant or variable, as part of a signle action. Multiple optional bindings can appear in a single if statement as a comma-separated list of assignment expression

if let actualNumber01 = possibleNumber01.toInt(), actualNumber02 = possibleNumber02.toInt() {
	println("It is \(actualNumber01) and \(actualNumber02)")
}

Implicility Unwrapped Optionals

If you try to access an implicity unwrapped optional when it does not contain a value, you will trigger a runtime error.

Enums

You can use

  • Int
  • String
  • floating point numbers

as the raw type of an enumeration.

enum Rank: Int {
	case Ace = 1
	case Two, THree, Four
	case Jack, QUeen, King
  
	func simpleDescription() -> String {
		switch self {
		case .Ace:
		return "ace"
		case .Jack:
		return "jack"
		default:
		return String(self.rawValue)
	    }
	}
}

let ace = Rank.Ace

When assigning a value to the ace constant, the enumeration case Rank.Ace is referred to by its full name because the constant doesn't have an explicit type specified. Inside the switch, the enumeration case is referred to by the abbreviated form .Ace because the value of self is already known to be a Rank. You can use the abbreviated form anytime the value's type is already known

An instance of an enumeration case can have values associated with the instance.

enum ServerResponse {
	case Result(Int, String)
	case Error(Int)
}
let success = ServerResponse.Result(200, "Hello World")
let failure = ServerResponse.Error(404)

Here we have 2 values associated with the Result case and one value with the Error case. Instances of the same enumeration case can have different values associated with them. Associated values and raw values are different. The raw value of an enumeration case is the same for all of its instances(e.g the raw values above are Result and Error)

Structs

Structs support many of the same behaviours as classes, including methods and intializers. One of the most important differences between structs and classes is that structs are always copied when they are passed around in your code, but classes are passed by reference.

stuct Card {
	var rank: Rank
	var suit: Suit
	func simpleDescription() -> String {
		return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
	}
}

let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescrption = threeOfSpades.simpleDescription()

Protocols

protocol ExampleProtocol {
  var simpleDescription: String { get }
  mutating func adjust()
}

Classes, enumerations, and structs can all adopt protocols

class SimpleClass: ExampleProtocol {
  
  var simpleDescription: String = "A very simple class."
  var anotherProperty: Int = 69105
  
  func adjust() {
      simpleDescription += "  Now 100% adjusted."
  }
  
}

struct SimpleStructure: ExampleProtocol {
  
  var simpleDescription: String = "A simple structure"
  
  mutating func adjust() {
     simpleDescription += " (adjusted)"
  }
}

the mutating keyword in the declaration SimpleStructure to mark a method that modifies the structure. The declaration of SimpleClass doesn't need any of its methods marked as mutating because methods on a class can always modify the class

Extensions

Use extensions to add functionality to an existing type, such as

  • new methods
  • computed properties
  • protocol conformance to a type

Generics

Write a name inside angle brackets to make a generic function or type

func repeat<Item>(item: Item, times: Int) -> [Item] {
	var result = [Item]()
	for i in 0..<times {
		result.append(item)
	}
	return result
}

You can make generic forms of:

  • functions
  • methods
  • classes
  • enumerations
  • structures
// Reimplement the Swift standard library's optional type
enum OptionalValue<T> {
	case None
	case Some(T)
}

var possibleInteger: OptionalValue<Int> = .None
possibelInteger = .Some(100)

Use where after the type name to specify a list of requirements - for example:

  • to require the type to implement a protocol
  • to require two types to be the same class
  • or to require a class to have a particular superclass
func anyCommonElements <T, U where T: SequenceType, 
	U:SequenceType, 
	T.Generator.Element: Equatable, 
	T.Generator.Element == U.Generator.Element> 
	(lhs: T, rhs: U) -> Bool {
	
	for lhsItem in lhs {
		for rhsItem in rhs {
			if lhsItem == rhsItem {
				return true
			}
		}
	}
	
	return false
}

anyCommonElements([1,2,3], [3])

for simple cases you can shorten the sytnax and exclude the where. Writing <T: Equatable> is the same as writing <T where T: Equatable>

Intermediate

Protocols

###Protocol Extensions

Protocols can be extended to provide method and property implementations to conforming types. This allows you to define behavior on protocols themselves, rather than in each type’s individual conformance or in a global function.

protocl RandomNumberGenerator {
	func randomBool() -> Bool
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

By creating an extension on the protocol, all conforming types automatically gain this method implementation without any additional modification.

Providing Default Implementations

You can use protocol extensions to provide a default implementation to any method or property requirement of that protocol. If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment