Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

The_Swift_Programming_Language

.pdf
Скачиваний:
24
Добавлен:
18.03.2015
Размер:
5 Мб
Скачать
☆

8}

9 }

46381295[0]

/returns 5 46381295[1]

/returns 9 46381295[2]

/returns 2 46381295[8]

/returns 7

If the Int value does not have enough digits for the requested index, the subscript implementation returns 0, as if the number had been padded with zeroes to the left:

1

2

3

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:

1 extension Character {

2enum Kind {

3case Vowel, Consonant, Other

4}

5var kind: Kind {

6switch String(self).lowercaseString {

7case "a", "e", "i", "o", "u":

8return .Vowel

9 case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":

return .Consonant default:

"Hello".

return .Other

}

}

This example adds a new nested enumeration to Character. This enumeration, called Kind, expresses the kind of letter that a particular character represents. Specifically, it expresses whether the character is a vowel or a consonant in a standard Latin script (without taking into account accents or regional variations), or whether it is another kind of character.

This example also adds a new computed instance property to Character, called kind, which returns the appropriate Kind enumeration member for that character.

The nested enumeration can now be used with Character values:

1 func printLetterKinds(word: String) {

2println("'\(word)' is made up of the following kinds of letters:")

3for character in word {

4switch character.kind {

5case .Vowel:

6print("vowel ")

7case .Consonant:

8print("consonant ")

9 case .Other:

print("other ")

}

}

print("\n")

rintLetterKinds("Hello")

/'Hello' is made up of the following kinds of letters:

/consonant vowel consonant consonant vowel

This function, printLetterKinds, takes an input String value and iterates over its characters. For each character, it considers the kind computed property for that character, and prints an appropriate description of that kind. The printLetterKinds function can then be called to print the kinds of letters in an entire word, as shown here for the word

N O T E

character.kind is already known to be of type Character.Kind. Because of this, all of the Character.Kind member values can be written in shorthand form inside the switch statement, such as .Vowel rather than Character.Kind.Vowel.

Protocols

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol doesn’t actually provide an implementation for any of these requirements—it only describes what an implementation will look like. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.

Protocols can require that conforming types have specific instance properties, instance methods, type methods, operators, and subscripts.

Protocol Syntax

You define protocols in a very similar way to classes, structures, and enumerations:

1

2

3

protocol SomeProtocol {

// protocol definition goes here

}

Custom types state that they adopt a particular protocol by placing the protocol’s name after the type’s name, separated by a colon, as part of their definition. Multiple protocols can be listed, and are separated by commas:

1

2

3

struct SomeStructure: FirstProtocol, AnotherProtocol {

// structure definition goes here

}

If a class has a superclass, list the superclass name before any protocols it adopts, followed by a comma:

1

2

3

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {

// class definition goes here

}

Property Requirements

A protocol can require any conforming type to provide an instance property or type property with a particular name and type. The protocol doesn’t specify whether the property should be a stored property or a computed property—it only specifies the required property name and type. The protocol also specifies whether each property must be gettable or gettable and settable.

If a protocol requires a property to be gettable and settable, that property requirement cannot be fulfilled by a constant stored property or a read-only computed property. If the protocol only requires a property to be gettable, the requirement can be satisfied by any kind of property, and it is valid for it also to be settable if this is useful for your own code.

Property requirements are always declared as variable properties, prefixed with the var keyword. Gettable and settable properties are indicated by writing { get set } after their type declaration, and gettable properties are indicated by writing { get }.

1

2

3

4

protocol SomeProtocol {

var mustBeSettable: Int { get set }

var doesNotNeedToBeSettable: Int { get }

}

Always prefix type property requirements with the class keyword when you define them in a protocol. This is true even though type property requirements are prefixed with the static keyword when implemented by a structure or enumeration:

1

2

3

protocol AnotherProtocol {

class var someTypeProperty: Int { get set }

}

Here’s an example of a protocol with a single instance property requirement:

1

2

3

protocol FullyNamed {

var fullName: String { get }

}

The FullyNamed protocol defines any kind of thing that has a fully-qualified name. It doesn’t specify what kind of thing it must be—it only specifies that the thing must be able to provide a full name for itself. It specifies this requirement by stating that any FullyNamed type must have a gettable instance property called fullName, which is of type

String.

Here’s an example of a simple structure that adopts and conforms to the FullyNamed protocol:

1

2

3

4

5

struct Person: FullyNamed {

var fullName: String

}

let john = Person(fullName: "John Appleseed")

// john.fullName is "John Appleseed"

This example defines a structure called Person, which represents a specific named person. It states that it adopts the FullyNamed protocol as part of the first line of its definition.

Each instance of Person has a single stored property called fullName, which is of type String. This matches the single requirement of the FullyNamed protocol, and means that Person has correctly conformed to the protocol. (Swift reports an error at compile-time if a protocol requirement is not fulfilled.)

Here’s a more complex class, which also adopts and conforms to the FullyNamed protocol:

1 class Starship: FullyNamed {

2var prefix: String?

3var name: String

4init(name: String, prefix: String? = nil) {

5self.name = name

6self.prefix = prefix

7}

8var fullName: String {

9 return (prefix ? prefix! + " " : "") + name

}

ar ncc1701 = Starship(name: "Enterprise", prefix: "USS") / ncc1701.fullName is "USS Enterprise"

This class implements the fullName property requirement as a computed read-only property for a starship. Each Starship class instance stores a mandatory name and an optional prefix. The fullName property uses the prefix value if it exists, and prepends it to the beginning of name to create a full name for the starship.

Method Requirements

Protocols can require specific instance methods and type methods to be implemented by conforming types. These methods are written as part of the protocol’s definition in exactly the same way as for normal instance and type methods, but without curly braces or a method body. Variadic parameters are allowed, subject to the same rules as for normal methods.

N O T E

Protocols use the same syntax as normal methods, but are not allowed to specify default values for method parameters.

As with type property requirements, you always prefix type method requirements with the class keyword when they are defined in a protocol. This is true even though type method requirements are prefixed with the static keyword when implemented by a structure or enumeration:

1

2

3

protocol SomeProtocol {

class func someTypeMethod()

}

The following example defines a protocol with a single instance method requirement:

1

2

3

protocol RandomNumberGenerator {

func random() -> Double

}

This protocol, RandomNumberGenerator, requires any conforming type to have an instance method called random, which returns a Double value whenever it is called. (Although it is not specified as part of the protocol, it is assumed that this value will be a number between 0.0 and 1.0 inclusive.)

The RandomNumberGenerator protocol does not make any assumptions about how each random number will be generated—it simply requires the generator to provide a standard way to generate a new random number.

Here’s an implementation of a class that adopts and conforms to the RandomNumberGenerator protocol. This class implements a pseudorandom number generator algorithm known as a linear congruential generator:

1

2

3

class LinearCongruentialGenerator: RandomNumberGenerator {

var lastRandom = 42.0

let m = 139968.0

4let a = 3877.0

5let c = 29573.0

6func random() -> Double {

7lastRandom = ((lastRandom * a + c) % m)

8return lastRandom / m

9}

t generator = LinearCongruentialGenerator() rintln("Here's a random number: \(generator.random())")

/prints "Here's a random number: 0.37464991998171" rintln("And another one: \(generator.random())")

/prints "And another one: 0.729023776863283"

Mutating Method Requirements

It is sometimes necessary for a method to modify (or mutate) the instance it belongs to. For instance methods on value types (that is, structures and enumerations) you place the mutating keyword before a method’s func keyword to indicate that the method is allowed to modify the instance it belongs to and/or any properties of that instance. This process is described in Modifying Value Types from Within Instance Methods.

If you define a protocol instance method requirement that is intended to mutate instances of any type that adopts the protocol, mark the method with the mutating keyword as part of the protocol’s definition. This enables structures and enumerations to adopt the protocol and satisfy that method requirement.

N O T E

If you mark a protocol instance method requirement as mutating, you do not need to write the mutating keyword when writing an implementation of that method for a class. The mutating keyword is only used by structures and enumerations.

The example below defines a protocol called Togglable, which defines a single instance method requirement called toggle. As its name suggests, the toggle method is intended to toggle or invert the state of any conforming type, typically by modifying a property of that type.

The toggle method is marked with the mutating keyword as part of the Togglable protocol definition, to indicate that the method is expected to mutate the state of a conforming

instance when it is called:

1

2

3

protocol Togglable {

mutating func toggle()

}

If you implement the Togglable protocol for a structure or enumeration, that structure or enumeration can conform to the protocol by providing an implementation of the toggle method that is also marked as mutating.

The example below defines an enumeration called OnOffSwitch. This enumeration toggles between two states, indicated by the enumeration cases On and Off. The enumeration’s toggle implementation is marked as mutating, to match the Togglable protocol’s requirements:

1 enum OnOffSwitch: Togglable {

2case Off, On

3mutating func toggle() {

4switch self {

5case Off:

6self = On

7case On:

8self = Off

9 }

}

ar lightSwitch = OnOffSwitch.Off ghtSwitch.toggle()

/ lightSwitch is now equal to .On

Protocols as Types

Protocols do not actually implement any functionality themselves. Nonetheless, any protocol you create will become a fully-fledged type for use in your code.

Because it is a type, you can use a protocol in many places where other types are allowed, including:

As a parameter type or return type in a function, method, or initializer As the type of a constant, variable, or property

As the type of items in an array, dictionary, or other container

N O T E

Because protocols are types, begin their names with a capital letter (such as FullyNamed and RandomNumberGenerator) to match the names of other types in Swift (such as Int, String, and Double).

Here’s an example of a protocol used as a type:

1 class Dice {

2let sides: Int

3let generator: RandomNumberGenerator

4init(sides: Int, generator: RandomNumberGenerator) {

5self.sides = sides

6self.generator = generator

7}

8func roll() -> Int {

9 return Int(generator.random() * Double(sides)) + 1

}

This example defines a new class called Dice, which represents an n-sided dice for use in a board game. Dice instances have an integer property called sides, which represents how many sides they have, and a property called generator, which provides a random number generator from which to create dice roll values.

The generator property is of type RandomNumberGenerator. Therefore, you can set it to an instance of any type that adopts the RandomNumberGenerator protocol. Nothing else is required of the instance you assign to this property, except that the instance must

adopt the RandomNumberGenerator protocol.

Dice also has an initializer, to set up its initial state. This initializer has a parameter called generator, which is also of type RandomNumberGenerator. You can pass a value of any conforming type in to this parameter when initializing a new Dice instance.

Dice provides one instance method, roll, which returns an integer value between 1 and the number of sides on the dice. This method calls the generator’s random method to create a new random number between 0.0 and 1.0, and uses this random number to create a dice roll value within the correct range. Because generator is known to adopt

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]