KEMBAR78
NSCoder Swift - An Introduction to Swift | PDF
NSCoder Swift 
An introduction to Swift 
Andreas Blick - @aquarioverde 
October 18th, 2014
Swift
Swift 
• New programming language introduced by 
Apple on June 2014 at WWDC 
• Builds on the best of C and Objective-C, without 
the constraints of C compatibility 
• Development started by Chris Lattner, who also 
started the LLVM and Clang project
Swift 
• Safe programming patterns and modern 
features 
• Seamless access to all existing Cocoa 
frameworks 
• Mix-and-match interoperability with Objective-C
Playgrounds
Playgrounds 
• Tool / Feature for 
• learning 
• code development 
• experimentation
Playgrounds 
• Strings 
• Arrays & dictionaries 
• Color 
• Views 
• Images 
• … etc
var / let
Variables & Constants 
• Defined using var or let keyword 
• No need to define type 
• Swift can mostly infer the type 
• Can contain any unicode character 
var instrument: String = “guitar” 
let nrOfSongs = 42 
var trackLength = 3.23
Strings 
• Collection of characters that can be accessed easily 
• Seamlessly bridged: NSString API available 
• Mutability defined by assignment (var or let) 
• String interpolation for literals, variables and expressions 
let instrument = “guitar" 
! 
let music = "play (instrument)" 
let noise = "play " + instrument
Numbers 
• Integers are stored as Int or UInt 
• can be written as decimal, binary, octal, hexadecimal 
• have max and min properties: Int.max 
• Floating point numbers are stored as Float and Double 
• can be written as decimal and hexadecimal 
• Can contain underscores for readability: 7_777.77 
• Bool can be true or false
Arrays 
• Stores ordered list of multiple values 
• Arrays are type specific 
• Accessed using properties, methods or subscripting 
var numbers: Array<Int> = Array<Int>() 
var numbers: [Int] = [Int]() 
! 
numbers = [1,2,3] 
numbers = [Int](count:7,repeatedValue:0]
Dictionaries 
• Containers that store multiple key-value pairs of the 
same type 
• Accessed using properties, methods or subscripting 
• Assigning nil as a value removes it 
var numbers:Dictionary<Int,String> = Dictionary<Int,String>() 
! 
numbers = [1:“One”,2:“Two”,3:”Three”]
Tuples 
• Group multiple values of any type into a single compound value 
• Values can be of any type 
• Values can be accessed by index 
• Tuples can have named values 
let instrument = ("guitar", “strings") 
println(instrument.0) 
! 
let (name, family) = instrument 
! 
let instrument = (name: "guitar", family: "strings")
Loops 
• for-in loop to iterate using range 
• for-in loops to iterate over a collection 
• while / do-while loops if unknown number of iterations 
for ix in 1…5 { . . . } 
for var ix = 0; ix < 7; ix++ { . . . } 
for (key, value) in aDictionary { . . . } 
! 
while ix < 7 { . . . } 
do { . . . } while ix < 7
Conditionals 
• if / else for simple conditions 
• switch statements for complex conditions 
if number == 5 { . . . } 
! 
switch number { 
case 5: 
println(“5”) 
default: 
println(“default”) 
}
Conditionals - Switch 
• do not fall through (no break needed) 
• must be exhaustive (use default:) 
• support ranges: case 0…7: 
• support tuples: case (1, 2): 
• value binding: case (let x, 2): 
• can use where clause to check additional conditions 
case (let x, let y) where x == y:
Control Transfer Statements 
• continue 
• break 
• return 
• fallthrough 
• loops and switches can be labeled to be used with break and continue 
counter: for i in 1...7 { 
for j in 1...7 { 
println("(i) - (j)") 
if i == 3 { continue counter} 
} 
}
?
Optionals 
• Used where a value might be missing by adding a ? sign 
• Similar to nil in Objective-C, but can be used on any type 
• non optionals can not be nil 
• Optionals can implicitly be unwrapped by adding a ! sign (forced 
unwrapping) 
let instruments = ["guitar" : 6, "bass": 4, “ukulele”: “8”] 
let nrOfStrings: Int? = instruments[“guitar"] 
! 
if nrOfStrings != nil { 
println(nrOfStrings!) 
} else { . . . }
Optionals 
• Use optional binding to find out if an optional 
contains a value 
let instruments = ["guitar" : 6, "bass": 4, “ukulele”: “8”] 
let nrOfStrings: Int? = instruments[“guitar"] 
! 
if let nrOfStrings = instruments["guitar"] { 
println(nrOfStrings) 
} else { . . . } 
• Use optional chaining when a value might be nil 
let familyName = guitar.family?.name?
func
Functions 
• Functions are blocks of code that execute a specific task 
• Functions have parameters and can return values - default to void 
• Parameters can have default values 
• They should always be placed at the end of the list 
func play(instrument: String = "guitar") -> String { 
return "play (instrument)" 
} 
! 
play(instrument: "drums")
Functions 
• Parameter names are not required when calling a function 
that has no default values 
• Functions can return multiple values using tuples 
• Use # if external and internal parameter names are the same 
func play(instrument: String) -> (String, volume: Int) { 
return ("play (instrument)", 7) 
} 
play(“guitar").volume 
! 
func play(instrument instr: String) -> String { . . . } 
func play(#instrument: String) -> String { . . . }
Functions 
• Functions can take a variable number of 
arguments using variadic parameters 
• One function can only have one variadic 
parameter 
func playInstruments(instruments: String...) -> String { 
var play = "playing " 
for instrument in instruments { 
play += instrument + " " 
} 
return play 
} 
playInstruments("guitar", "drums", "sax")
Functions 
• Parameters are constants by default and can not be changed 
• They can be defined as variables using var 
• Variables parameters can only be modified within a function 
• To persist a variable outside a function the inout parameter is needed 
func swapTwoInts(inout a: Int, inout b: Int) { 
let tmpA = a 
a = b 
b = tmpA 
} 
var x = 3, y = 7 
swapTwoInts(&x, &y)
Functions 
• Functions have a specific function type that can be assigned to 
variables 
• They can be passed as parameters and returned by other 
functions 
• Functions can be nested 
func iPlay(player:(String) ->String, song:String) ->String { 
return player(song) 
} 
func playGuitar(song: String) -> String { 
return "playing (song) on guitar" 
} 
let playing = iPlay(playGuitar, "I sat by the ocean")
Closures 
{}
Closures 
• Closures are self-contained blocks of code that 
can be passed around 
• Functions are named closures! 
• Closures can capture and store references and 
constants from their surrounding context 
• Trailing closures can be used for readability 
• Closures start using the in keyword
Closures - Example 
let playMusic = { println("make some noise”) } 
! 
! 
! 
func repeat(count:Int, song:String, player:(String)->(String)) { 
for i in 0..<count { 
println(player(song)); 
} 
} 
repeat(5, "Even Flow") { 
(song: String) -> String in 
return "playing (song)" 
}
class
Classes 
• Classes are program code templates for creating 
objects, consisting of properties and methods 
• No need for interface/header files 
• No need to inherit from any other class 
• No abstract class support 
• Compare using the identity operator ===
Classes - Properties 
• Access to properties handled automatically 
• Properties do not have a corresponding instance 
variable 
• Every property needs to have a value assigned 
• Properties without a setter are read-only 
• Can have lazy stored properties (lazy) 
• Type properties are defined using class keyword
Classes - Example 
class Instrument { 
let type: String 
var isUsed: Bool = false 
init(type: String) { 
self.type = type 
} 
var info: String { 
get { 
return isUsed ? "used (type)" : "new (type)" 
} 
} 
} 
! 
let electricGuitar = Instrument(type: "guitar")
Classes - Properties 
• Property observers can respond to changes in a 
properties’ value (willSet & didSet) 
var isUsed: Bool = false { 
willSet(willBeUsed) { 
let msg = (self.isUsed ? "was" : "was not") + 
" used and “ + 
(willBeUsed ? "is used now" : "is not used now”) 
println(msg) 
} 
}
Classes - Methods 
• Methods are functions associated with a particular type 
• Subscripts are special methods (subscript) for 
accessing members. They can contain any number of 
input parameters of any type. 
class Instrument { 
func play() { 
println("make some noise") 
} 
subscript(ix: Int) -> String { 
return "model (ix)" 
} 
}
Initialization 
• The class needs to be completely initialized before 
accessing any property or method 
• Properties that are allowed to have no value can be 
declared optional using the ? sign 
• Property observers are NOT called on initialization 
• Every class must have at least one designated initialiser 
• Every class can have multiple convenience initializers 
• Deinitializers can be used for cleaning up
Initializer Chaining 
• Designated initializers must call a designated 
initializer from their immediate superclass 
• Convenience initializers must call another 
initializer available in the same class 
• Convenience initializers must ultimately end up 
calling a designated initializer
Initialization - Example 
class Instrument { 
let type: String 
var family: String? 
init(type: String) { 
self.type = type 
} 
convenience init(type: String, family: String) { 
self.init(type: type) 
self.family = family 
} 
deinit { 
println("instrument (type) destroyed") 
} 
} 
! 
let guitar = Instrument(type: “guitar")
Initialization 
• Closures can be used for initialising complex 
properties 
class Some { 
let some: String = { 
return "some" 
}() 
}
Inheritance 
• Classes can inherit methods, properties from other 
classes 
• Initializers are not inherited by default 
• Initialize own class properties before calling super.init 
• Overridden methods need override keyword 
• Methods marked as final can not be overridden 
• An entire class can be marked as final
Inheritance - Example 
class Guitar : Instrument { 
init() { 
super.init(type: "guitar") 
family = "Strings" 
} 
! 
override func play() { 
println("make some noise") 
} 
} 
! 
let guitar = Guitar()
struct
Structures 
• Structures are value types, they are always 
copied, not referenced 
• Structures can have initializers, methods and 
properties 
• Automatically generated memberwise initializer 
• Structures do not have deinitializers 
• Array and Dictionary are structures
Structures 
• All properties of constant structures are constant as 
well 
• Methods for modifying structure properties need 
the mutating keyword 
• Structures do not support inheritance 
• No type-casting 
• Type properties are defined using static keyword
Enumerations 
• An enumeration is a complete collection of items 
• Enumeration values are fully-fledged in their own 
• Enumerations are first-class types! 
• Enumerations can have methods & initialisers 
• Enumeration can have default raw values of the 
same type
Enumerations - Example 
enum Instrument: Int { 
case Guitar = 1, Base 
case Drums, Bongo, Trumpet, Saxophone 
func description() -> String { 
switch self { 
case .Guitar, .Base: 
return "Strings" 
case .Trumpet, .Saxophone: 
return "Wind" 
default: 
return "Unknown" 
} 
} 
} 
let instrument = Instrument.Trumpet 
instrument.description() 
instrument.toRaw() 
let unknown = Instrument.fromRaw(1)?.description()
Enumerations 
• Enumerations can have associated values 
enum Instrument { 
case Guitar(nrOfStrings: Int), Base 
case Drums, Bongo 
func description() -> String { 
switch self { 
case .Guitar(let nrOfStrings): 
return ("(nrOfStrings)-string guitar") 
default: 
return "Unknown" 
} 
} 
} 
let instrument = Instrument.Guitar(nrOfStrings: 6) 
instrument.description()
++
Extensions 
• Extensions add new functionality to existing classes, 
structures, … etc. 
They are similar to Categories in Objective-C 
• They can add methods, computed properties, initialisers 
but no stored properties 
extension Instrument { 
func rock() { 
println("(type) is rocking") 
} 
}
Protocols 
• A protocol defines methods and properties a class, struct 
or enumeration can or must adopt 
• Existing types can be extended to adopt a protocol 
• A protocol can inherit from one or more other protocols 
• Protocol composition can be used to combine protocols 
• Any protocol can be used as type 
• Define as @objc for type checking and optional support
Protocols - Example 
@objc protocol Instrument { 
func play() 
} 
! 
class Guitar: Instrument { 
func play() { 
println("playing guitar") 
} 
} 
! 
let items = [Guitar(), NSString()] 
! 
for item in items { 
if let instrument = item as? Instrument { 
instrument.play() 
} 
}
Generics 
• Generic code enables writing flexible, reusable 
functions and types 
func swapTwoValues<T>(inout a: T, inout b: T) { 
let tmpA = a 
a = b 
b = a 
}
Generics 
• You can define your own generic type 
• and specify type constraints 
struct Stack<T: Instrument> { 
var items = [T]() 
mutating func push(item: T) { 
items.append(item) 
} 
mutating func pop() { 
items.removeLast() 
} 
} 
var guitarStack = Stack<Guitar>()
References 
• WWDC 2014 Videos 
• Apple Books 
• The Swift Programming Language 
• Using Swift with Cocoa & Objective-C 
• Web 
• https://developer.apple.com/swift/
+++
ARC 
• Swift handles memory management using 
automatic reference counting 
• Think about relationship between object instead 
of memory 
• Avoid strong reference cycles by using weak or 
unowned references
Assertions 
• Use assertions whenever a condition has the 
potential to be false, but must be true 
• End code execution 
let age = -3 
assert(age >= 0, "age can no be less than 0")
Thank you! 
Andreas Blick - @aquarioverde

NSCoder Swift - An Introduction to Swift

  • 1.
    NSCoder Swift Anintroduction to Swift Andreas Blick - @aquarioverde October 18th, 2014
  • 2.
  • 3.
    Swift • Newprogramming language introduced by Apple on June 2014 at WWDC • Builds on the best of C and Objective-C, without the constraints of C compatibility • Development started by Chris Lattner, who also started the LLVM and Clang project
  • 4.
    Swift • Safeprogramming patterns and modern features • Seamless access to all existing Cocoa frameworks • Mix-and-match interoperability with Objective-C
  • 5.
  • 6.
    Playgrounds • Tool/ Feature for • learning • code development • experimentation
  • 7.
    Playgrounds • Strings • Arrays & dictionaries • Color • Views • Images • … etc
  • 8.
  • 9.
    Variables & Constants • Defined using var or let keyword • No need to define type • Swift can mostly infer the type • Can contain any unicode character var instrument: String = “guitar” let nrOfSongs = 42 var trackLength = 3.23
  • 10.
    Strings • Collectionof characters that can be accessed easily • Seamlessly bridged: NSString API available • Mutability defined by assignment (var or let) • String interpolation for literals, variables and expressions let instrument = “guitar" ! let music = "play (instrument)" let noise = "play " + instrument
  • 11.
    Numbers • Integersare stored as Int or UInt • can be written as decimal, binary, octal, hexadecimal • have max and min properties: Int.max • Floating point numbers are stored as Float and Double • can be written as decimal and hexadecimal • Can contain underscores for readability: 7_777.77 • Bool can be true or false
  • 12.
    Arrays • Storesordered list of multiple values • Arrays are type specific • Accessed using properties, methods or subscripting var numbers: Array<Int> = Array<Int>() var numbers: [Int] = [Int]() ! numbers = [1,2,3] numbers = [Int](count:7,repeatedValue:0]
  • 13.
    Dictionaries • Containersthat store multiple key-value pairs of the same type • Accessed using properties, methods or subscripting • Assigning nil as a value removes it var numbers:Dictionary<Int,String> = Dictionary<Int,String>() ! numbers = [1:“One”,2:“Two”,3:”Three”]
  • 14.
    Tuples • Groupmultiple values of any type into a single compound value • Values can be of any type • Values can be accessed by index • Tuples can have named values let instrument = ("guitar", “strings") println(instrument.0) ! let (name, family) = instrument ! let instrument = (name: "guitar", family: "strings")
  • 15.
    Loops • for-inloop to iterate using range • for-in loops to iterate over a collection • while / do-while loops if unknown number of iterations for ix in 1…5 { . . . } for var ix = 0; ix < 7; ix++ { . . . } for (key, value) in aDictionary { . . . } ! while ix < 7 { . . . } do { . . . } while ix < 7
  • 16.
    Conditionals • if/ else for simple conditions • switch statements for complex conditions if number == 5 { . . . } ! switch number { case 5: println(“5”) default: println(“default”) }
  • 17.
    Conditionals - Switch • do not fall through (no break needed) • must be exhaustive (use default:) • support ranges: case 0…7: • support tuples: case (1, 2): • value binding: case (let x, 2): • can use where clause to check additional conditions case (let x, let y) where x == y:
  • 18.
    Control Transfer Statements • continue • break • return • fallthrough • loops and switches can be labeled to be used with break and continue counter: for i in 1...7 { for j in 1...7 { println("(i) - (j)") if i == 3 { continue counter} } }
  • 19.
  • 20.
    Optionals • Usedwhere a value might be missing by adding a ? sign • Similar to nil in Objective-C, but can be used on any type • non optionals can not be nil • Optionals can implicitly be unwrapped by adding a ! sign (forced unwrapping) let instruments = ["guitar" : 6, "bass": 4, “ukulele”: “8”] let nrOfStrings: Int? = instruments[“guitar"] ! if nrOfStrings != nil { println(nrOfStrings!) } else { . . . }
  • 21.
    Optionals • Useoptional binding to find out if an optional contains a value let instruments = ["guitar" : 6, "bass": 4, “ukulele”: “8”] let nrOfStrings: Int? = instruments[“guitar"] ! if let nrOfStrings = instruments["guitar"] { println(nrOfStrings) } else { . . . } • Use optional chaining when a value might be nil let familyName = guitar.family?.name?
  • 22.
  • 23.
    Functions • Functionsare blocks of code that execute a specific task • Functions have parameters and can return values - default to void • Parameters can have default values • They should always be placed at the end of the list func play(instrument: String = "guitar") -> String { return "play (instrument)" } ! play(instrument: "drums")
  • 24.
    Functions • Parameternames are not required when calling a function that has no default values • Functions can return multiple values using tuples • Use # if external and internal parameter names are the same func play(instrument: String) -> (String, volume: Int) { return ("play (instrument)", 7) } play(“guitar").volume ! func play(instrument instr: String) -> String { . . . } func play(#instrument: String) -> String { . . . }
  • 25.
    Functions • Functionscan take a variable number of arguments using variadic parameters • One function can only have one variadic parameter func playInstruments(instruments: String...) -> String { var play = "playing " for instrument in instruments { play += instrument + " " } return play } playInstruments("guitar", "drums", "sax")
  • 26.
    Functions • Parametersare constants by default and can not be changed • They can be defined as variables using var • Variables parameters can only be modified within a function • To persist a variable outside a function the inout parameter is needed func swapTwoInts(inout a: Int, inout b: Int) { let tmpA = a a = b b = tmpA } var x = 3, y = 7 swapTwoInts(&x, &y)
  • 27.
    Functions • Functionshave a specific function type that can be assigned to variables • They can be passed as parameters and returned by other functions • Functions can be nested func iPlay(player:(String) ->String, song:String) ->String { return player(song) } func playGuitar(song: String) -> String { return "playing (song) on guitar" } let playing = iPlay(playGuitar, "I sat by the ocean")
  • 28.
  • 29.
    Closures • Closuresare self-contained blocks of code that can be passed around • Functions are named closures! • Closures can capture and store references and constants from their surrounding context • Trailing closures can be used for readability • Closures start using the in keyword
  • 30.
    Closures - Example let playMusic = { println("make some noise”) } ! ! ! func repeat(count:Int, song:String, player:(String)->(String)) { for i in 0..<count { println(player(song)); } } repeat(5, "Even Flow") { (song: String) -> String in return "playing (song)" }
  • 31.
  • 32.
    Classes • Classesare program code templates for creating objects, consisting of properties and methods • No need for interface/header files • No need to inherit from any other class • No abstract class support • Compare using the identity operator ===
  • 33.
    Classes - Properties • Access to properties handled automatically • Properties do not have a corresponding instance variable • Every property needs to have a value assigned • Properties without a setter are read-only • Can have lazy stored properties (lazy) • Type properties are defined using class keyword
  • 34.
    Classes - Example class Instrument { let type: String var isUsed: Bool = false init(type: String) { self.type = type } var info: String { get { return isUsed ? "used (type)" : "new (type)" } } } ! let electricGuitar = Instrument(type: "guitar")
  • 35.
    Classes - Properties • Property observers can respond to changes in a properties’ value (willSet & didSet) var isUsed: Bool = false { willSet(willBeUsed) { let msg = (self.isUsed ? "was" : "was not") + " used and “ + (willBeUsed ? "is used now" : "is not used now”) println(msg) } }
  • 36.
    Classes - Methods • Methods are functions associated with a particular type • Subscripts are special methods (subscript) for accessing members. They can contain any number of input parameters of any type. class Instrument { func play() { println("make some noise") } subscript(ix: Int) -> String { return "model (ix)" } }
  • 37.
    Initialization • Theclass needs to be completely initialized before accessing any property or method • Properties that are allowed to have no value can be declared optional using the ? sign • Property observers are NOT called on initialization • Every class must have at least one designated initialiser • Every class can have multiple convenience initializers • Deinitializers can be used for cleaning up
  • 38.
    Initializer Chaining •Designated initializers must call a designated initializer from their immediate superclass • Convenience initializers must call another initializer available in the same class • Convenience initializers must ultimately end up calling a designated initializer
  • 39.
    Initialization - Example class Instrument { let type: String var family: String? init(type: String) { self.type = type } convenience init(type: String, family: String) { self.init(type: type) self.family = family } deinit { println("instrument (type) destroyed") } } ! let guitar = Instrument(type: “guitar")
  • 40.
    Initialization • Closurescan be used for initialising complex properties class Some { let some: String = { return "some" }() }
  • 41.
    Inheritance • Classescan inherit methods, properties from other classes • Initializers are not inherited by default • Initialize own class properties before calling super.init • Overridden methods need override keyword • Methods marked as final can not be overridden • An entire class can be marked as final
  • 42.
    Inheritance - Example class Guitar : Instrument { init() { super.init(type: "guitar") family = "Strings" } ! override func play() { println("make some noise") } } ! let guitar = Guitar()
  • 43.
  • 44.
    Structures • Structuresare value types, they are always copied, not referenced • Structures can have initializers, methods and properties • Automatically generated memberwise initializer • Structures do not have deinitializers • Array and Dictionary are structures
  • 45.
    Structures • Allproperties of constant structures are constant as well • Methods for modifying structure properties need the mutating keyword • Structures do not support inheritance • No type-casting • Type properties are defined using static keyword
  • 46.
    Enumerations • Anenumeration is a complete collection of items • Enumeration values are fully-fledged in their own • Enumerations are first-class types! • Enumerations can have methods & initialisers • Enumeration can have default raw values of the same type
  • 47.
    Enumerations - Example enum Instrument: Int { case Guitar = 1, Base case Drums, Bongo, Trumpet, Saxophone func description() -> String { switch self { case .Guitar, .Base: return "Strings" case .Trumpet, .Saxophone: return "Wind" default: return "Unknown" } } } let instrument = Instrument.Trumpet instrument.description() instrument.toRaw() let unknown = Instrument.fromRaw(1)?.description()
  • 48.
    Enumerations • Enumerationscan have associated values enum Instrument { case Guitar(nrOfStrings: Int), Base case Drums, Bongo func description() -> String { switch self { case .Guitar(let nrOfStrings): return ("(nrOfStrings)-string guitar") default: return "Unknown" } } } let instrument = Instrument.Guitar(nrOfStrings: 6) instrument.description()
  • 49.
  • 50.
    Extensions • Extensionsadd new functionality to existing classes, structures, … etc. They are similar to Categories in Objective-C • They can add methods, computed properties, initialisers but no stored properties extension Instrument { func rock() { println("(type) is rocking") } }
  • 51.
    Protocols • Aprotocol defines methods and properties a class, struct or enumeration can or must adopt • Existing types can be extended to adopt a protocol • A protocol can inherit from one or more other protocols • Protocol composition can be used to combine protocols • Any protocol can be used as type • Define as @objc for type checking and optional support
  • 52.
    Protocols - Example @objc protocol Instrument { func play() } ! class Guitar: Instrument { func play() { println("playing guitar") } } ! let items = [Guitar(), NSString()] ! for item in items { if let instrument = item as? Instrument { instrument.play() } }
  • 53.
    Generics • Genericcode enables writing flexible, reusable functions and types func swapTwoValues<T>(inout a: T, inout b: T) { let tmpA = a a = b b = a }
  • 54.
    Generics • Youcan define your own generic type • and specify type constraints struct Stack<T: Instrument> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() { items.removeLast() } } var guitarStack = Stack<Guitar>()
  • 55.
    References • WWDC2014 Videos • Apple Books • The Swift Programming Language • Using Swift with Cocoa & Objective-C • Web • https://developer.apple.com/swift/
  • 56.
  • 57.
    ARC • Swifthandles memory management using automatic reference counting • Think about relationship between object instead of memory • Avoid strong reference cycles by using weak or unowned references
  • 58.
    Assertions • Useassertions whenever a condition has the potential to be false, but must be true • End code execution let age = -3 assert(age >= 0, "age can no be less than 0")
  • 59.
    Thank you! AndreasBlick - @aquarioverde