Skip to content

Instantly share code, notes, and snippets.

@ChrisMarshallNY
Last active April 13, 2020 13:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChrisMarshallNY/857f72ca2a2a38cf056a94c9b0edeba1 to your computer and use it in GitHub Desktop.
Save ChrisMarshallNY/857f72ca2a2a38cf056a94c9b0edeba1 to your computer and use it in GitHub Desktop.
Playground Accompaniment to the Introductory Swiftwater Post
/*
PLAYGROUND 0: Runtime Information Example
©2019 Rift Valley Software. All Rights Reserved.
*/
func printInfo(_ inVal: Any) {
print("\n\tValue of Parameter:")
print("\t\tSimple Value : \(inVal)")
print("\t\tSelf : \(inVal.self)")
print("\t\tDescription : \(String(describing: inVal))")
print("\t\tReflection : \(String(reflecting: inVal))")
dump(inVal, name: "Dump ", indent: 9)
print("\n\tType of Parameter:")
print("\t\tType Of : \(type(of: inVal))")
print("\t\tType Of Self : \(type(of: inVal).self)")
print("\t\tType Of Description : \(String(reflecting: type(of: inVal)))")
print("\t\tType Of Reflection : \(String(reflecting: type(of: inVal)))")
dump(type(of: inVal), name: "Dump ", indent: 9)
}
print("-- Direct Allocations:")
let integerConstant = 10
var integerVariable = 11
let stringConstant = "10"
var stringVariable = "11"
printInfo(integerConstant)
printInfo(integerVariable)
printInfo(stringConstant)
printInfo(stringVariable)
print("\n-- Class Allocations:")
class A {
let integerConstant = 10
var integerVariable = 11
let stringConstant = "10"
var stringVariable = "11"
}
class B: A {
override var integerVariable: Int { get { return 1 } set {} }
override var stringVariable: String { get { return "1" } set {} }
let anotherIntegerConstant = 12
var anotherIntegerVariable = 13
let anotherStringConstant = "12"
var anotherStringVariable = "13"
}
let a = A()
let b = B()
printInfo(a)
printInfo(b)
printInfo(a.integerVariable)
printInfo(b.integerVariable)
print("\n-- Function Allocations:")
let functionA = { (_ inBool: Bool) -> Bool in return inBool }
let functionB = { () -> A in return A() }
printInfo(functionA)
printInfo(functionB)
printInfo(functionB())
printInfo(functionB().integerVariable)
print("\n-- Introspection:")
func mirrorMask(_ inValue: Any, indent inIndent: Int = 1) {
for case let (label?, value) in Mirror(reflecting: inValue).children { // That "label?" thing means that only children that actually have a label will be selected.
let indentStr = String(repeating: "\t", count: inIndent)
if let moBunny = value as? RabbitHole {
if nil != moBunny.babyBunny {
print("\(indentStr)\(label):")
mirrorMask(moBunny, indent: inIndent + 1)
} else {
print("\(indentStr)\(label): \(moBunny.name)")
}
} else {
print("\(indentStr)\(label): \(value)")
}
}
}
class RabbitHole {
let name: String
var babyBunny: RabbitHole!
func makeBabies(_ inBunnies: [String]! = nil) {
if 1 == inBunnies.count {
babyBunny = type(of: self).init(name: inBunnies[0])
} else if let newBunniesArray = inBunnies?.dropFirst(), !newBunniesArray.isEmpty {
babyBunny = type(of: self).init(name: inBunnies[0], babies: [String](newBunniesArray))
}
}
required init(name inName: String, babies inBunnies: [String]! = nil) {
name = inName
if nil != inBunnies {
makeBabies(inBunnies)
}
}
}
let bunny = RabbitHole(name: "MoBunnyMoBunny", babies: ["Fiver", "Hazel", "BigWig", "Dandelion", "KEEHAR"])
mirrorMask(bunny)
class RabbitHoleCustomReflectable: RabbitHole, CustomReflectable {
var customMirror: Mirror {
if let shorties = babyBunny, "KEEHAR" != shorties.name {
return Mirror(self, children: ["tag": name, "shorties": shorties as Any])
} else {
return Mirror(self, children: ["tag": name, "OMG! Your kid is a bird!": "Yeah...not sure how that happened."])
}
}
required init(name inName: String, babies inBunnies: [String]! = nil) {
super.init(name: inName, babies: inBunnies)
}
}
let bunny2 = RabbitHoleCustomReflectable(name: "MoBunnyMoBunny", babies: ["Fiver", "Hazel", "BigWig", "Dandelion", "KEEHAR"])
mirrorMask(bunny2)
/*
PLAYGROUND 1: Protocols Example
©2019 Rift Valley Software. All Rights Reserved.
*/
// You define a protocol in much the same way that you define a class or a struct, except that you do not provide any "body" to the methods or properties
protocol A {
var someVar: String { get } // The "get" means that you (as a user of a class or struct conforming to the protocol) can only read from the property. You can't write to it.
var someOtherVar: String { get set } // You can read and write to this property. That means that calculated properties need to have both a "get" and a "set" handler.
func someFunc() -> Bool // You define a method/function as a "bald" declaration.
}
// You can inherit a protocol. B has the same requirements as A, plus any that it adds.
protocol B: A {
var yetAnotherVar: String { get set }
func someFunc() -> Bool // There's no problem defining the same function signature as the inherited one.
func someOtherFunc() -> Bool
}
// Protocols A and B are both "open" protocols. They can be applied to classes, structs, other protocols or enums.
// Protocol C, on the other hand, cannot be applied to structs or enums; only classes. Other protocols can inherit it, but then they need to be class protocols, as well.
protocol C: class, A {
func someSpecialCFunc() -> Bool
}
// We can write a struct that conforms to Protocol A. Because Protocol A has no default implementation, SA is required to implement everything.
struct SA: A {
var someVar: String = "SomeVar"
var someOtherVar: String {
get {
return "SomeOtherVar"
}
set {
print("I am ignoring \(newValue)")
}
}
func someFunc() -> Bool {
return false
}
}
let structA = SA()
let string1 = structA.someVar
let string2 = structA.someOtherVar
// Now, let's add a default to B:
// Adding this default effectively makes "yetAnotherVar" "optional." It is not required for classes, structs or enums that conform to it.
extension B {
var yetAnotherVar: String { // In the default extension, we print nothing, and ignore any input.
get {
return ""
}
set {
}
}
}
// We can write a struct that conforms to Protocol B. We implement the same vars as struct SA, and add a method for "someOtherFunc()". Note that we don't do "yetAnotherVar".
struct SB: B {
var someVar: String = "SomeVar"
var someOtherVar: String {
get {
return "SomeOtherVar"
}
set {
print("I am ignoring \(newValue)")
}
}
func someFunc() -> Bool {
return false
}
func someOtherFunc() -> Bool { // This is required.
return true
}
}
/* This is an error. You can't define a struct from a class protocol.
struct SC: C {
}
*/
// You can, however, define a class from it.
class CC: C {
// You need to implement all the Protocol A stuff:
var someVar: String = "SomeVar"
var someOtherVar: String {
get {
return "SomeOtherVar"
}
set {
print("I am ignoring \(newValue)")
}
}
func someFunc() -> Bool {
return false
}
// Note that we don't need to do any Protocol B stuff, as C was based on A; not B.
// And the new func that Protocol C requires:
func someSpecialCFunc() -> Bool {
return false
}
}
// Now, define Protocol D, from C:
protocol D: C {
// In this one, we will define the same stuff that B defined
var yetAnotherVar: String { get set }
func someOtherFunc() -> Bool
}
// Now, let's try defining a struct from D:
/*
No dice. An error. D requires a class, because C required a class.
struct SD: D {
}
*/
class CD: D {
// You need to implement all the Protocol A stuff:
var someVar: String = "SomeVar"
var someOtherVar: String {
get {
return "SomeOtherVar"
}
set {
print("I am ignoring \(newValue)")
}
}
func someFunc() -> Bool {
return false
}
// And the new func that Protocol C requires:
func someSpecialCFunc() -> Bool {
return false
}
// However, it won't work unless we add the calculated property that was default for B. We defined it in D, but didn't get the default that B defined:
var yetAnotherVar: String {
get {
return "I AM GROOT"
}
set {
print("I AM GROOT")
}
}
// But we're not done yet. We also need to add that method that D required:
func someOtherFunc() -> Bool {
return false
}
}
let classD = CD()
let yetAnotherString = classD.yetAnotherVar
/*
PLAYGROUND 2: Extending Array Example
©2019 Rift Valley Software. All Rights Reserved.
*/
import Foundation // Foundation is required for DateComponents.
/*
If this is an Array of String, let's assume that the Array is laid out thusly, with Integer values (as Strings) in the Array:
0 - Hours
1 - Minutes
2 - seconds
*/
extension Array where Element == String {
var time: DateComponents! {
var ret: DateComponents!
if 2 < count { // This is a runtime state, so it can't be in the where clause.
ret = DateComponents()
ret.hour = Int(self[0])
ret.minute = Int(self[1])
ret.second = Int(self[2])
}
return ret
}
}
/*
If this is an Array of Int, let's assume that the Array is laid out thusly, with Integer values in the Array:
0 - Hours
1 - Minutes
2 - seconds
*/
extension Array where Element == Int {
var time: DateComponents! {
var ret: DateComponents!
if 2 < count {
ret = DateComponents()
ret.hour = self[0]
ret.minute = self[1]
ret.second = self[2]
}
return ret
}
}
/*
PLAYGROUND 3: Basic Enum Stuff
©2019 Rift Valley Software. All Rights Reserved.
*/
// In this exercise, I compare the bahaivor of Swift enums to structs
enum EA {
case caseA
case caseB
case caseC
}
// Internally, this is a lot more like this:
struct SEA {
struct caseA { }
struct caseB { }
struct caseC { }
}
// So when you define an instance of EA:
let ea_caseB = EA.caseB
// It is very similar to this:
let sea_caseB = SEA.caseB()
// The similarity only increases if you add associated values:
enum EB {
case caseA(String)
case caseB(String)
case caseC(String)
}
struct SEB {
struct caseA {
var value: String = ""
init(_ inValue: String) {
value = inValue
}
}
struct caseB {
var value: String = ""
init(_ inValue: String) {
value = inValue
}
}
struct caseC {
var value: String = ""
init(_ inValue: String) {
value = inValue
}
}
}
let eb_caseB = EB.caseB("caseB")
let seb_caseB = SEB.caseB("caseB")
// Where the difference comes in, is when you use them:
// There are two different ways to resolve an enum.
// The first is the structic switch:
switch eb_caseB {
case let .caseB(value):
print("Case Value (switch): \(value)")
default:
print("Something Else")
}
// The second is as a case clause in an if.
if case let EB.caseB(value) = eb_caseB {
print("Case Value (if): \(value)")
} else {
print("Something Else")
}
// It's a bit more complicated to get the value of the struct we defined.
/*
You can't do this:
switch type(of: seb_caseB) {
case SEB.caseB.self:
let value = seb_caseB.value
print("Struct Value: \(value)")
default:
print("Something Else")
}
*/
// So you do a direct compare, instead.
if type(of: seb_caseB) == SEB.caseB.self {
let value = seb_caseB.value
print("Struct Value: \(value)")
} else {
print("Something Else")
}
// Now, enums that are based on types are different. They act more like static variables in structes:
enum EC: String {
case caseA = "caseA" // In regular use, you wouldn't need to do this.
case caseB = "caseB"
case caseC = "caseC"
}
// Probably looks more like this:
struct SEC {
struct caseA {
static var value: String = "caseA"
}
struct caseB {
static var value: String = "caseB"
}
struct caseC {
static var value: String = "caseC"
}
}
// In the above case, you would not be able to associate a value with an instance of SEC. You can't associate a value with an enum that is type-based, either.
// Next, let's talk extension. You can extend an enum, but you can't add new cases:
/*
This won't work
extension EB {
case caseD
}
*/
// This will work.
extension EB {
func caseD(_ inString: String) {
print("I got this String from you: \"\(inString)\"")
}
}
// But you can't store the string, because you can't add stored properties to extended entities.
// Instead, this may be more along what you want to do:
extension EB {
var caseD2: String {
return "caseD"
}
static var s_caseD: String {
return "caseD"
}
}
let eb_eb = EB.caseD
// Waitaminute...something's strange.
print("What dis? \(String(reflecting: eb_eb))")
// How about this?
let eb_eb_eb = EB.s_caseD
// That's better.
print("What dis? \(String(reflecting: eb_eb_eb))")
// But we can't use the .caseD2 calculated property.
// Looks like enums won't let us access general type properties unless we instantiate them, and we can't do that without an initializer.
// You can't instantiate EB. Only contained "classes" in EB, and Swift won't let you extend them.
/*
PLAYGROUND 4: Basic Generics
©2019 Rift Valley Software. All Rights Reserved.
*/
let aStringArray = ["Some String","AnotherString"] // Direct type assignment
let myFirstArray = aStringArray // Implicit type assignment, where "aStringArray" is already a String Array.
let myDirectArray = [String]() // Explicit empty Array definition, using brackets and an initializer. This is a common way to instantiate an Array.
let mySecondArray:[String] = [] // Explicit basic type definition, using brackets. This is the most common form.
let myThirdArray:Array<String> = [] // This is the first (explicitly defined) generic form.
let myFourthArray = Array<String>() // This is the second (type-assigned) generic form.
/*
Now, let's look at the Array definition, here: https://github.com/apple/swift/blob/master/stdlib/public/core/Array.swift
@_fixed_layout
public struct Array<Element>: _DestructorSafeContainer {
#if _runtime(_ObjC)
@usableFromInline
internal typealias _Buffer = _ArrayBuffer<Element>
#else
@usableFromInline
internal typealias _Buffer = _ContiguousArrayBuffer<Element>
#endif
@usableFromInline
internal var _buffer: _Buffer
/// Initialization from an existing buffer does not have "array.init"
/// semantics because the caller may retain an alias to buffer.
@inlinable
internal init(_buffer: _Buffer) {
self._buffer = _buffer
}
}
Note that it is defined as a generic (<...>).
However, you [almost] never use that, when you define an Array.
That's because Swift "Hides" the generic (<Element>). Since it's implied, there's no need to expose the guts.
When you instantiate an Array, you give it the type as you create (actually when you define) it, and the generic forms a structure around that type.
*/
typealias SwiftGenericType<T> = T
struct SwiftGenericStruct<T> {
let value: T
init(value: T) { self.value = value }
}
class SwiftGenericClass<T> {
let value: T
init(value: T) { self.value = value }
}
func swiftGenericEqualityTester<T: Comparable>(_ leftSide: T, _ rightSide: T) -> Bool { return leftSide == rightSide }
let is1EqualTo1 = swiftGenericEqualityTester(1, 1) // Casts both as Int
let is2EqualTo2Point5 = swiftGenericEqualityTester(2, 2.5) // Casts both as Double
// You can't do this. It's an error.
// let is1EqualTo1 = swiftGenericEqualityTester<Int>(1, 1)
// You can't do this. It's an error.
// let leftSide: Int = 2
// let rightSide: Double = 2.5
// let is2EqualTo2Point5 = swiftGenericEqualityTester(rightSide, leftSide)
// You can do this, however.
func swiftGenericEqualityTester2<T: Comparable, S: Comparable>(_ leftSide1: T, _ rightSide: S) -> Bool { return true }
let leftSide1: Int = 2
let rightSide1: Double = 2.6
let is2EqualTo2Point6 = swiftGenericEqualityTester2(leftSide1, rightSide1)
// You can't do this. It's an error.
// func swiftGenericEqualityTester3<T: Comparable, S: Comparable>(_ leftSide: T, _ rightSide: S) -> Bool { return rightSide == leftSide }
// You can't do this either. Swift doesn't want the types to be the same.
// func swiftGenericEqualityTester3<T: Comparable, S: Comparable> (_ leftSide: T, _ rightSide: S) -> Bool where S == T { return rightSide == leftSide }
// You can't do this. It's an error.
// typealias SwiftGenericTuple = Tuple<T>( key: T, value: T )
// You can't do this. It's an error.
// protocol SwiftGenericProtocol<T> {}
// This is a completely generic protocol. You can use any type for "T".
protocol GenericBaseProtocol {
associatedtype T
var myProperty: T {get set}
init(_ myProperty: T )
}
// You can declare both Comparable and non-Comparable types with this protocol.
// This is Comparable
class GenClassB: GenericBaseProtocol {
typealias T = Int
var myProperty: T = 0
// When you conform to a protocol with an init(), you need to add the "required" keyword to your implementation.
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
// This is non-Comparable
class GenClassA: GenericBaseProtocol {
typealias T = [String:String]
var myProperty: T = [:]
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
// This will not work. You cannot redefine associated types in a protocol extension.
//extension GenericBaseProtocol {
// associatedtype T: Comparable
//
// var myProperty: T {get set}
//}
// This will not work. You cannot add an associatedType via an extension.
//extension GenericBaseProtocol {
// associatedtype S
//}
// Now, here we will add conditional extensions to the original, generic protocol.
// These allow classes based on this protocol to act as Equatable and Comparable, if the data type is Comparable,
// or just Equatable, if the data type is Equatable, but not Comparable.
// This extension is for when the class is not Equatable (or Comparable).
// The == always returns false, and we have an isEquatable Bool that tells us the class is not Equatable.
// Thanks to Alain T. for his guidance: https://stackoverflow.com/a/48711730/879365
// This is the default extension, implementing non-functional stubs.
extension GenericBaseProtocol {
static func ==(lhs: Self, rhs: Self) -> Bool {
return false
}
static func <(lhs: Self, rhs: Self) -> Bool {
return false
}
static func >(lhs: Self, rhs: Self) -> Bool {
return false
}
var isEquatable:Bool { return false }
var isComparable:Bool { return false }
}
// This extension uses the Equatable protocol (Comparable extends Equatable). Note the capitalized "Self".
// If the class is Equatable, then we return a true for isEquatable.
// This extension is used when the associated type conforms to the Equatable protocol.
extension GenericBaseProtocol where T: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty == rhs.myProperty
}
var isEquatable:Bool { return true }
}
// This extension is used when the associated type conforms to the Comparable protocol.
extension GenericBaseProtocol where T: Comparable {
static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty < rhs.myProperty
}
static func >(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty > rhs.myProperty
}
var isComparable:Bool { return true }
}
// Here, we define a Comparable type
class GenClassB3: GenericBaseProtocol {
typealias T = Int
var myProperty: T = 0
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
// Test the Comparable behavior
let lhs2 = GenClassB3(3)
let rhs2 = GenClassB3(4)
print ( "lhs2 is" + (lhs2.isEquatable ? "" : " not") + " Equatable." )
print ( "lhs2 is" + (lhs2.isComparable ? "" : " not") + " Comparable." )
print ( "rhs2 is" + (rhs2.isEquatable ? "" : " not") + " Equatable." )
print ( "rhs2 is" + (rhs2.isComparable ? "" : " not") + " Comparable." )
let leftEqualToRight2 = lhs2 == rhs2
let leftGreaterThanRight2 = lhs2 > rhs2
let leftLessThanRight2 = lhs2 < rhs2
let rightEqualToLeft2 = rhs2 == lhs2
let rightGreaterThanLeft2 = rhs2 > lhs2
let rightLessThanLeft2 = rhs2 < lhs2
let leftEqualToLeft2 = lhs2 == GenClassB3(3)
let rightEqualToRight2 = lhs2 == GenClassB3(4)
// Here, we define a type that is not Comparable.
class GenClassB3A: GenericBaseProtocol {
typealias T = [String]
var myProperty: T = []
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
let lhs2A = GenClassB3A(["HI"])
let rhs2A = GenClassB3A(["Howaya"])
print ( "lhs2A is" + (lhs2A.isEquatable ? "" : " not") + " Equatable." )
print ( "lhs2A is" + (lhs2A.isComparable ? "" : " not") + " Comparable." )
print ( "rhs2A is" + (rhs2A.isEquatable ? "" : " not") + " Equatable." )
print ( "rhs2A is" + (rhs2A.isComparable ? "" : " not") + " Comparable." )
// Because of the game we played up there, these will compile, but the comparisons will always fail.
let leftEqualToRight2A = lhs2A == rhs2A
let leftGreaterThanRight2A = lhs2A > rhs2A
let leftLessThanRight2A = lhs2A < rhs2A
let rightEqualToLeft2A = rhs2A == lhs2A
let rightGreaterThanLeft2A = rhs2A > lhs2A
let rightLessThanLeft2A = rhs2A < lhs2A
let leftEqualToLeft2A = lhs2A == GenClassB3A(["HI"])
let rightEqualToRight2A = lhs2A == GenClassB3A(["Howaya"])
// Here, we define an Equatable (but not Comparable) type
class GenClassB4: GenericBaseProtocol {
typealias T = Bool
var myProperty: T = false
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
let lhs2B = GenClassB4(true)
let rhs2B = GenClassB4(true)
print ( "lhs2B is" + (lhs2B.isEquatable ? "" : " not") + " Equatable." )
print ( "lhs2B is" + (lhs2B.isComparable ? "" : " not") + " Comparable." )
print ( "rhs2B is" + (rhs2B.isEquatable ? "" : " not") + " Equatable." )
print ( "rhs2B is" + (rhs2B.isComparable ? "" : " not") + " Comparable." )
let leftEqualToRight2B = lhs2B == rhs2B
let leftGreaterThanRight2B = lhs2B > rhs2B
let leftLessThanRight2B = lhs2B < rhs2B
let rightEqualToLeft2B = rhs2B == lhs2B
let rightGreaterThanLeft2B = rhs2B > lhs2B
let rightLessThanLeft2B = rhs2B < lhs2B
let leftEqualToLeft2B = lhs2B == GenClassB4(true)
let rightEqualToRight2B = lhs2B == GenClassB4(false)
// This defines a new protocol, based on Comparable.
protocol GenericBaseProtocolBothCompType: Comparable {
associatedtype T: Comparable
var myProperty: T {get set}
init(_ myProperty: T )
}
// You cannot define a protocol extension initializer.
//extension GenericBaseProtocolBothCompType {
// init(_ myProperty: T ) {
// self.myProperty = myProperty
// }
//}
// This extension satisfies the Equatable protocol.
extension GenericBaseProtocolBothCompType {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty == rhs.myProperty
}
}
// This extension satisfies the Comparable protocol.
extension GenericBaseProtocolBothCompType {
static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty < rhs.myProperty
}
}
// This is an error. It will not work, because Dictionary is not Comparable.
//class GenClassA2: GenericBaseProtocolBothCompType {
// typealias T = [String:String]
// var myProperty: T = [:]
//}
class GenClassB2: GenericBaseProtocolBothCompType {
typealias T = Int
var myProperty: T = 0
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
let lhs = GenClassB2(3)
let rhs = GenClassB2(4)
let leftEqualToRight = lhs == rhs
let leftGreaterThanRight = lhs > rhs
let leftLessThanRight = lhs < rhs
let rightEqualToLeft = rhs == lhs
let rightGreaterThanLeft = rhs > lhs
let rightLessThanLeft = rhs < lhs
let leftEqualToLeft = lhs == GenClassB2(3)
let rightEqualToRight = lhs == GenClassB2(4)
// This defines a new protocol, based on Comparable, and leaves the associated type as a free agent.
protocol GenericBaseProtocolBothCompType2: Comparable {
associatedtype T
var myProperty: T {get set}
init(_ myProperty: T )
}
// However, these won't work, as the data type is not Comparable.
//// This extension satisfies the Equatable protocol.
//extension GenericBaseProtocolBothCompType2 {
// static func ==(lhs: Self, rhs: Self) -> Bool {
// return lhs.myProperty == rhs.myProperty
// }
//}
//
//// This extension satisfies the Comparable protocol.
//extension GenericBaseProtocolBothCompType2 {
// static func <(lhs: Self, rhs: Self) -> Bool {
// return lhs.myProperty < rhs.myProperty
// }
//}
// These will work, because you are specifying that the type needs to be Equatable/Comparable.
// This extension satisfies the Equatable protocol.
extension GenericBaseProtocolBothCompType2 where T: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty == rhs.myProperty
}
}
// This extension satisfies the Comparable protocol.
extension GenericBaseProtocolBothCompType2 where T: Comparable {
static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.myProperty < rhs.myProperty
}
}
class GenericClassFromType2: GenericBaseProtocolBothCompType2 {
typealias T = Int
var myProperty: T = 0
required init(_ myProperty: T ) {
self.myProperty = myProperty
}
}
let lhs4 = GenericClassFromType2(3)
let rhs4 = GenericClassFromType2(4)
let leftEqualToRigh4t = lhs4 == rhs4
let leftGreaterThanRight4 = lhs4 > rhs4
let leftLessThanRight4 = lhs4 < rhs4
let rightEqualToLeft4 = rhs4 == lhs4
let rightGreaterThanLeft4 = rhs4 > lhs4
let rightLessThanLeft4 = rhs4 < lhs4
let leftEqualToLeft4 = lhs4 == GenericClassFromType2(3)
let rightEqualToRight4 = lhs4 == GenericClassFromType2(4)
// This protocol has two associated types.
protocol GenericBaseProtocolWithTwoTypes {
associatedtype T
associatedtype S
var myProperty1: T {get set}
var myProperty2: S {get set}
init(myProperty1: T, myProperty2: S)
}
// This will not work. You need to define and implement BOTH types.
//class GenericTwoTypes: GenericBaseProtocolWithTwoTypes {
// typealias T = Int
//
// var myProperty1: T
//
// required init(myProperty1: T) {
// self.myProperty1 = myProperty1
// }
//}
struct GenericTwoTypesStruct: GenericBaseProtocolWithTwoTypes {
typealias T = Int
typealias S = [String:String]
var myProperty1: T
var myProperty2: S
// You do not need the "required" keyword when implementing a struct from the protocol.
init(myProperty1: T, myProperty2: S) {
self.myProperty1 = myProperty1
self.myProperty2 = myProperty2
}
}
class GenericTwoTypesClass: GenericBaseProtocolWithTwoTypes {
typealias T = Int
typealias S = [String:String]
var myProperty1: T
var myProperty2: S
required init(myProperty1: T, myProperty2: S) {
self.myProperty1 = myProperty1
self.myProperty2 = myProperty2
}
}
/*
PLAYGROUND 5: Optionals
©2019 Rift Valley Software. All Rights Reserved.
*/
// Optionals are declared by either using a question mark:
var optionalVar: String?
// It is automatically set to nil when you declare it, so assignment after the fact clears the nil state:
optionalVar = "Hello, World."
// Or an exclamation point:
var implicitOptionalVar: String!
// Same deal here:
implicitOptionalVar = "HELO WRLD"
// You can declare an optional with let:
let anotherOptionalVar: String?
// But once you set it the first time, it's for keeps.
anotherOptionalVar = "Helow Woild"
// That applies to all let constants, but optionals actually have a nil value when first declared.
// This is a compile-time error:
// anotherOptionalVar = "Hello, World"
// if you simply assign from an optional, you get an optional:
let myValue = optionalVar
// This will print "Optional("Hello, World.")", and you will get a compile-time warning; telling you that it was cast to Any:
print(String(describing: myValue))
// In order to uwrap an optional, you use an exclamation mark:
let myNewValue = optionalVar!
// If you do that, then myNewValue is now a regular String.
// This will print "Hello, World":
print(myNewValue)
// If you try that with a nil value:
optionalVar = nil
// It will result in a runtime (not compile time) fatal error:
// let myNewValue2 = optionalVar!
// You can unwrap an optional with a question mark, or even directly, but that needs to happen in some kind of branch statement:
tryMeOut: do { // Yeah that looks like a GOTO, but it isn't; I swear.
guard let yetAnotherNewValue = optionalVar else {
print("Ah-Ah-Ah! You didn't say the magic word!")
break tryMeOut
}
print(yetAnotherNewValue)
}
// What happened up there, was that the guard test figured out that optionalVar is nil, and triggered the break. The print after the guard never got called.
// You can also unwrap with a question mark, if the optional has another "stage" to it, like a property or a subscript (which is a function):
var optionalArray: [String]? = ["A", "B", "C"]
// The guard will pass this, and print "C"
tryMeOut: do {
guard let yetAnotherNewValue = optionalArray?[2] else {
print("Ah-Ah-Ah! You didn't say the magic word!")
break tryMeOut
}
print(yetAnotherNewValue)
}
// Now, we set the optional to nil, and try again:
optionalArray = nil
// This time, the guard fails:
tryMeOut: do {
guard let yetAnotherNewValue = optionalArray?[2] else {
print("Ah-Ah-Ah! You didn't say the magic word!")
break tryMeOut
}
print(yetAnotherNewValue)
}
// Because optionalArray is an explicit optional, you can't do this:
//tryMeOut: do {
// guard let yetAnotherNewValue = optionalArray[2] else {
// print("Ah-Ah-Ah! You didn't say the magic word!")
// break tryMeOut
// }
//
// print(yetAnotherNewValue)
//}
// Note that there's no question mark. The guard needs that to indicate that the optional is to be tested before accessing it.
// Now, let's try the same thing with an implicit optional:
var implicitOptionalArray: [String]! = ["D", "E", "F"]
// The guard will pass this, and print "F"
tryMeOut: do {
guard let yetAnotherNewValue = implicitOptionalArray?[2] else {
print("Ah-Ah-Ah! You didn't say the magic word!")
break tryMeOut
}
print(yetAnotherNewValue)
}
// Now, we set the optional to nil, and try again:
implicitOptionalArray = nil
// This time, the guard fails:
tryMeOut: do {
guard let yetAnotherNewValue = implicitOptionalArray?[2] else {
print("Ah-Ah-Ah! You didn't say the magic word!")
break tryMeOut
}
print(yetAnotherNewValue)
}
// And let's try without the question mark again:
// This time, you get a different compiler error.
// Since the implicit optional insists on being treated as a nermal value, the guard doesn't think it can make a choice:
//tryMeOut: do {
// guard let yetAnotherNewValue = implicitOptionalArray[2] else {
// print("Ah-Ah-Ah! You didn't say the magic word!")
// break tryMeOut
// }
//
// print(yetAnotherNewValue)
//}
// Now, let's look at what Swift calls the "nil-coalescing" operator. This is a double-question mark. It looks like a ternary operator, but is a bit different.
// What it says, is "if the statements/arguments to my left resolve to nil, then use the thing on my right, instead". It is used for assignments; not execution.
var myTargetString: String = "" // We have a normal (non-optional) String
var myOptionalString: String? = "OPTIONA"
myTargetString = myOptionalString! + "L" // Need to force-unwrap to use directly. NOTE: This is consiodered bad practice, unless you are GUARANTEED content.
print(myTargetString) // This will print "OPTIONAL".
// Now, we try the same thing with nil-coalescing:
myTargetString = (myOptionalString ?? "ORTHOGONA") + "L" // Note no need to unwrap. That's handled in the nil-coalescing operator.
print(myTargetString) // This will print "OPTIONAL", because the nil-coalescing operator was not invoked.
// Now, let's try that with nil:
myOptionalString = nil
// If we try this, we get a runtime error:
// myTargetString = myOptionalString + "L"
// The nil-coalescing operator, however, gives us a smooth changeover:
myTargetString = (myOptionalString ?? "ORTHOGONA") + "L"
print(myTargetString) // This will print "ORTHOGONAL", because the nil-coalescing operator was invoked, and supplied the alternative value.
// Finally, let's look at optional chaining.
// Optional chaining is best applied in branch statements. We'll use an if, this time:
// First, let's set up a few instances of optional String:
var optionalStringI: String! = "I"
var optionalStringAm: String! = "am"
var optionalStringA: String! = "a"
var optionalStringLeaf: String! = "leaf"
var optionalStringOn: String! = "on"
var optionalStringThe: String! = "the"
var optionalStringWind: String! = "wind"
// Next, let's set up an optional String Array
var famousLastWords: [String?]! = [optionalStringI, optionalStringAm, optionalStringA, optionalStringLeaf, optionalStringOn, optionalStringThe, optionalStringWind]
// So what we have here, is an implicit optional Array of optional String. It should be noted that, even though the Strings are declared implicit optional, they are still
// allowed in the Array.
// This prints "Optional([Optional("I"), Optional("am"), Optional("a"), Optional("leaf"), Optional("on"), Optional("the"), Optional("wind")])"
print(String(describing: famousLastWords))
// Let's try adding a last, normal, non-optional String literal:
famousLastWords.append("GLURK!")
// This prints "Optional([Optional("I"), Optional("am"), Optional("a"), Optional("leaf"), Optional("on"), Optional("the"), Optional("wind"), Optional("GLURK!")])"
print(String(describing: famousLastWords))
// Note that the last "word" was turned into an optional after assignment, even though it was a String literal. The Array is of Optional String.
// OK. We're now set to try chaining.
// Let's assemble a statement:
let inTheWashOrTheRinse: String = famousLastWords.reduce("") { (current, next) -> String in
return current + " " + next! // We need to force-unwrap, unless we will use a branch or nil-coalescing operator (more on that in a bit)
}
// We had to fold, because the Array is of optional String, not String (in which case we could have done a simple joined())
print(inTheWashOrTheRinse) // Prints " I am a leaf on the wind GLURK!"
// Let's set one of the elements to nil. Remember that this is an Array of optional, so it is legit to set an element to nil:
famousLastWords[3] = nil // Was "leaf"
// This time, you will get a runtime error when you get to the nil (we are force-unwrapping the optional in the reduce closure):
//let inTheWashOrTheRinse2: String = famousLastWords.reduce("") { (current, next) -> String in
// return current + " " + next!
//}
//
//print(inTheWashOrTheRinse2)
// You can use guard to ameliorate that:
let inTheWashOrTheRinse3: String = famousLastWords.reduce("") { (current, next) -> String in
guard nil != next else { return current } // If we run into issues, we return what we have so far.
return current + " " + next!
}
print(inTheWashOrTheRinse3) // Prints " I am a on the wind GLURK!"
// Or a nil-coalescing operator:
let inTheWashOrTheRinse4: String = famousLastWords.reduce("") { (current, next) -> String in
return current + " " + (next ?? "flatus") // We don't need to unwrap, because of the nil-coalescing operator.
}
print(inTheWashOrTheRinse4) // Prints " I am a flatus on the wind GLURK!"
// Let's put "leaf" back:
famousLastWords[3] = "leaf"
// We create a class that will hold and render our last words.
class FamousLastWords {
var declarationString: [String?]!
init(_ inStringArray: [String?]!) {
declarationString = inStringArray
}
var tellMeQuicklyBeforeYouCroak: String {
return declarationString.reduce("") { (current, next) -> String in
guard nil != next else { return current }
return current + " " + next!
}
}
}
var famousLastWordObject: FamousLastWords! = FamousLastWords(famousLastWords)
// Since everything is copacetic, this prints no problem.
print(famousLastWordObject.tellMeQuicklyBeforeYouCroak) // Prints "I am a leaf on the wind GLURK!"
// Now, time for flies in the ointment...
// Let's mess with "leaf":
famousLastWordObject.declarationString[3] = nil
// The rendering routine won't have an issue with it.
print(famousLastWordObject.tellMeQuicklyBeforeYouCroak) // Prints "I am a on the wind GLURK!"
// However, how will we get this?
// These will give fatal errors:
//let index2 = String.Index(utf16Offset: 2, in: famousLastWordObject.declarationString[3]!)
//let toBePrinted2: Character = (famousLastWordObject.declarationString[3]?[index])!
// We can quickly find out if the string is good.
if let lastWordString = famousLastWordObject.declarationString[3] {
print("We have a Last Word: \(lastWordString)!")
} else {
print("No Last Word!")
}
// However, we may switch the object to a different variable, and nil out the original:
let famousLastWordObject2 = famousLastWordObject
famousLastWordObject = nil
// Let's try that again:
// Ooh..That's gonna leave a mark. We get a runtime error here, because we reference an implicit optional (the famousLastWordObject variable).
//if let lastWordString = famousLastWordObject.declarationString[3] {
// print("We have a Last Word: \(lastWordString)!")
//} else {
// print("No Last Word!")
//}
// Watch what happens when we add a question mark in there:
if let lastWordString = famousLastWordObject?.declarationString[3] {
print("We have a Last Word: \(lastWordString)!")
} else {
print("No Last Word!")
}
// What we just did, was "chain" optionals. There were actually two tests in that assignment. The first, was we checked to see if the object was good (it wasn't).
// The second would have been to make sure that the fourth element was non-nil. We never made it that far. The first test failed.
// You can make long lines, with lots of question marks. If any one of them is bad, the whole thing politely barfs.
/*
PLAYGROUND 6: Branches
©2019 Rift Valley Software. All Rights Reserved.
*/
// Branches (decisons) in Swift are done by switch, if, guard, ternary (?:), nil-coalescing (??), for, and while.
// While and for are loops, and incorporate branches.
// You can use the where clause to constrain a branch.
// switch
// Swift has very powerful switch statements. They can have ranges, full Strings, and enums with associated values.
// They can also be [sort of] incorporated into other branches via the case statement.
// You declare a switch thusly:
let someInteger: Int = 13
switch someInteger { // Will print "13 is between 10 and 15\n"
case 0..<5:
print("\(someInteger) is between 0 and 4")
case 5..<10:
print("\(someInteger) is between 5 and 9")
case 10..<15:
print("\(someInteger) is between 10 and 15")
case 15..<20:
print("\(someInteger) is between 15 and 20")
case 20:
print("\(someInteger) is 20")
default:
print("\(someInteger) is over 20")
}
// First of all, Swift requires that all switch statements be exhaustive. That means that you must have explicit code to handle ALL eventualities.
// Most times, this means that you need to have a default case, although a switch statement that has cases for every possible case in an enum will also work:
enum OneToFive: Int {
case one = 1, two, three, four, five
}
let three = OneToFive.three
switch three { // Prints "3\n"
case .one:
print("ONE IS THE LONLIEST NUMBAH...")
case .two:
print("2")
case .three:
print("3")
case .four:
print("4")
case .five:
print("5")
}
// If your switch is not exhaustive, the compiler will yell at you.
// There's also a "shortcut" for single-value switch tests:
if case three = OneToFive.three {
print("TWEE!")
} else {
print("Something Else!")
}
let two = OneToFive.two
if case two = OneToFive.three {
print("TWEE!")
} else {
print("Something Else!")
}
// Also, Swift switch statements, unlike most other language switch statements, will not "fall through" to the next case, unless you explicitly tell it to:
// In other languages, the switch statement above would have printed "3\n4\n5\n". Instead, it, only printed "3\n", because that was where the switch ended.
// You can add a "fallthrough" statement, which will give the same functionality as standard switches:
switch three { // Prints "3\n4\n5\n"
case .one:
print("ONE IS THE LONLIEST NUMBAH...")
fallthrough
case .two:
print("2")
fallthrough
case .three:
print("3")
fallthrough
case .four:
print("4")
fallthrough
case .five:
print("5")
}
// This allows case clauses to have an explicit context, like braces:
func printSwitch(_ inSwitchVal: OneToFive) {
var extra = "OUTSIDE ISSUES"
switch inSwitchVal {
case .one:
print("ONE IS THE LONLIEST NUMBAH...")
let oldExtra = extra
extra = "I AM ONE"
print(extra)
extra = oldExtra
printSwitch(.two)
case .two:
print("2")
let oldExtra = extra
extra = "I AM TWO"
print(extra)
extra = oldExtra
printSwitch(.three)
case .three:
print("3")
let oldExtra = extra
extra = "I AM THREE"
print(extra)
extra = oldExtra
printSwitch(.four)
case .four:
print("4")
let oldExtra = extra
extra = "I AM FOUR"
print(extra)
extra = oldExtra
printSwitch(.five)
case .five:
print("5")
let oldExtra = extra
extra = "I AM FIVE"
print(extra)
extra = oldExtra
}
print(extra)
}
printSwitch(.one)
// In the above recursive function, each case had its own context for the "extra" variable, and also called the function to handle the next value, so it printed:
// "ONE IS THE LONLIEST NUMBAH...\nI AM ONE\n2\nI AM TWO\n3\nI AM THREE\n4\nI AM FOUR\n5\nI AM FIVE\nOUTSIDE ISSUES\nOUTSIDE ISSUES\nOUTSIDE ISSUES\nOUTSIDE ISSUES\nOUTSIDE ISSUES"
// That means that the lines between the "case ...:" and the next "case ...:" had their own context that did not go beyond that area. It is as if there were baces there.
// The surrounding context still applied, so you could access and affect the outside context, but the internal context remained internal.
// There's a LOT more down this rabbit-hole, but this gives you a bit of the flavor for the switch statement, which is really powerful in Swift.
// if
// The Swift if statement is also quite powerful. One of the things about Swift, is that if REQUIRES that the test result in a Boolean true or false, EXCEPT when making an assignment from an optional.
// If we want to test if an Int is 0 or 1, we can't do the same as other languages:
let testSubject: Int = 1
// This will result in a compile-time error:
//if testSubject {
// print("true")
//} else {
// print("false")
//}
// You need to do this:
if 1 == testSubject {
print("true")
} else {
print("false")
}
// That makes the test Boolean.
// The same goes for nil-testing optionals:
var nilTestOptional: Bool! = nil
// This will result in a compile-time error:
//if nilTestOptional {
// print("true")
//} else {
// print("false")
//}
// You need to do this:
if nil != nilTestOptional {
print("true")
} else {
print("false")
}
// Of course, Swift throws a bit of a curve-ball into things, by allowing you to assign from optionals, and it does allow that to be tested:
// If the assignment results in nil, we execute the else path:
if let fromOptional = nilTestOptional { // Prints "fromOptional is false\n"
print("fromOptional is \(fromOptional)")
} else {
print("fromOptional is false")
}
nilTestOptional = true
// If it is non-nil; regardless of the value, we run the if path:
if let fromOptional = nilTestOptional { // Prints "fromOptional is true\n"
print("fromOptional is \(fromOptional)")
} else {
print("fromOptional is false")
}
// That's weird. Why did we not use "\(fromOptional)" in the else path?
// That's because "fromOptional" doesn't exist in the else path. It is constrained to the context formed by the if path.
// This is tremendously useful, as you can restrict a context to only if the comparison is successful (or not successful).
// You can also "chain" assignments in an if statement:
var leafValue: String? = "leaf"
var container1: [String?]? = [leafValue]
var container2: [[String?]?]? = [container1]
var dictionary: [String: [[String?]?]?]? = ["TEST": container2]
if let firstStage = dictionary?["TEST"], let secondStage = firstStage?[0], let leaf = secondStage[0] {
print("Leaf: \(leaf)")
} else {
print("NOPE")
}
// You need to add question marks, to tell the if that it is unwrapping optionals. The last step doesn't need it, as seconStage will only be valid if its optional unwrapped, so it is sort of an optional.
// Now, let's bork the first step in that chain:
if let firstStage = dictionary?["TURKEY"], let secondStage = firstStage?[0], let leaf = secondStage[0] {
print("Leaf: \(leaf)")
} else {
print("NOPE")
}
// Since the first assignment failed, all the ones after it failed as well.
// Now, we can also do that with implicit optionals:
var leafValue2: String! = "leaf"
var container12: [String?]! = [leafValue]
var container22: [[String?]?]! = [container1]
var dictionary2: [String: [[String?]?]?]! = ["TEST": container2]
if let firstStage = dictionary2?["TEST"], let secondStage = firstStage?[0], let leaf = secondStage[0] {
print("Leaf: \(leaf)")
} else {
print("NOPE")
}
// Note the question marks still need to be there.
// Again, this is a deep rabbit-hole. We just scratched the surface, but this shows you that there's a lot under the hood, here.
// guard
// Guard is actually a variant of an if that is especially made to interrupt program flow. It can be though of as a "safe exception."
// You use guard to exit a context if a condition resolves to false. It uses the same assignment and test rules as if.
// guard always has an "else" clause. That clause MUST exit the context. You'll get a compile-time error if it doesn't.
// The context must be an "exitable" one, such as a function, loop, or "do," which really just wraps a context.
// Let's look at the above, with guard used to exit a function if the assignmant fails:
func testGuard(_ inDictionaryKey: String, dictionary inDictionary: [String: [[String?]?]?]?) -> Bool {
let printPass = "Leaf"
let printFail = "ARGH!"
guard let firstStage = dictionary?[inDictionaryKey], let secondStage = firstStage?[0], let leaf = secondStage[0] else {
print(printFail) // Note that it had access to the main context up to this point.
return false
}
print("\(printPass): \(leaf)") // The leaf variable is available in the main context.
return true
}
if !testGuard("TEST", dictionary: dictionary) { // Prints "Leaf: leaf\n"
print("NOPE")
}
// The function printed "Leaf: leaf\n", as the assignment passed.
if !testGuard("TURKEY", dictionary: dictionary) { // Prints "ARGH!\nNOPE\n"
print("NOPE")
}
// The function didn't print anything other than "ARGH!". It returned false without printing "Leaf: leaf".
// The secondStage and leaf constants were not available to the guard else context.
// Think of it as a "reverse if." If the test passes, then the assignments are available to the surrounding context, and execution continues.
// If the test fails, then the main context is terminated, and the assignments are not available. There is a temporary contained context after "else" before exit.
// We won't cover the ternary operator, as that is really just another flavor of if, and we've already covered the nil-coalescing operator.
// Loops
// Swift discourages use of the traditional for(i = 0; i < 10; i++){...} style loop from C aand other C-based languages.
// Instead, it has a couple of specialized variants of for, and wants you to use iterators and higher-order functions, where possible.
// Remember that Swift has cribbed a lot from Functional Programming. FP doesn't really use loops. Instead, it tends to use collections, higher-order functions and recursion.
// It does have for loops, though; just not the standard ones. It has a for in loop, like many scripting languages, where you provide a collection or range:
let someCollection = [0,1,2,3,4,5,6,7,8,9]
let intRange: Range<Int> = 0..<10
// You can loop iteratively through a collection (This could also be a Dictionary or Set, but order is not guaranteed for them):
for item in someCollection {
print("Collection Component: \(item)")
}
// Or you could step through a Range:
for item in intRange {
print("Range Component: \(item)")
}
// However, you can't step through a Float Range the same way:
let floatRange: Range<Float> = 0.0..<10.0
// This will experience a compile-time error:
//for item in floatRange {
// print("Range Component: \(item)")
//}
// As you can't sequentially step through a Float. We'll cover how to deal with that in a later playground.
// With a collection, you can also use higher-order functions, like forEach:
someCollection.forEach { item in
print("Collection Component: \(item)")
}
// One difference between using a for in and a forEach, is that the for in can be interrupted:
for item in someCollection {
if 5 < item {
break // Break says "We're done here."
}
print("Collection Component: \(item)")
}
for item in someCollection {
if 5 < item && 8 > item {
continue // Continue says "Skip the rest of this stuff, and just go to the next iteration."
}
print("Collection Component: \(item)")
}
// Can't do that with forEach. The following will give a compile-time error:
//someCollection.forEach { item in
// if 5 < item {
// break
// }
// print("Collection Component: \(item)")
//}
// You can also use where to constrain for loops, so that they only get executed for matching states.
// The following will only print even numbers:
for item in someCollection where 0 == item % 2 {
print("Collection Component: \(item)")
}
// while
// While can be used either at the start of a loop, or at the end.
// Before
// In order to use a while at the start of a loop, you simply declare it and open a context:
var index = 0
while index < someCollection.count {
let item = someCollection[index]
print("Collection Component: \(item)")
index += 1
}
// After
// To test a condition after a loop, use repeat to open the loop, and put the while at the bottom.
index = 0
repeat {
let item = someCollection[index]
print("Collection Component: \(item)")
index += 1
} while index < someCollection.count - 1 // Need to add -1, here, because we are now testing after the increment.
// While can be continued or broken, but not filtered.
// throw
// Swift doesn't really have exceptions, like C++. Instead, it has "throw," which is more of an error-handler than the hard-core stack-unwrapping of C++.
// Throw is a bit like guard. It allows the program flow to be altered upon request. Unlike guard, it isn't a test. It's a simple imperative.
// You use throw by simply setting up the throw keyword, followed by something that conforms to the Error protocol.
// Program flow stops at that point, and is sent to the nearest context that can "capture" that throw.
// Error throws stop at function boundaries, unless a function is specifically marked as a "throws" function. This is a big difference from classic exceptions.
// You will still encounter exceptions, if using Apple operating systems, as Objective-C throws them, and Objective-C is still prevalent in the Cocoa [Touch] toolkit.
// You won't find any from Swift code, though.
// We'll start by defining a simple enum that conforms to Error (You should define enums as errors for throws):
enum MyError: Error {
case genericError
case internalError
case externalError
case errorWithBaggage(String) // This is cool, because you can do things like attach specific error information.
}
// Now, we define a function that has an error throw in it:
//func throwsInternalError() {
// throw MyError()
//}
// The above won't compile, because it isn't handled. We either need to declare the function as "throws":
func throwsExternalError() throws {
print("about to throw external error")
throw MyError.externalError
}
// Or add code to the function to catch and deal with the error:
func handlesInternalError() {
print("about to throw internal error")
do {
throw MyError.internalError
} catch { // Catch needs to be "exhaustive" That means that we need to have at least one generic "catch" that grabs the error.
print("Internal Error: " + String(describing: error.self))
}
}
handlesInternalError()
// In order to handle an external error, you need to either wrap the function in a do...catch structure:
do {
try throwsExternalError() // try says "I know that this could throw an error, and I'm ready for it."
} catch {
print("External Error: " + String(describing: error.self))
}
// Or use a question-mark try, which means that the failure will be ignored:
try? throwsExternalError() // In this case, you will see "about to throw external error", and then nothing else, as the execution continues.
// If you use a question-mark try in a do...catch block, you'll get a warning, saying that catch will never be executed.
do {
try? throwsExternalError()
} catch { // You will see a warning here, that says the catch block is unreachable.
// That's because the question mark above, tells the execution to continue, even if the function throws.
print("External Error: " + String(describing: error.self))
}
// However, the reason for try? is not so you can avoid dealing with errors. It's so that you can deal with them smoothly.
// Here is a more realistic approach:
// This function will return the given String, but if it is empty, it will throw an error, instead.
func returnAStringIfNotEmpty(_ inString: String = "") throws -> String {
if inString.isEmpty {
print("Can't Touch This")
throw MyError.errorWithBaggage("Can't Touch This")
}
return inString
}
// This tries three variants, with the third one succeeding.
func fetchAString() -> String {
if let retStr = try? returnAStringIfNotEmpty() { return retStr } // Can't touch this
if let retStr = try? returnAStringIfNotEmpty("") { return retStr } // Can't touch this
if let retStr = try? returnAStringIfNotEmpty("Hammertime") { return retStr } // Hammertime!
return "Oh dear" // Won't ever touch this
}
print(fetchAString())
// You can also treat catch a bit like a switch statement:
// First, we'll define a func that throws whatever you give it:
func throwsTheGivenError(_ inError: MyError) throws {
throw inError
}
// Next, we do a do...catch, with catch keying on the types of errors.
func throwAnErrorAndHandleIt(_ inError: MyError) { // Note no "throws."
do {
try throwsTheGivenError(inError)
} catch MyError.genericError {
print("Generic Error")
} catch MyError.internalError {
print("Internal Error")
} catch MyError.externalError {
print("External Error")
} catch MyError.errorWithBaggage(let errStr) where "Tumi" == errStr {
print("Error With Expensive Baggage: \(errStr)")
} catch MyError.errorWithBaggage(let errStr) {
print("Error With Baggage: \(errStr)")
} catch { // This will never get called, but is required in order to make the catch exhaustive.
print("Default Error: " + String(describing: error.self))
}
}
throwAnErrorAndHandleIt(.genericError)
throwAnErrorAndHandleIt(.internalError)
throwAnErrorAndHandleIt(.externalError)
throwAnErrorAndHandleIt(.errorWithBaggage("Samsonite"))
throwAnErrorAndHandleIt(.errorWithBaggage("Tumi"))
// There's a lot more to go here, but this is a good intro.
/*
PLAYGROUND 7: Collections and Ranges
©2019 Rift Valley Software. All Rights Reserved.
*/
// Standard Swift Collections
// The Swift Standard Library comes with three basic collection types: Array, Dictionary, Set. They are generic, and will aggregate any type of Swift objects.
// Swift standard collections are value semantic. They aggregate copies of data; not references. You should keep this in mind when using them.
// Array
// Arrays are linear, ordered collections that aggregate data in the order they are stored. Access to Array data is via integer position indexes or iteration.
// Let's set up an Array of Strings:
var stringArray = ["Eeny", "Meeny", "Miney", "Moe"]
// All collections have a "count" calculated property that indicates how many items it stores:
print(stringArray.count) // Prints "4"
// Arrays can have their contents directly accessed via a 0-based index/subscript:
print(stringArray[2]) // Prints "Miney"
// Or they can iterate via a loop:
for element in stringArray { // Prints "Eeny\nMeeny\nMiney\nMoe\n"
print(element)
}
// Or they can be iterated using higher-order functions:
stringArray.forEach { print($0) } // Prints "Eeny\nMeeny\nMiney\nMoe\n"
print(stringArray.map { (element) -> Character in // Prints ["E", "M", "M", "M"]
return element[element.startIndex]
})
print(stringArray.reduce("") { current, new in // Prints "Eeny, Meeny, Miney, Moe"
return current + (!current.isEmpty ? ", " : "") + new
})
// Here's a simple proof that they are value semantics:
let newArray = stringArray
print(newArray) // Prints ["Eeny", "Meeny", "Miney", "Moe"]
stringArray[2] = "Money"
print(stringArray) // Prints ["Eeny", "Meeny", "Money", "Moe"]
print(newArray) // Prints ["Eeny", "Meeny", "Miney", "Moe"]
// All collections also have an "isEmpty" flag, that is true, if there are zero items:
print(stringArray.isEmpty) // Prints "false"
stringArray = []
print(stringArray.isEmpty) // Prints "true"
// Dictionary
// Dictionaries are a standard hash table, random-access aggregator type.
// You set up a Dictionary by defining it as Dictionary<AnyHashable, Any>, or [AnyHashable: Any]
// For example, if you were creating a Dictionary of integers, with String keys, you would define it as Dictionary<String, Int> or [String: Int]
var integerDictionary = ["Two": 2, "One": 1, "Four": 4, "Zero": 0, "Three": 3]
// Which is exactly the same as:
//var integerDictionary: [String: Int] = ["Two": 2, "One": 1, "Four": 4, "Zero": 0, "Three": 3]
// or
//var integerDictionary: Dictionary<String, Int> = ["Two": 2, "One": 1, "Four": 4, "Zero": 0, "Three": 3]
// With a Dictionary, you access items directly, using a hash (O1 complexity):
print(integerDictionary["Three"] ?? "ERROR") // Prints "3"
// Note the nil-coalescing operator (??). That's there, because the lookup could fail. The return type is Int?. If that happens, you get nil. Let's try a failure:
print(integerDictionary["Five"] ?? "ERROR") // Prints "ERROR"
// The order of the stored items is not guaranteed. A straight printing could yield inconsistent results:
print(integerDictionary) // Almost certainly will not print ["Zero": 0, "One": 1, "Two": 2, "Three": 3, "Four": 4] or ["Two": 2, "One": 1, "Four": 4, "Zero": 0, "Three": 3]
// You can iterate Dictionaries via higher-order functions.
// The order of the following will be undefined. They will be similar to above.
integerDictionary.forEach { print("\($0.key) = \($0.value)") } // Since we are iterating a Dictionary, each element is actually a tuple (key: String, value: Int)
print(integerDictionary.map { (element) -> Int in // Transform to an Array of Int, using map
return element.value
})
print(integerDictionary.reduce("") { current, new in
return current + (!current.isEmpty ? ", " : "") + String(new.value)
})
// Or with a standard for in iterator (again, order is not defined):
for element in integerDictionary {
print("\(element.key) = \(element.value)")
}
// Dictionaries have an Array of Dictionary.Keys type that is called "keys":
var keyArray = integerDictionary.keys
print("Raw: \(keyArray)")
// You can use this to impose some kind of order on the Dictionary.
// You can't do sorts on Dictionaries or Sets; only Arrays.
// This blunt-instrument stupid sort will only work for five sequential values. Don't try this at home.
var sortedKeys = (keyArray.map { return String($0) } as [String]).sorted { return ("Four" == $1) || ("Zero" == $0) || ("One" == $0 && "Zero" != $1) || ("Two" == $0 && "Three" == $1) }
print("Sorted: \(sortedKeys)")
sortedKeys.forEach {
print(integerDictionary[$0] ?? "ERROR")
}
// Set
// Sets look like unordered Arrays, with a twist: You can't repeat values in a Set, and the values have to be hashable.
// A Set is a keyless hash table; where the value itself is hashed.
// Use a set to force uniqueness, or quickly match for membership.
// A hash lookup is a O(1) operation, while an Array search could be as high as O(n), if you are simply looking for membership (contains() function).
// Since a Set assignment looks exactly like an Array assignment, you need to explicitly indicate that this is a Set:
// Note that we didn't need to indicate Set<String>. That was inferred by Swift (that's what I mean by Swift "hiding" generics).
var integerNameSet: Set = ["Two", "One", "Four", "Zero", "Three", "Zero"] // Note that we added "Zero" twice, but it will only be in the Set once (look at the print).
print(integerNameSet) // Since the Set is unordered, this will almost certainly not print ["Two", "One", "Four", "Zero", "Three"]
// You use "contains()" to find membership:
print(integerNameSet.contains("Three")) // Prints "true"
print(integerNameSet.contains("Five")) // Prints "false"
// There's a whole bunch of other useful operations for sets, like unions, intersections, differences, etc. We won't cover that, here.
// However, we will cover one very useful aspect of Sets. Remember that we added "Zero" twice, above, but it only came up once?
// We'll use that to enforce uniqueness.
// Let's create a String Array with a few repeats:
let repeatingArray = ["Two", "Two", "Three", "One", "Four", "Zero", "Three", "Zero"]
// We didn't need to specify that it was an [String], as the compiler implies this.
// Now, we assign it to a Set:
let filteredSet = Set(repeatingArray)
// In the printout, see the differences between the two:
print(repeatingArray)
print(filteredSet)
// Now, we can use the sorting ability from before, to create a unique, sorted Array from that random, repeated input:
let filteredAndSortedArray = [String](filteredSet).sorted { return ("Four" == $1) || ("Zero" == $0) || ("One" == $0 && "Zero" != $1) || ("Two" == $0 && "Three" == $1) }
print(filteredAndSortedArray)
// PART 1: SPECIFYING RANGES AND INTERVALS
// 1.1: RANGES
// First, we look at the Range type. Ranges are designed for incrementing. They have to be scalar (incrementable, usually, integers).
// Ranges always represent internally as min..<max When you do an inclusive range, it will represent internally as min..<max+1
let range1:CountableRange<Int> = 0..<10 /* This contains 0,1,2,3,4,5,6,7,8,9 */
let range2:CountableRange = 0..<10 /* This also contains 0,1,2,3,4,5,6,7,8,9 */
let range3 = 0..<10 /* This also contains 0,1,2,3,4,5,6,7,8,9 */
let range4:CountableClosedRange<Int> = 0...9 /* This also contains 0,1,2,3,4,5,6,7,8,9 */
let range5:CountableClosedRange = 0...9 /* This also contains 0,1,2,3,4,5,6,7,8,9 */
let range6 = 0...9 /* This also contains 0,1,2,3,4,5,6,7,8,9 */
// let range7:Range<Float> = 0..<10 /* This is an error. Floats can't be incremented. */
// let range8:Range = 0.0...9.0 /* This is also an error. */
// let range9:Range<String> = "0"..."9" /* This is an error. Even though these are strings in sequence, they can't be incremented. */
// 1.2: INTERVALS
// Next, let's look at the Interval type. Intervals represent spreads of values, and can be non-integer types.
// 1.2.1: TYPES OF INTERVALS
// 1.2.1.1: CLOSED
// Closed intervals
let closed1:ClosedRange<Int> = 1...5 /* This represents 1,2,3,4,5 */
let closed2:ClosedRange = 3...7 /* This represents 3,4,5,6,7 */
// let closed3:ClosedInterval = 3..<8 /* This is an error. You can't specify a closed interval with an open operator. */
let closed4 = 3...7 /* This is not an Interval. It is a Range. */
let closed5 = 3..<8 /* This is not an Interval. It is a Range. */
let closed6Float:ClosedRange<Float> = 2...9 /* This represents 2.0 -> 9.0 as a continuous range. */
let closed7Double = 2.0...9.0 /* This represents 2.0 -> 9.0 as a continuous range. Specifying as a Double makes it an Interval. */
// String Intervals
// These are odd. Looks like it is using the ASCII values. I should experiment with Unicode, and see where we go...
let aThroughFClosed:ClosedRange<String> = "A"..."F"
let dThroughQClosed:ClosedRange = "D"..."Q"
let mThroughSClosed:ClosedRange = "M"..."S"
let tThroughWClosed:ClosedRange = "T"..."W"
let whiskeyTangoFoxtrot1 = "QED"..."WTF" /* Not sure what will happen when I start working with this... */
// 1.2.1.2: HALF-OPEN
// Half-open intervals can only be open in the last value. The first value is inclusive.
let afopen1:Range<Int> = 5..<10 /* This represents 5,6,7,8,9 */
let afopen2:Range<Int> = 7..<20 /* This represents 7,8,9,10,11,12,13,14,15,16,17,18,19 */
let afopenFloat1:Range<Float> = 2..<9 /* This represents 2.0 < 9.0 as a continuous range. */
let afopenFloat2:Range<Float> = 7..<13 /* This represents 7.0 < 13.0 as a continuous range. */
// let afopen3:HalfOpenInterval<Int> = 5>..10 /* This is an error. You can't have half-open intervals open on the bottom. */
// let afopenFloat3:HalfOpenInterval<Float> = 2...9 /* This is an error. You can't specify a half open as a closed. */
let aThroughHHalfOpen:Range<String> = "A"..<"H"
let dThroughRHalfOpen:Range = "D"..<"R"
let mThroughTHalfOpen:Range = "M"..<"T"
let tThroughXHalfOpen:Range = "T"..<"X"
let whiskeyTangoFoxtrot2 = "QED"..<"WTF"
// 1.2.2: CLAMPING
// Clamping is basically the same as a set intersect. It selects the highest low value as the start, and the lowest high value as the end.
// You can clamp intervals, but not ranges.
let clampedValue1 = closed2.clamped ( to: closed1 ) /* This represents 3,4,5 */
let clampedValue2 = afopen2.clamped ( to: afopen1 ) /* This represents 7,8,9 */
// let clampedValue3 = closed2.clamped ( to: afopen1 ) /* This is an error. You can't mix interval types. */
// let clampedValue4 = afopenFloat2.clamped ( to: afopen1 ) /* This is an error. You can't clamp mixed types. */
// let clampedValue5 = closed4.clamped ( to: closed1 ) /* This is an error. Ranges can't clamp. */
let clampedString1 = dThroughQClosed.clamped ( to: aThroughFClosed ) /* This represents "D"..."F" */
let clampedString2 = aThroughFClosed.clamped ( to: dThroughQClosed ) /* This represents "D"..."F" */
let clampedString3 = mThroughSClosed.clamped ( to: dThroughQClosed ) /* This represents "M"..."Q" */
let clampedString4 = tThroughWClosed.clamped ( to: dThroughQClosed ) /* This represents "Q"..."Q" */
let clampedString5 = tThroughWClosed.clamped ( to: aThroughFClosed ) /* This represents "F"..."F" */
let clampedString6 = dThroughRHalfOpen.clamped ( to: aThroughHHalfOpen ) /* This represents "D"..<"G" */
let clampedString7 = aThroughHHalfOpen.clamped ( to: dThroughRHalfOpen ) /* This represents "D"..<"H" */
let clampedString8 = mThroughTHalfOpen.clamped ( to: dThroughRHalfOpen ) /* This represents "M"..<"R" */
let clampedString9 = tThroughXHalfOpen.clamped ( to: dThroughRHalfOpen ) /* This represents "R"..<"R" */
let clampedString0 = tThroughXHalfOpen.clamped ( to: aThroughHHalfOpen ) /* This represents "H"..<"H" (Not exactly sure why) */
// PART 2: USING RANGES AND INTERVALS
// 2.1 USING RANGES
// 2.1.1 RANGES AS LOOP ITERATORS
// The main use for ranges is cheap iterators for loops. They are easy to specify, and easy to use.
// A standard iterator
for i in range1 { print ( "Loop Iteration \(i)" ) }
// You can use the wildcard if you don't care about the actual iterator value.
for _ in range1 { print ( "Another Loop Iteration." ) }
// 2.2: USING INTERVALS
// Intervals are used for purposes of comparison and value matching.
// 2.2.1: INTEGER INTERVALS
// This is an error. You can't iterate Intervals.
// for i in closed1 { print ( "Loop Iteration \(i)" ) }
// 2.2.1.1 INTEGER INTERVALS AS SWITCH TESTS
// Use Intervals in switch statements to specify a range of possible values (a "catchbasket").
var testValue1 = 1
switch ( testValue1 )
{
// This is an error. You can't match against Ranges.
// case closed4:
// print ( "In range!" )
// This is an error. The Interval is a Double, but the test is an Int.
// case closed7Double:
// print ( "In closed7Double" )
case closed1:
print ( "In closed1." ) /* This will catch the value. */
default:
print ( "In catchall." )
}
switch ( testValue1 ) /* This will test against the interval of 3 -> 5 */
{
case clampedValue1:
print ( "In clampedValue1." )
default:
print ( "In catchall." ) /* Since it is not in the clamped interval, we fall into the catchall. */
}
// We try it with 3 as the value.
testValue1 = 3
switch ( testValue1 )
{
case closed1:
print ( "In closed1." ) /* This will catch the value again. */
default:
print ( "In catchall." )
}
switch ( testValue1 )
{
case clampedValue1:
print ( "In clampedValue1." ) /* Now that the test value is in the interval window, we catch it here. */
default:
print ( "In catchall." )
}
// This is a logical error (but not flagged by the compiler, so it counts as a "gotcha"). The two intervals have overlapping ranges.
// You are allowed to specify intervals that overlap, but only the first "hit" will count.
switch ( testValue1 )
{
case closed1: /* This will catch all numbers between 1 and 5. */
print ( "In closed1." ) /* This will catch the value, even though it also falls into the next one. */
case clampedValue1: /* This will not catch any numbers, as the interval is 3,4,5. */
print ( "In clampedValue1." )
default:
print ( "In catchall." )
}
// If we switch the two tests, then the clampedValue1 test is the hit.
switch ( testValue1 )
{
case clampedValue1:
print ( "In clampedValue1." ) /* This will catch the value, even though it also falls into the next one. */
case closed1:
print ( "In closed1." )
default:
print ( "In catchall." )
}
// However, in this one, the second test will hit, because 1 is not in the first interval.
testValue1 = 1
switch ( testValue1 )
{
case clampedValue1:
print ( "In clampedValue1." )
case closed1:
print ( "In closed1." ) /* You sunk my battleship! */
default:
print ( "In catchall." )
}
// 2.2.1.2 INTEGER INTERVALS AS BOOLEAN TESTS
// You test by using the Interval.contains() method.
if ( closed1.contains ( testValue1 ) )
{
print ( "We gots us a match!" )
}
if ( !clampedValue1.contains ( testValue1 ) )
{
print ( "We gots us a mismatch!" )
}
// 2.2.2: FLOATING POINT INTERVALS
// 2.2.2.1: FLOATING POINT INTERVALS AS SWITCH TESTS
var testValue2:Float = 2.0
switch ( testValue2 )
{
// This is an error. You can't compare against other types.
// case closed1:
// print ( "In closed1." )
case afopenFloat1: /* This will catch the value, as it is within the interval range. */
print ( "In the range of 2..<9!" )
case afopenFloat2:
print ( "In the range of 7..<13!" )
default:
print ( "In catchall." )
}
testValue2 = 7.0
switch ( testValue2 )
{
case afopenFloat1: /* This will catch it, even though it is also in the next test range. */
print ( "In the range of 2..<9!" )
case afopenFloat2:
print ( "In the range of 7..<13!" )
default:
print ( "In catchall." )
}
testValue2 = 8.999999 /* NOTE: Look at the displayed value. */
switch ( testValue2 )
{
case afopenFloat1: /* This will catch it. */
print ( "In the range of 2..<9!" )
case afopenFloat2:
print ( "In the range of 7..<13!" )
default:
print ( "In catchall." )
}
// This illustrates a precision "gotcha." Note what happens when we add one more "9" to the end.
testValue2 = 8.9999999
switch ( testValue2 )
{
case afopenFloat1:
print ( "In the range of 2..<9!" )
case afopenFloat2: /* This will catch it, even though the number is "less" than 9.0. */
print ( "In the range of 7..<13!" )
default:
print ( "In catchall." )
}
testValue2 = 9.0
switch ( testValue2 )
{
case afopenFloat1: /* This will not catch it, as the value needs to be LESS than 9.0 to match. */
print ( "In the range of 2..<9!" )
case closed6Float:
print ( "In the range of 2...9!" ) /* This will catch the value, as it is within the closed interval range. */
case afopenFloat2:
print ( "In the range of 7..<13!" )
default:
print ( "In catchall." )
}
testValue2 = 9.00001
switch ( testValue2 )
{
// This is an error. The Interval is a ClosedInterval<Double>, but the test value is a Float
// case closed7Double:
// print ( "In closed7Double" )
case afopenFloat1: /* This will not catch it, as the value needs to be LESS than 9.0 to match. */
print ( "In the range of 2..<9!" )
case closed6Float:
print ( "In the range of 2...9!" )
case afopenFloat2:
print ( "In the range of 7..<13!" ) /* This will catch the value, as it is within the interval range. */
default:
print ( "In catchall." )
}
testValue2 = 1.0
switch ( testValue2 )
{
case afopenFloat1:
print ( "In the range of 2..<9!" )
case afopenFloat2:
print ( "In the range of 7..<13!" )
default: /* Since neither of the above intervals has this value, we get it. */
print ( "In catchall." )
}
// Test with a Double (not a Float).
var testValue2Double:Double = 2.0
switch ( testValue2Double )
{
case closed7Double: /* This will catch it. */
print ( "In closed7Double" )
default:
print ( "In catchall." )
}
testValue2Double = 1.999999999999999 /* There is enough precision to make this just less than 2.0 */
switch ( testValue2Double )
{
case closed7Double:
print ( "In closed7Double" )
default: /* This will catch it. */
print ( "In catchall." )
}
// 2.2.2.2 FLOATING POINT INTERVALS AS BOOLEAN TESTS
testValue2 = 2.345
if ( afopenFloat1.contains ( testValue2 ) )
{
print ( "We gots us a match!" )
}
if ( !afopenFloat2.contains ( testValue2 ) )
{
print ( "We gots us a mismatch!" )
}
// 2.2.3: STRING INTERVALS
// String intervals are weird. Just sayin'...
// 2.2.3.1: STRING INTERVALS AS SWITCH TESTS
var testValue3:String = "B"
switch ( testValue3 )
{
case aThroughFClosed: /* This will catch it. */
print ( "In A...F." )
default:
print ( "In catchall." )
}
// Looks like the test is only on the first letter.
testValue3 = "Badz-Maru"
switch ( testValue3 )
{
case aThroughFClosed: /* This will catch it. */
print ( "In A...F." )
default:
print ( "In catchall." )
}
testValue3 = "\tBadz-Maru" /* If we add a tab character to the start of the string, then the first test will fail. */
switch ( testValue3 )
{
case aThroughFClosed:
print ( "In A...F." )
default: /* This will catch it. */
print ( "In catchall." )
}
// Now, we'll get really strange. Let's look at our multi-character intervals...
testValue3 = "W"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2: /* This catches it. */
print ( "WTF, dude?" )
default:
print ( "In catchall." )
}
testValue3 = "T"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2: /* This catches it. */
print ( "WTF, dude?" )
default:
print ( "In catchall." )
}
testValue3 = "F"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2:
print ( "WTF, dude?" )
default: /* However, in this case, it falls through to default. */
print ( "In catchall." )
}
testValue3 = "WT"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2: /* "WT" is caught. */
print ( "WTF, dude?" )
default:
print ( "In catchall." )
}
testValue3 = "WTF"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2:
print ( "WTF, dude?" )
default: /* "WTF" is not caught. */
print ( "In catchall." )
}
testValue3 = "QED"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2: /* "QED" is caught. */
print ( "WTF, dude?" )
default:
print ( "In catchall." )
}
testValue3 = "QTF"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2: /* "QTF" is caught. */
print ( "WTF, dude?" )
default:
print ( "In catchall." )
}
testValue3 = "QSF"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2: /* "QSF" is caught. */
print ( "WTF, dude?" )
default:
print ( "In catchall." )
}
testValue3 = "QAF"
switch ( testValue3 )
{
case whiskeyTangoFoxtrot2:
print ( "WTF, dude?" )
default: /* QAF falls through. */
print ( "In catchall." )
}
// Basically, I don't think we should use multi-character strings in intervals. The behavior seems undefined.
// PART 3: STRIDES
// Strides are sort of a Range. They are mostly used to iterate for loops. Use a stride to iterate in "jumps," or to iterate backwards (Ranges cannot be defined in reverse).
let randallFlagg:StrideTo<Int> = stride ( from: 0, to: 6, by: 3 ) /* StrideTo is a "half open" Range. It does not include the last value. */
for i1 in randallFlagg
{
print ( "i1 is \(i1)" )
}
let aragorn:StrideThrough<Int> = stride ( from: 0, through: 6, by: 3 ) /* StrideThrough is a "closed" Range, which includes the last value. */
for i2 in aragorn
{
print ( "i2 is \(i2)" )
}
// We can also use Strides to iterate backwards.
let frodo:StrideThrough<Int> = stride ( from: 6, through: 0, by: -3 )
for i3 in frodo
{
print ( "i3 is \(i3)" )
}
// You can implicitly type the strides.
let bubbaThrough = stride ( from: 1, through: 5, by: 1 )
for bub in bubbaThrough
{
print( "Bubba be \(bub)" )
}
let bubbaTo = stride ( from: 0, to: 5, by: 2 )
for bub in bubbaTo
{
print( "Bubba be \(bub)" )
}
// Strides are often defined directly in the loop declarations.
for bub in stride ( from: 6, through: 0, by: -2 )
{
print( "Bubba be \(bub)" )
}
// You can define a nonsensical stride, but it won't result in any loop executions.
let bubbaBad = stride ( from: 0, to: 5, by: -2 )
for bub in bubbaBad
{
print( "Bubba be bad: \(bub)" )
}
// One advantage of Strides, is that you can increment in floating point steps.
let strideFloat:StrideTo<Float> = stride ( from: Float(0), to: 5, by: 2.1 )
for i4 in strideFloat
{
print( "i4 is \(i4)" )
}
// These are the basics of the Range, Interval and Stride types, but there's a lot more depth to this rabbit hole...
// PART 4: Range Generators (SequenceType)
// Range generators are basically iterators. Iterable classes can spit out a generator, which is basically a disposable iterator.
// This example came from here: http://schani.wordpress.com/2014/06/03/playing-with-swift/
let arr = [1, 2, 3]
for x in arr
{
print ( x )
}
// Which is really...
var arrGen = arr.makeIterator()
while let x = arrGen.next()
{
print ( x )
}
// Try the same thing with a dictionary.
let uncleBobIsCool = [ "A":1, "B":2, "C":3, "D":4, "E":5 ]
for x in uncleBobIsCool
{
print ( x )
}
var dictGen = uncleBobIsCool.makeIterator()
while let x = dictGen.next()
{
print ( x )
}
// This is an example I plucked from here: http://sketchytech.blogspot.com/2014/08/swift-adopt-sequence-protocol-and-use.html
// This shows how to create a simple struct-based iterator.
// SequenceType is a struct (not a class), and requires a GeneratorType-based struct as a typealias
struct MySequence:Sequence
{
var x, y, length:Int // These are the hypothetical struct data members.
// The SequenceType protocol dictates that we have a generate() method that emits a GeneratorType-based struct.
// Swift is kinda cool, in that you can define a typealias as a required prototype component.
typealias GeneratorType = MyGenerator
// This function instantiates a generator object, and returns that.
// Since this is a struct, the object is a struct, and is returned by value.
// That means that this object will not be affected by the iterator.
func makeIterator() -> GeneratorType
{
// Length indicates how many iterations will be allowed.
// The other two parameters are the starting values of the data members.
return MyGenerator ( x:x, y:y, length:length )
}
mutating func next() -> GeneratorType.Element? {
if length == 0 {
return nil
} else {
length -= 1
x += 1
y += 1
return ( x, y )
}
}
}
// This struct models one generator object.
// The generator is a "disposable" instance that is created by an instance of MySequence.
// The GeneratorType protocol requires a generic type, called "Element," which can be anything you want,
// and a "next()" method that returns one of the "Element" instances.
struct MyGenerator:IteratorProtocol
{
var x, y, length:Int // These are the values
typealias Element = ( Int, Int ) // In the case of this example, "Element" is a tuple, containing x and y Int values.
// This is the required next() method. Since this is a struct, we need the "mutating" keyword.
mutating func next() -> Element? // The return is optional, as we return nil when done.
{
// We just go for as many counts as "length" gave us, incrementing the values as we go.
if length > 0
{
length -= 1
x += 1
y += 1
return ( x, y )
}
else
{
return nil // At the end, we return nil.
}
}
}
var seq = MySequence ( x:10, y:10, length:10 )
for point in seq
{
print ( point )
}
// We can go again without a rewind.
// Note the data member values are at the old starting point.
// That's because internally, a new generator is created.
for point in seq
{
print ( point )
}
// Let's create a new instance.
seq = MySequence ( x:10, y:10, length:10 )
// This is what's going on inside.
// Note that we need a "var", as we are mutating the struct.
var seqGen = seq.makeIterator()
// Since we generated a new generator, we start from the beginning again.
while let x = seqGen.next()
{
print ( x )
}
// Now, if we try to go again, we won't have any luck.
while let x = seqGen.next()
{
print ( x )
}
// However, if we reset length in the generator, we can go again, continuing to increment the values.
seqGen.length = 10
while let x = seqGen.next()
{
print ( x )
}
// Just to prove that the original instance remains untouched.
print ( "x: \( seq.x ), y: \( seq.y ), length: \( seq.length )" )
// Ranges, and Strides
//: Ranges (Open-Ended) can be empty
let rangeIsEmpty = (0..<0).isEmpty
//: ClosedRanges cannot be empty.
let closedRangeIsEmpty = (0...0).isEmpty
//: This will cause a nasty error. Uncomment to see the error.
//let downwardSpiral = 1...0
//: Same here (which makes sense).
//let downwardSpiral = 1..<0
let onwardAndUpward1 = -10..<10 // This is a half-open Range, from -10 to &lt;10, in integer steps (19 steps, 20 values).
let onwardAndUpward2 = -10...10 // This is a closed Range, from -10 to 10, in integer steps (20 steps, 21 values).
//: You cannot specify Ranges backwards. This will cause an error.
//let downwardSpiral = 1>..0
//: You can specify Ranges and ClosedRanges as Int:
let openIntRange = 0..<10
let closedIntRange = 0...10
//: You can specify Ranges and ClosedRanges as Float:
let openFloatRange = 0.0..<1.0
let closedFloatRange = 0.0...1.0
//: And even as Strings:
let openStringRange = "aardvark"..<"zebra"
let closedStringRange = "aardvark"..."zebra"
//: This will work, as Int has discrete, iterable steps:
for integ in openIntRange {
print(integ)
}
//: These will not work, because Doubles and Strings don't have discrete steps:
//for fl in openFloatRange {
// print(fl)
//}
//for animal in openStringRange {
// print(animal)
//}
//: Now, let's do a simple Int switch.
//: Pay close attention to what happens here:
let someNumber = 3
switch someNumber {
case openIntRange:
//: It will get caught here. 3 is within both Ranges, so the first match gets it.
print("We has a match (open)! \(someNumber)")
case closedIntRange:
print("We has a match (closed)! \(someNumber)")
default:
print("No Match! \(someNumber)")
}
switch someNumber {
case closedIntRange:
//: First match.
print("We has a match (closed)! \(someNumber)")
case openIntRange:
print("We has a match (open)! \(someNumber)")
default:
print("No Match! \(someNumber)")
}
let someOtherNumber = 10
switch someOtherNumber {
case openIntRange:
//: 10 is not actually in this Range.
print("We has a match (open)! \(someOtherNumber)")
case closedIntRange:
//: It is in here.
print("We has a match (closed)! \(someOtherNumber)")
default:
print("No Match! \(someNumber)")
}
//: OK. Let's get weird.
var someString = "antelope"
switch someString {
case openStringRange:
print("We have a match! \(someString)")
default:
print("No Match! \(someString)")
}
someString = "monster"
switch someString {
case openStringRange:
print("We have a match! \(someString)")
default:
print("No Match! \(someString)")
}
someString = "zeb"
switch someString {
case openStringRange:
print("We have a match! \(someString)")
default:
print("No Match! \(someString)")
}
someString = "zebr"
switch someString {
case openStringRange:
print("We have a match! \(someString)")
default:
print("No Match! \(someString)")
}
someString = "zebra"
switch someString {
case openStringRange:
print("We have a match! \(someString)")
default:
print("No Match! \(someString)")
}
someString = "zebpa"
switch someString {
case openStringRange:
print("We have a match! \(someString)")
default:
print("No Match! \(someString)")
}
func nameSort(_ inName: String) -> String {
var ret = "No Match!"
switch inName {
case "a"..<"ab", "A"..<"AB":
ret = "Group 0"
case "ab"..<"ac", "AB"..<"AC":
ret = "Group 1"
case "ac"..<"ad", "AC"..<"AD":
ret = "Group 2"
case "ad"..<"ae", "AD"..<"AE":
ret = "Group 3"
case "ae"..<"af", "AE"..<"AF":
ret = "Group 4"
case "af"..<"ag", "AF"..<"AG":
ret = "Group 5"
case "ag"..<"ah", "AG"..<"AH":
ret = "Group 6"
case "ah"..<"ai", "AH"..<"AI":
ret = "Group 7"
case "ai"..<"ak", "AI"..<"AK":
ret = "Group 8"
case "ak"..<"al", "AK"..<"AL":
ret = "Group 9"
case "al"..<"am", "AL"..<"AM":
ret = "Group 10"
default:
break
}
return ret
}
print(nameSort("abby"))
print(nameSort("aiesha"))
print(nameSort("aeisha"))
print(nameSort("akbar"))
print(nameSort("andy"))
//: So this should work, right?
print(nameSort("Abby"))
//: Damn. No dice. How about this?
print(nameSort("ABBY"))
//: Well, won't bother fixing it. The best way is to force case on the string, and do a simple comparison. This is really just a demo.
let strider = stride(from: 10.0, to: -10.0, by: -3.25)
for aragorn in strider {
print(aragorn)
}
/*:
# SEQUENCES
This is about as simple as you'll get.
The data is stored internally in a Sequence (an Array of Int), so most of our work is passing the protocol into our storage Array.
*/
struct SimpleListy: Sequence {
typealias Element = Int
typealias Iterator = Array<Element>.Iterator
private var _myInts:[Element] = []
func makeIterator() -> SimpleListy.Iterator {
return self._myInts.makeIterator()
}
mutating func addInteger(_ inInt: Element) {
self._myInts.append(inInt)
}
}
var myIntBucket = SimpleListy()
myIntBucket.addInteger(10)
myIntBucket.addInteger(3)
myIntBucket.addInteger(1000)
myIntBucket.addInteger(21)
myIntBucket.addInteger(450)
myIntBucket.addInteger(105)
print("\nPrint the integers:\n")
for integ in myIntBucket {
print(integ)
}
/*:
The following code implements a simple sequence, where items are stored in a Dictionary, which has an unspecified sort order, but are iterated alphabetically, by first name.
An iterator is basically a struct or class with a "next()" method. How it gets there is why you may (or may not) want to do your own custom one.
This is a simple demo of why you may want a custom iterator. Say that you want an unorganized list to be iterated in an organized manner?
*/
//: First, we declare a crony type, with the important information.
typealias MyLittleCrony = (name: String, favorsOwed: Int, isRelative: Bool)
//: Next, we declare a Sequence struct, which will include a custom ordered iterator.
struct CronyList: Sequence {
//: This is a typealias for our internal Dictionary
typealias CronyListDictionary = [String:Element]
//: This is required by the protocol
typealias Element = MyLittleCrony
//: This is our internal opaque storage.
private var _cronyList: CronyListDictionary = [:]
//: This is the iterator we'll use. Note that it sorts the response, using the Dictionary keys.
struct Iterator : IteratorProtocol {
//: We capture a copy of the list here.
private let _iteratorList:CronyListDictionary
//: This is an array of Dictionary keys that we use to sort.
private let _keysArray:[String]
//: This is the current item index.
private var _index: Int
//: Just capture the main object at the time the iterator is made.
init(_ myLittleCronies: CronyListDictionary) {
self._iteratorList = myLittleCronies
self._keysArray = self._iteratorList.keys.sorted() // This sorts the iteration
self._index = 0
}
//: This is required by the protocol.
mutating func next() -> Element? {
//: We use the sorted keys array to extract our response.
if self._index < self._keysArray.count {
let ret = self._iteratorList[self._keysArray[self._index]]
self._index += 1
return ret
} else {
return nil
}
}
}
//: This is required by the protocol.
func makeIterator() -> CronyList.Iterator {
return Iterator(self._cronyList)
}
//: We are legion
var count: Int {
get {
return self._cronyList.count
}
}
//: This is simply a convenient way to cast our random Dictionary into an ordered Array.
var cronies: [Element] {
get {
var ret: [Element] = []
//: We use the iterator to order the Array.
for crony in self {
ret.append(crony)
}
return ret
}
}
//: This allows us to make believe we're an Array.
subscript(_ index: Int) -> Element {
return self.cronies[index]
}
//: This is just how we'll load up the list.
mutating func append(_ crony: Element) {
self._cronyList[crony.name] = crony
}
}
var myCronies = CronyList()
myCronies.append((name: "Babs", favorsOwed: 7, isRelative: true))
myCronies.append((name: "Zulinda", favorsOwed: 10, isRelative: true))
myCronies.append((name: "Adriaaaaaan!", favorsOwed: 3, isRelative: false))
myCronies.append((name: "Sue", favorsOwed: 14, isRelative: true))
myCronies.append((name: "Cross-Eyed Mary", favorsOwed: 14, isRelative: false))
myCronies.append((name: "Charmain", favorsOwed: 14, isRelative: false))
myCronies.append((name: "Lucrecia McEvil", favorsOwed: 2, isRelative: false))
print("\nWe have \(myCronies.count) cronies.\n")
print("\nFirst, this is the order in which the entries are stored in the Dictionary:\n")
print(myCronies)
print("\nNext, we iterate the Dictionary, using our custom iterator, which sorts alphabetically:\n")
for (name, favorsOwed, isRelative) in myCronies {
print("\(name) is owed \(favorsOwed) favors, and is \(isRelative ? "" : "not ")family.")
}
print("\nNext, we show how our accessor has sorted the contents of the Dictionary for us:\n")
print(myCronies.cronies)
print("\nFinally, we show how we can subscript our Sequence directly, and get it in order:\n")
for index in 0..<myCronies.count {
let (name, favorsOwed, isRelative) = myCronies[index]
print("myCronies[\(index)]:\(name) is owed \(favorsOwed) favors, and is \(isRelative ? "" : "not ")family.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment