When to use protocol in Swift
Protocols have more than one use case, but think of it this way:
For classes, protocols provide a lightweight inheritance hierarchy separate from the class hierarchy. Given a class Animal and its subclasses Cat, Dog, Bird, and Insect, how would you specify that only Bird and Insect share the
fly
method?For structs, protocols provide an inheritance hierarchy that would otherwise be missing entirely! A struct has no superstruct. So, given a struct Bird and a struct Insect, how would you specify that they share a
fly
method?
Now, you could answer that Bird and Insect just happen to have fly
methods and that's the end of the story. But that won't do when you need to speak of the set "all types that have a fly
method". And you do need to speak of that set when you want the compiler to be able to send the fly
method to an object on the grounds that it has a fly
method.
The solution is a protocol:
protocol Flier {
func fly()
}
Now Flier is a type. An instance of any type that conforms to Flier can be used where a Flier is expected, and the compiler will let you tell any Flier to fly
, so the problem is solved:
var myFlier : Flier = Bird() // if Bird conforms to Flier
myFlier.fly() // legal
When to use `protocol` and `protocol: class` in Swift?
Swift 4 version
AnyObject
added to a protocol definition like this
protocol FilterViewControllerDelegate: AnyObject {
func didSearch(parameters:[String: String]?)
}
means that only a class will be able to conform to that protocol.
So given this
protocol FilterViewControllerDelegate: AnyObject {
func didSearch(parameters:[String: String]?)
}
You will be able to write this
class Foo: FilterViewControllerDelegate {
func didSearch(parameters:[String: String]?) { }
}
but NOT this
struct Foo: FilterViewControllerDelegate {
func didSearch(parameters:[String: String]?) { }
}
Swift 3 version
:class
added to a protocol definition like this
protocol FilterViewControllerDelegate: class {
func didSearch(Parameters:[String: String]?)
}
means that only a class will be able to conform to that protocol.
So given this
protocol FilterViewControllerDelegate: class {
func didSearch(Parameters:[String: String]?)
}
You will be able to write this
class Foo: FilterViewControllerDelegate {
func didSearch(Parameters:[String: String]?) { }
}
but NOT this
struct Foo: FilterViewControllerDelegate {
func didSearch(Parameters:[String: String]?) { }
}
Proper way to set up a protocol in Swift 3
I followed silentBob answer, and it was great except that it didn't work for me. I needed to set FlickrAPIRequests class as a singleton, so here is what I did:
protocol FlickrAPIRequestDelegate{
func showFlickrPhoto(photo: UIImage) }
class FlickrAPIRequests{
private static let _instance = FlickrAPIRequests()
static var Instance: FlickrAPIRequests{
return _instance
}
var flickrDelegate: FlickrAPIRequestDelegate? = nil
// Make the Flickr API call
func flickrAPIRequest(_ params: [String: AnyObject], page: Int){
Then in the view controller when I press the search button:
// Set flickrDelegate protocol to self
FlickrAPIRequests.Instance.flickrDelegate = self
// Call the method for api requests
FlickrAPIRequests.Instance.flickrAPIRequest(paramsDictionary as [String : AnyObject], page: 0)
And Here is the view controller's extension to conform to the protocol:
extension FlickrViewController: FlickrAPIRequestDelegate{
func showFlickrPhoto(photo: UIImage){
self.imgView.image = photo
self.prepFiltersAndViews()
self.dismissAlert()
}
}
When the api method returns a photo I call the protocol method in the main thread:
// Perform this code in the main UI
DispatchQueue.main.async { [unowned self] in
let img = UIImage(data: photoData as Data)
self.flickrDelegate?.showFlickrPhoto(photo: img!)
self.flickrDelegate?.setPhotoTitle(photoTitle: photoTitle)
}
Why protocol is better than class in swift?
Lets take a downloading example.
You have a Base class FileDownloadModel, and have 3 subclasses AudioFileDownloadModel, VideoFileDownloadModel, and ImageDownloadModel.
You have a DownloadManager that takes a FileDownloadModel input and uses this model's urlToDownload property to download the file.
Later down the line you are told that there is one more model coming but it's of type UserDownloadModel which is a subclass of User, and not FileDownloadModel.
See now it becomes difficult to handle this scenario where you will have to change a lot of code to incorporate downloading methods.
How protocol oriented programming will help you here:
- Create a protocol named DownloadingFileProtocol and add
methods and properties that you need for downloading a file. eg. urlToDownload,
pathToSave, extension etc. - Implement the same protocol in FileDownloadModel and
UserDownloadModel. The benefit here is that you don't have to change a
lot of code in UserDownloadModel. You will just implement the
methods from the DownloadingFileProtocol. - If a new entity comes down the line again, you will not
change any code. Rather, you'll just implement the protocol methods. - And now your DownloadManager can take a
DownloadingFileProtocol as input instead of a specific model. As well, you can now make any model "downloadable" by having it adopt this protocol.
Swift - implement protocol with generic method for different T types
The way you have it written, by conforming to the protocol Client, you have to implement a function get
, with a generic, unconstrained argument T
. In the example implementations provided, you added a type constraint to the generic parameter T
, which does not match the function in the protocol.
There's more than one way you can approach a solution to this problem. Keeping in mind that you said all entities will conform to Mockable
, the solution that requires the least change to the code you provided is to use protocol composition to enforce that all parameters T
conform to both Decodable
and Mockable
.
protocol Client {
func get<T: Decodable & Mockable>(_ url: String) -> Promise<T>
}
In your clients, you would implement that function exactly as written.
Now, in your MockClient
, you can call T.mock()
, and in your real implementations, you can treat T
as Decodable
as required. Of course, this now requires that even your mock arguments conform to Decodable
, but I would assume your mock arguments will be fairly lightweight and thus this wouldn't be a problem.
How does protocol extension work in Swift?
I don't think there is even a need to inherit from NSObject
if you are making a BaseClass
to be inherited by other classes.
You can simply add classTag
in the BaseClass
itself, i.e.
class BaseClass {
var classTag: String {
return String(describing: type(of: self))
}
}
class SubClass: BaseClass {
func someFunc() {
print(self.classTag)
}
}
Another option can be to use protocol
and protocol extension
and provide the default definition of classTag
, i.e.
protocol SomeProtocol {
var classTag: String { get }
}
extension SomeProtocol {
var classTag: String {
return String(describing: type(of: self))
}
}
Then, you can conform SomeProtocol
to the classes wherever required, i.e.
class SubClass: SomeProtocol {
func someFunc() {
print(self.classTag)
}
}
In any case, inheriting from NSObject
is unnecessary since you don't need any NSObject
specific functionality for that.
Related Topics
Is There a Github Markdown Language Identifier for Swift Code
Why Can't I Use .Reduce() in a One-Liner Swift Closure with a Variadic, Anonymous Argument
How to Fix Error: Abort Trap 6 (In Target 'Realmswift' from Project 'Pods')
Classes in Swift Files Inside Folder References Not Seen by Xcode 10's Compiler
How to Add Detect Button Presses in Tvos
Core Data with Pre-Filled .Sqlite (Swift3)
Avaudiosinknode with Non-Default, But Still Device-Native Sample Rates
How to Build a Swift Executable for Linux on Macos
Resulting Mtltexture Lighter Than Cgimage
Bringing iOS Frameworks Through Carthage in Xcode 12.0
Running Swift Build in Terminal Leading to "Platform Path" Errors
Left Aligned Horizontal Stackview and Top Aligned Vertical Stackview
Set Custom Uiview Frame in Uiviewrepresentable Swiftui
How to Print Escape Sequence Characters in Swift
Change the Font of a Datepicker
How to Submit Swift 2.2 App with Xcode 7.3 When iOS 10 Is Released