A taste of Swift part 4

Updated vegaseat 1 Tallied Votes 279 Views Share

In part 4 of tasting Swift, we explore structures. Swift has added some interesting features to structures like defaults, methods, multiple init(). This way struct objects come close to class objects with a little less overhead. As an option, methods can be added externally too. Take a look! Down the road there may be a part 4, exploring some odds and ends.

Remember, Swift uses three access levels:
public (least restrictive)
internal (default)
private (most restrictive)
If no access level is used, Swift defaults to internal.

//
//  main.swift
//  Taste_of_swift4
//
// exploring Apple's Swift language, part 4
// vegaseat   28jun2015    used Swift version 1.2

import Foundation

// structures ...

// as a classic example create a structure named Books
// a structure has members like title, author, ...
// Swift auto-includes the init() here
struct Books {
    var title: String
    var author: String
    var subject: String
    var book_id: Int
}

// create an instance of struct Books
// and put in data
var book1 = Books(
    title: "My First Animals",
    author: "Tom Klanzie",
    subject: "Children read animal names",
    book_id: 1234)

println(book1.title)   // My First Animals
println(book1.author)  // Tom Klanzie

// correct/change author
book1.author = "Tim Klanzie"
println(book1.author)  // Tim Klanzie

println("---------------------")

// Swift adds some extra features to structures
// structures can have methods (internal methods shown)
struct Companies {
    var company: String
    var city: String
    var state: String
    var value: Float
    var profit: Float
    
    // profitability of the company
    func percProfit() -> Float {
        return 100.0 * profit/value
    }
    
    func showProfit() {
        // represent float with 2 decimals
        var s1 = String(format:"%0.2f", percProfit())
        println("\(company) --> \(s1)%")
    }
}

// create an empty array of Company instances
var comps = [Companies]()

comps.append(Companies(
    company: "KLV",
    city: "Miami",
    state: "FL",
    value: 540000,
    profit: 119000))

comps.append(Companies(
    company:"FST",
    city: "Oakland",
    state: "CA",
    value: 678000,
    profit: 145000))

comps.append(Companies(
    company: "CAMP",
    city: "Detroit",
    state: "MI",
    value: 942000,
    profit: 177000))

comps.append(Companies(
    company: "MOTO",
    city: "Dearborn",
    state: "MI",
    value: 1242000,
    profit: 229000))

// call the struct method show()
comps[0].showProfit()  // KLV --> 22.04%

// or ...
var sf = String(format:"%@ --> %0.2f%%", comps[0].company,
    comps[0].percProfit())
println(sf)            // KLV --> 22.04%

println("---------------------")

// show all companies
for item in comps {
    item.showProfit()
}

/* result ...
KLV --> 22.04%
FST --> 21.39%
CAMP --> 18.79%
MOTO --> 18.44%
*/

println("---------------------")

// show only companies in Michigan ("MI")
for item in comps {
    if item.state == "MI" {
        item.showProfit()
    }
}

/* result ...
CAMP --> 18.79%
MOTO --> 18.44%
*/

println("---------------------")

// company "FST" has just reported a new annual profit of $127000
comps[1].profit = 127000

// show updated data
comps[1].showProfit()  // FST --> 18.73%

println("---------------------")

// create a structure Box with 3 members width, height, depth
// and with a number of methods
// by default instance methods of structs cannot modify instance
// variables, unless you prefix "func" with "mutating"
struct Box {
    // infer the type (here Double)
    var width = 1.0
    var height = 1.0
    var depth = 1.0
    
    func dimensions() -> String {
        return "box \(width) * \(height) * \(depth)"
    }
    
    func boxVolume() -> Double {
        return self.width * self.height * self.depth
    }
    
    func boxSurface() -> Double {
        return 2 * self.width * self.height +
        2 * self.width * self.depth +
        2 * self.height * self.depth
    }
    
    // increase all box dimensions by 1
    mutating func increase() {
        self.width++
        self.height++
        self.depth++
    }
}

// create an instance and fill in the required data
var box1 = Box(width: 3, height: 5, depth: 4)

println("\(box1.dimensions())")            // box 3.0 * 5.0 * 4.0
println("volume = \(box1.boxVolume())")    // volume = 60.0
println("surface = \(box1.boxSurface())")  // surface = 94.0

box1.increase()

println("\(box1.dimensions())")            // box 4.0 * 6.0 * 5.0
println("volume = \(box1.boxVolume())")    // volume = 120.0
println("surface = \(box1.boxSurface())")  // surface = 148.0

println("---------------------")

// create a structure where only the name is required to instantiate
// ? indicates that age and weight are optional
// method init() is needed
struct Friends {
    let name: String
    var age: Int?
    var weight: Int?
    
    init(name: String) {
        self.name = name
    }
}

var larry = Friends(name: "Larry Lard")

println(larry.weight)  // nil  (weight not supplied yet)

// now you can add Larry's optional weight
larry.weight = 279
println(larry.weight)  // Optional(279)

// since weight is an optional value use ! to get actual value
println(larry.weight!)  // 279

var abby = Friends(name: "Abby Froth")
// Abby's optional weight
abby.weight = 325
println("\(abby.name) and \(larry.name) are friends.")

// again use ! to get actual values from optional values
println("Together they weigh \(abby.weight! + larry.weight!) pounds")

// methods can be internal or added external
// add an external method to struct Friends via "extension"
extension Friends {
    func best() {
        println("My best friend is \(self.name)")
    }
}

larry.best()  // My best friend is Larry Lard

println("---------------------")

// name is required, age and weight are optional
struct Friends2 {
    let name: String
    var age: Int?
    var weight: Int?
}

// you can add all methods external to existing structs
extension Friends2 {
    // needed here
    init(name: String) {
        self.name = name
    }
    
    // method with input argument
    func showAge(age: Int) {
        println("\(self.name) is \(age) years old")
    }
    
    func showCity(city: String) {
        println("\(self.name) lives in \(city)")
    }
}

// create an instance of struct Friends2, name is required
var dean = Friends2(name: "Dean Axium")

// supply the age
dean.showAge(32)           // Dean Axium is 32 years old

dean.showCity("Reno NV")  // Dean Axium lives in Reno NV

println("---------------------")

// since String is a structure use the extension option
// to allow for integer indexing
extension String
{
    subscript(integerIndex: Int) -> Character {
        let index = advance(startIndex, integerIndex)
        return self[index]
    }
    
    subscript(integerRange: Range<Int>) -> String {
        let start = advance(startIndex, integerRange.startIndex)
        let end = advance(startIndex, integerRange.endIndex)
        let range = Range(start: start, end: end)
        return self[range]
    }
}

let digits = "0123456789"
println(digits[5])      // 5
println(digits[4...6])  // 456

println("---------------------")

// a structure with default values
// init() is needed to request name
struct Friends3 {
    var age    = 199
    var weight = 555
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    func showAge(var age: Int = 0) {
        if age == 0 {
            // apply default value
            age = self.age
        }
        println("\(self.name) is \(age) years old")
    }
    
    func showWeight(var weight: Int = 0) {
        if weight == 0 {
            weight = self.weight
        }
        println("\(self.name) weighs \(weight) pounds")
    }
}

var tom = Friends3(name: "Tom")
var eve = Friends3(name: "Eve")

// using default values
tom.showAge()                // Tom is 199 years old
eve.showWeight()             // Eve weighs 555 pounds
// supplying actual values
tom.showAge(age: 17)         // Tom is 17 years old
eve.showWeight(weight: 147)  // Eve weighs 147 pounds

println("---------------------")

// structures with multiple initializers

struct Celsius {
    var tcelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        tcelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        tcelsius = kelvin - 273.15
    }
    // use _ if you don't want to specify fromCelsius
    // would be redundant
    init(_ celsius: Double) {
        tcelsius = celsius
    }
}

// normal human body temperature in C, F, K
let bodyC = 37.0
let bodyF = 98.6
let bodyK = 310.15

// create 3 different instances
// using celsius, fahrenheit. kelvin values
let tc = Celsius(bodyC)
let tf = Celsius(fromFahrenheit: bodyF)
let tk = Celsius(fromKelvin: bodyK)

println("\(bodyF)F = \(tf.tcelsius)C")   // 98.6F = 37.0C
println("\(bodyK)K = \(tk.tcelsius)C")   // 310.15K = 37.0C
ddanbe 2,724 Professional Procrastinator Featured Poster

Looks a bit like the struct in C#. Do you happen to know if the Swift struct is a value type? Or is it also a reference type like a class?

vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

In Swift structures are a value type.

A note from the tutorial:
"Structures are always copied when they are passed around in your code, and do not use reference counting."

vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

Amongst other things, show that Swift structures are value types ...

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
    func show() -> String {
        return "red: \(self.red), green: \(self.green). blue: \(self.blue)"
    }
    // static functions/methods don't need to be initialized
    // call directly with Color.help()
    static func help() {
        println("structures are value types")
    }
}

// call the static method
Color.help()  // structures are value types

// create instances of struct Color
// external names are required
// use color values between 0.0 and 1.0
var magenta1 = Color(red: 1.0, green: 0.0, blue: 1.0)
var halfGray = Color(white: 0.5)

// structures are value types
// changes to a copy should not change original instance
// make a copy of the instance
var magenta2 = magenta1
// now change magenta2 and see if it affects magenta1
magenta2 = Color(red: 0.9, green: 0.0, blue: 1.0)
println(magenta2.red)  // 0.9
println(magenta1.red)  // 1.0  no change

println(magenta1.show())  // red: 1.0, green: 0.0. blue: 1.0
println(magenta2.show())  // red: 0.9, green: 0.0. blue: 1.0
vegaseat 1,735 DaniWeb's Hypocrite Team Colleague
// apply generic types to a structure
// T is a placeholder for the actual type used
struct Stack<T> {
    // an empty array of type T
    var items = [T]()

    mutating func push(item: T) {
        items.append(item)
    }

    mutating func pop() -> T {
        return items.removeLast()
    }

    // a getter
    var count: Int {
        get {
            return items.count
        }
    }
    // a getter simplified
    var count2: Int {
        return items.count
    }    
}

// test the stack with integers
// create an empty stack for integers
var intStack = Stack<Int>()
intStack.push(111)
intStack.push(222)
intStack.push(333)
println(intStack.items)  // [111, 222, 333]
println(intStack.pop())  // 333
println(intStack.items)  // [111, 222]

// test the stack with strings
var strStack = Stack<String>()
strStack.push("red")
strStack.push("white")
strStack.push("blue")
println(strStack.items)  // [red, white, blue]
println(strStack.pop())  // blue
println(strStack.items)  // [red, white]

// test getters
println(strStack.count)  // 2
println(strStack.count2) // 2

// another way to load the stack
var strStack2 = Stack<String>(items: ["Joe", "Bob", "Meg", "Sue"])

println(strStack2.items)  // [Joe, Bob, Meg, Sue]
println(strStack2.count)  // 4
vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

Learned more about structures ...

struct Matrix {
    let rows: Int
    let columns: Int
    // the flattened matrix
    var flat: [Double]

    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        // initially populate with 0.0
        flat = Array(count: rows * columns, repeatedValue: 0.0)
    }

    // ForRow is added since "row:" does not show up on call
    func indexValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }

    // allows to call matrix with [row, col]
    // shows use of get, set, assert
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexValidForRow(row, column: column), "Index out of range")
            return flat[(row * columns) + column]
        }
        set {
            assert(indexValidForRow(row, column: column), "Index out of range")
            flat[(row * columns) + column] = newValue
        }
    }

    // display the matrix
    func show() {
        for row in 0..<self.rows {
            for col in 0..<self.columns {
                print("\(self[row, col])  ")
            }
            println()
        }
    }

    // call with Matrix.info()
    // general information, does not need an instance
    static func info() {
        println("A 2x3 matrix has 2 rows and 3 columns")
    }
}

// create an instance of a 2x3 matrix, values are all 0.0
var matrix2x3 = Matrix(rows: 2, columns: 3)

println(matrix2x3[1, 2])    // 0.0
println(matrix2x3.columns)  // 3
println(matrix2x3.rows)     // 2
println(matrix2x3.flat)     // [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

// index too high, should show an error
//println(matrix2x3[1, 3])    // assertion failed: Index out of range

// populate the matrix
var n = 0.0
for row in 0..<matrix2x3.rows {
    for col in 0..<matrix2x3.columns {
        matrix2x3[row, col] = n
        n++
    }
}

println(matrix2x3.flat)  // [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]

matrix2x3.show()
/* 2 rows and 3 columns
0.0  1.0  2.0
3.0  4.0  5.0
*/

// info() is a static method
Matrix.info()  // A 2x3 matrix has 2 rows and 3 columns
vegaseat 1,735 DaniWeb's Hypocrite Team Colleague

Coming in the fall of 2015 ...
Swift2 replaces println() with just print() that has a newline default. The sort of thing that Python3 did to print(). There are some other syntax changes.
Good news, there will be a Swift 1-to-2 migrator utility to change Swift1 syntax to Swift2 syntax.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.