The following lines declare a PartyError
enum that conforms to the ErrorType
protocol. This way, we will be able to throw a specific exception in the next class that we will create. The code file for the sample is included in the swift_3_oop_chapter_06_03
folder:
public enum PartyError: Error {
case insufficientMembersToRemoveLeader
case insufficientMembersToVoteLeader
}
The following lines declare a Party
class that takes advantage of generics to work with many types. The class name is followed by a less than sign (<
), an AnimalElement
name that identifies the generic type parameter, a colon (:
), and a protocol name that the AnimalElement
generic type parameter must conform to, which is the AnimalProtocol
protocol. The greater than sign (>
) ends the type constraint declaration that is included within angle brackets (< >
). Then, we follow it with the where
keyword, followed by AnimalElement
(which identified the type) and a colon (:
) that indicates that the AnimalElement
generic type parameter has to be of a type that also conforms to another protocol, that is, the Equatable
protocol. The following code highlights the lines that use the AnimalElement
generic type parameter. Remember that we imported Foundation
in the first line when we started creating the first protocol. We require the import for the arc4random_uniform
function. In case you work with the web-based sandbox or Linux, the code won’t use this function because it won’t be easily available. In these cases, the code will execute another line that generates an integer each time we run the code. It is not an exactly equivalent line but it will provide us the results we need for this example. The code file for the sample is included in the swift_3_oop_chapter_06_03
folder:
open class Party<AnimalElement: AnimalProtocol> where AnimalElement: Equatable { private var members = [AnimalElement]() open var leader: AnimalElement init(leader: AnimalElement) { self.leader = leader members.append(leader) } open func add(member: AnimalElement) { members.append(member) leader.sayWelcomeTo(destination: member) } open func remove(member: AnimalElement) throws -> AnimalElement? { if (member == leader) { throw PartyError.insufficientMembersToRemoveLeader } if let memberIndex = members.index(of: member) { let removedMember = members.remove(at: memberIndex) removedMember.sayGoodbyeTo(destination: leader) return removedMember } else { return AnimalElement?.none } } open func dance() { for (_, member) in members.enumerated() { member.dance() } } open func sing() { for (_, member) in members.enumerated() { member.sing() } } open func voteLeader() throws { if (members.count == 1) { throw PartyError.insufficientMembersToVoteLeader } var newLeader = leader while (newLeader == leader) { #if os(Linux) // The following line of code will only be executed if the // underlying operating system is Linux // Only BSD-based operating systems provide // arc4random_uniform in Swift // However, take into account that the lines aren't // equivalent // We use this solution for this example only and to make it // possible // to run the code in either the Swift web-based sandbox or // Swift on Linux let randomLeaderIndex = Int(NSDate().timeIntervalSinceReferenceDate) % members.count #else // The following line runs on macOS, iOS, tvOS and watchOS let randomLeaderIndex = Int(arc4random_uniform(UInt32(members.count))) #endif newLeader = members[randomLeaderIndex] } leader.say(message: "(newLeader.name) has been voted as our new party leader.") newLeader.dance() leader = newLeader } }
Now, we will analyze many code snippets to understand how the code included in the Party<AnimalElement>
class works. The following line starts the class body, declares a private Array<AnimalElement>
of the type specified by AnimalElement
, and initializes it with an empty Array<AnimalElement>
. Array
uses generics to specify the type of the elements that will be accepted and added to the array. In this case, we will use the array shorthand [AnimalElement]
that is equivalent to Array<AnimalElement>
, that is, an array of elements whose type is AnimalElement
or conforms to the AnimalElement
protocol, as follows:
private var members = [Animalelement]()
The previous line is equivalent to the following line:
private var members = Array<AnimalElement>()
The following line declares an open Leader
property whose type is AnimalElement
:
open var leader: AnimalElement
The following lines declare an initializer that receives a leader
argument whose type is AnimalElement
. The argument specifies the first party leader and also the first member of the party, that is, the first element added of members Array<AnimalElement>
:
init(leader: AnimalElement) { self.leader = leader members.append(leader) }
The following lines declare the add
method, which receives a member
argument whose type is AnimalElement
. The code adds the member received as an argument to members
Array<AnimalElement>
and calls the leader.sayWelcomeTo
method with member
as an argument to make the party leader welcome the new member:
open func add(member: AnimalElement) { members.append(member) leader.sayWelcomeTo(member) }
The following lines declare the remove
method, which receives a member
argument whose type is AnimalElement
, returns an optional AnimalElement
(AnimalElement?
), and throws exceptions. The throws
keyword after the method arguments and before the returned type indicates that the method can throw exceptions. The code checks whether the member to be removed is the party leader. The method throws a PartyError.insufficientMembersToRemoveLeader
exception in case the member is the party leader.
The code returns an optional AnimalElement
(AnimalElement?
). The code calls retrives the index for the member received as an argument and then calls the remove
method for the Array<AnimalElement>
array with this index as an argument. Finally, the code calls the sayGoodbyeTo
method for the successfully removed member. This way, the member that leaves the party says goodbye to the party leader. In case the member isn't removed, the method returns none
, specifically, AnimalElement?.none
:
open func remove(member: AnimalElement) throws -> AnimalElement? { if (member == leader) { throw PartyError.insufficientMembersToRemoveLeader } if let memberIndex = members.index(of: member) { let removedMember = members.remove(at: memberIndex) removedMember.sayGoodbyeTo(destination: leader) return removedMember } else { return AnimalElement?.none } }
The following lines declare the dance
method, which calls the method with the same name for each member of members
Array<AnimalElement>
. As we declare the method as open, we will be able to override this method in a future subclass:
open func dance() { for (_, member) in members.enumerated() { member.dance() } }
The following lines declare the sing
method, which calls the method with the same name for each member of members
Array<AnimalElement>
. We will also be able to override this method in a future subclass:
open func sing() { for (_, member) in members.enumerated() { member.sing() } }
Finally, the following lines declare the voteLeader
method, which throws exceptions. As it happened in another method, the throws
keyword after the method arguments indicates that the method can throw exceptions. The code makes sure that there are at least two members in members
Array<AnimalElement>
when we call this method. In case we just have one member, the method throws a PartyError.insufficientMembersToVoteLeader
exception. If we have at least two members, the code generates a new random leader for the party, which is different from the existing one. The code calls the say
method for the actual leader to explain to the
other party members that another leader is voted. Finally, the code calls the dance
method for the new leader and sets the new value to the leader
stored property:
open func voteLeader() throws { if (members.count == 1) { throw PartyError.insufficientMembersToVoteLeader } var newLeader = leader while (newLeader == leader) { #if os(Linux) // The following line of code will only be executed if the // underlying operating system is Linux // Only BSD-based operating systems provide arc4random_uniform // in Swift // However, take into account that the lines aren't equivalent // We use this solution for this example only and to make it // possible // to run the code in either the Swift web-based sandbox or // Swift on Linux let randomLeaderIndex = Int(NSDate().timeIntervalSinceReferenceDate) % members.count #else // The following line runs on macOS, iOS, tvOS and watchOS let randomLeaderIndex = Int(arc4random_uniform(UInt32(members.count))) #endif newLeader = members[randomLeaderIndex] } leader.say(message: "(newLeader.name) has been voted as our new party leader.") newLeader.dance() leader = newLeader }
3.144.38.253