Hey all! I'm happy to have you back, and thanks for taking an interest and reading! If you missed part one, it can be found here.
I realize now that I never described what a protocol is in Swift in the first post, but was able to get away with it since the first post just covered design. Allow me to rectify that right now. According to the Apple documentation a protocol “defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.” That protocol can then be adopted to a class, struct, or enum that will be responsible for giving the blueprints some implementation. One of the largest benefits with using protocols over classes is that you run the risk of having to update a lot of code if your architecture changes in the future. Since nothing is implemented by the protocols themselves, the classes, structs, or enums that conform to them are less coupled. Now that that’s out of the way and we’ve got an idea of where we want our protocols to be, let’s fire up Xcode and actually start developing!
Side Note: A quick update before we get started. I renamed the fight() function from the previous post to attack(). It makes more sense when writing code to say Character A attacks Character B. It’s basically a more descriptive term. Now, on with the post!
I used the standard “Single Page Application” to get the app off the ground. The first thing I like to do when creating a new application is organize my files in Xcode. To that end I created three groups to store my files; Enumerations, Protocols, and Structs. I’ll probably add more as time and development goes on, but this is a good way to start.
I believe it makes the most sense to start with the most abstract protocol for the application, which in our case is BasicCharacter. Reviewing the UML diagram from the last post I know I’ll need six attributes and three functions. To create a protocol, first create a new Swift file. Name it whatever you’d like and Xcode will produce a file with some general boilerplate code and a single import statement. Use the keyword ‘protocol’ and followed by the protocol’s name and you’re ready to go! In our case it will look something like this:
import Foundation
protocol BasicCharacter {
}
Now we name all of the attributes and write out the functions.
Well that’s…..sub-optimal. There are errors everywhere! What’s happening? Taking it from the top, I used the ‘let’ keyword to define the name attribute. This makes sense if I don’t want the user to have a chance to change it after it’s been set. A character’s name should be constant. There’s a debate that can be made that we should be able to change it in some way (people do change their names after all) but for the purposes of the game, I would rather not allow that to happen. Xcode disagrees with me and there’s a reason for that. If you refer to the definition of a protocol that Apple provides from the first paragraph, a protocol should have no impact on how a class, struct, or enum conforms to it. By stating an attribute is a constant by using ‘let’, the protocol would be forcing any conforming classes to also use let. It is still valid to set the attribute as a constant in the conforming class or struct (spoiler alert: it’s what I do). All other errors are the same, the property should have at least a { get } and possibly a { get set } specifier. These keywords determine whether the attribute is read or read-write. Something to keep in mind when dealing with { get set } is that it must be settable, meaning that the attribute must be a var. If we were to only use { get }, as is the case for name in the picture below, then when we define the property in the conforming class or struct, it can be a let. Let’s clean this up.
That’s much better! Now it’s time to add the function signatures.
No issues here (spoiler alert 2: yet). Now we repeat this process for our other most-abstract protocols; Item and Objective.
import Foundation
protocol Item {
var strength: Int { get set }
var agility: Int { get set }
var intelligence: Int { get set }
var durability: Double { get set }
var isBroken: Bool { get set }
func breakItem()
func repairItem()
}
import Foundation
protocol Objective {
var experience: Int { get }
var reward: Item! { get set }
}
Good job! We’ve just created the foundation for the rest of the app. Think about our design from the first post now. Hero and Enemy both inherit from BaseCharacter and Weapon and Armor both inherit from Item. How do we express that in our code? It’s actually simple. We say that the sub-protocols conform to the foundational protocols. In the case of Hero we would have:
import Foundation
protocol Hero: BasicCharacter {
var inventory: [Item]! { get set }
func loot(theEnemy: Enemy)
func equip(theItem: Item)
func complete(theObjective: Objective)
}
And for Enemy it’d be:
import Foundation
protocol Enemy: BasicCharacter {
var loot: Item! { get set }
func flee()
}
Following suit with Weapon and Armor we get:
import Foundation
protocol Weapon: Item {
var minDamage: Int { get set }
var maxDamage: Int { get set }
var type: WeaponType { get }
}
import Foundation
protocol Armor: Item {
var value: Int { get set }
var type: ArmorType { get }
}
What you’re seeing here is an elegant and powerful idea. Each sub-protocol will take on the responsibilities of the foundational protocol. What this means in practice is that if I were to create a Hero struct, I wouldn’t need this:
struct HeroStruct: Hero, BasicCharacter {
...
}
But rather I could do this:
struct HeroStruct: Hero { ... }
It seems simple with the given example but the fact conforming to the terminal protocol in a waterfall-like series of other protocols will hold a class, struct, or enum responsible for all of the “methods, properties, and other requirements (to quote Apple)” laid out in the protocols before it make life easier on us as developers. We have less of a chance to miss out on conforming to a protocol if there are fewer protocols of which to conform. Y’all know me, the less I have to think about configuration and setup like this, the better off I am!
I had more planned for this post but it’s already gotten long. I’ll move on to creating the structs in post three! I have also created three more protocols that conform to Hero and three more that conform to Enemy. I wanted to have something more concrete to play around with when writing the code. For Hero they are; Wizard, Warrior, and Archer. For Enemy they are; Orc, Goblin, and Android (yeah I went there). I’ll cover these more in post three as well (I told you I had a lot planned haha). In the meantime, see if you can write up your implementations of these. I’d love to hear what types of attributes or functions you think each should have!
Yours in code,
Zack