Chapter    11

Generic Programming

Up to this point in this book, function and method parameter and return value types have been explicitly specified. Overloading offers a way to define multiple functions that share the same signature except for varying parameter and/or return value types. And protocols provide for declaring a set of requirements that an adopting type must contractually fulfill. Yet there is one more feature of Swift that may at first feel like a mere combination of aforementioned capabilities; however, in due time, it may prove to be one the most powerful features of the language: generics. As much as the Swift standard library is made up of protocols and extensions, generic types are more abundantly used than the other two combined. Objective-C does not facilitate true generic programming. As such, this chapter will focus on introducing and explaining how to use Swift generics to write more powerful, flexible, and reusable code, without sacrificing type safety.

Specific versus Generic

Stored value types are either implicitly inferred or explicitly stated. Function parameter and return value types have, to this point, also been type-specific. And although the use of Any or AnyObject (or its type alias, AnyClass) can provide a way of being nonspecific about type, working with such values can quickly lead to code that is littered with type checking and casting, and type safety is generally lost. Generics, by contrast, offer a clean way to write code that defers type specification until instantiation or use—that is often more succinct than comparative nongeneric code—and additional requirements may optionally be defined that a generic type must abide by. What’s more, generics preserve type safety, one of the hallmarks of Swift.

Forms of generic syntax were briefly demonstrated in Chapter 2 with the example Optional<String>, and in Chapter 3, wherein an array and dictionary were defined using the syntax Array<Type> and Dictionary<KeyType, ValueType>, respectively. In fact, Swift Array and Dictionary types are actually generic collections in which the value type that an array can hold, or the key and value types that a dictionary can hold, are specified in angle brackets immediately following the collection type name:

var intArray: Array<Int> // An array of type Int
var intStringDictionary: Dictionary<Int, String> // A dictionary that can hold key-value pairs of type Int and String, respectively

The types are explicitly specified in the previous example, yet the types could also have been specified using a protocol, for example, expressing that any type that conforms to that protocol can be added as a value to the collection:

class CustomClass: Printable {
  var description = "CustomClass"
}
struct CustomStruct: Printable {
  var description = "CustomClass"
}
enum CustomEnum: String, Printable {
  case One = "One"
  case Two = "Two"
  var description: String {
    return "CustomEnum.(self.rawValue)"
  }
}
let customClass = CustomClass()
let customStruct = CustomStruct()
let customEnum = CustomEnum.One
var arrayOfPrintableItems: Array<Printable> = [customClass, customStruct, customEnum]
var dictionaryOfPrintableItems: Dictionary<Int, Printable> = [1: customClass, 2: customStruct, 3: customEnum]

Syntax

Going beyond the precursor examples in the last section, custom functions (including initializers and methods), types (including classes, structures, and enumerations), and protocols can be defined using generics, optionally with additional requirements placed on the generic types.

Functions and Types

The syntax for defining functions and types using generics is to enclose a generic parameter list in angle brackets immediately after the name. A generic parameter list is made up of one or more type parameters that each act as an abstract placeholder, each also optionally paired with a constraint. A placeholder is simply a name, typically T to represent the first generic type, U to represent the second generic type, and so on, but any valid type name can be used.

Note  A generic type parameter name must follow the same rules as type names in general (see the Naming section in Chapter 2 for details). That said, Apple suggests using a single uppercase letter character, except in cases in which longer, more descriptive names are necessary.

Once defined, a generic type parameter can be used throughout the definition of the function or type, and all references to a generic type parameter anywhere in the definition will be of the same type represented by the generic type parameter when the function is called or type is instantiated.

A constraint can be used to indicate that the generic type inherits from a specified superclass or adopts a protocol or protocol composition, using the same syntax as defining regular class inheritance or protocol adoption. Table 11-1 provides basic syntax for defining generic functions and types.

Table 11-1. Basic syntax for defining generic functions and types in Swift

Function of type T that takes a parameter of type T

func functionName<T>(paramOneName: T) {
      statements
}

Function of a type T that subclasses SomeClass and takes a parameter of type T

func functionName<T: SomeClass>(paramOneName: T) {
       statements
}

Function of a type T that adopts ProtocolName and takes a parameter of type T

func functionName<T: ProtocolName>(paramOneName: T) {
       statements
}

Function of types T and U that takes parameters of types T and U and returns a value of type T

func functionName<T, U>(paramOneName: T, paramTwoName: U) -> T {
       statements
       return valueOfTypeT
}

Class of a type T that subclasses ParentClass, with an array property of type T

class ClassName<T: ParentClass> {
     var propertyName: [T]!
}

Structure of a type T that adopts ProtocolName with a property of type T

struct StructName<T: ProtocolName> {
     var propertyName: T
}

Enumeration of type T that has an associated value of type T

enum EnumName<T> {
     case None
     case Some(T)
}

Where Clauses and Protocol Associated Types

In addition to specifying class inheritance or protocol adoption constraints, a generic parameter list can also contain where clauses that further constrain one or more generic type parameters. A where clause is defined by writing the where keyword after the generic parameter list, followed by one or more boolean equality checkes.

Protocols can also define generic associated types using typealias definitions within the protocol definition. The actual types represented by the type aliases are determined when the protocol is adopted, yet the type adopting the protocol can also utilize generics in conforming to the protocol, which has the effect of further deferring determination of the actual types until instantiation or use.

Table 11-2 provides basic syntax for defining constraints using where clauses, defining protocols with generic associated types, and combining the use of where clauses with protocol generic associated types.

Table 11-2. Basic syntax for defining generic constraints using where clauses and defining protocols with generic associated types in Swift

Function with where clause contraining the generic type T to be of a type that adopts Equatable, with two parameters each of type T

func functionName<T where T: Equatable> (paramOne: T, paramTwo: T) {
     statements
}

Protocol with generic associated type V and property of type V

protocol ProtocolOne {
   typealias V
   var propertyName: V { get set }
}

Function of types T and U, each conforming to ProtocolOne, with where clause constraining type V of T and type V of U to be the same type

func functionName<T: ProtocolOne, U: ProtocolOne where T.V == U.V> (paramOne: T, paramTwo: U) {
     statements
}

Usage Examples

The following example demonstrates a class Holder of a generic type T is defined with an items array property of type T. Holder also defines a subscript to provide a convenient way to print out the type name that is determined at point of instantiation of a Holder instance, along with the value at the specified index. Holder can be instantiated with any type and hold that type in its items array property, as demonstrated with instances of Holder created using both value and reference types:

class CustomClass: DebugPrintable {
  var debugDescription = "CustomClass"
}
class Holder<T> {
  var items: [T]
  init(_ items: [T]) {
    self.items = items
  }
  subscript(index: Int) -> String {
    let typeName = _stdlib_getDemangledTypeName(items[index])
    return "(typeName): (items[index])"
  }
}
let intHolder = Holder([1, 2, 3])
println(intHolder.items) // Prints "[1, 2, 3]"
println(intHolder[0]) // Prints "Swift.Int: 1"
let doubleHolder = Holder([1.1, 2.2, 3.3])
println(doubleHolder.items) // Prints "[1.1, 2.2, 3.3]"
println(doubleHolder[0]) // Prints "Swift.Double: 1.1"
let stringHolder = Holder(["One", "Two", "Three"])
println(stringHolder.items) // Prints "[One, Two, Three]"
println(stringHolder[0]) // Prints "Swift.String: One"
let customClassHolder = Holder([CustomClass(), CustomClass(), CustomClass()])
println(customClassHolder.items) // Prints "[CustomClass, CustomClass, CustomClass]"
println(customClassHolder.items[0]) // Prints "CustomClass"

Note  The DebugPrintable protocol can be adopted by types that want to customize their textual representation for debugging purposes. The protocol requires implementing the read-only variable debugDescription. This is similar to overriding –[NSObject description] in Objective-C. At the time of this writing, Swift playgrounds do not properly print out the value of debugDescription; however, the value is correctly printed out if run in an Xcode project.

This next example demonstrates use of generics with additional constraints imposed on them. Specifically, a TrailMix structure is defined of a type T that adopts the Edible protocol, where that type T also adopts the Printable protocol, and TrailMix itself also adopts the Printable protocol:

protocol Edible {
  var name: String { get }
  var caloriesPerServing: Int { get }
}
struct Ingredient: Edible, Printable {
  let name: String
  let caloriesPerServing: Int
  var description: String {
    return "(name) ((caloriesPerServing) calories per serving)"
  }
  init(_ name: String, _ caloriesPerServing: Int) {
    self.name = name
    self.caloriesPerServing = caloriesPerServing
  }
}
struct TrailMix<T: Edible where T: Printable>: Printable {
  var ingredients: [T]
  var description: String {
    var caloriesPerServing = 0
    var description = ""
      let count = countElements(ingredients)
      for ingredient in ingredients {
        caloriesPerServing += ingredient.caloriesPerServing / count
        description += "image (ingredient.description) "
      }
      return "Trail mix, calories per serving: (caloriesPerServing) Ingredients: (description)"
  }
  init(_ ingredients: [T]) {
    self.ingredients = ingredients
  }
}
let chocolateChips = Ingredient("Chocolate chips", 201)
let driedFruit = Ingredient("Dried fruit", 85)
let granola = Ingredient("Granola", 113)
let mixedNuts = Ingredient("Mixed nuts", 219)
let miniPretzels = Ingredient("Mini pretzels", 110)
var trailMix = TrailMix([chocolateChips, driedFruit, granola, mixedNuts, miniPretzels])
println(trailMix.description)
/* Prints:
Trail mix, calories per serving: 144
Ingredients:
image Chocolate chips (201 calories per serving)
image Dried fruit (85 calories per serving)
image Granola (113 calories per serving)
image Mixed nuts (219 calories per serving)
image Mini pretzels (110 calories per serving)
*/

As an example of using generics with enumerations, the following example reimplements the Optional enumeration type from the Swift standard library:

enum OptionalType<T> {
  case None
  case Some(T)
  init() {
    self = .None
  }
}
var someOptionalValue = OptionalType<String>()
switch someOptionalValue {
case .None:
  println("No value")
case .Some(let value):
  println(value)
}
// Prints "No value"
someOptionalValue = .Some("Hello world!")
switch someOptionalValue {
case .None:
  println("No value")
case .Some(let value):
  println(value)
}
// Prints "Hello world!"

For a final example, a protocol HasMiddleValue is defined with a generic associated type T, requiring implementation of a middle() method that returns an array of type T. The Array type is then extended to adopt HasMiddleValue by implementing middle() to return an array containing the middle one or two items of an array of type T, based on whether the count of the array is odd or even, respectively:

protocol HasMiddleValue {
  typealias T
  func middle() -> [T]?
}
extension Array: HasMiddleValue {
  func middle() -> [T]? {
    if self.count > 0 {
      var middleIndex = self.count / 2 - 1
      var middleArray = [T]()
      if self.count % 2 == 0 {
        let middleIndex1 = middleIndex
        let middleIndex2 = middleIndex1 + 1
        middleArray = [self[middleIndex1], self[middleIndex2]]
      } else {
        middleArray = [self[middleIndex + 1]]
      }
      return middleArray
    }
    return nil
  }
}
let arrayOfEvenNumberOfInts = [1, 2, 3, 4, 5]
println(arrayOfEvenNumberOfInts.middle()!) // Prints "[3]"
let arrayOfOddNumberOfStrings = ["A", "B", "C", "D"]
println(arrayOfOddNumberOfStrings.middle()!) // Prints "[B, C]"

Summary

This chapter introduced generic programming in Swift and provided examples of using generics with functions, types, and protocols.

I personally thank you for reading this book, and am honored by this opportunity to help you get started programming in Swift. Please share feedback or ask questions via Twitter (@scotteg). Good luck!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.55.151