In Objective-C, you are allowed to define protocols which declare the methods expected to be used for a particular situation. In t, protocols are basically a named contract that your types should conform to. If your type says it conforms to Equatable
, then it better fulfil all of the required methods to make it equatable.
Protocol
A protocol defines the body 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 type, struct, or enum to provide an actual implementation of those requirements. Any type that declares itself to conform to this protocol must implement the requirements dictated in the protocol.
Protocols can require that conforming types have specific instance properties, instance methods, type methods, operators, and subscripts.
Syntax
You define protocols in a very similar way to types, structs, and enums:protocol SomeProtocol { // protocol definition goes here func someFunc() // sample method }
Implementation
The protocol name is appended beside the superclass name, separated with a comma. If there isn’t a superclass, then you just write the protocol in place of the superclass.// protocol without superclass class MyClass : SomeProtocol { // Conforming to SomeProtocol func someFunc() { } } // protocol with superclass class AnotherClass : SomeSuperClass, SomeProtocol { // A subclass conforming to SomeProtocol func someFunc() { } }
Declaring Requirements
A protocol can require any type that comply to provide an instance or type property with a particular name and type. The protocol doesn’t require 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 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 }
.protocol SomeProtocol { var mustBeSettable: Int { get set } // read-write var doesNotNeedToBe: Int { get } // read-only }
The mustBeSettable
property is { get set }
, so it must be settable and thus must be a var
in the type that conforms to this protocol. If you want a property like doesNotNeedToBe
as immutable, you can not use let
but you should rather use var
and only declare a { get }
specifier.
Protocols
As far as Swift is concerned, there are three protocols you can use to broaden your object’s capabilities: Equatable
, Comparable
, and Printable
. This is referred to as Apple’s official Swift guide protocols. It is possible to guess the protocol’s function to induce a specific behaviour just by reading their name.
Equatable Protocol
Objects who comply with the Equatable
protocol can be evaluated for equality and inequality, hence the name – Equal. Declaring an object as equatable provide several useful capabilities, notably the capability of making it possible to determine whether two values of the same type are considered equal.
For a type to be Equatable
, an implementation of the ==
operator must exist.func ==(lhs: Self, rhs: Self) -> Bool
For value types, equality is determined by evaluating each property’s equality. The method returns a Bool
value type, this means the method should return true
if the provided values are equal, otherwise return false
.
As an example, consider a struct
with properties name
, value
, and category
with types String
, Int
, and String
respectively.struct Person { var name: String var value: Int var category: String }
Since our struct
is comprised of three properties, two Person
are equal if, and only if, the properties’ respective values are equal.// MARK: – Extensions extension Person : Equatable {} func ==(lhs: Person, rhs: Person) -> Bool { return lhs.name == rhs.name && lhs.value == rhs.value && lhs.category == rhs.category }let value1 = Person(name: “Anna”, value: 6, category: “Ballet”) var value2 = Person(name: “Giesel”, value: 5, category: “Contemporary”) let firstCheck = value1 == value2 // firstCheck is false value2.name = “Anna” let secondCheck = value1 == value2 // secondCheck is false value2 = value1 let thirdCheck = value1 == value2 // thirdCheck = true let fourthCheck = value1 != value2 // fourthCheck is false
An implementation of `!=` is automatically added drawing it’s implementation from the standard library.
!=
is implemented as a generic function for Equatable
and any type that conforms to Equatable automatically gets the !=
operator.
If you really wanted to, you can override the implementation of !=
:func !=(lhs: Person, rhs: Person) -> Bool { return !(lhs == rhs) }
Operators: Equatable Protocol | |
---|---|
Operator | Description |
== | Determines whether the left hand value is equal with the right hand value |
!= | Determines whether the left hand value is not equal with the right hand value |
It makes sense that two name
property of the struct
with same values would be equal, but two Person
objects can have the same name, but be different people.
Comparable Protocol
The Comparable
protocol makes it possible to compare two values of the same type.
Building on Equatable
, Comparable
allows for more specific inequality, distinguishing cases where the left hand value is greater than the right hand value.
There is one required operator overload defined in the protocol <
, as well as one defined in the inherited Equatable
protocol ==
. By adopting the Comparable
protocol and adding an operator overload for <
, you automatically gain the ability to use >
, <=
, and >=
.func <(lhs: Self, rhs: Self) -> Bool func <=(lhs: Self, rhs: Self) -> Bool func >(lhs: Self, rhs: Self) -> Bool func >=(lhs: Self, rhs: Self) -> Bool
Operators: Comparable Protocol | |
---|---|
Operator | Description |
< | Determines whether the left hand value is less than the right hand value |
<= | Determines whether the left hand value is less than or equal with the right hand value |
> | Determines whether the left hand value is greater than the right hand value |
>= | Determines whether the left hand value is greater than or equal with the right hand value |
Since the Comparable
protocol inherits the Equatable
protocol, ==
is used with <
and >
operators to create <=
and >=
operators.
Extending our Person
type to comply with the Comparable
protocol:extension Person : Comparable {} func <(lhs: Person, rhs: Person) -> Bool { return lhs.name < rhs.name && lhs.category < rhs.category }
In this example, we excluded the value
property to make the comparing more clearer by using a single property type: String
value2 = Person(name: “Giesel”, value: 5, category: “Contemporary”) // Let’s reassign the value let fifthCheck = value1 < value2 // fifthCheck is true let sixthCheck = value1 > value2 // sixthCheck is false
Just like the !=
operator of the Equatable
protocol, all other Comparable
operators are automatically added after declaring the <
operator. It’s because of the Swift Standard Library provides a default implementation of the Comparable
protocol based entirely on the existential type _Comparable
.
This means that all other operators can be derived from ==
and <
, all of that functionality is made available automatically through type inference.
Printable Protocol
Printable Protocol is the third and last protocol on Apple’s official Swift guide protocols.
Objective-C has a similar method `- description` that returns an `NSString`.
The Printable protocol allows you to customize the textual representation of any type ready for printing.
A type must adopt this protocol if you wish to supply a value of that type to the print(_:)
and println(_:)
functions.
Extending, again, our Person
type to comply with the Printable
protocol:extension Person : Printable { var description: String { return “<\(self): Name: \(self.name), Value: \(self.value), Category: \(self.category)>” } }
This protocol only consist of one variable: description
; a string containing a suitable textual representation of the receiver and is read-only.print(value2.description) // <__lldb_expr_138.Person: Name: Giesel, Value: 5, Category: Contemporary>
This property is required for any type that adopts the Printable
protocol. Use it to determine the textual representation to print when, for example, you need to display to the user their list of to dos or shopping list.
Leave a Reply