Skip to content

naderghanbari/swift-cheat-sheet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 

Repository files navigation

Swift Language Guide Cheat Sheet

Constants and Variables
let aspectRatio = 1.6                     // Constant, Inferred Type

var remainingTime = 60                    // Variable, Inferred Type

var userName: String = "[email protected]"     // Variable, Annotated Type

let age = 10, name = "John"               // Multiple Constants, Inferred Type

var x, y, z: Double                       // Multiple Variable Declarations, The same Annotated Type

var x = 0, y = 0, z = 0                   // Multiple Variable Initializations, Inferred Type

typealias Distance = Int64                // Type Alias
Relations and Logical Operations
obj1 == obj2                              // Equality check

obj1 != obj2                              // Not-Equals check

if 2 < 3, i <= 5, i > 7, i >= 8           // Usual order relations
String
print(welcomeMessage)                     // Print, Acts like println by default

print("My name is \(userName)")           // String Interploation, Basic 

let longString = """                      // Multi-line Strings
  Long multiline 
  string.
  """                                     // Closing quote determines the alignment (no need for strip)

let immutableString = "no can change"     // Constant strings are immutable

var changeMeLater = "Yes, "               // Variable strings are mutable
chaneMeLater += "We Can!"

let exclamationMark: Character = "!"      // Character type, Chracter String literal (no single quotes)

let unicode = "Penguin 🐧"                // Unicode

let s = "Guten Tag!"                      // Indexing
s[s.startIndex]                           // G
s[s.index(before: s.endIndex)]            // !
s[s.index(after: s.startIndex)]           // u

for character in "Dog!🐶" {               // Iterate over characters 
  print(character)
}

let threeMoreDoubleQuotationMarks = #"""  // Escape from escaping!
Here are three more double quotes: """
"""#
Numeric Types
let minValue: UInt8 = UInt8.min           // Unsigned Integer, a la C types

let largeNumber: Int = 102334             // Platform-specific, Either Int32 or Int64

let highPrecision: Double = 2.718182198   // 64-bit floating point number 

let ascpetRatio: Float = 1.333333         // 32-bit floating point number 
Booleans
let thisIsATautology: Bool = true         // Bool type for booleans, true litreal

let examFailed = false                    // `false` literal 

let isSafe = zombiesCount == 0            // Equality comparison operator ==

if isSafe { print("No worries") }         // Conditional 
else { print("Run!") }
Tuples
let binding = ("localhost", 8080)         // 2-Tuple Construction, Inferred Type (String, Int)

let p: (Int, Int, Int) = (1, 3, 5)        // 3-Tuple Construction, Annotated Type

let (x, y, z) = coordinates               // Deconstruction

let (host, _) = binding                   // Discarding extra information with _

print("Host: \(binding.0)")               // Accessing the First component (0-based index)
print("Port: \(binding.1)")               // Accessing the Second component

let user = (name: "John", age: 10)        // Named tuples

print("User name: \(user.name)")          // Accessing named component

(b, a) = (a, b)                           // Trick to swap two variables!

(10, "Zebra") < (10, "Apple")             // Component-wise left-to-right comparison
Optionals
var maybeNumber: Int?                     // A type qualified with a `?` means optional

maybeNumber = nil                         // `nil` indicates absence of a value

var policyNoOpt: String?                  // Initial value is `nil` for uninitialized variables

policyNoOpt = "#FF2345"                   // Assign an actual value, indicates presense of a value

if (maybeNumber != nil) {                 // Presence of value Check
  let num = maybeNumber!                  // Access value by forcing matters using !
  print("Lucky number is: \(num)")        // Forcing throws runtime error if value is nil
}

if let policyNo = policyNoOpt {           // Optional Binding, checks presence and binds
  print("Policy No: \(policyNo)")         // the actual value, if any, 
}                                         // at the same time (a la monadic map)

if var name = nameOpt,                    // Multiple Optional Bindings
   let age = ageOpt,                      // Unpacking to a variable or constant is allowed
   age > 2 {                              // Using unpacked value in more conditionals is allowed
     name = "Passenger \(name)"           // Condition fails if any of optionals are nil or
     print("\(name): \(age)")             // if subsequent constraints don't hold
}

var email: String!                        // Implicitly Unwrapped Optional, Type qualified by !
email = "[email protected]"                    // Initialization, Most probably in a class constructor
print(email)                              // Auto-unwrapped, email acts as email! everyhwere

maybeNumber ?? 0                          // Nil Coalesce, Like maybeNumber.getOrElse(0) in Scala
Error Handling
func getPermission() throws  {            // Indicates a possibility of throwing errors
  throw Permissions.deniedByUser          // Throws an error
}

do {                                      // Error handling
  try getUserPermission()
} catch Permissions.deniedByUser {
  // handle error
} catch Permissions.deniedByAdmin(let name) where name == "CEO" {
  println("Ohhhkay")
} catch catch Permissions.deniedByAdmin(let name) {
  println("Gotcha \(name)! ")
}

let x = try? someThrowingFunction()      // Convert Errors to Optionals

let photo = try! someThrowingButSound()  // Disables error propagation, aka "I'm sure it never fails" 

assert(age >= 0, "Unexpected!")          // Assertions
precondition(index > 0, "Must be true")  // Preconditions, compiler flag dependent
Ranges
for index in 1...5 {                     // a..b is the inclusive range [a, b]
    print("\(index)")                    // 1, 2, 3, 4, 5
}

a..<b                                    // [a, b) half-open range, includes a, excludes b

myArray[2...]                            // One-Sided range, Array Slicing a la Python's myArray[2:]
myArray[...2]                            // One-Sided inclusive range, myArray[:3] in Python
myArray[..<2]                            // One-Sided exclusive range, myArray[:2] in Python
Collections: Arrays
var arr: Array<Int>                      // Mutable array of integers, var signals mutability

var arr: [Int] = []                      // Shorthand and preferred notation for array types

["a", "b", "c"]                          // Array Literal

arr.append(1)                            // Append an element
arr += [1, 2]                            // Append an array to the end of this array

arr1 + arr2                              // Concatenate two arrays

arr.count                                // Size of the array
arr.isEmpty                              // Emptiness check

arr[0]                                   // Access elements, Subscript syntax

arr[0] = "NewValue"                      // Update element at a specific index

const justRemoved = arr.remove(at: 2)    // Remove the item at index 2 and return it
const wasLast = arr.removeLast()         // Remove last element from array

for item in shoppingList {               // Iterate over an array
  print(item)
}

for (idx, elem) in arr.enumerated() {   // Iterate with index
    print("Item \(idx + 1): \(elem)")
}

myArray[2...]                            // One-Sided range, Array Slicing a la Python's myArray[2:]
myArray[...2]                            // One-Sided inclusive range, myArray[:3] in Python
myArray[..<2]                            // One-Sided exclusive range, myArray[:2] in Python
Collections: Sets
var mySet: Set<SomeType>                // Set type, SomeType must be Hashable

var letters = Set<Character>()          // Empty set

const genres: Set = ["Rock", "Jazz"]    // Array litreal can be used for sets

letters.insert("b")                     // Add a new member

letters.count                           // Size of the set

letters.contains("c")                   // Membership, indicator function of the set

setA.intersection(setB)                 // Set-theoric functions
setA.symmetricDifference(setB)
setA.union(setB)
setA.subtracting(setB)
setA.isSubset(of: setB) 
setA.isSuperset(of: setB) 
setA.isStrictSubset(of: setB) 
setA.isStrictSuperset(of: setB) 
setA.isDisjoint(with: setB) 
Collections: Dictionaries
var dict: Dictionary<String, String>    // Dictionary<Key, Value>

var population: [String: Int]           // Shorthand and preferred type notation

var population = [String: Int]()        // Empty dictionary with explicit types

population = [:]                        // Empty set litreal for an already proven type

["Earth": 42, "Mars": 1, "Moon": -14]   // Dictionary literal   

population["Earth"] = 42                // Assign value to key

                                        // Alternative syntax for updating
const oldVal: Int? = population.updateValue(43, forKey: "Earth")

if let old = population.updateValue(43, forKey: "Earth") {
  print("The old value for Earth was \(old).")
}

population.removeValue(forKey: "Earth") // Remove a key

population["Earth"] = nil               // Syntactic sugar for removing a key

const p: Int? = population["Moon"]      // Access values for a key, if any

for (where, howMany) in population {    // Iterate over <K,V> pairs
  print("\(where): \(howMany)")
}

population.keys                         // Keys set
population.values                       // Values set

[String](population.keys.sorted())      // Convert the key set to a sorted array
Switch
switch someCharacter {                  
  case "a": print("The first letter of the alphabet")
  case "z": print("The last letter of the alphabet")
  default : print("Some other character")
}

// No fall through
// First match wins
// Must be exhaustive, that is should always match any value of the target type
// Compile error if switch is not exhaustive, very helpful

swith someCharacter {
  case "a", "A": print("A or a")       // Either "a" or "A"  
}

switch approximateCount {
  case 0     : naturalCount = "no"
  case 1..<5 : naturalCount = "a few"  // Range matching
} 

let anotherPoint = (2, 0)             
switch anotherPoint {       
  case (let x, 0):                     // Binding and partial matcing
    print("on x-axis \(x)")
  case (0, let y):
    print("on y-axis (y)")
  case let (x, y) where x == y:        // Where clause for bound variables
    print("on the diagnoal")
  case let (x, y):
    print("any other case") 
} 

ler origin = (0, 0)
let myPoint = (9, 0)
switch myPoint {
  case origin:                         // Value match: checks equality
    print("Is Origin")
  case (let dist, 0), (0, let dist):   // Compound case with binding: all of the same type
    print("On an axis, \(dist) ")      // If it doesn't make sense at first sight, play with it 
  default:
    print("Not on an axis")
} 
Control Flow
for char in input {
  if char == "a" {
    continue                            // Skip the rest of current and go to next iteration
  }
  process(char)
}


for char in input {
  if char == "E" {
    break                               // Break out of the whole loop
  }
  process(char)
}
print("E pressed, game over!")

let myInt = 5
var description = "\(myInt) is"
switch myInt {
  case 1:
   break                                // Match and ignore case using break
  case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " is prime, and also"
    fallthrough                         // Falls through to next case, like C
  default:
    description += " an integer."
}
// prints "5 is prime and also an integer"


func greet(user: [String: String]) {
  guard let name = user["name"] else {  // Guard statement 
    return                              // Early exit pattern
  }

  print("Hello \(name)!")

  guard let loc = user["loc"] else {    // Watch out, another guard!
    print("Where are you?")             // Another not so early exist
    return
  }

  print("Weather is nice in \(loc).")
}
Functions
func abs(x: Int) -> Int {               // Defining a function of type Int -> Int
  return x < 0 ? -x : x 
}

abs(x: -3)                              // Calling the function, naming the param is required

                                        // A function that returns an optional tuple
func minMax(array: [Int]) -> (min: Int, max: Int)? {
  if array.isEmpty {
    return nil
  }
  // Find min and max is array is not empty
  return (min, max)
}

                                        // Calling the function
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
  print("min is \(bounds.min) and max is \(bounds.max)")
}

func sgn(x: Int) -> Int {               // Implicit Return
  x < 0 ? -1 : x > 0 ? 1 : 0            // If the entire body is a single expression, there
}                                       // is no need for the "return" keywrod

                                        // Argument label "from"
                                        // Parameter name "hometown"
func greet(person: String, from hometown: String) -> String {
  "You're from \(hometown)."            // Parameter name is used in the body
}

greet(person: "Joe", from: "Montreal")  // Argument label is used at call site
                                        // By default param name is used as the arg label
                                        // Like "person" param in this function

func abs(_ x: Int) -> Int {             // Omitting the arg label
  x < 0 ? -x : x 
}

abs(4)                                  // Now "abs" can be called wihtout any label

                                        // Default values for parameters
func dockerPort(host: String = "localhost", port: Int = 80) -> Int {
  // function body
}

func mean(_ arr: Double...) -> Double { // Variadic params, like Varargs in Java 
  var total: Double = 0                 // At mpst one varaidic arg is allowed per function
  for number in numbers {               // Type of variadic arg "arr" is [Double]
    total += number
  }
  return total / Double(numbers.count)
}

mean(1, 2, 3, 4)

                                        // inout params, annotated by putting the 
                                        // "inout" keyword before the type
                                        // Indicates that the param is passed as a mutable reference.
                                        // Please avoid this at all costs!  
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  let temporaryA = a
  a = b
  b = temporaryA
}

var someInt = 3
var anotherInt = 107                    // Only mutable vars are allowed as inout params
swapTwoInts(&someInt, &anotherInt)      // Prefixing with & is required for inout vars

func add(_ a: Int, _ b: Int) -> Int {   // Type of this function is (Int, Int) -> Int
  return a + b
}

var myFunc: (Int, Int) -> Int = add     // Function variable, 
                                        // For λ calculus geeks: Eta conversion 

                                        // Higher order function
func hof(_ adder: (Int, Int) -> Int, _ a: Int, _ b: Int) -> Int {
  adder(a, b)    
}
                                        // Nested functions, and returning functions
func choose(dir: Bool) -> (Int) -> Int {
  func forward(_ x: Int) -> Int { x + 1 }
  func backward(_ x: Int) -> Int {x - 1 }
  return dir ? forward : backward
}

func sideEffect() {                     // Type of this function is () -> Void or () -> ()
  print("Hello")                        // () indicates no input params and also is an alias for Void 
}                                       // Void is the same as Unit in Haskell and Scala
                                        // If you find it confusing that Unit in Haskell is Void in 
                                        // Swift and the Void type in Haskell is 
                                        // a totally different thing, then congrats, you are a 
                                        // normal person. 
                                        // Keywords for search: "Initial and Terminal objects"  
                                        
Closures, aka Lambdas
let nums = [1, 5, 3, 4]

                                        // "by" is a lambda passed to the sorted function
                                        // syntax : { (Params) -> ReturnType in expr } 
nums.sorted(by: { (x: Int, y: Int) -> Bool in x < y })

nums.sorted(by: { x, y in x < y })      // Inferred types and implicit return, short and nice!

                                        // Bash and Perl fans, brace for impact
names.sorted(by: { $0 > $1 } )          // Shorthand notation, $0 for 1st arg, $1 the 2nd and so on  

names.sorted(by: >)                     // Operator methods, String::< in this case   
names.sorted(by: <)                     // Beautiful and simple and type-safe at the same time
                                        // Not as powerful as type-classes in Haskell or Scala, but it'll do

names.sorted { $0 > $1 }                // Trailing closure syntax
                                        // At call site if the last arg of a function call is
                                        // a closure, it can be passed in after the parens or 
                                        // after the function name if there's no parens

nums.map { x in x * 2 }                 // Another example with the map method of arrays


var globalHandlers: [() -> ()]
                                        // @escaping is needed if the closure escapes 
                                        // Compiler is rightfully very strict, no surprises like youKnowWho.js
func register(handler: @escaping () -> ()) {
  globalHandlers.append(handler)
}

                                        // AutoClosures, aka Call-by-Name
                                        // Passed param won't be evaluated until and if used
                                        // in the body at runtime
func serve(customer customerProvider: @autoclosure () -> String) {
  print("Now serving \(customerProvider())!")
}
                                        // Because customer is an autoclosure we can pass it
                                        // like a regular param without parens
serve(customer: customersInLine.remove(at: 0))
Enums
enum CompassPoint {                     // Enum type
  case north                            // Enum case            
  case south                            // Unlike C, there's no integer value by default
  case east
  case west
}

enum Direction {
  case up, left, down, right            // Multiple comma-separated cases    
}

let nextMove = Direction.up             // Using it with a fully qualified name

var dir: CompassPoint                   
dir = .north                            // Shorthand, works only if the type is known

switch dir {                            // Pattern matching on enums
  case .north: print("Us")              // Must be exhaustive as usual
  case .south: print("Them")
  case .east:  print("Far")
  case .west:  print("Also us")
}  

enum Drink: CaseIterable {              // Makes it iterable
  case coffee, tea, juice
}
let all: [Drink] = Drink.allCases       // allCases return the array of all cases


enum TwoFactorAuthMethod {              // Enums cas be used for ADTs
  case disabled
  case phone(String)                    // Associated values, works like tuples, can be named
  case textMessage(String)      
  case google(token: String, time: Int)
  case accessCodes(codes: [String])     
  case email(String, secondary: String)
}

var method: TwoFactorAuthMethod
method = .phone("+1-111-111111")        // Passing associated values, just like tuples
method = .accessCodes(codes: ["1", "2"])
method = .disabled

switch method {                         // Pattern matching on ADTs                
  case .disabled:                    
    print("No way!")
  case .phone(let number):              // Binding the associated value, think tuples
    print(number)
  case let .accessCodes(codes):         // Let or var can be placed before case name 
    print(codes.count)
  case .email:                          // Associated values can be ignored    
    print("Temporarily unsupported")
  default:
    print("Other methods are not supported yet!") 
}

enum ASCII: Character {                 // Raw values, lifting value types to enums
  case tab = "\t"                       // Each raw value must be unique
  case lf = "\n"                        // Compiler asserts this because it's smart and it
  case cr = "\r"                        // knows how to distinguish different literals!
}

                                        // Implicit raw values
                                        // Each case's raw value is the previous' + 1
enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
extension Planet: CaseIterable {}       // Making it iterable using extensions 

Planet.earth.rawValue                   // Accessing raw values

enum CompassPoint: String {             // For String raw values, the name of each
    case north, south, east, west       // case is implicitly assigned as the raw value
}

CompassPoint.north.rawValue             // "north"

                                        // Initializing from a raw value
                                        // Returns an optional, reasonably, because not 
                                        // all raw values necessarily correspond to an actual case
let maybe: Planet? = Planet(rawValue: 7)


                                        // And now, the super power of enums in Swift!
                                        // Recursive enumerations
enum Expr {
  case number(Int)
  indirect case add(Expr, Expr)         // indirect keyword is needed for recursive branches
  indirect case mul(Expr, Expr)
}

indirect enum Expr {                    // indirect keyword can be put before the enum
  case number(Int)                      // keyword which then applies to all cases with    
  case add(Expr, Expr)                  // associated values  
  case mul(Expr, Expr)
}

func eval(_ expr: Expr) -> Int {        // A recursive function
  switch expr {                         // Pattern matching recursive enums
    case let .number(value): return value
    case let .add(l, r): return eval(l) + eval(r)
    case let .mul(l, r): return eval(l) * eval(r)
  }
}
Structures, Value Types
struct Resolution {                     // Structs, similar to case classes in Scala
  var width = 0                         // A stored property with a default property value      
  var height = 0
}
                                        // Member-wise initializer
                                        // Compiler automatically generates it for structs
let vga = Resolution(width: 640, height: 480)

vga.width                               // Accessing properties of a struct

                                    
// Structs, enums, strings, inetegres, floats, and booleans are all value types
// When assigned to a variable or constant, they get copied

var currentResoltion = vga              // Gets copied, because right hand side is a value type

currentResoltion.width = 320            // Only the new copy is mutated 
vga.width                               // still 640, original copy is intact


vga.width = 100                         // Impossible! Compile error! Kaboom!
                                        // If a value type is assigned to a constant, none of 
                                        // its properties can be mutated, even if they are defined
                                        // as vars. This behavior is different for reference types.
Classes, Reference Types
class VideoMode {                       // Class definition
  var resolution = Resolution()         // Stored property
  var interlaced = false
  var frameRate = 0.0
  var name: String?
}

// Classes are reference types
// They don't get copied when assigned, all references of a class 
// point to the same instance

let tenEighty = VideoMode()             // Initialization
tenEighty.resolution = hd               // Set property 
tenEighty.interlaced = true             // Because classes are reference types, we can
tenEighty.name = "1080i"                // mutate mutable properties of an instance, even if
tenEighty.frameRate = 25.0              // that instance is a constant like tenEighty here

let alsoTenEighty = tenEighty           // Assign the instance to a new reference

alsoTenEighty.frameRate = 30.0          // Mutate the property via the new reference

tenEighty.frameRate                     // Is now 30.0

                                        // Triple equal signs is the "identical to" operator
                                        // Returns true if both references refer to exactly the
                                        // same class instance
                                        // Similar to == in Java (reference equality)
tenEighty === alsoTenEighty             // True

someOther !==  tenEighty                // Not idential to, Similar to != in Java
Properties
struct FixedLengthRange {               // Stored properties    
  var firstValue: Int                   // Variable stored property, can be mutated if the struct 
                                        // instance is a variable
  let length: Int                       // Constant stored property, can't be mutated even if
                                        // the struct instance is a variable
}

class DataManager {
  lazy var importer = DataImporter()    // Lazy stored properties, their initial value is 
  var data = [String]()                 // not calculated until the first time it is used
                                        // Opposite to Scala, in Swift only a var can be lazy
  func manage() {
    // use importer here
  }
}

struct Rect {
  var origin = Point()
  var size = Size()
  var center: Point {                   // Computed property, no storage just getter and setter
    get {                               // Compute the value from other properties
      let x = origin.x + (size.width / 2)
      let y = origin.y + (size.height / 2)
      return Point(x: x, y: y)
    }
    set(newCenter) {                    // Setter: sets values to other properties
      origin.x = newCenter.x - (size.width / 2)
      origin.y = newCenter.y - (size.height / 2)
    }
  }
}

struct Rect {
  var origin = Point()
  var size = Size()
  var center: Point {                   // A computed property can't be a constant     
    get {                               // Shorthand getter, no return needed for a single expression                 
      Point(x: origin.x + (size.width / 2), y: origin.y + (size.height / 2))
    }
    set {                               // Shorthand for setter (no explicit name for the param)
      origin.x = newValue.x - (size.width / 2)      // newValue is the implicit name for the param
      origin.y = newValue.y - (size.height / 2)
    }
  }
}

var square = Rect(origin: Point(x: 0.0, y: 0.0),
                  size: Size(width: 10.0, height: 10.0))

square.center = Point(x: 15.0, y: 15.0) // Setting a computed property

struct Cuboid {
  var width = 0.0, 
  var height = 0.0
  var depth = 0.0
  var volume: Double {                  // Read only computed property
    width * height * depth              // No setter. Getter can be omitted like here
  }
}


class StepCounter {                     // Property observers, a door to reactive programming 
  var total: Int = 0 {                  // A normal stored property with observers
    willSet(newTotal) {                 // Called right before a new value is stored
      print("Will set to \(newTotal)")  // Like componentWillUpdate in old versions of React
    }
    didSet {                            // Called immediately after value is stored
      if total > oldValue  {            // oldValue is the default param name if no override is given
        print("Added \(total - oldValue) steps")
      }
    }                                   // Similar to componentDidUpdate in React
  }
}

// Wrapped properties are not covered here

// Global variables are those that are defined outside of any class, struct, or function  
// Global variables can also be computed or stored and can have observers
// Global variables are always lazy! Unlike properties, global constants can, and always are, lazy

let myGlobal = expensive()              // Global, automatically lazy
Property Wrappers
// Write code that manages how a property is set and re-use it for multiple properties
// Defined in a struct, enum or class


@propertyWrapper                        // Annotation indicating a property wrapper definition
struct TwelveOrLess {
  private var number = 0                // Internal state of the propety wrapper (arbitrary)
  var wrappedValue: Int {               // wrappedValue is a special name, by-convention like oldValue in didSet
    get { number }                      // Getter logic
    set { number = min(newValue, 12) }  // Setter logic
  }
}

@TwelveOrLess var height: Int           // Usage, annotation before property declaration
print(height)                           // 0 
height = 10                             
print(height)                           // 10
height = 24
print(height)                           // 12


@propertyWrapper                        // A property wrapper with initializers 
struct SmallNumber {
  private var maximum: Int
  private var number: Int

  var wrappedValue: Int {
    get { number }
    set { number = min(newValue, maximum) }
  }

  init() {                              // Called when used as below
    maximum = 12                        // @SmallNumber var height: Int
    number = 0
  }
  init(wrappedValue: Int) {             // Called when assigned a value when initialized as below
    maximum = 12                        // @SmallNumber var height: Int = 8
    number = min(wrappedValue, maximum)
  }
  init(wrappedValue: Int, max: Int) {   // Used when params are passed as below variants
    self.maximum = max                  // @SmallNumber(max: 44) var height: Int = 23
    number = min(wrappedValue, max)     // @SmallNumber(wrappedValue: 23, max: 44) var height: Int
  }
}


@propertyWrapper
struct SmallNumber {
  private var number = 0
  var projectedValue = false            // Another convention, projectedValue allows accessing an additional
  var wrappedValue: Int {               // value or type using the $ syntax
    get { number }
    set {
      if newValue > 12 {
        number = 12
        projectedValue = true
      } else {
        number = newValue
        projectedValue = false
      }
    }
  }
}

@SmallNumber var x: Int
x = 4                                   // setter
x                                       // get wrapped value 4
$x                                      // get projected value (true)

@DbObject user: User                    // Another example
user.name                               // Wrapped value
$user.flushConnection()                 // Projected value can expose a lower-level object
                                        // Don't do that in practice, instead use proper designs like domain-driven
                                        // design but keep in mind that projected value is there and can be used
                                        // if it does not vioalte your code of honour
Type Properties
struct User {                               // Type properties belong to the type (struct or class), not instances
  static const placeholder = "John Doe"     // Like static final in C, and Java
  static var minAge: Int {                  // static computed property
    18
  }
}

class SomeClass {                           // Overridable computed type property (JVM langs are crying now!) 
  class var computed: Int {                 // keyword class has the same meaning as
    107                                     // static but it allows sub-classes to override the
  }                                         // computed property
}

enum Rejection {
  static var message = "You got rejected"   // Enums can also have type properties
  case noToken, badToken, expiredToken       
}

User.placeholder                            // Query the stored type property
User.minaAge = 21                           // Set the stored type property
Methods
                                        // Classes, Structs, and Enums can have instance methods

class Counter {
  var count = 0
  func increment() {                    // An instance method, defined exactly like a function
    count += 1 
  }
  func increment(by amount: Int) {      // Another instance method with named params
    count += amount
  }
  func exceeds(count: Int) {            // Param name is the same as the instance property name, self is needed
    self.count > count                  // self, is the instance itself, always implicitly available 
  }                                     // like self in Python and this in Java
  func reset() {
    count = 0
  }
}


struct Point {                          // Mutating a value type from an instance method
  var x = 0.0, y = 0.0                  // Method has to be qualified with the mutating keyword
  mutating func moveBy(x deltaX: Double, y deltaY: Double) {
      x += deltaX
      y += deltaY
  }
}

var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)        // A mutating method can be called only on variables
                                        // Calling them on a constant gives a compile error

struct Point {
  var x = 0.0, y = 0.0
  mutating func moveBy(x deltaX: Double, y deltaY: Double) {
      self = Point(x: x + deltaX, y: y + deltaY)    // Big bang! Assign a new value to self is possible
  }
}

enum TriStateSwitch {
  case off, low, high
  mutating func next() {
    switch self {
      case .off : self = .low           // Assigning self in an enum is also possible
      case .low : self = .high
      case .high: self = .off
    }
  }
}
Type Methods
class SomeClass {                   
  static func someTypeMethod() {        // Similar to static methods in Java
    // impl                             // It means the method belongs to the type not to a single instance
  }
  class func someTypeMethod() {         // class keyword means the type method can be overridden by 
    //type method implementation        // subclasses
  }
}
SomeClass.someTypeMethod()              // Usage, similar to other languages, TypeName.typeMethod()


struct LevelTracker {                   // Structs and enums can have type methods as well
  static var highestUnlocked = 1        // Type property
  var current = 1                       // Instance property

  static func unlock(_ level: Int) {
    if level > self.highestUnlocked {   // self in a type method refers to the type 
      self.highestUnlocked = level      // so here self.highestUnlocked refers to type property
    }
  }

  static func isUnlocked(_ l: Int) {
    l <= highestUnlocked
  }

  @discardableResult
  mutating func advance(to level: Int) -> Bool {
    if LevelTracker.isUnlocked(level) {
      currentLevel = level
      return true
    } else {
      return false
    }
  }
}

class Player {
  var tracker = LevelTracker()            // An instance of LevelTracker
  let name: String
  func complete(level: Int) {
    LevelTracker.unlock(level + 1)        // Calling the type method, unlocks it for all players
    tracker.advance(to: level + 1)        // Calling the instance method, advances only this player
  }
  init(name: String) {
    self.name = name
  }
}
Subscripts
a["value"]                                // This is a subscript, syntax similar to Python and JS
                                          // But how does it work? Very imilar to __getitem__() in Python
                                          // or the apply/updateValue syntactic sugars in Scala:
                                          // The type has to implement it so the compiler is just a proxy

struct TimesTable {
  let multiplier: Int
  subscript(index: Int) -> Int {          // Here is how you make something subscriptable
    return multiplier * index             // Here it's a read only subscript
  }
}
let tab = TimesTable(multiplier: 3)
tab[6]                                    // 18

struct Matrix {                           // Subscripts can be of any dimensions
  // impl details here
  subscript(r: Int, c: Int) -> Double {
    get { 
      grid[(r * columns) + c]             // Getter, called when print(m[2,3])
    }
    set {                                 // Setter, called when m[2, 3]=6
      grid[(r * columns) + c] = newValue
    }
  }
}

var m: Matrix
m[2,3] = 6                                // subscript set is called with r=2,c=3
print(m[2,3])                             // subscript get is called with r=2,c=3


enum Planet {                             // Type subscripts are allowed! Nice!
  case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
  static subscript(n: Int) -> Planet {    // Type or static subscript
    Planet(rawValue: n)!
  }
}
let mars = Planet[4]                      // Usage, subscrpiting a type



                                          // Subscripts can accept and return arbitraty types

subscript(key: K) -> V?                   // Somewhere in the definition of the Dictionary class
                                          // Don't worry about K and V types for now

var speedLimit = ["day": 95, "night": 85]
speedLimit["day"]                         // subscripting a dict by a string, returns an Int?
speedLimit["morning"]=105                 // subscript set
Inheritance
class Vehicle {                           // A base class, that is a class the inherits from no other class
  var speed = 0.0
  var description: String {
    "traveling at \(speed) km/h"
  }
  func makeNoise() {
    // do nothing
  }
}

class Bicycle: Vehicle {                  // Subclassing, class A: B means A is a sub-class of A
  var hasBasket = false                   // Additional property that is not inherited
}

let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.speed = 15.0                      // speed property is inherited

class Tandem: Bicycle {                   // Subclassing a subclass, Tandem is a Bicycle and a Vehicle
  var numberOfPassengers = 0
}

class Train: Vehicle {
  override func makeNoise() {             // Overriding an inherited function
    print("Choo Choo")                    // override keyword is mandatory in Swift, a blessing (belive me)
  }
}

class Car: Vehicle {
  var gear = 1
  override var description: String {      // Overriding a computed property
    super.description + " in \(gear)"     // super keyword can be used to access parent's property
  }                                       // similar to other languages like Java
}

class AutomaticCar: Car {
  override var speed: Double {
    didSet {                              // Overriding property observers is also possible
      gear = Int(currentSpeed/10.0)+1
    }
  }
}

override subscript(n:Int) -> Int { }      // Subscripts can also be overridden 

final class A {}                          // final keywords prevents a class from being subclassed
class B: A {}                             // compile error!

class Sample {
  final var x: Int                        // prevents the property from being overridden
  final func f() {}                       // prevents the function from being overridden
  final class func g {}                   // seals the type method so it can't be subclassed
  final subscript(n: Int) -> Int {}       // seals the subscript
}

About

Swift 5 abridged-code-and-comment-only guide

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published