In this tutorial we cover the following topics
Chaining queries are quite common when working with objects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
import Foundation class ClassChain1 { var value: Int init (withWalue value: Int){ self.value = value } func increaseBy1() -> ClassChain2{ return ClassChain2(withWalue: value + 1) } } class ClassChain2 { var value: Int init (withWalue value: Int){ self.value = value } func increaseBy2() -> ClassChain3{ return ClassChain3(withWalue: value + 2) } } class ClassChain3 { var value: Int init (withWalue value: Int){ self.value = value } func increaseBy3() -> ClassChain4{ return ClassChain4(withWalue: value + 3) } } class ClassChain4 { var value: Int init (withWalue value: Int){ self.value = value } func getFinalResult() -> Int { return value } } // We can make the following call var chainLink1 = ClassChain1(withWalue: 2) var chainLink2 = chainLink1.increaseBy1() var chainLink3 = chainLink2.increaseBy2() var chainLink4 = chainLink3.increaseBy3() print(chainLink4.getFinalResult()) // or much simpler print(ClassChain1(withWalue: 2).increaseBy1().increaseBy2().increaseBy3().getFinalResult()) |
8
8
Now imagine that, for some reason, one of the chain's item may returns or may has value of nil
. Let's look into modified version of a previous code. We have change
1 2 3 4 5 6 7 8 9 |
[...] func increaseBy2() -> ClassChain3?{ if (value > 3){ return nil } return ClassChain3(withWalue: value + 2) } [...] print(ClassChain1(withWalue: 2).increaseBy1().increaseBy2()!.increaseBy3().getFinalResult()) |
note an extra exclamation mark in print
1 |
print(ClassChain1(withWalue: 2).increaseBy1().increaseBy2()!.increaseBy3().getFinalResult()) |
in the following full code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import Foundation class ClassChain1 { var value: Int init (withWalue value: Int){ self.value = value } func increaseBy1() -> ClassChain2{ return ClassChain2(withWalue: value + 1) } } class ClassChain2 { var value: Int init (withWalue value: Int){ self.value = value } func increaseBy2() -> ClassChain3?{ if (value > 3){ return nil } return ClassChain3(withWalue: value + 2) } } class ClassChain3 { var value: Int init (withWalue value: Int){ self.value = value } func increaseBy3() -> ClassChain4{ return ClassChain4(withWalue: value + 3) } } class ClassChain4 { var value: Int init (withWalue value: Int){ self.value = value } func getFinalResult() -> Int { return value } } print(ClassChain1(withWalue: 2).increaseBy1().increaseBy2()!.increaseBy3().getFinalResult()) |
It should work as previously,
8
but changing
increaseBy2
method (note >=
)
1 2 3 4 5 6 7 8 9 |
[...] func increaseBy2() -> ClassChain3?{ if (value >= 3){ return nil } return ClassChain3(withWalue: value + 2) } [...] print(ClassChain1(withWalue: 2).increaseBy1().increaseBy2()!.increaseBy3().getFinalResult()) |
results in error
fatal error: unexpectedly found nil while unwrapping an Optional value
2017-09-08 19:20:05.272966+0200 Classes_03[27444:6846170] fatal error: unexpecte
dly found nil while unwrapping an Optional value
Current stack trace:
0 Classes_03 0x0000000100341bc0 swift_reportError + 1
29
1 Classes_03 0x000000010035e5e0 _swift_stdlib_reportF
atalError + 60
2 Classes_03 0x00000001000bd1f0 specialized specializ
ed StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) ->
A) -> A + 342
3 Classes_03 0x00000001002c7730 partial apply for (_f
atalErrorMessage(StaticString, StaticString, file : StaticString, line : UInt, f
lags : UInt32) -> Never).(closure #2) + 109
4 Classes_03 0x00000001000bd1f0 specialized specializ
ed StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) ->
A) -> A + 342
5 Classes_03 0x000000010027dd20 specialized _fatalErr
orMessage(StaticString, StaticString, file : StaticString, line : UInt, flags :
UInt32) -> Never + 96
6 Classes_03 0x00000001000016d0 main + 204
7 libdyld.dylib 0x00007fff96dd7234 start + 1
(lldb)
As we can see, with exclamation mark code compiles but when the optional is
nil
triggers a runtime error. Now there is a time when optional chaining can help. Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might, at the time of executing, be nil
. If the optional contains a value, the call succeeds; if the optional is nil
, the call returns nil
. Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil
.
To use optional chaining, use a question mark in place of the exclamation mark
1 2 3 4 5 |
if let chainCallResult = ClassChain1(withWalue: 3).increaseBy1().increaseBy2()?.increaseBy3().getFinalResult() { print(chainCallResult); } else { print("chainCallResult fails, do something eles"); } |
chainCallResult fails, do something eles
Notice that the optional chaining always returns a value of optional type even though in source code we have nonoptional; in our case Int?
(optional Int) instead of Int
.
Add the following lines to our code
1 2 |
let chainCallResult = ClassChain1(withWalue: 2).increaseBy1().increaseBy2()?.increaseBy3().getFinalResult() print(chainCallResult) |
As we can see, the result is not simple 8
but Optional(8)
chainCallResult fails, do something eles
Optional(8)
What is interesting, we can do this even if method does not define a return value. It is possible, because functions and methods with no return type have an implicit return type of Void
and in consequence the return type with optional chaining will be Void?
, not Void
.
In Swift we have two special types for working with indefinite, or beter say: any, type
Any
which can represent an instance of any type at all (including function types);AnyObject
which can represent an instance of any class type.
The most basic example of Any
usage is an array to store items of any type
1 2 3 4 5 6 7 8 9 10 |
var someInt: Int? = 5 var someString: String = "text" var arrayOfAnyInstances = [Any]() arrayOfAnyInstances.append(5) arrayOfAnyInstances.append(1.23) arrayOfAnyInstances.append("test") arrayOfAnyInstances.append({ (arg: String) -> String in "Echo: \(arg)" }) print(arrayOfAnyInstances) |
[5, 1.23, "test", (Function)]
The Any
type represents values of any type, including optional types. Swift gives us a warning if we use an optional value where a value of type Any
is expected. If we really do need to use an optional value as an Any
value, we can use the as
operator to explicitly cast the optional to Any
1 2 3 |
let optionalNumber: Int? = 5 arrayOfAnyInstances.append(optionalNumber) // Warning arrayOfAnyInstances.append(optionalNumber as Any) // No warning |
To check whether an instance is of a certain subclass type, use the type check operator: is
. This operator returns true
if the instance is of that subclass type and false
if it is not.
1 2 3 4 5 6 7 8 9 |
for item in arrayOfAnyInstances { if item is Int { print("\(item) is of Int type"); } else if item is String { print("\(item) is of String type"); }else { print("\(item) is of \(type(of: item)) type"); } } |
5 is of Int type
1.23 is of Double type
test is of String type
(Function) is of (String) -> String type
A constant or variable of a certain class type may actually refer to an instance of a subclass. We can try to downcast to the subclass type with a type cast operator
- in the conditional form
as?
when returns an optional value of the type we are trying to downcast to; - in the forced form
as!
to attempt the downcast and force-unwraps the result as a single compound action.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class A { } class B: A { } class C: B { } var b = B() print("\(b) is of \(type(of: b)) type"); var x = B() as? A // Warning: Conditional cast from 'B' to 'A' always succeeds print("\(x ?? A()) is of \(type(of: x)) type"); var y = B() as? C print("\(y ?? C()) is of \(type(of: y)) type"); |
Classes_03.B is of B type
Classes_03.B is of Optional<A> type
Classes_03.C is of Optional<C> type
Sometimes it can be convenient to define utility structure or class purely for use within the context of a more complex type simply to support this type’s functionality. Types can be nested to as many levels as we need. To use a nested type outside of its definition context, we have to prefix its name with the name of the type it is nested within.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class DataToSynchronize { enum Status { case New case WaitForConfirmation case Synchronized case Unknown } var status = Status.New var data: Any? init (with value: Any){ data = value; } } var data = [String:DataToSynchronize]() data["d1"] = DataToSynchronize(with: "Value 1") data["d2"] = DataToSynchronize(with: 123) for (key, value) in data { print("key: \(key) value: \(value) status: \(data[key]?.status ?? DataToSynchronize.Status.Unknown)") data[key]?.status = DataToSynchronize.Status.WaitForConfirmation print("key: \(key) value: \(value) status: \(data[key]?.status ?? DataToSynchronize.Status.Unknown)") } |
key: d1 value: Classes_03.DataToSynchronize status: New
key: d1 value: Classes_03.DataToSynchronize status: WaitForConfirmation
key: d2 value: Classes_03.DataToSynchronize status: New
key: d2 value: Classes_03.DataToSynchronize status: WaitForConfirmation
Access control restricts access to/from parts our code. With this feature we can hide the implementation details, and enable access to it with a preferred interface through which that code can be used. Swift provides five different access levels. These access levels are relative to the source file in which an entity is defined, and also relative to the module that source file belongs to.
- Open access (
open
keyword) and public access (public
) enable entities to be used within any source file from their defining module, as well as in a source file from another module that imports the defining module. See below for difference between open and public access. - Internal access (
internal
) enables entities to be used within any source file from their defining module, but not in any source file outside of that module. - File-private access (
fileprivate
) restricts the use of an entity to its own defining source file. - Private access (
private
) restricts the use of an entity to the enclosing declaration.
Open access differs from public access as follows
- Open access applies only to classes and class members.
- Classes with public access, or any more restrictive access level, can be subclassed only within the module where they’re defined.
- Class members with public access, or any more restrictive access level, can be overridden by subclasses only within the module where they’re defined.
- Open classes can be subclassed within the module where they’re defined, and within any module that imports the module where they’re defined.
- Open class members can be overridden by subclasses within the module where they’re defined, and within any module that imports the module where they’re defined.
- Rule 1. Almost all entities in our code, if we do not specify an explicit access level ourself, have a default access level of internal.
- Rule 2. The access control level of a type also affects the default access level of that type’s members: properties, methods, initializers, and subscripts. For example, having defined type with a private access level, the default access level of its members will also be private.
- Rule 3. A public type defaults for its members is internal. This ensures that the open to the public API for a type is something we agree to publishing, and avoids presenting the internal workings details of a type as public API by mistake.
- Rule 4. A tuple type’s access level is deduced automatically when the tuple type is used, and can’t be specified explicitly.
- Rule 5. The access level for a function type is calculated as the most restrictive access level of the function’s parameter types and return type. You must specify the access level explicitly as part of the function’s definition if the function’s calculated access level doesn’t match the contextual default.
- Rule 6. Nested types defined within a private (or file-private) type have an automatic access level of private (or file-private). Nested types defined within a public type or an internal type have an automatic access level of internal.
- Rule 7. A subclass can’t have a less restrictive access level than its superclass. For example, we can’t write a public subclass of an internal superclass.
- Rule 8. An override can make an inherited class member more accessible than its superclass version.
- Rule 9. A constant, variable, or property can’t be more public than its type. For example it’s not valid to have a public property with a private type.
More important rules
For more details please refer to official documentation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
// Compile Error: Only classes and overridable class members can be declared 'open'; use 'public' //open var variableOpen = 0 public var variablePublic = 0 //internal var variableInternal = 0 var variableInternal = 0 // Implicitly internal fileprivate var variableFilePrivate = 0 private var variablePrivate = 0 open class classOpen{} public class classPublic { // Explicitly public class public var propertyPublic = 0 // Explicitly public class member var propertyInternal = 0 // By default internal (Rule 3) fileprivate func methodFilePrivate() {} // Explicitly file private class member private var propertyPrivate = 0 // Explicitly private class member } //internal class classInternal {...} class classInternal { // By default internal (Rule 1) var propertyInternal = 0 // By default internal (Rule 2) fileprivate func methodFilePrivate() {} // Explicitly file private class member private var propertyPrivate = 0 // Explicitly private class member } fileprivate class classFilePrivate { // Explicitly file private class func methodFilePrivate() {} // By default file private (Rule 2) private var propertyPrivate = 0 // Explicitly private class member } private class classPrivate { // Explicitly private class var variable = 0 // By default private (Rule 2) private var variableExplicitlyPrivate = 0 class classPrivateNestedType {} // Rule 6 - this nested type has an automatic // access level of private; see below var xx = classPrivateNestedType() } private var xx = classPrivate() // Rule 9 // Without private: Compile Error: Variable must be declared private or fileprivate because its type // 'classPrivate.classPrivateNestedType' uses a private type private var yy = xx.xx // Compile Error: 'variableExplicitlyPrivate' is inaccessible due to 'private' protection level //var ww = xx.variableExplicitlyPrivate // ??? According to Rule 2 variable should be private but it is not. Why? ??? var zz = xx.variable // Without private: Compile Error: Function must be declared private or fileprivate because its results uses a private type private func someFunction() -> (classPublic, classPrivate) { ... } // Rule 4 and 5 // Rule 7 // Compile Error: Class cannot be declared public because its superclass is private public class classPublicWithPrivateSuperclass: classPrivate { fileprivate func someMethod() {} } // Rule 8 internal class classInternalOverrides: classPublic { override internal func methodFilePrivate() { super.methodFilePrivate() } } |