print("Hello, world!")
Hello, world!
Syntax:
- Input / output and string handlings are builtin and do not require importing anything
- do not need the
main()
function - Do not require semicolons
;
Simple Values
- use
let
to make a constant type. constant type can be assigned exactly once. - use
var
to make a variable. should have the same type if we want to reassign value on it. - when we create a constant or variable with initial value, we do not need to write its type, cause the compiler can infer its type.
var myVar = 42
myVar = 50
let myConst = 3.14
If we do not have initial values, or explicitly want specify its type, then we need to specify its type:
let explicitFloat = 4
Converting values to different type
let label = "The width is "
let width = 94
let widthWithLabel = label + String(width)
print(widthWithLabel)
The width is 94
String interpolation
print("Desk width is \(width)")
Desk width is 94
Multi-line String
let quotation = """
Nobody ever figures out what life is all about, and it doesn't matter.
Study hard what interests you the most in the most undisciplined, irreverent and original manner possible.
You have no responsibility to live up to what other people think you ought to accomplish.
"""
print(quotation)
Nobody ever figures out what life is all about, and it doesn't matter.
Study hard what interests you the most in the most undisciplined, irreverent and original manner possible.
You have no responsibility to live up to what other people think you ought to accomplish.
Arrays
var fruits = ["🍏","🍎","🍐"]
print(fruits[1])
🍎
Add elements to array
fruits.append("🍊")
print(fruits)
["🍏", "🍎", "🍐", "🍊"]
Create Empty Array
let emptyArray : [String] = []
Dictionary
var occupations = [
"Feynman" : "Physicist",
"Jobs" : "Entertainer"
]
print(occupations["Feynman"])
Add elements to Dictionary
occupations["Musk"] = "Scammer"
print(occupations)
["Musk": "Scammer", "Jobs": "Entertainer", "Feynman": "Physicist"]
Create an Empty Dict
let emptyDict : [String:Float] = [:]
Control Flow
for ... in
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
25
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
var belongsTo : String = ""
for (group, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
belongsTo = group
}
}
}
print("largest is \(largest) in group:\(belongsTo)")
largest is 25 in group:Square
var total = 0
for i in 0..<4 {
total += i
}
print(total)
6
if ... let
woring with optionals
var optionalString: String? = "Hello"
print(optionalString == nil)
false
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
print(greeting)
Hello, John Appleseed
var optionalName: String? = nil
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
print(greeting)
Hello!
let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"
print(informalGreeting)
Hi John Appleseed
Switch
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
while
Use while to repeat a block of code until a condition changes.
var n = 2
while n < 100 {
n *= 2
}
print(n)
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
Functions and Closures
Declaring a function with func
Use func to declare a function.
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")
"Hello Bob, today is Tuesday."
func greet(person: String, special: String) -> String {
return "Hello \(person), today's special is \(special)."
}
greet(person: "Bob", special: "Lagman")
"Hello Bob, today\'s special is Lagman."
Function with Positional arguments
Or we can use positional arguments,
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
_
made it positional
Function return compound value with Tuple
Use a tuple to make a compound value
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
Functions can be nested
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
15
Functions are a first-class type
Functions are a first-class type. This means that:
A function can return another function as its value.
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
8
A function can take another function as one of its arguments.
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
true
Functions are special case of closures
Functions are actually a special case of closures: blocks of code that can be called later.
numbers.map({ (number: Int) -> Int in
let result = 3 * number
return result
})
▿ 4 elements
- 0 : 60
- 1 : 57
- 2 : 21
- 3 : 36
numbers.map({ (number: Int) -> Int in
let result = 0
if number % 2 == 0 { return result }
return number
})
▿ 4 elements
- 0 : 0
- 1 : 19
- 2 : 7
- 3 : 0
When a closure’s type is already known, such as the callback for a delegate, you can omit the type of its parameters, its return type, or both.
Single statement closures implicitly return the value of their only statement.
let mappedNumbers = numbers.map({number in 3 * number})
print(mappedNumbers)
[60, 57, 21, 36]
You can refer to parameters by number instead of by name
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
Objects and Classes
Defining a Class
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
Add a constant property with let, and add another method that takes an argument.
class Shape {
var numberOfSides = 0
let pi = 3.14
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
func sayHello(name:String) -> String {
return "Hello \(name)!"
}
}
Creating the class instance
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
Class Initializer init
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name:String){
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
Use deinit
to create a deinitializer if you need to perform some cleanup before the object is deallocated.
Class inheritance (Subclass)
Subclasses include their superclass name after their class name, separated by a colon.
Methods on a subclass that override the superclass’s implementation are marked with override.
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
"A square with sides of length 5.2."
Make another subclass of NamedShape called Circle that takes a radius and a name as arguments to its initializer. Implement an area() and a simpleDescription() method on the Circle class.
class Cirlce: NamedShape {
var radius: Double
init(radius: Double, name: String) {
self.radius = radius
super.init(name: name)
}
func area() -> Double {
return 2 * 3.14 * radius
}
override func simpleDescription() -> String {
return "A square with radius \(radius)."
}
}
let testCircle = Cirlce(radius : 4, name: "Test Circle")
testCircle.area()
25.12
testCircle.simpleDescription()
"A square with radius 4.0."
Getter and Setter
In addition to simple properties that are stored, properties can have a getter and a setter.
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
willSet
and didSet
If you don’t need to compute the property but still need to provide code that’s run before and after setting a new value, use willSet and didSet.
For example, the class below ensures that the side length of its triangle is always the same as the side length of its square.
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
Optional Values in Class
When working with optional values, you can write ? before operations like methods, properties, and subscripting. If the value before the ? is nil, everything after the ? is ignored and the value of the whole expression is nil.
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
Enumerations and Structures
Enum
Use enum to create an enumeration. Like classes and all other named types, enumerations can have methods associated with them.
By default, Swift assigns the raw values starting at zero and incrementing by one each time.
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
Write a function that compares two Rank values by comparing their raw values.
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
func isBigger(first: Self, second: Self) -> Bool {
return first.rawValue > second.rawValue
}
}
Use the init?(rawValue:) initializer to make an instance of an enumeration from a raw value.
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
Add a color() method to Suit that returns “black” for spades and clubs, and returns “red” for hearts and diamonds.
enum Suit {
case spades, hearts, diamonds, clubs
func color() -> String {
switch self {
case .spades:
return "black"
case .hearts:
return "red"
case .diamonds:
return "red"
case .clubs:
return "black"
}
}
}
or shorter:
enum Suit {
case spades, hearts, diamonds, clubs
func color() -> String {
switch self {
case .spades, .clubs:
return "black"
case .hearts,.diamonds :
return "red"
}
}
}
Struct
Use struct
to create a structure.
Differences between struct and class
One of the most important differences between structures and classes is that structures are always copied when they’re passed around in your code, but classes are passed by reference.
In a MMVM pattern, we use struct for our data or UI, because the UI might have many Text() but their content can vary. But we usually have only one sources where the UI gets its data, and that is throught the ViewModule, so everyone talking about a VM, they should talk the same VM.
Creating Structs
struct Card{
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpadex = Card (rank: .three, suit: .spades)
threeOfSpadex.simpleDescription()
"The 3 of spades"
Concurrency
async
func fetchUserID(from server:String) async -> Int {
if server == "primary" {
return 97
}
return 501
}
await
func fetchUsername(from server:String) async -> String {
let userID = await fetchUserID(from: server)
if userID == 501 {
return "John"
}
return "Guest"
}
async let
Use async let
to call an asynchronous function, letting it run in parallel with other asynchronous code. When you use the value it returns, write await
.
func connectUser(to server: String) async {
async let userID = fetchUserID(from: server)
async let username = fetchUsername(from: server)
let greeting = await "Hello \(username), user ID \(userID)"
print(greeting)
}
In the code above, we can fetch the userID and the username in paralell because they are indepent to each other.
Task
Use Task
to call asynchronous functions from synchronous code, without waiting for them to return.
Task {
await connectUser(to: "primary")
}
// Prints "Hello Guest, user ID 97"
Protocols and Extensions
Protocol used to add requirements ot to add standrads to classes, structes or enumerations. Protocols similar to the Interfaces in Java, or somehow a little bit like templates in c++.(not that much)
Declaring a protocol
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
Classes, enumerations, and structures can all adopt protocols.
Class adopting protocol
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
a.simpleDescription
"A very simple class. Now 100% adjusted."
Struct adopting protocol
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
b.simpleDescription
"A simple structure (adjusted)"
mutating
Notice the use of the mutating
keyword in the declaration of 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.
extension
Use extension
to add functionality to an existing type, such as new methods and computed properties.
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
Write an extension for the Double type that adds an absoluteValue property.
extension Double: ExampleProtocol {
var simpleDescription: String {
return "The double number \(self)"
}
mutating func adjust() {
self += 42.0
}
mutating func absoluteValue() {
if self < 0 {self = self * -1 }
}
}
var testDouble = -3.14
testDouble.absoluteValue()
print(testDouble)
3.14
If a class is behaves like a protocol ( a protocol type), class should only access the vars and funcs that are provided from the protocol. you can’t accidentally access methods or properties that the class implements in addition to its protocol conformance.
Error Handling
writing Errors
You represent errors using any type that adopts the Error protocol.
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
` throw`
Use throw to throw an error and throws to mark a function that can throw an error. If you throw an error in a function, the function returns immediately and the code that called the function handles the error.
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
do-catch
Inside the do block, you mark code that can throw an error by writing try in front of it. Inside the catch block, the error is automatically given the name error unless you give it a different name.
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
Job sent
Change the printer name to “Never Has Toner”, so that the send(job:toPrinter:) function throws an error.
do {
let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner")
print(printerResponse)
} catch {
print(error)
}
noToner
You can provide multiple catch blocks that handle specific errors. You write a pattern after catch just as you do after case in a switch.
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
Add code to throw an error inside the do block. What kind of error do you need to throw so that the error is handled by the first catch block? What about the second and third blocks?
1st
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}else if printerName == "Make Fire" {
throw PrinterError.onFire
}
return "Job sent"
}
do {
let printerResponse = try send(job: 1440, toPrinter: "Make Fire")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
I'll just put this over here, with the rest of the fire.
2nd
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}else if printerName == "Make Fire" {
throw PrinterError.onFire
}else if printerName == "Other" {
throw PrinterError.outOfPaper
}
return "Job sent"
}
do {
let printerResponse = try send(job: 1440, toPrinter: "Other")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
Printer error: outOfPaper.
3rd
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
enum NetworkError: Error {
case noConnection
}
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}else if printerName == "Make Fire" {
throw PrinterError.onFire
}else if printerName == "Other" {
throw PrinterError.outOfPaper
}else if printerName == "Something Else?" {
throw NetworkError.noConnection
}
return "Job sent"
}
do {
let printerResponse = try send(job: 1440, toPrinter: "Something Else?")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
noConnection
try
Another way to handle errors is to use try? to convert the result to an optional. If the function throws an error, the specific error is discarded and the result is nil. Otherwise, the result is an optional containing the value that the function returned.
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
print(printerSuccess)
print(printerFailure)
Optional("Job sent")
nil
defer
Use defer to write a block of code that’s executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error.
You can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
I think we can use defer for file handling, whatever we do, we still need to close the file after reading or writing anything.
Generics
Write a name inside angle brackets to make a generic function or type.
Generics aka “we don’t care the type”
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result: [Item] = []
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
We use Generics (also in other languages) to represent something, we do not want to know it’s type or it can be any other basic data type, but we want it to be consistent as a single type during the class scope (or in struct)
As in above code, we can make array of anything, but we can only provide the func some type of something.
You can make generic forms of functions and methods, as well as classes, enumerations, and structures.
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
Use where
right before the body to specify a list of requirements—for example, to require the type to implement a protocol, to require two types to be the same, or to require a class to have a particular superclass.
[ I think this is same as assert in Java and Dart, to check the conditions before doing the real initialization or action ].
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Element: Equatable, T.Element == U.Element
{
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])