protocol FullName {
var firstName: String {get set}
var lastName: String {get set}
}
When defining a property within a protocol, we must specify whether the property is a read-only or a read-write property by using the get and set keywords.
protocol FullName {
var firstName: String {get set}
var lastName: String {get set}
mutating func getFullName() -> String
}
For value types, such as the structure, if we intend for a method to modify the instances that it belongs to, we must prefix the method definition with the mutating keyword. This keyword indicates that the method is allowed to modify the instance it belongs to.
@objc protocol Phone {
var phoneNumber: String {get set}
@objc optional var emailAddress: String {get set}
func dialNumber()
@objc optional func getEmail()
}
There are times when we want protocols to define optional requirements. To use optional requirements, we need to start off by marking the protocol with the @objc attribute. To mark a property or method as optional, we use the optional keyword.
protocol ProtocolThree: ProtocolOne, ProtocolTwo {
// Add requirements here
}
Protocols can inherit requirements from one or more additional protocols and then add additional requirements.T he syntax for protocol inheritance is very similar to class inheritance in Swift, except that we are able to inherit from more than one protocol.
Protocol composition lets our types adopt multiple protocols. This is a major advantage that we get when we use protocols rather than a class hierarchy because classes, in Swift and other single-inheritance languages, can only inherit from one superclass.
Protocol composition allows us to break our requirements into many smaller components rather than inheriting all requirements from a single protocol or single superclass.
func updatePerson(person: Person) -> Person {
var newPerson: Person
// Code to update person goes here
return newPerson
}
var personArray = [Person]()
var personDict = [String: Person]()
var myPerson: Person
myPerson = SwiftProgrammer(firstName: "Jon", lastName: "Hoffman", birthDate: birthDateProgrammer)
myPerson = FootballPlayer(firstName: "Dan", lastName: "Marino", birthdate: birthDatePlayer)
We can also use them as the type for variables, constants, and collections.
protocol Queue {
associatedtype QueueType
mutating func addItem(item: QueueType)
mutating func getItem() -> QueueType?
func count() -> Int
}
An associated type gives us a placeholder name that we can use within the protocol in place of a type.
struct IntQueue: Queue {
var items = [Int]()
mutating func addItem(item: Int) {
items.append(item)
}
mutating func getItem() -> Int? {
if items.count > 0 {
return items.remove(at: 0)
}
else {
return nil
}
}
func count() -> Int {
return items.count
}
Any type that implements the Queue protocol must specify the type to use for the QueueType placeholder, and must also ensure that only items of that type are used where the protocol requires the QueueType placeholder.
In the protocol-oriented programming world, we use protocols instead of superclasses, and it is preferable to break the requirements into smaller, more specific protocols rather than having bigger monolithic protocols.
Apple uses protocols extensively in the Swift standard library. This section lists the protocols that the Dictionary type conforms to. If we click on the View Protocol Hierarchy link.
This section lists the protocols that the Dictionary type conforms to. If we click on the View Protocol Hierarchy link
struct MyStruct {
var oneProperty: String
func oneFunction() {
}
}
In the structure, we are not required to define an initializer because the structure will create a default initializer for us if we do not provide one to set any properties that need to be initialized. This default initializer will require us to provide initial values for all non-optional properties when we create an instance of the structure.
Structures are value types; therefore, by default, the properties of the structure cannot be changed from within instance methods. By using the mutating keyword, we are opting for the mutating behavior for that particular method. We must use the mutating keyword for any method within the structure that changes the values of the structure's properties.
let mathGrade2 = (name: "Jon", grade: 100)
print("\(mathGrade2.name) - \(mathGrade2.grade)")
We can create a named tuple and access the information stored within it as shown
typealias myTuple = (tipAmount: Double, totalAmount: Double)
In Swift, a tuple is a value type. Tuples are also compound types; however, we are able to give a tuple an alias using the typealias keyword.
extension Collection where Self: ExpressibleByArrayLiteral {
//Extension code here
}
extension Collection where Iterator.Element: Comparable {
// Add functionality here
}
In the Collection protocol extensions in the previous example, only types that also conform to the ExpressibleByArrayLiteral protocol. Constraints give us the ability to limit which types receive the functionality defined in the extension.
struct Place {
let id: String
let latitude: Double
let longitude: Double
}
extension Place: Equatable {
static func ==(lhs: Place, rhs: Place) -> Bool {
return lhs.id == rhs.id &&
lhs.latitude == rhs.latitude &&
lhs.longitude == rhs.longitude
}
}
var placeOne = Place(id: "Fenway Park", latitude: 42.3467, longitude: -71.0972)
var placeTwo = Place(id: "Wrigley Field", latitude: 41.9484, longitude: -87.6553)
print(placeOne == placeTwo)
When a type conforms to the Equatable protocol, we can use the equal-to (==) operator to compare for equality and the not-equal-to (!=) operator to compare for inequality.
enum Optional<T>{
case None
case Some(T)
}
Optionals are another example of where generics are used in the Swift language. The optional type is defined as an enumeration with two possible values: None and Some(T), where T is the associated value of the appropriate type.
func swapInts (a: inout Int,b: inout Int) {
let tmp = a
a = b
b = tmp
}
func swapDoubles(a: inout Double,b: inout Double) {
let tmp = a
a = b
b = tmp
}
func swapStrings(a: inout String, b: inout String) {
let tmp = a
a = b
b = tmp
}
//Use generic
func swapGeneric<T>(a: inout T, b: inout T) {
let tmp = a
a = b
b = tmp
}
func swapGeneric <G>(a: inout G, b: inout G) {
//Statements
}
func swapGeneric <xyz>(a: inout xyz, b: inout xyz) {
//Statements
}
There is nothing special about the capital T, we could use any valid identifier in place of it.
func testGeneric<T,E>(a:T, b:E) {
print("\(a) \(b)")
}
If we need to use multiple generic types, we can create multiple placeholders by separating them with commas.
func testGenericComparable<T: Comparable>(a: T, b: T) -> Bool{
return a == b
}
func testFunction<T: MyClass, E: MyProtocol>(a: T, b: E) {
}
A type constraint specifies that a generic type must inherit from a specific class or conform to a particular protocol. This allows us to use the methods or properties defined by the parent class or protocol with the generic types.
In this function, the type defined by the T placeholder must inherit from the MyClass class, and the type defined by the E placeholder must implement the MyProtocol protocol.
struct List<T> {
var elements: [T]
}
var stringList = List<String>(elements: ["apple", "banana"])
var intList = List<Int>(elements: [1, 2, 3])
A generic type is a class, structure, or enumeration that can work with any type, just like Swift arrays and optionals can work with any type.
protocol MyProtocol {
associatedtype E
var items: [E] {get set}
mutating func add(item: E)
}
An associated type declares a placeholder name that can be used instead of a type within a protocol.