Closures in Swift

Swift Typealias: How to use them and Why?

Generics

For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be.

The Problem That Generics Solve

Here’s a standard, nongeneric function called swapTwoInts(_:_:), which swaps two Int values:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
   let temporaryA = a
   a = b
   b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print(“someInt is now (someInt), and anotherInt is now (anotherInt)”)
// Prints “someInt is now 107, and anotherInt is now 3”

The swapTwoInts(_:_:) function is useful, but it can only be used with Int values. If you want to swap two String values, or two Double values, you have to write more functions, such as the swapTwoStrings(_:_:) and swapTwoDoubles(_:_:) functions shown below:

func swapTwoStrings(_ a: inout String, _ b: inout String) {
   let temporaryA = a
   a = b
   b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
   let temporaryA = a
   a = b
   b = temporaryA
}

It’s more useful, and considerably more flexible, to write a single function that swaps two values of any type. Generic code enables you to write such a function. (A generic version of these functions is defined below.)

Generic Functions

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
   let temporaryA = a
   a = b
   b = temporaryA
}

  1. func swapTwoInts(_ a: inout Int, _ b: inout Int)
  2. func swapTwoValues<T>(_ a: inout T, _ b: inout T)

The generic version of the function uses a placeholder type name (called T, in this case) instead of an actual type name (such as Int, String, or Double). The placeholder type name doesn’t say anything about what must be, but it does say that both a and b must be of the same type T, whatever T represents. The actual type to use in place of T is determined each time the swapTwoValues(_:_:) function is called.

var someInt = 3

var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = “hello”
var anotherString = “world”
swapTwoValues(&someString, &anotherString)
// someString is now “world”, and anotherString is now “hello”

Type Parameters

In the swapTwoValues(_:_:) example above, the placeholder type T is an example of a type parameter. Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as <T>).

Once you specify a type parameter, you can use it to define the type of a function’s parameters (such as the a and b parameters of the swapTwoValues(_:_:) function), or as the function’s return type, or as a type annotation within the body of the function. 

You can provide more than one type parameter by writing multiple type parameter names within the angle brackets, separated by commas.

Naming Type Parameters

In most cases, type parameters have descriptive names, such as Key and Value in Dictionary<Key, Value>and Element in Array<Element>, which tells the reader about the relationship between the type parameter and the generic type or function it’s used in. However, when there isn’t a meaningful relationship between them, it’s traditional to name them using single letters such as T, U, and V.

NOTE

Always give type parameters upper camel case names (such as T and MyTypeParameter

Generic Types

In addition to generic functions, Swift enables you to define your own generic types. These are custom classes, structures, and enumerations that can work with any type, in a similar way to Array and Dictionary.

The illustration below shows the push and pop behavior for a stack:

  1. There are currently three values on the stack.
  2. A fourth value is pushed onto the top of the stack.
  3. The stack now holds four values, with the most recent one at the top.
  4. The top item in the stack is popped.
  5. After popping a value, the stack once again holds three values.

Here’s how to write a nongeneric version of a stack, in this case for a stack of Int values:

struct IntStack {
   var items = [Int]()
   mutating func push(_ item: Int) {
       items.append(item)
   }
   mutating func pop() -> Int {
       return items.removeLast()
   }
}

This structure uses an Array property called items to store the values in the stack. Stack provides two methods, push and pop, to push and pop values on and off the stack. These methods are marked as mutating, because they need to modify (or mutate) the structure’s items array.

The IntStack type shown above can only be used with Int values, however. It would be much more useful to define a generic Stack class, that can manage a stack of any type of value.

Here’s a generic version of the same code:

struct Stack<Element> {
   var items = [Element]()
   mutating func push(_ item: Element) {
       items.append(item)
   }
   mutating func pop() -> Element {
       return items.removeLast()
   }
}

var stackOfStrings = Stack<String>()
stackOfStrings.push(“uno”)
stackOfStrings.push(“dos”)
stackOfStrings.push(“tres”)
stackOfStrings.push(“cuatro”)
// the stack now contains 4 strings

let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to “cuatro”, and the stack now contains 3 strings

Extending a Generic Type

When you extend a generic type, you don’t provide a type parameter list as part of the extension’s definition. Instead, the type parameter list from the original type definition is available within the body of the extension, and the original type parameter names are used to refer to the type parameters from the original definition.

(예를 들어 아래 예시에서 Element가 type parameter 이며 이것이 extending하는 부분에서 접근가능하다는 이야기)

struct Stack<Element> {   
}

extension Stack {

 // Element가 extended 정의의 type parameter이므로 접근 가능
   var topItem: Element? {
       return items.isEmpty ? nil : items[items.count – 1]
   }
}

if let topItem = stackOfStrings.topItem {
   print(“The top item on the stack is (topItem).”)
}
// Prints “The top item on the stack is tres.”

Type Constraints

Type constraints specify that a type parameter must inherit from a specific class, or conform to a particular protocol or protocol composition.

For example, Swift’s Dictionary type places a limitation on the types that can be used as keys for a dictionary. As described in Dictionaries, the type of a dictionary’s keys must be hashable. That is, it must provide a way to make itself uniquely representable. Dictionary needs its keys to be hashable so that it can check whether it already contains a value for a particular key. Without this requirement, Dictionary could not tell whether it should insert or replace a value for a particular key, nor would it be able to find a value for a given key that is already in the dictionary.

This requirement is enforced by a type constraint on the key type for Dictionary, which specifies that the key type must conform to the Hashable protocol, a special protocol defined in the Swift standard library. All of Swift’s basic types (such as String, Int, Double, and Bool) are hashable by default.

You can define your own type constraints when creating custom generic types, and these constraints provide much of the power of generic programming. Abstract concepts like Hashable characterize types in terms of their conceptual characteristics, rather than their concrete type.

Type Constraint Syntax

You write type constraints by placing a single class or protocol constraint after a type parameter’s name, separated by a colon, as part of the type parameter list. The basic syntax for type constraints on a generic function is shown below (although the syntax is the same for generic types):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
   // function body goes here
}

The hypothetical function above has two type parameters. The first type parameter, T, has a type constraint that requires T to be a subclass of SomeClass. The second type parameter, U, has a type constraint that requires U to conform to the protocol SomeProtocol.

Type Constraints in Action

type constaints의 사용 예시

Here’s a nongeneric function called findIndex(ofString:in:), which is given a String value to find and an array of String values within which to find it. The findIndex(ofString:in:) function returns an optional Intvalue, which will be the index of the first matching string in the array if it’s found, or nil if the string can’t be found:

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
   for (index, value) in array.enumerated() {
       if value == valueToFind {
           return index
       }
   }
   return nil
}

The findIndex(ofString:in:) function can be used to find a string value in an array of strings:

let strings = [“cat”, “dog”, “llama”, “parakeet”, “terrapin”]
if let foundIndex = findIndex(ofString: “llama”, in: strings) {
   print(“The index of llama is (foundIndex)”)
}
// Prints “The index of llama is 2”

The principle of finding the index of a value in an array isn’t useful only for strings, however. You can write the same functionality as a generic function by replacing any mention of strings with values of some type Tinstead.

Here’s how you might expect a generic version of findIndex(ofString:in:), called findIndex(of:in:), to be written. Note that the return type of this function is still Int?, because the function returns an optional index number, not an optional value from the array. Be warned, though—this function doesn’t compile, for reasons explained after the example:

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
   for (index, value) in array.enumerated() {
       if value == valueToFind {
           return index
       }
   }
   return nil
}

This function doesn’t compile as written above. The problem lies with the equality check, “if value == valueToFind”. Not every type in Swift can be compared with the equal to operator (==). If you create your own class or structure to represent a complex data model, for example, then the meaning of “equal to” for that class or structure isn’t something that Swift can guess for you. Because of this, it isn’t possible to guarantee that this code will work for every possible type T, and an appropriate error is reported when you try to compile the code.

All is not lost, however. The Swift standard library defines a protocol called Equatable, which requires any conforming type to implement the equal to operator (==) and the not equal to operator (!=) to compare any two values of that type. All of Swift’s standard types automatically support the Equatable protocol.

Any type that is Equatable can be used safely with the findIndex(of:in:) function, because it’s guaranteed to support the equal to operator. To express this fact, you write a type constraint of Equatable as part of the type parameter’s definition when you define the function:

func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
   for (index, value) in array.enumerated() {
       if value == valueToFind {
           return index
       }
   }
   return nil
}

The single type parameter for findIndex(of:in:) is written as T: Equatable, which means “any type T that conforms to the Equatable protocol.”

The findIndex(of:in:) function now compiles successfully and can be used with any type that is Equatable, such as Double or String:

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn’t in the array
let stringIndex = findIndex(of: “Andrea”, in: [“Mike”, “Malcolm”, “Andrea”])
// stringIndex is an optional Int containing a value of 2

Associated Types

참고 사항) protocol에서 generic을 사용해야 하는 경우는 위에서 사용한 방법들과는 다르다. <T>의 방법은 사용 불가능하고 associatedtype을 대신 사용한다. 그 이전에는 typealias를 이용했으나 deprecated되었다.

참고 사항) 

generic protocols with associated type  https://blog.bobthedeveloper.io/generic-protocols-with-associated-type-7e2b6e079ee2

When defining a protocol, it’s sometimes useful to declare one or more associated types as part of the protocol’s definition. An associated type gives a placeholder name to a type that is used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.

Associated Types in Action

Here’s an example of a protocol called Container, which declares an associated type called Item:

protocol Container {
   associatedtype Item
   mutating func append(_ item: Item)
   var count: Int { get }
   subscript(i: Int) -> Item { get }
}

The Container protocol defines three required capabilities that any container must provide:

  • It must be possible to add a new item to the container with an append(_:) method.
  • It must be possible to access a count of the items in the container through a count property that returns an Int value.
  • It must be possible to retrieve each item in the container with a subscript that takes an Int index value.

This protocol doesn’t specify how the items in the container should be stored or what type they’re allowed to be. The protocol only specifies the three bits of functionality that any type must provide in order to be considered a Container. A conforming type can provide additional functionality, as long as it satisfies these three requirements.

Any type that conforms to the Container protocol must be able to specify the type of values it stores. Specifically, it must ensure that only items of the right type are added to the container, and it must be clear about the type of the items returned by its subscript.

To define these requirements, the Container protocol needs a way to refer to the type of the elements that a container will hold, without knowing what that type is for a specific container. The Container protocol needs to specify that any value passed to the append(_:) method must have the same type as the container’s element type, and that the value returned by the container’s subscript will be of the same type as the container’s element type.

To achieve this, the Container protocol declares an associated type called Item, written as associatedtype Item. The protocol doesn’t define what Item is—that information is left for any conforming type to provide.(generic type은 protocol 에서 정의되는 것이 아니고 conform하는 부분에서 수행된다.) Nonetheless, the Item alias provides a way to refer to the type of the items in a Container, and to define a type for use with the append(_:) method and subscript, to ensure that the expected behavior of any Container is enforced.

Here’s a version of the nongeneric IntStack type from earlier, adapted to conform to the Container protocol:

struct IntStack: Container {
   // original IntStack implementation
   var items = [Int]()
   mutating func push(_ item: Int) {
       items.append(item)
   }
   mutating func pop() -> Int {
       return items.removeLast()
   }

   // conformance to the Container protocol

  // 아래에서 설명하겠지만 이렇게 명시적으로 하는 방법이 있지만 암묵적으로도가능하다. 
   typealias Item = Int

   mutating func append(_ item: Int) {
       self.push(item)
   }
   var count: Int {
       return items.count
   }
   subscript(i: Int) -> Int {
       return items[i]
   }
}

The IntStack type implements all three of the Container protocol’s requirements, and in each case wraps part of the IntStack type’s existing functionality to satisfy these requirements.

Moreover, IntStack specifies that for this implementation of Container, the appropriate Item to use is a type of Int. The definition of typealias Item = Int turns the abstract type of Item into a concrete type of Int for this implementation of the Container protocol.

Thanks to Swift’s type inference, you don’t actually need to declare a concrete Item of Int as part of the definition of IntStack. Because IntStack conforms to all of the requirements of the Container protocol, Swift can infer the appropriate Item to use, simply by looking at the type of the append(_:) method’s itemparameter and the return type of the subscript. Indeed, if you delete the typealias Item = Int line from the code above, everything still works, because it’s clear what type should be used for Item. (명시적으로    typealias Item = Int 이렇게 하지 않아도 암묵적으로 swift가 알맞은 type을 찾을수 있다.)  

You can also make the generic Stack type conform to the Container protocol:

struct Stack<Element>: Container {
   // original Stack<Element> implementation
   var items = [Element]()
   mutating func push(_ item: Element) {
       items.append(item)
   }
   mutating func pop() -> Element {
       return items.removeLast()
   }
   // conformance to the Container protocol
   mutating func append(_ item: Element) {
       self.push(item)
   }
   var count: Int {
       return items.count
   }
   subscript(i: Int) -> Element {
       return items[i]
   }
}

This time, the type parameter Element is used as the type of the append(_:) method’s item parameter and the return type of the subscript. Swift can therefore infer that Element is the appropriate type to use as the Item for this particular container.

Extending an Existing Type to Specify an Associated Type

You can extend an existing type to add conformance to a protocol, as described in Adding Protocol Conformance with an Extension. This includes a protocol with an associated type.

Swift’s Array type already provides an append(_:) method, a count property, and a subscript with an Int index to retrieve its elements. These three capabilities match the requirements of the Container protocol. This means that you can extend Array to conform to the Container protocol simply by declaring that Array adopts the protocol. You do this with an empty extension, as described in Declaring Protocol Adoption with an Extension:

extension Array: Container {}

Array’s existing append(_:) method and subscript enable Swift to infer the appropriate type to use for Item, just as for the generic Stack type above. After defining this extension, you can use any Array as a Container.

Using Type Annotations to Constrain an Associated Type

You can add a type annotation to an associated type in a protocol, to require that conforming types satisfy the constraints described by the type annotation. For example, the following code defines a version of Container that requires the items in the container to be equatable.

protocol Container {

   // Item 은 Equatable protocol을 conform하는 것이어야 한다.
   associatedtype Item: Equatable
   mutating func append(_ item: Item)
   var count: Int { get }
   subscript(i: Int) -> Item { get }
}

To conform to this version of Container, the container’s Item type has to conform to the Equatable protocol.

Using Type Annotations to Constrain an Associated Type

You can add a type annotation to an associated type in a protocol, to require that conforming types satisfy the constraints described by the type annotation. For example, the following code defines a version of Container that requires the items in the container to be equatable.

protocol Container {
   associatedtype Item: Equatable
   mutating func append(_ item: Item)
   var count: Int { get }
   subscript(i: Int) -> Item { get }
}

To conform to this version of Container, the container’s Item type has to conform to the Equatable protocol.

Using a Protocol in Its Associated Type’s Constraints

(이해 못했던 부분)

A protocol can appear as part of its own requirements. For example, here’s a protocol that refines the Container protocol, adding the requirement of a suffix(_:) method. The suffix(_:) method returns a given number of elements from the end of the container, storing them in an instance of the Suffix type.

protocol SuffixableContainer: Container {
   associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
   func suffix(_ size: Int) -> Suffix
}

In this protocol, Suffix is an associated type, like the Item type in the Container example above. Suffix has two constraints: It must conform to the SuffixableContainer protocol (the protocol currently being defined), and its Item type must be the same as the container’s Item type. The constraint on Item is a generic whereclause, which is discussed in Associated Types with a Generic Where Clause below.

Here’s an extension of the Stack type from earlier that adds conformance to the SuffixableContainerprotocol:

extension Stack: SuffixableContainer {
   func suffix(_ size: Int) -> Stack {
       var result = Stack()
       for index in (count-size)..<count {
           result.append(self[index])
       }
       return result
   }
   // Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30

In the example above, the Suffix associated type for Stack is also Stack, so the suffix operation on Stackreturns another Stack. Alternatively, a type that conforms to SuffixableContainer can have a Suffix type that’s different from itself—meaning the suffix operation can return a different type. For example, here’s an extension to the nongeneric IntStack type that adds SuffixableContainer conformance, using Stack<Int> as its suffix type instead of IntStack:

extension IntStack: SuffixableContainer {
   func suffix(_ size: Int) -> Stack<Int> {
       var result = Stack<Int>()
       for index in (count-size)..<count {
           result.append(self[index])
       }
       return result
   }
   // Inferred that Suffix is Stack<Int>.
}

Generic Where Clauses

Type constraints, as described in Type Constraints, enable you to define requirements on the type parameters associated with a generic function, subscript, or type.

It can also be useful to define requirements for associated types. You do this by defining a generic where clause. A generic where clause enables you to require that an associated type must conform to a certain protocol, or that certain type parameters and associated types must be the same. A generic where clause starts with the where keyword, followed by constraints for associated types or equality relationships between types and associated types. You write a generic where clause right before the opening curly brace of a type or function’s body.

The example below defines a generic function called allItemsMatch, which checks to see if two Containerinstances contain the same items in the same order. The function returns a Boolean value of true if all items match and a value of false if they don’t.

The two containers to be checked don’t have to be the same type of container (although they can be), but they do have to hold the same type of items. This requirement is expressed through a combination of type constraints and a generic where clause:

// 

Type Constraints 

<C1: Container, C2: Container> 이부분

// 

defining a generic where clause

func allItemsMatch<C1: Container, C2: Container>
   (_ someContainer: C1, _ anotherContainer: C2) -> Bool
   where C1.Item == C2.Item, C1.Item: Equatable {
       
       // Check that both containers contain the same number of items.
       if someContainer.count != anotherContainer.count {
           return false
       }
       
       // Check each pair of items to see if they’re equivalent.
       for i in 0..<someContainer.count {
           if someContainer[i] != anotherContainer[i] {
               return false
           }
       }
       
       // All items match, so return true.
       return true
}

This function takes two arguments called someContainer and anotherContainer. The someContainer argument is of type C1, and the anotherContainer argument is of type C2. Both C1 and C2 are type parameters for two container types to be determined when the function is called.

The following requirements are placed on the function’s two type parameters:

  • C1 must conform to the Container protocol (written as C1: Container).
  • C2 must also conform to the Container protocol (written as C2: Container).
  • The Item for C1 must be the same as the Item for C2 (written as C1.Item == C2.Item).
  • The Item for C1 must conform to the Equatable protocol (written as C1.Item: Equatable).

The first and second requirements are defined in the function’s type parameter list, and the third and fourth requirements are defined in the function’s generic where clause.

These requirements mean:

  • someContainer is a container of type C1.
  • anotherContainer is a container of type C2.
  • someContainer and anotherContainer contain the same type of items.
  • The items in someContainer can be checked with the not equal operator (!=) to see if they’re different from each other.

The third and fourth requirements combine to mean that the items in anotherContainer can also be checked with the != operator, because they’re exactly the same type as the items in someContainer.

These requirements enable the allItemsMatch(_:_:) function to compare the two containers, even if they’re of a different container type.

The allItemsMatch(_:_:) function starts by checking that both containers contain the same number of items. If they contain a different number of items, there’s no way that they can match, and the function returns false.

After making this check, the function iterates over all of the items in someContainer with a forin loop and the half-open range operator (..<). For each item, the function checks whether the item from someContainer isn’t equal to the corresponding item in anotherContainer. If the two items aren’t equal, then the two containers don’t match, and the function returns false.

If the loop finishes without finding a mismatch, the two containers match, and the function returns true.

Here’s how the allItemsMatch(_:_:) function looks in action:

var stackOfStrings = Stack<String>()
stackOfStrings.push(“uno”)
stackOfStrings.push(“dos”)
stackOfStrings.push(“tres”)
var arrayOfStrings = [“uno”, “dos”, “tres”]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
   print(“All items match.”)
} else {
   print(“Not all items match.”)
}
// Prints “All items match.”

The example above creates a Stack instance to store String values, and pushes three strings onto the stack. The example also creates an Array instance initialized with an array literal containing the same three strings as the stack. Even though the stack and the array are of a different type, they both conform to the Container protocol, and both contain the same type of values. You can therefore call the allItemsMatch(_:_:)function with these two containers as its arguments. In the example above, the allItemsMatch(_:_:) function correctly reports that all of the items in the two containers match.

Extensions with a Generic Where Clause

You can also use a generic where clause as part of an extension. The example below extends the generic Stack structure from the previous examples to add an isTop(_:) method.

// Element 는 

Equatable protocol을 conform해야만 한다.

extension Stack where Element: Equatable {
   func isTop(_ item: Element) -> Bool {

       // 맨위 아이템이 있는지 확인
       guard let topItem = items.last else {
           return false
       }
       return topItem == item
   }
}

This new isTop(_:) method first checks that the stack isn’t empty, and then compares the given item against the stack’s topmost item. If you tried to do this without a generic where clause, you would have a problem: The implementation of isTop(_:) uses the == operator, but the definition of Stack doesn’t require its items to be equatable, so using the == operator results in a compile-time error. Using a generic where clause lets you add a new requirement to the extension, so that the extension adds the isTop(_:) method only when the items in the stack are equatable.

Here’s how the isTop(_:) method looks in action:

if stackOfStrings.isTop(“tres”) {
   print(“Top element is tres.”)
} else {
   print(“Top element is something else.”)
}
// Prints “Top element is tres.”

If you try to call the isTop(_:) method on a stack whose elements aren’t equatable, you’ll get a compile-time error.

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()

// 임의로 

Equatable 를 conform 하지않는 요소 만들기
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // Error

You can use a generic where clause with extensions to a protocol. The example below extends the Containerprotocol from the previous examples to add a startsWith(_:) method.

extension Container where Item: Equatable {
   func startsWith(_ item: Item) -> Bool {
       return count >= 1 && self[0] == item
   }
}

The startsWith(_:) method first makes sure that the container has at least one item, and then it checks whether the first item in the container matches the given item. This new startsWith(_:) method can be used with any type that conforms to the Container protocol, including the stacks and arrays used above, as long as the container’s items are equatable.

if [9, 9, 9].startsWith(42) {
   print(“Starts with 42.”)
} else {
   print(“Starts with something else.”)
}
// Prints “Starts with something else.”

The generic where clause in the example above requires Item to conform to a protocol, but you can also write a generic where clauses that require Item to be a specific type. For example:

extension Container where Item == Double {
   func average() -> Double {
       var sum = 0.0
       for index in 0..<count {
           sum += self[index]
       }
       return sum / Double(count)
   }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints “648.9”

This example adds an average() method to containers whose Item type is Double. It iterates over the items in the container to add them up, and divides by the container’s count to compute the average. It explicitly converts the count from Int to Double to be able to do floating-point division.

You can include multiple requirements in a generic where clause that is part of an extension, just like you can for a generic where clause that you write elsewhere. Separate each requirement in the list with a comma.

Associated Types with a Generic Where Clause

You can include a generic where clause on an associated type. For example, suppose you want to make a version of Container that includes an iterator, like what the Sequence protocol uses in the standard library. Here’s how you write that:

protocol Container {
   associatedtype Item
   mutating func append(_ item: Item)
   var count: Int { get }
   subscript(i: Int) -> Item { get }
   
   associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
   func makeIterator() -> Iterator
}

The generic where clause on Iterator requires that the iterator must traverse over elements of the same item type as the container’s items, regardless of the iterator’s type. The makeIterator() function provides access to a container’s iterator.

For a protocol that inherits from another protocol, you add a constraint to an inherited associated type by including the generic where clause in the protocol declaration. For example, the following code declares a ComparableContainer protocol that requires Item to conform to Comparable:

protocol ComparableContainer: Container where Item: Comparable { }

Generic Subscripts

Subscripts can be generic, and they can include generic where clauses. You write the placeholder type name inside angle brackets after subscript, and you write a generic where clause right before the opening curly brace of the subscript’s body. For example:

extension Container {
   subscript<Indices: Sequence>(indices: Indices) -> [Item]
       where Indices.Iterator.Element == Int {
           var result = [Item]()
           for index in indices {
               result.append(self[index])
           }
           return result
   }
}

This extension to the Container protocol adds a subscript that takes a sequence of indices and returns an array containing the items at each given index. This generic subscript is constrained as follows:

The generic parameter Indices in angle brackets has to be a type that conforms to the Sequenceprotocol from the standard library.

The subscript takes a single parameter, indices, which is an instance of that Indices type.

The generic where clause requires that the iterator for the sequence must traverse over elements of type Int. This ensures that the indices in the sequence are the same type as the indices used for a container.

Taken together, these constraints mean that the value passed for the indices parameter is a sequence of integers.

All about protocols in swift – Abhimuralidharan – Medium

Extensions

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code (known as retroactive modeling). 

Extensions in Swift can:

  • Add computed instance properties and computed type properties (stored property는 추가 할수 없다.)
  • Define instance methods and type methods
  • Provide new initializers
  • Define subscripts
  • Define and use new nested types
  • Make an existing type conform to a protocol

In Swift, you can even extend a protocol to provide implementations of its requirements or add additional functionality that conforming types can take advantage of. For more details, see Protocol Extensions.

NOTE

Extensions can add new functionality to a type, but they cannot override existing functionality.

Extension Syntax

Declare extensions with the extension keyword:

extension SomeType {
   // new functionality to add to SomeType goes here
}

An extension can extend an existing type to make it adopt one or more protocols. To add protocol conformance, you write the protocol names the same way as you write them for a class or structure:

extension SomeType: SomeProtocol, AnotherProtocol {
   // implementation of protocol requirements goes here
}

Adding protocol conformance in this way is described in Adding Protocol Conformance with an Extension.

An extension can be used to extend an existing generic type, as described in Extending a Generic Type. You can also extend a generic type to conditionally add functionality, as described in Extensions with a Generic Where Clause.

NOTE

If you define an extension to add new functionality to an existing type, the new functionality will be available on all existing instances of that type, even if they were created before the extension was defined

Computed Properties

Extensions can add computed instance properties and computed type properties to existing types. This example adds five computed instance properties to Swift’s built-in Double type, to provide basic support for working with distance units:

extension Double {
   var km: Double { return self * 1_000.0 }
   var m: Double { return self }
   var cm: Double { return self / 100.0 }
   var mm: Double { return self / 1_000.0 }
   var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print(“One inch is (oneInch) meters”)
// Prints “One inch is 0.0254 meters”
let threeFeet = 3.ft
print(“Three feet is (threeFeet) meters”)
// Prints “Three feet is 0.914399970739201 meters”

let aMarathon = 42.km + 195.m
print(“A marathon is (aMarathon) meters long”)
// Prints “A marathon is 42195.0 meters long”

NOTE

Extensions can add new computed properties, but they cannot add stored properties, or add property observers to existing properties.

Initializers

Extensions can add new initializers to existing types. 

Extensions can add new convenience initializers to a class, but they cannot add new designated initializers or deinitializers to a class. Designated initializers and deinitializers must always be provided by the original class implementation.

NOTE

If you use an extension to add an initializer to a value type that provides default values for all of its stored properties and does not define any custom initializers, you can call the default initializer and memberwise initializer for that value type from within your extension’s initializer.

This would not be the case if you had written the initializer as part of the value type’s original implementation, as described in Initializer Delegation for Value Types.

struct Size {
   var width = 0.0, height = 0.0
}
struct Point {
   var x = 0.0, y = 0.0
}
struct Rect {
   var origin = Point()
   var size = Size()
}

let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
                         size: Size(width: 5.0, height: 5.0))

You can extend the Rect structure to provide an additional initializer that takes a specific center point and size:

extension Rect {
   init(center: Point, size: Size) {
       let originX = center.x – (size.width / 2)
       let originY = center.y – (size.height / 2)
       self.init(origin: Point(x: originX, y: originY), size: size)
   }
}

This new initializer starts by calculating an appropriate origin point based on the provided center point and size value. The initializer then calls the structure’s automatic memberwise initializer init(origin:size:), which stores the new origin and size values in the appropriate properties:

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                     size: Size(width: 3.0, height: 3.0))
// centerRect’s origin is (2.5, 2.5) and its size is (3.0, 3.0)

Methods

Extensions can add new instance methods and type methods to existing types. The following example adds a new instance method called repetitions to the Int type:

extension Int {
   func repetitions(task: () -> Void) {

      // 0과 self사이의 정수를 인덱스로 순환

      // 참고) https://stackoverflow.com/a/26083896

       for _ in 0..<self {
           task()
       }
   }
}

3.repetitions {
   print(“Hello!”)
}
// Hello!
// Hello!
// Hello!

Mutating Instance Methods

(참고사항 tumblr #swift #mutating #enum: 

Structures and enumerations are value types. By default, the properties of a value type cannot be modified from within its instance methods.)

Instance methods added with an extension can also modify (or mutate) the instance itself. Structure and enumeration methods that modify self or its properties must mark the instance method as mutating, just like mutating methods from an original implementation.

extension Int {
   mutating func square() {
       self = self * self
   }
}
var someInt = 3
someInt.square()
// someInt is now 9

Subscripts

Extensions can add new subscripts to an existing type. 

123456789[0] returns 9

123456789[1] returns 8

extension Int {
   subscript(digitIndex: Int) -> Int {
       var decimalBase = 1
       for _ in 0..<digitIndex {
           decimalBase *= 10
       }
       return (self / decimalBase) % 10
   }
}
746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7

746381295[9]
// returns 0, as if you had requested:
0746381295[9]

Nested Types

Extensions can add new nested types to existing classes, structures, and enumerations:

extension Int {
   enum Kind {
       case negative, zero, positive
   }
   var kind: Kind {
       switch self {
       case 0:
           return .zero
       case let x where x > 0:
           return .positive
       default:
           return .negative
       }
   }
}

func printIntegerKinds(_ numbers: [Int]) {
   for number in numbers {
       switch number.kind {
       case .negative:
           print(“- ”, terminator: “”)
       case .zero:
           print(“0 ”, terminator: “”)
       case .positive:
           print(“+ ”, terminator: “”)
       }
   }
   print(“”)
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// Prints “+ + – 0 – 0 + ”

NOTE

number.kind is already known to be of type Int.Kind. Because of this, all of the Int.Kind case values can be written in shorthand form inside the switch statement, such as .negative rather than Int.Kind.negative

Nested Types

To nest a type within another type, write its definition within the outer braces of the type it supports. Types can be nested to as many levels as are required.

Nested Types in Action

struct BlackjackCard {
   
   // nested Suit enumeration
   enum Suit: Character {
       case spades = “♠”, hearts = “♡”, diamonds = “♢”, clubs = “♣”
   }
   
   // nested Rank enumeration
   enum Rank: Int {
       case two = 2, three, four, five, six, seven, eight, nine, ten
       case jack, queen, king, ace
       struct Values {
           let first: Int, second: Int?
       }
       var values: Values {
           switch self {
           case .ace:
               return Values(first: 1, second: 11)
           case .jack, .queen, .king:
               return Values(first: 10, second: nil)
           default:
               return Values(first: self.rawValue, second: nil)
           }
       }
   }
   
   // BlackjackCard properties and methods
   let rank: Rank, suit: Suit
   var description: String {
       var output = “suit is (suit.rawValue),”
       output += “ value is (rank.values.first)”
       if let second = rank.values.second {
           output += “ or (second)”
       }
       return output
   }
}

let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print(“theAceOfSpades: (theAceOfSpades.description)”)
// Prints “theAceOfSpades: suit is ♠, value is 1 or 11”

let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// heartsSymbol is “♡”

Type Casting

Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy.

Type casting in Swift is implemented with the is and as operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type.

Defining a Class Hierarchy for Type Casting

class MediaItem {
   var name: String
   init(name: String) {
       self.name = name
   }
}

class Movie: MediaItem {
   var director: String
   init(name: String, director: String) {
       self.director = director
       super.init(name: name)
   }
}
class Song: MediaItem {
   var artist: String
   init(name: String, artist: String) {
       self.artist = artist
       super.init(name: name)
   }
}

The final snippet creates a constant array called library, which contains two Movie instances and three Song instances. The type of the library array is inferred by initializing it with the contents of an array literal. Swift’s type checker is able to deduce that Movie and Song have a common superclass of MediaItem, and so it infers a type of [MediaItem] for the library array:

let library = [
   Movie(name: “Casablanca”, director: “Michael Curtiz”),
   Song(name: “Blue Suede Shoes”, artist: “Elvis Presley”),
   Movie(name: “Citizen Kane”, director: “Orson Welles”),
   Song(name: “The One And Only”, artist: “Chesney Hawkes”),
   Song(name: “Never Gonna Give You Up”, artist: “Rick Astley”)
]
// the type of “library” is inferred to be [MediaItem]

The items stored in library are still Movie and Song instances behind the scenes. However, if you iterate over the contents of this array, the items you receive back are typed as MediaItem, and not as Movie or Song. In order to work with them as their native type, you need to check their type, or downcast them to a different type, as described below.

Checking Type

Use the type check operator (is) to check whether an instance is of a certain subclass type. The type check operator returns true if the instance is of that subclass type and false if it is not.

var movieCount = 0
var songCount = 0
for item in library {
   if item is Movie {
       movieCount += 1
   } else if item is Song {
       songCount += 1
   }
}
print(“Media library contains (movieCount) movies and (songCount) songs”)
// Prints “Media library contains 2 movies and 3 songs”

Downcasting

A constant or variable of a certain class type may actually refer to an instance of a subclass behind the scenes. Where you believe this is the case, you can try to downcast to the subclass type with a type cast operator (as? or as!).

Because downcasting can fail, the type cast operator comes in two different forms. The conditional form, as?, returns an optional value of the type you are trying to downcast to. The forced form, as!, attempts the downcast and force-unwraps the result as a single compound action.

Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.

Use the forced form of the type cast operator (as!) only when you are sure that the downcast will always succeed. This form of the operator will trigger a runtime error if you try to downcast to an incorrect class type.

for item in library {
   if let movie = item as? Movie {
       print(“Movie: (movie.name), dir. (movie.director)”)
   } else if let song = item as? Song {
       print(“Song: (song.name), by (song.artist)”)
   }
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley

Type Casting for Any and AnyObject

Swift provides two special types for working with nonspecific types:

  • Any can represent an instance of any type at all, including function types.
  • AnyObject can represent an instance of any class type.

var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append(“hello”)
things.append((3.0, 5.0))
things.append(Movie(name: “Ghostbusters”, director: “Ivan Reitman”))
things.append({ (name: String) -> String in “Hello, (name)” })

(참고사항 if case https://stackoverflow.com/a/37888514)

(참고사항

Optional Pattern

https://stackoverflow.com/a/37738664)

for thing in things {
   switch thing {
   case 0 as Int:
       print(“zero as an Int”)
   case 0 as Double:
       print(“zero as a Double”)
   case let someInt as Int:
       print(“an integer value of (someInt)”)
   case let someDouble as Double where someDouble > 0:
       print(“a positive double value of (someDouble)”)
   case is Double:
       print(“some other double value that I don’t want to print”)
   case let someString as String:
       print(“a string value of “(someString)””)
   case let (x, y) as (Double, Double):
       print(“an (x, y) point at (x), (y)”)
   case let movie as Movie:
       print(“a movie called (movie.name), dir. (movie.director)”)
   case let stringConverter as (String) -> String:
       print(stringConverter(“Michael”))
   default:
       print(“something else”)
   }
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of “hello”
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael

NOTE

The Any type represents values of any type, including optional types. Swift gives you a warning if you use an optional value where a value of type Any is expected. If you really do need to use an optional value as an Any value, you can use the as operator to explicitly cast the optional to Any, as shown below.

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning