@ALL NOTES OR PROGRAM ARE COLLECTED FROM SOLOLEARN
Welcome to Swift!
Swift combines the best of C and Objective-C.
It's ideal for creating new apps and works seamlessly with Objective-C, so it's compatible with apps previously created in that language.
Swift is ready for your next iOS and OSX project - or for addition into your current app - because Swift code works side-by-side with Objective-C.
We will be covering Swift version 4 in this course.
"Hello World"
Printing text is easy:
To print the value of a variable inside a text, place it in parentheses, and insert a backslash just prior to the opening parenthesis:
You do not need the backslash for printing only the variable value:
Use of the semicolon (;) following each statement is fully optional in Swift, unless you wish to place several independent statements on a single line, in which case the semi-colon is required.
Comments
The Swift compiler ignores comments, which are used to include non-executable text in your code that may be used as a reminder or note-to-self.
A single-line comment opens with two forward-slashes (//):// this is a comment
A multiline comment begins with a single forward-slash, followed by an asterisk (/*). It concludes with an asterisk, then a forward-slash (*/):/* this is also a comment,
but written over multiple lines */
Multiline comments in Swift can be nested within other multiline comments.
Constants and Variables
Constants and variables are used to associate a name (yourName or welcomeMessage) with a value (the number 42 or the string "Hi!").
A constant has a set value that cannot be changed; the value of a variable can be changed.
The keyword var is used to declare a variable.
The following example declares a variable, "a", and assigns its value as "42".
It is possible to change variable values over time:
Multiple variables can be declared on a single line, separated with commas.
Constants
Declare a constant using the let keyword.
This example declares a constant named "one" and assigns it a value of 1:
Declare multiple constants on a single line and separate them with commas:
The value of a constant can never be changed. Trying to assign a new value to a constant results in an error.
Constant and Variable Names
Constant and variable names can contain almost any character, including Unicode characters:
However, constant and variable names must have no blank spaces, mathematical symbols, arrows, private-use (or invalid) Unicode code points, or line and box-drawing characters. Numbers can appear anywhere within a name, except for at the beginning.
Constants or variables of a certain type can't be declared again with the same name, nor can they be altered to store values of differing types. Also, a constant cannot be made a variable, nor can a variable be made a constant.
Type Annotations
Type annotations ensure that your code is clear about the value stored within your constant or variable. Swift's basic types include:
Int: Integers
Double and Float: Floating-Point Values
Bool: Boolean Values
String: Textual Data.
Add a type annotation by placing a colon (:) after the constant or variable name, then add a space, and then add the type name, as follows:
This example provides a type annotation for a variable called "welcomeMsg", that indicates that the variable can store String values.
It's possible to define multiple related variables of the same type. Include them all on a single line, separated by commas, then add a single type annotation after the final variable name:
var red, green, blue: Double
Type Annotations
In practice, you will rarely need to add type annotations. Providing an initial value for a constant or a variable at the point at which it is defined, will almost always be sufficient for Swift to infer which type should be used:
Swift always chooses Double (as opposed to Float) when inferring the type for floating-point numbers.
Swift is a type safe language, meaning that it supports clarity when specifying value types for code. When part of your code expects a String, you can't pass it an Int by mistake.
Basic Operators
An operator is a special symbol or phrase used to check, change, or combine values.
Operators are unary, binary, or ternary:
-Unary Operator: Has a single target (-a). A unary prefix operator is placed before the target (!b).
-Binary Operator: Has two targets (4 + 5) and is infixed, appearing between the two targets.
-Ternary Operator: Has three targets. Like C, Swift has one ternary operator, the ternary conditional operator (a ? b : c).
The values targeted by operators are called operands. In the expression 1 + 2, the + symbol is a binary operator; its two operands are the values 1 and 2.
Assignment Operator
The assignment operator (a = b) initializes or updates the value of a with the value of b:
Arithmetic Operators
Swift supports the four standard arithmetic operators for all number types:
Addition (+)
Subtraction (-)
Multiplication (*)
Division (/)
The addition operator is also supported for String concatenation:
Tap Try It Yourself to play around with the code!
Remainder Operator
The remainder operator (a % b) calculates the number of multiples of b that fit within a, and returns the value that is left over, or the remainder.
In other languages, the remainder operator (%) is called a modulo operator. In Swift, however, its behavior for negative numbers means that it is, strictly speaking, a remainder, rather than a modulo operation.
Compound Operators
Like C, Swift provides compound operators that combine assignment (=) with another operation.
The expression a += 2 is shorthand for a = a + 2. The addition and the assignment are combined into one operator that performs both tasks at the same time.
Similarly, there are shorthand operators for other arithmetic operations.
Comparison Operators
Swift supports all of the standard comparison operators in C:
Equal to (a == b)
Not equal to (a != b)
Greater than (a > b)
Less than (a < b)
Greater than or equal to (a >= b)
Less than or equal to (a <= b)
Each of the comparison operators returns a Bool value indicating whether or not the statement is true:
Swift also provides two identity operators, === and !==, which test whether two object references both refer to the same object instance.
Ternary Conditional Operator
The ternary conditional operator is a special operator with three parts, taking the form (question ? answer1 : answer2).
This operator acts as a shortcut in evaluating one of two expressions, based on whether the question is true or false. For a true question, it evaluates answer1 and returns its value; otherwise, it evaluates answer2 and returns its value.
The example above will evaluate the expression gender == 0. If true, it prints "male". Otherwise, it prints "female".
Range Operators
Swift offers two range operators, which are shortcuts for expressing a range of values.
The closed range operator (a...b) defines a range running from a to b, and includes the values a and b. The value of a must not be greater than that of b.
The half-open range operator (a..<b) defines a range that runs from a to b, but does not include b. It is said to be half-open because it contains its first value, but not its final value. As with the closed range operator, the value of a must not be greater than that of b.
You will understand the functions of range operators in the upcoming lessons.
Logical Operators
Logical operators modify or combine the Boolean logic values true and false. Swift supports the three standard logical operators found in C-based languages:
Logical NOT operator (!a): Inverts a Boolean value so that true becomes false and false becomes true.
Logical AND operator (a && b): Creates logical expressions in which both values must be true for the overall expression to be true.
Logical OR operator (a || b): An infixed operator made from two adjacent pipe characters. It creates logical expressions in which only one of the two values has to be true for the overall expression to be true.
Multiple logical operators can be combined to create longer compound expressions.
The logical operators && and || are left-associative, meaning that compound expressions with multiple logical operators evaluate the leftmost subexpression before the right.
Optionals
Optionals are used in situations in which a value may be absent.
An optional says:
-There is a value, and it equals x
or
-There isn't a value at all
An optional Int is written as Int?, not Int. The question mark indicates that the value contained within is optional, meaning that it might contain some Int value, or it might contain no value at all.
It can't contain anything else, such as a Bool value or a String value. It's either an Int, or it's nothing at all.
nil
You set an optional variable to a valueless state by assigning it the special value nil:
An optional variable with no default value is automatically set to nil for you:
nil cannot be used with non-optional constants and variables. If your code contains a constant or variable that needs to work with the absence of a value under certain conditions, always declare it as an optional value of the appropriate type.
Conditional Statements
A conditional statement executes certain code under certain conditions. For example, you can run a particular code when an error occurs, or display a message when a value exceeds a certain baseline. To set conditions, use if or switch statements.
The if Statement
The most basic if statement contains a single if condition, and executes a set of statements only if that condition is true:
You can specify additional conditions by chaining together multiple if statements.
You can add as many else-if statements as needed.
The switch Statement
Use the switch statement as an alternative to the if statement for multiple potential states. The switch statement compares a value with several possible matching patterns, executing a block of code using the first matching pattern.
Each case begins with the keyword case:
A single case can contain multiple values, as in our example above. It can also contain ranges, using the range operators.
Every switch statement must be exhaustive, i.e. take every possible value into consideration. In cases in which it is not appropriate to provide a switch case for every possible value, you can define a default catch-all case to cover any values that are not explicitly addressed. Indicate the catch-all case by using the keyword default. This always appears last.
Swift doesn't require break statements, but will still accept one to match and ignore a particular case, or to break out of a matched case before that case has completed its execution.
Where
The where clause checks for additional conditions.
The three switch cases declare placeholder constants x and y, which temporarily take on the two values from myPoint, creating a dynamic filter as part of a where clause. The switch case matches the current value of point only if the where clause's condition evaluates to true for that value.
The final case matches all possible remaining values; a default case is not necessary to have an exhaustive switch statement.
The while Loop
A while loop performs a set of statements until a condition becomes false. These kinds of loops are best used when the number of iterations is not known before the first iteration begins.
while evaluates its condition at the start of each pass through the loop.
The while loop is demonstrated in the example below:
The code will execute until the a+=1 statement renders a < b as false.
Repeat-While
The repeat-while loop is the alternate while loop. It first makes a single pass through the loop block, then considers the loop's condition, and repeats the loop until the condition shows as false.
Swift's repeat-while loop is similar to a do-while loop in other languages.
The for-in Loop
Use the for-in loop to iterate over a sequence, such as ranges of numbers, items in an array, or characters in a string.
The following example prints the first few entries in the five-times-table:
The index variable is set at the first value in the range (1). The statements within the for loop are then executed in sequence, through the final item in the range (5).
Control Transfer
Control transfer statements alter the code execution by transferring control from one piece of code to another. Swift's four control transfer statements are continue, break, fallthrough, and return (which will be discussed in the upcoming lessons).
Continue
The continue statement stops the loop, then restarts it at the beginning of its next cycle.
The example below shows how to use the continue statement to skip over even numbers.
A for loop with a condition and an incrementer still evaluates the incrementer after the continue statement is initiated. The loop itself continues to work as usual; only the code within the loop's body is skipped.
Break
Use the break statement to immediately end the execution of an entire control flow statement. Also, the break statement is used within a switch statement or a loop statement to terminate its execution sooner than would otherwise be the case.
Break in a Loop Statement
When a break statement is used within a loop statement, the loop's execution immediately stops. Control transfers to the first line of code following the loop's closing brace (}). The current iteration's remaining code is skipped, and no further iterations of the loop are initiated.
For example, you can have a loop that breaks out when the value of a becomes less than that of b:
Break in a Switch Statement
A break causes a switch statement to end its execution immediately, and transfers control to the first line of code that follows the switch statement's closing brace (}).
This example breaks out of the switch statement as soon as the default case is matched.
Always use a break statement to ignore a switch case.
Fallthrough
In Swift, switch statements do not fall through the bottom of each case into the next. Instead, the entire switch statement completes its execution when the first matching case is completed.
By contrast, C requires insertion of an explicit break statement at the end of every switch case to prevent fallthrough. By eliminating default fallthrough, Swift allows for more concise and predictable switch statements in comparison with C, and thus avoids inadvertently executing multiple switch cases.
In cases that require C-style fallthrough behavior, use the fallthrough keyword on a case-by-case basis. The example below uses fallthrough to create a number's textual description.
This prints "The number 5 is a prime number, and also an integer."
If myInt's value is one of the prime numbers in the list, text noting that the number is prime is appended to the end of the description. The fallthrough keyword then causes it to "fall into" the default case.
The fallthrough keyword does not check case conditions in the switch case into which execution falls. As with C’s standard switch statement behavior, the fallthrough keyword moves code execution directly to the statements inside the next (or default) case block.
Strings
A string is an ordered collection of characters, such as "Hello, World" or "SoloLearn". Swift strings are represented by the String type, which in turn represents a collection of Character type values.
Predefined String values can be included within code as string literals, or fixed sequences of textual characters within double quotation marks (""). Use a string literal as an initial value for a constant or variable.
Because it is initialized with a string literal value, Swift infers a type of String for the someString constant.
Empty Strings
An empty String value can be created as the starting point for a longer string. To do this, either assign an empty string literal to a variable or initialize a new String instance using initializer syntax:
Both strings are empty and equivalent to each other.
Determine whether a String value is empty by checking its Boolean isEmpty property:
Tap Try It Yourself to play around with the code!
Concatenation
String values can be added together (or concatenated) with the addition operator (+) to create a new String value:
The addition assignment operator (+=) appends a String value to an existing String variable.
Tap Try It Yourself to play around with the code!
String Interpolation
String interpolation includes the values of a mix of constants, variables, literals, and expressions inside a string literal to form a new String value. Prefix each item with a backslash, place the item in parentheses, and insert it into the string literal.
In the above example, the multiplier value is inserted into the string literal as \(mult). When the string interpolation is evaluated prior to creating the actual string, this placeholder is replaced with the actual value of mult.
Later in the string, the value of mult appears within a larger expression within the string literal: \(Double(mult) * 1.5). The expression calculates the value of Double(mult) * 1.5 and then inserts the result (6) into the string.
The expressions appearing inside of parentheses within an interpolated string cannot contain an unescaped double quote ("), backslash (\), carriage return, or line feed.
Counting Characters
To retrieve a count of the Character values in a string, use the count property of the string:
When using the characters property, the character count does not always match the length property of an NSString containing the same characters. The length of an NSString is based on the number of 16-bit code units within the string's UTF-16 representation, as opposed to the number of Unicode extended grapheme clusters within the string.
Comparing Strings
Swift offers three options for comparing textual values: string and character equality, prefix equality, and suffix equality.
Use the "equal to" operator (==) and the "not equal to" operator (!=) to determine string and character equality.
Use the string's hasPrefix and hasSuffix methods to determine whether a string has a particular string prefix or suffix. Both methods take a single argument of type String and return a Boolean value.
Arrays
An array is an ordered list of values of the same type, in which the same value can appear multiple times at different positions. In Swift, the array type can be written in full as Array<T>, in which T represents which value type the array is allowed to store. The array type can also be expressed in shorthand form, as [T].
Although the two forms are identical in function, the shorthand will appear throughout this tutorial in reference to an array type.
Creating an Empty Array
Create an empty array of a certain type using initializer syntax.
Note that the type of the someInts variable is inferred to be [Int],
from the type of the initializer.
Array with a Default Value
Swift's Array type also provides an initializer for creating an array of a certain size with all of its values set to the same default value. You pass this initializer the number of items to be added to the new array (called count) and a default value of the appropriate type (called repeating):
fourDoubles is of type [Double], and equals [0.0, 0.0, 0.0, 0.0].
Array Literal
Using an array literal is another way to initialize an array. The array literal is shorthand for one or more values written as an array collection, and is written as a list of values, separated by commas, with square brackets at beginning and end.[value 1, value 2, value 3]
The example below creates an array called shoppingList, for storing String values:
This particular array can store only String values, as it has String specified as its value type.
Because of Swift's type inference, you don't have to write out the array type. Be sure to initialize with an array literal containing values of that same type. The initialization of shoppingList could have been written in a shorter form:
All values in the array literal are of the same type, enabling Swift to infer that [String] is the correct type for the shoppingList variable.
Combining two existing arrays with compatible types using the addition operator (+) allows you to create a new array. Swift infers the new array's type based on the type of the two combined arrays.
Accessing and Modifying an Array
Access and modify an array through its methods and properties or by using subscript syntax.
An array's read-only count property provides the number of items in an array.
Use the Boolean isEmpty property as a shortcut when you want to know whether the count property is equal to 0.
Tap Try It Yourself to play around with the code!
Modifying an Array
An array's append method allows you to add a new item at the array's end.
Alternatively, add an array of one or more compatible items using the addition assignment operator (+=):
Accessing an Array
Using subscript syntax, you can retrieve a value from the array, inserting the index of the value you want to retrieve within square brackets immediately after the name of the array:
Arrays in Swift are always zero-indexed, meaning that the first item's index is 0, rather than 1, as you might expect.
Accessing or modifying a value for an index that is outside of an array's existing bounds triggers a runtime error. Check the validity of an index prior to using it by comparing it with the array's count property.
Modifying an Array
Use subscript syntax to change an existing value at a given index:
Subscript syntax also changes a range of values all at once. This will even work with a replacement set of values with a length that is different from the original range.
In the following example, the elements with index 1, 2, 3 are replaced with two new values.
Don't use subscript syntax to append a new item to an array.
Modifying an Array
An array's insert method will insert an item into the array at a specified index.
"Syrup" is now the first item in the list.
Similarly, the remove method allows you to remove an item from the array. This method removes the item at the specified index, and also returns the removed item. Note that the returned value can be ignored if it is not needed.
When an item is removed from an array, Swift closes any gaps that have been created.
If you want to remove the final item from an array, use the removeLast() method rather than the removeAtIndex method to avoid the need to query the array's count property:
Tap Try It Yourself to play around with the code!
Iterating Over an Array
The for-in loop allows you to iterate over the entire set of values in an array.
Alternatively, use the enumerated() method to iterate over an array when you need the integer index for each item in addition to its value. This returns a tuple for each item in the array that indicates that item's index and value. You can decompose the tuple into temporary constants or variables as part of the iteration:
This will print the index and the value of the elements in the array.
Tuples will be discussed in the upcoming lessons.
Sets
A set stores distinct values of the same type in a collection with no defined ordering. Sets are used as an alternative to arrays when item order is not a concern or when you need to ensure that an item appears only once.
For a Swift set, write the type as Set<T> where T is the type that the set is allowed to store. Unlike arrays, there is no equivalent shorthand for sets.
You can create an empty set of a certain type using initializer syntax:
Based on the initializer type, Swift infers the type of the letters to be Set<Character>.
An array literal will also work as shorthand when initializing a set with one or more values as a set collection.
When initializing the type of set with an array literal that contains values of the same type, it is not necessary to write the type of set. The initialization could have been written in a shorter form:
Because all values in the array literal are of the same type, Swift infers that Set<String> is the correct type to use for the names variable.
Accessing and Modifying a Set
The count and isEmpty properties work the same way with a set as they do with an array.
Calling the set's insert method adds a new item to a set.
You can remove an item from a set by calling the set's remove method. The item is removed if it's a member of the set, and the removed value is returned. It returns nil if the item is not contained in the set. Alternatively, use the set's removeAll() method to remove all of the items in a set.
The contains method tells you whether or not a particular item is present in the set.
Tap Try It Yourself to play around with the code!
Iterating Over a Set
You can iterate over the values in a set with a for-in loop.
Since Swift's Set type does not provide defined ordering, use the sorted() method to iterate over the values of a set in a specific order.
Tap Try It Yourself to play around with the code!
Set Operations
Swift allows you to efficiently perform fundamental set operations, such as combining sets, determining which values two sets have in common, or determining whether two sets contain all, some, or none of the same values.
Fundamental Set Operations
The illustration below depicts sets a and b, and shows the results of various set operations, as represented by the shaded regions:
The intersection method creates a new set, with only the values common to both sets.
The symmetricDifference method creates a new set with values in either set, but not both.
The union method creates a new set with all of the values in both sets.
The subtracting method creates a new set with values not in the specified set.
For example, to combine the two sets:
Tap Try It Yourself to play around with the code!
Set Membership and Equality
The illustration below depicts three sets: a, b, and c. The overlapping regions represent the elements that are shared among sets.
Set a is a superset of set b, because a contains all elements in b.
Conversely, set b is a subset of set a, because all elements in b are also contained by a.
Sets b and c are disjointed with one another, because they share no elements in common.
"is equal" operator (==): Determines whether two sets contain all of the same values.
isSubset(of: ) method: Determines whether all of the values of a set are contained in the specified set.
isSuperset(of: ) method: Determines whether a set contains all of the values in a specified set.
isStrictSubset(of: ) or sStrictSuperset(of: ) method: Determines whether a set is a subset or superset of, but not equal to, a specified set.
isDisjoint(with: ) method: determines whether two sets have any values in common.
Spend a little time playing with the methods to learn and understand their functions.
Dictionaries
A dictionary stores associations between keys of the same type and values of the same type, in a collection with no defined ordering. Each value is associated with a unique key, which acts as an identifier for that value within the dictionary. A dictionary is used to look up values based on their identifiers, in much the same way that a real-world dictionary is used to look up the definition of a particular word.
Written in its entirety, a Swift dictionary's type is Dictionary<Key, Value>. Key indicates which type of value can be used as a dictionary key, and Value indicates which type of value the dictionary stores for those keys. The shorthand form for the type of a dictionary is [Key: Value].
As with arrays, initializer syntax is used to create an empty Dictionary of a specified type:var airports = [Int: String]()
In the dictionary airports, the keys are of type Int, and the values are of type String.
The Dictionary Literal
A dictionary literal provides a way to write in shorthand one or more key-value pairs as a Dictionary collection.
The key and value in each key-value pair are separated by a colon. The key-value pairs are written as a list, separated by commas, surrounded by a pair of square brackets.
The example below creates a dictionary in which the keys are three-letter codes, and the values are airport names:
As with arrays, it isn't necessary to write the type of the dictionary when initializing it with a dictionary literal whose keys and values have consistent types. The initialization of airports could have been written in a shorter form instead.
Because all keys and values in the literal share the same type, Swift can infer that [String: String] is the correct type to use for the airports dictionary.
Accessing and Modifying a Dictionary
The count and isEmpty properties also work for the dictionary.
Add a new item to a dictionary using subscript syntax:
Subscript syntax can be used to change the value associated with a particular key:
Use a dictionary's updateValue method as an alternative to subscripting when setting or updating a key's value. The updateValue method returns the old value after performing an update:
Subscript syntax is also used to retrieve a value for a particular key from the dictionary.
If the value for the requested key does not exist, Swift returns a value of nil.
Use subscript syntax to assign a value of nil to a key in order to remove a key-value pair from a dictionary.
Alternatively, the removeValue(forKey: ) method removes a key-value pair from a dictionary, if the pair exists, and returns the removed value. nil is returned if no value exists.
Tap Try It Yourself to play around with the code!
Iterating Over a Dictionary
Use a for-in loop to iterate over the key-value pairs in a dictionary. Each item in the dictionary is returned as a (key, value) tuple, which you can decompose into temporary constants or variables as part of the iteration:
In addition, accessing a dictionary's keys and values properties will retrieve an iterable collection of the dictionary's keys or values.
Since Swift's Dictionary type does not have a defined ordering, use the sort() method on the dictionary's keys or values property to iterate over the keys or values in a specific order.
Functions
Functions are self-contained chunks of code that perform a specific task. Name a function in a way that identifies what it does, and then use the name to call the function to perform its task when needed.
In Swift, every function has a type that contains the function's parameter types and return type. This type can be used like any other type in Swift, and this makes passing functions as parameters to other functions and returning functions from functions easier.
Functions can also be written within other functions to encapsulate useful functionality within a nested function scope.
Defining and Calling Functions
When you define a function, you have the option of defining one or more named and typed values that the function takes as input (parameters), and/or a type of value that the function passes back as output (return type).
To use a function, call it using its name and pass to it input values (arguments) that match the types of the function's parameters. Arguments must always be provided in the same order as in the function's parameter list. Also, the argument label is mandatory.
The example below defines a function sayHello, which takes a String (personName) as a parameter and then returns another String:
The function definition begins with the func keyword. The keyword is followed with a return arrow -> that indicates the function's return type, and the name of the type to return ends the definition.
The return statement indicates the value to be returned by the function.
Once the function has been defined, it can be called in other parts of your code.
You can call the defined function multiple times, and with different input values.
Multiple Input Parameters
Multiple input parameters can be written within the function's parentheses. Use commas to separate the parameters.
This function takes a start and an end index for a half-open range, and works out how many elements are contained in the range.
When calling a function parameters, all arguments should be labeled according to its corresponding parameter name.
If you don't want an argument label for a parameter, write an underscore (_) instead of an explicit argument label for that parameter.
Functions Without Parameters
Defining input parameters within a function is not a requirement. It is possible to have a function with no input parameters. That function will return the same String message every time it's called:
The function definition still needs parentheses after the function's name, even though there may be no parameters. The parentheses following the function name are simply empty when the function is called.
Functions Without Return Values
When defining a function it's not mandatory to define a return type. In the example below, the function sayHi() prints its own String value, rather than returning it.
Because it does not need to return a value, the function's definition does not include the return arrow (->) or a return type.
The sayHi() function does still return a value, even though it has no defined return value. Functions without a defined return type return a special value of type Void.
Return values can be ignored, but a function that says it will return a value must always do so.
Multiple Return Values
You can use a tuple type as the return type for a function to return multiple values as part of one compound return value.
The example below defines a function that returns the smallest and largest numbers in an array of Int values:
The minMax function returns a tuple containing two Int values. The values are labeled min and max, to facilitate easy access when querying the function's return value.
Because the tuple's member values are named as part of the function's return type, they can be accessed using dot syntax to retrieve the minimum and maximum found values:
It's not necessary to name the tuple's members when the tuple is returned from the function, because their names have already been specified as part of the function's return type.
External Parameter Names
Function parameters have both an external parameter name and a local parameter name.
The external parameter name is used to label arguments that are passed to a function call, while a local parameter name is used as the function is implemented.
By default, the first parameter omits its external name, and the second and subsequent parameters use their local name as their external name. Each parameter must be given its own, unique local name. External parameters may be shared.
An external parameter name appears just before the local parameter name that it supports. The two parameter names are separated by a space:func someFunc(externalName localName: Int) {
// function can use localName
}
Here's a version of the sayHello() function that takes the names of two people and returns a greeting for both of them:
The words to and and are the external parameter names, which are used when the function is called. The use of external parameter names can allow a function to be called in an expressive, sentence-like manner, and still produce a readable and clear function body.
If you provide an external parameter name for a given parameter, that external name must be used every time the function is called.
Default Parameter Values
Adding a value after a function parameter's type defines a default value for the parameter. If a default value is defined, that parameter can be omitted when calling the function.
Place parameters with default values at the end of a function's parameter list.
Variadic Parameters
Variadic parameters indicate that the number of input values of a specified type passed to the parameter can vary. The variadic parameter can accept zero and/or more parameters of a certain type, and is indicated by adding three period characters (...) immediately following the parameter's type name.
The values passed to a variadic parameter appear as an array of the specified type in the function's body.
In the example below, the function calculates the arithmetic mean, or average, for a list of numbers of any length:
One variadic parameter is allowed per function. The variadic parameter must always be the last parameter in the list, to ensure clarity when calling the function with multiple parameters.
For a function with one or more parameters containing default values, as well as a variadic parameter, the variadic parameter is added at the very end of the list, following all defaulted parameters.
In-Out Parameters
Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can't change the value of a parameter by mistake. If you want a function to modify a parameter's value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.
The inout keyword is placed at the beginning of the parameter's type. Its value is passed in to the function, where it is modified. It's then passed back out of the function, where it replaces the original value.
This example swaps out two Integer values.
An in-out parameter cannot be assigned a default value. A variadic parameter cannot be marked as inout.
An ampersand (&) that is inserted directly before a variable's name when it's passed as an argument to an inout parameter, indicates that the variable can be modified by the function:
The in-out parameter presents an alternative method when a function is desired to have an effect outside of the scope of its function body.
Function Types
Every function has a specific function type that consists of the function's parameter types and return type. Here's an example:
This example defines two simple mathematical functions called addInts and multiplyInts, which take two Int values, which return an Int value.
For both of these functions, the type is (Int, Int) -> Int. This can be interpreted as
"A function type that has two parameters, both of type Int; and that returns a value of type Int."
Here's another example. This one shows a function with no parameters or return value:
The type of this function is () -> Void, which translates into "a function that has no parameters, and returns Void."
Tap Continue to learn how you can use function types!
Using Function Types
Function types are used in the same way as any other Swift types.
For example, define a constant or a variable to be of a function type. Then assign an appropriate function to that variable:
You can now call the assigned function using its name, mathFunction.
A different function with the same type can be assigned to the same variable, in the same way as for non-function types.
Function Types as Parameter Types
A function type, such as (Int, Int) -> Int, can act as a parameter type for a different function, allowing for leaving some aspects of a function's implementation for the function's caller to provide calling the function.
The following example defines a function called printResult, which takes another function as its parameter and calls it as it's implemented:
It doesn't matter what the called function's implementation actually does - it matters only that the function is of the correct type.
Function Types as Return Types
A function type may be used as another function's return type, by writing a complete function type immediately following the return arrow (->) in the returning function.
In the above example, the chooseFunc function returns another function of type (Int) -> Int, based on the value of its flag parameter.
Nested Functions
You also have the option of defining functions inside the bodies of other functions. These are called nested functions.
By default, a nested function is hidden from the outside world. It can still be called and used by its enclosing function. An enclosing function can also return one of its nested functions, thus allowing the nested function to be used in another scope.
From the previous lesson, the function chooseFunc can be rewritten to use and return nested functions:
Tap Try It Yourself to play around with the code!
Recursion
The term recursion is used to describe the situation in which a function calls itself:
The function calls itself recursively, until n is equal to 0, at which point the recursion ends.
Be sure to always add a condition that ends the recursion. Otherwise, your recursion will continue infinitely.
Closures
A Closure is a self-contained block of functionality that can be passed around and used in your code.
Global and nested functions are actually special kinds of closures.
Nested functions offer a convenient way to name and define self-contained blocks of code within a larger function.
However, it is sometimes useful to have the option of writing shorter versions of function-like constructs that do not require full declarations and names. This is particularly true when working with functions that take other functions as one or more of their arguments.
Closures
Closure expressions present a way to write inline closures using brief, focused syntax. Closure expressions offer a number of syntax optimizations for use in writing closures in a shortened form, without losing clarity or intent.
Closure expression syntax takes the following general form:{ (parameters) -> return type in
statements
}
Closure expression syntax can use constant parameters, variable parameters, and inout parameters, but default values cannot be used. Variadic parameters can be used if you name them and make sure that it's placed last in the parameter list.
The Sorted Function
To understand how Closures are used, let's take a look at a function called sorted, which is available in Swift's standard library. The sorted function orders an array of values of a known type.
The sorted method takes two arguments:
- An array of values of a known type.
- A closure that takes two arguments of the same type as the array's contents, and returns a Bool value to say whether the first value should appear before or after the second value once the sorting is complete.
The example below shows the sorting of an array of String values. The sorting closure needs to be a function of type (String, String) -> Bool.
One way to provide the sorting closure is to write a normal function of the correct type, and to pass it in as the sorted method's parameter:
The example compares the Strings in the array names, based on the backwards function.
However, this is a long-winded way to write what is really a function that contains a single-expression (a > b).
In this situation, it would be preferable to write the sorting closure inline, using closure expression syntax:
For the inline closure expression, the parameters and return type are written within the curly braces, rather than outside of them.
The in keyword is used to introduce the beginning of the closure's body, and indicates that the definition of the closure's parameters and return type has finished, and the body of the closure is about to begin.
Inferring Type from Context
The sorting closure is passed as an argument to a function, making it possible for Swift to infer the types of its parameters and the type of the return value from the type of the sorted method's second parameter. This parameter expects a function of type (String, String) -> Bool. This means that writing the (String, String) and Bool types as part of the closure expression's definition is not necessary. Because all of the types can be inferred, the return arrow (->) and the parentheses around the names of the parameters can also be omitted:
The parameter types and return type can always be inferred when passing a closure to a function as an inline closure expression. As a result, it is unnecessary to write an inline closure in its fullest form when the closure is used as a function argument.
You still have the option of writing out the types. In fact, doing so is encouraged when you need to avoid ambiguity for others who might read your code.
Inferring Type from Context
Single-expression closures can implicitly return the result of their single expression, simply by omitting the return keyword from the declaration, as in this new version of the previous example:
Here, the function type of the sorted method's second argument makes it clear that a Bool value must be returned by the closure.
Because the closure's body contains a single expression (s1 > s2) that returns a Bool value, there is no ambiguity, and the return keyword can be omitted.
Shorthand Argument Names
Swift automatically provides shorthand argument names for inline closures. These argument names can be used to refer to the values of the closure's arguments, with the names $0, $1, $2, and so on.
If you use these shorthand argument names within your closure expression, you can omit the closure's argument list from its definition, and the number and type of the shorthand argument names will be inferred from the expected function type. The in keyword can also be omitted, since the closure expression is entirely made up of its body:
Here, $0 and $1 refer to the closure's first and second String arguments.
Operator Functions
There's actually an even shorter way to write the closure expression above. Swift's String type defines its string-specific implementation of the greater-than operator (>) as a function that has two parameters of type String, and returns a value of type Bool. This exactly matches the function type needed for the sort method's second parameter. Just pass in the greater-than operator, and Swift will then infer that you want to use its string-specific implementation:
Tap Try It Yourself to play around with the code!
Tuples
Tuples group multiple values into a single, compound value. The values within a tuple can be of any type and do not have to share a common type.
The tuple above is of type (Int, String).
You can create tuples from any permutation of types, and they can contain as many different types as you like. There's nothing stopping you from having a tuple of type (Int, Int, Int), or (String, Bool), or any other permutation you require.
A tuple's contents can be broken down into separate constants or variables, which you then access as you would any constant or variable.
An alternative method of accessing individual element values in a tuple is to use index numbers, starting with zero.
When you define a tuple, you have the opportunity to name its individual elements. You can then use the element names to access the values of those elements:
Tuples are particularly useful as the return values of functions, and are used as temporary groupings of related values.
Enumerations
An enumeration defines a common type for a group of related values. It enables you to work with those values in a type-safe way within your code.
Use the enum keyword to introduce an enumeration, and place their entire definition within a pair of braces:enum Compass {
case North
case South
case East
case West
}
An enumeration's member values, or members, are the values that are defined within the enumeration, such as North, South, East, and West. The case keyword indicates that a new line of member values is about to be defined.
Unlike C and Objective-C, Swift enumeration members are not assigned a default integer value when they are created.
In the Compass example above, North, South, East and West do not implicitly equal 0, 1, 2 and 3. Instead, the different enumeration members are fully-fledged values in their own right, with an explicitly defined type of Compass.
Enumerations
Multiple member values can appear on a single line, separated by commas:enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter
}
Each enumeration definition specifies a brand new type. As with other types in Swift, their names (such as Compass and Planet) should be capitalized.
The values in an enumeration can be accessed using the dot syntax.
Give enumeration types singular rather than plural names, so that they read as self-evident.
Classes and Structures
Classes and structures are general-purpose, flexible constructs that become the building blocks of your program's code. You define properties and methods to add functionality to your classes and structures by using exactly the same syntax as for constants, variables, and functions.
Swift does not require that you create separate interface and implementation files for custom classes and structures. Rather, they are defined in a single file. The external interface to that class or structure is automatically made available for other code to use.
In other languages, an instance of a class is traditionally known as an object. However, because classes and structures in Swift are much closer in functionality than in other languages, the more general term instance is used.
Definition
Classes and structures have a similar definition syntax.
You introduce classes with the class keyword and structures with the struct keyword. Each places its entire definition within a pair of braces.
The example above defines a new structure called Resolution, with two stored properties called width and height.
It also defines a new class called VideoMode, which has three variable stored properties. The first, resolution, is initialized with a new Resolution structure instance, which infers a property type of Resolution.
When a new class or structure is defined, a brand new Swift type is defined.
Class and Structure Instances
The Resolution structure definition and the VideoMode class definition only describe what each one will look like. The definitions in themselves do not describe a specific resolution or video mode. To do that, you need to create an instance of the structure or class.
The syntax for creating instances is very similar for both structures and classes:
When creating new instances, structures and classes both use initializer syntax. The simplest form of initializer syntax uses the type name of the class or structure, followed by empty parentheses.
This creates a new instance of the class or structure. Properties are initialized to their default values.
Accessing Properties
Access an instance's properties with dot syntax. Write the property name immediately after the instance name, separated with a period (.), with no spaces:
In this example, someResolution.width refers to the width property of someResolution, and returns its value.
You can drill down into a sub-property, such as the width property, which is in the resolution property of a VideoMode.
Dot syntax is also used when assigning a new value to a variable property.
In the last example, the width property of the resolution property of someVideoMode is set directly, with no need to set the entire resolution property to a new value.
All structures have an automatically-generated memberwise initializer, which you can use to initialize the member properties of new structure instances. Initial values for the properties of the new instance can be passed to the memberwise initializer by name:
Unlike structures, class instances do not receive a memberwise initializer by default.
Structures are Value Types
A value type is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function.
In fact, all of the basic types in Swift - integers, floating-point numbers, Booleans, strings, arrays, and dictionaries - are value types, and are implemented as structures behind the scenes.
All Swift structures are value types. This means that any structure instances you create - and any value types they have as properties - are always copied when they are passed around in your code.
Consider this example, which uses the Resolution structure from the previous example:
The example declares a constant called hd and sets it to a Resolution instance.
It then declares a variable called cinema, and sets it to the current value of hd. Because Resolution is a structure, a copy of the existing instance is made, and this new copy is assigned to cinema. Even though hd and cinema now have the same width and height, behind the scenes they are two completely different instances.
Next, the width property of cinema is amended:
However, the width property of the original hd instance still has the old value of 1920.
The same behavior is true of enumerations.
Classes are Reference Types
Unlike value types, reference types are not copied when assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the existing instance is used instead.
Here's an example, using the VideoMode class:
The video mode is assigned a copy of the hd resolution of 1920 by 1080 from before.
Next, tenEighty is assigned to a new constant, named alsoTenEighty, and the interlaced property of alsoTenEighty is modified.
Because classes are reference types, tenEighty and alsoTenEighty actually both refer to the same VideoMode instance, so the interlaced value is changed for both. Effectively, they are just two different names for the same single instance.
Rather than being declared as variables, tenEighty and alsoTenEighty are declared as constants. However, it's still possible to change their properties, because the values of the tenEighty and alsoTenEighty constants themselves do not actually change.
Identity Operators
You may need to determine whether two constants or variables refer to the same instance of a class. To accomplish this, Swift provides the following identity operators:
Identical to (===)
Not identical to (!==)
Use these operators to check your constants or variables to see if they refer to the same single instance:
Note that "identical to" (represented by three equals signs, or ===) does not mean the same thing as "equal to" (represented by two equals signs, or ==).
Classes vs. Structures
Structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks.
As a general guideline, consider creating a structure instead of a class when one or more of these conditions apply:
1. The structure's primary purpose is to encapsulate a few relatively simple data values.
2. It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
3. Any properties stored by the structure are themselves value types, which you would also expect to be copied, as opposed to being referenced.
4. It is not necessary for the structure to inherit properties or behavior from another existing type.
For example, the size of a geometric shape would be a good choice for a structure, with the structure perhaps encapsulating a width property and a height property, both of type Double.
In all other cases, define a class. You will find that most custom data constructs should be defined as classes rather than as structures.
Swift's String, Array, and Dictionary types are implemented as structures.
Properties
Properties associate values with a particular class, structure, or enumeration. Stored properties store constant and variable values as part of an instance.
In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure.
In the example above, height is initialized when the new size is created. This is a constant property, and it cannot be changed once initialized.
When creating an instance of a structure, if you assign an instance of a structure to a constant, the instance's properties cannot be modified, even if they were declared as variable properties.
Lazy Stored Properties
A lazy stored property's initial value is not calculated until the first time it is used.
Lazy properties are useful when a property's initial value is dependent on outside factors, the values of which are not known until after an instance's initialization is complete; or when the initial value for a property requires complex or computationally expensive setup that should not be performed unless or until it is needed.
You must always declare a lazy property as a variable, using the var keyword, because its initial value might not be retrieved until after the completion of instance initialization. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.
Computed Properties
Rather than storing a value, a computed property provides a getter and, optionally, a setter, which indirectly retrieve and set other properties and values, respectively.
The Shape structure defines a custom getter and setter for a computed variable called center. The center property is then accessed through dot syntax, which causes the getter for center to be called to retrieve the current property value. Rather than returning an existing value, the getter actually calculates and returns a new point that represents the center of the shape.
If a computed property's setter does not define a name for the new value to be set, a default name of newValue is used.
Below is an alternative version of the Rect structure, which takes advantage of this shorthand notation:
A computed property with a getter but no setter is known as a read-only computed property. It always returns a value, and can be accessed through dot syntax. However, that value cannot be altered.
Property Observers
Property observers detect and respond to changes in a property's value. Property observers are called every time a property's value is set, even if the new value is the same as the property's current value.
Property observers can be added to any defined stored properties, with the exception of lazy stored properties.
You have the option of defining either or both of the following observers of a property:
- willSet is called just before the value is stored.
- didSet is called immediately after the new value is stored.
The example below defines a new class called StepCounter, which tracks the total number of steps that a person takes while walking.
The StepCounter class declares a totalSteps property of type Int, with willSet and didSet observers.
The willSet and didSet observers for totalSteps are called whenever the property is assigned a new value.
The willSet observer uses a custom parameter name of newSteps for the upcoming new value.
The didSet observer is called after the value of totalSteps is updated. It compares the new value of totalSteps against the old value. The didSet observer does not provide a custom parameter name for the old value; the default name of oldValue is used, instead.
Similarly, newValue could be used for the willSet observer, if no parameter name was defined.
If you assign a value to a property within its own didSet observer, the new assigned value will replace the one that was just set.
Type Properties
Type properties are useful when defining values that are universal to all instances of a particular type, such as a constant property used in all instances, or a variable property that stores a global value to all instances of that type. Stored type properties can be variables or constants. You define type properties with the static keyword:
Type properties are queried and set with dot syntax, just like instance properties. However, type properties are queried and set on the type, not on an instance of that type:
Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time.
Methods
Methods are functions that are associated with a particular type. Classes, structures, and enumerations can all define instance methods.
Instance methods are functions that belong to instances of a particular class, structure, or enumeration. Instance methods have exactly the same syntax as functions:
You call instance methods with the same dot syntax used for properties:
Tap Try It Yourself to play around with the code!
The self Property
The self property refers to the current instance within its own instance methods.
The increment() method in the previous example could have been written like this:
In practice, you rarely need to write self in your code.
You can use the self property to distinguish between the parameter name and the property name.
Here, self disambiguates between a method parameter called x and an instance property that is also called x:
Without the self prefix, Swift would assume that both uses of x referred to the method parameter called x.
Modifying Value Types
Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.
The mutating keyword is added to the method's definition so it can modify its properties.
You cannot call a mutating method on a constant of structure type, because its properties cannot be changed.
Mutating methods can assign an entirely new instance to the implicit self property.
Type Methods
Instance methods are called on an instance of a particular type. A type method is called on the type itself, and is indicated by writing the keyword static before the method's func keyword:
As with instance methods, type methods are called with dot syntax. However, type methods are called on the type, not on an instance of that type.
Within the body of a type method, the implicit self property refers to the type itself, rather than an instance of that type.
Subscripts
Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence. Subscripts enable you to query instances of a type by writing one or more values in square brackets after the instance name.
Subscript definitions are written using the subscript keyword, and specify one or more input parameters and a return type, in the same way as instance methods.
Here's an example of a read-only subscript implementation, which defines a TimesTable structure to represent an n-times-table of integers:
Multiple subscripts can be defined for a single type. The appropriate subscript overload to use is selected according to the type of index value you pass to the subscript. Subscripts are not limited to a single dimension, and can be defined using multiple input parameters that best suit your custom type's needs
Subscripts
A Matrix is a good example how a subscript is used.
The following example defines a Matrix structure, which represents a two-dimensional matrix of Double values. The subscript takes two integer parameters:
Matrix provides an initializer that takes two parameters called rows and columns to create an array large enough to store values of type Double. Each position in the matrix is given an initial value of 0.0.
Initialization is described in detail in the coming lessons.
You can construct a new Matrix instance by passing an appropriate row and column count to its initializer:
This results in the following grid:
Values in the matrix can be set by passing comma-separated row and column values into the subscript.:
This results in the following:
Tap Try It Yourself to play around with the code!
Inheritance
A class can inherit methods, properties, and other characteristics from another class. The inheriting class is a subclass, and the class from which it inherits from is its superclass. Inheritance is a fundamental behavior that differentiates classes from other types in Swift.
Any class that does not inherit from another class is known as a base class.
Subclassing is the act of basing a new class on an existing class.
To understand inheritance in action, let's create an example:
To indicate that a subclass has a superclass, write the subclass name before the superclass name, separated by a colon:
The new Bicycle class automatically takes on all of the characteristics of Vehicle, including properties such as currentSpeed and desc, as well as methods like makeNoise(). In addition to its inherited characteristics, the Bicycle class defines a new stored property, hasBasket.
You can also modify the inherited currentSpeed property of a Bicycle instance, and query the instance's inherited desc property:
Subclasses can themselves be subclassed. The next example creates a subclass of Bicycle for a two-seater bicycle, or a tandem.
If you create an instance of Tandem, you can work with any of its properties, both new and inherited. You can also query the read-only desc property it inherited from Vehicle:
Tap Try It Yourself to play around with the code!
Overriding
A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. This is known as overriding.
To override a characteristic that would otherwise be inherited, prefix the overriding definition with the override keyword.
The superclass version of a method, property, or subscript is accessed by using the super prefix.
For example:
- An overridden method named someMethod() can call the superclass version of someMethod() by calling super.someMethod() within the overriding method implementation.
- An overridden property called someProperty can access the superclass version of someProperty as super.someProperty within the overriding getter or setter implementation.
- An overridden subscript for someIndex can access the superclass version of the same subscript as super[someIndex] from within the overriding subscript implementation.
The following example defines a new subclass of Vehicle, called Train, which overrides the makeNoise() method that Train inherits from Vehicle:
The following example defines a new class called Car, which is a subclass of Vehicle. The Car class introduces a new stored property called gear, which has a default integer value of 1. The Car class also overrides the description property inherited from Vehicle to provide a custom description that includes the current gear:
You can prevent a method, property, or subscript override by marking it as final (such as final var, final func, final class func, and final subscript).
You can mark an entire class as final by placing the final modifier before the class keyword in its class definition (final class).
Initialization
The process of preparing an instance of a class, structure, or enumeration for use is called initialization. It involves setting an initial value for each property stored in that instance and performing any other setup or initialization that is required before the new instance is ready for use.
Classes and structures must set all of their stored properties to an appropriate initial value prior to creation of an instance.
Initializers are called to create a new instance of a particular type. In its simplest form, an initializer is like an instance method with no parameters, and is written using the init keyword:
As part of an initializer's definition, initialization parameters define the types and names of values that customize the initialization process.
The example above defines a structure called Celsius, which implements two custom initializers called init(fromFahrenheit:) and init(fromKelvin:). This initializes a new instance of the structure with a value from a different temperature scale.
When you assign a default value to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.
Initializers
In the case of structure types that have no defined custom initializers, Swift automatically provides a memberwise initializer, even if the structure types have stored properties that do not have default values.
The Size structure automatically receives an init(width:height:) memberwise initializer, which you can use to initialize a new Size instance:
Class Initialization
All stored properties of a class - including any properties inherited from its superclass - must be assigned an initial value during initialization. In other words, assign a default value or create an initializer for the properties of the class.
The structure from the above example would have the following form as a class with an initializer:
Tap Try It Yourself to play around with the code!
Required Initializers
Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer:
You must also insert the required modifier before every subclass implementation of a required initializer. This indicates that the initializer requirement applies to further subclasses along the chain. Do not write the override modifier when overriding a required designated initializer:
It's not necessary to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initializer.
Deinitialization
A deinitializer is called immediately before a class instance is deallocated, and is useful when you work with your own resources.
For example, if you create a custom class to open a file and write some data to it, you might need to close the file before the class instance is deallocated.
Class definitions can have at most one deinitializer per class.
Rather than providing you with the ability to call a deinitializer, Swift automatically calls it, just prior to instance deallocation. Superclass deinitializers are inherited by their subclasses, and the superclass deinitializer is called automatically at the end of a subclass deinitializer implementation. Superclass deinitializers are always called, even in cases in which subclasses do not provide their own deinitializers.
The deinit keyword is used to write a deinitializer, which is similar to writing an initializer using the init keyword. The deinitializer does not take any parameters and is written with no parentheses:
Deinitializers are only available on class types.
No comments:
Post a Comment