📜 ⬆️ ⬇️

Swift! Protocol Oriented

Hello!
No, this is not just another post in the style of “Meet Swift and its possibilities”, but rather a brief excursion on practical applications and subtleties, where the protocol-orientation of Apple’s language makes it possible to do nice and comfortable things.
image


A special greeting to those who looked under the habr-kat. Recently, a lot had to be developed on `Swift`, at the same time there is a lot of baggage on object C and some desire to express some things with code that I understood much easier and more elegantly can be implemented in a new language.

Attention : the article is not a detailed teaching material, but presents practical points that impressed me personally and I decided to share them, probably, to most readers they seem familiar, so who wants to add something - ask in the comments.
')

What to expect in this article?



What is the power of protocols?


First of all, as everyone knows, the protocol mechanism allows one to implement multiple inheritance of different types of protocols. Inheritance limits us to the fact that in the chain of heirs at the nth step one cannot “infuse” or “add” a new common behavior with some other object.
Secondly, in Swift it is possible to add default implementation (default implementation) for the specified protocol. In this case, the protocol may have several default implementations depending on the class or type of the object that inherits it.
Third, the protocol can be inherited from the protocol.
Fourth, protocols can be inherited not only by classes (Class), but structures (Struct) and enumerations (Enum).
Fifth, the protocols can add properties.
Sixth, you can add a default implementation for system protocols, and, if desired, override them in a particular class.
In conclusion, I will add that the protocols allow you to make the code reusable in different classes and structures. You can implement frequent tasks in them and connect them as decorators to those files where they are needed.
For example, in each project there is a need to process a click on UIView , so that each time you do not write extra code, make your class Tappable (code is here )
Personally, I lack some convention in the inheritance of the protocol so that the inherited methods and properties are clearly visible (I heard this in Ruby):

protocol FCActionProtocol { var actionButton: UIButton! {get set} func showActionView() } class FCController: FCActionProtocol { var actionButton: UIButton! // FCActionProtocol convenience func showActionView() {} } 

Here it would be desirable, that actionButton and showActionView () were substituted in automatically generated area.
I will wait with Swift 3.0

Decorate additional class behavior with the Extension


So, from theory to practice: life case number 1.
Imagine that we have logic on the view cycle of the controller and logic on the transfer of the model to the view. Suddenly, we have a new controller extension, where we need to fit the logic for displaying the mail client. With protocols, this is easy:

 class MyViewController: UIViewController { // a lot of code here } extension MyViewController: MFMailComposeViewControllerDelegate { func showMailController() { let mailComposeViewController = configuredMailComposeViewController() if MFMailComposeViewController.canSendMail() { self.presentViewController(mailComposeViewController, animated: true, completion: nil) } } func configuredMailComposeViewController() -> MFMailComposeViewController { let controller = MFMailComposeViewController() controller.mailComposeDelegate = self return controller // customize and set it here } // MARK: - MFMailComposeViewControllerDelegate func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {} } 

Very pleased that, unlike obj-c in Swift, you can specify new inherited protocols in the extension of the class MyViewController and implement their behavior.

Create a reusable element using the protocol and default implementation


Case №2: recently in the application on two screens there was the same button, which led to the same scenario - showing an actionSheet with actions, one of which showed an email client. The technical task was to implement the protocol with implementation and all the logic inside, so that the degree of complexity of its connection and dependencies was minimal. Here is the code in the project:

 protocol FCActionProtocol { var actionButton: UIButton! {get set} var delegateHandler: FCActionProtocolDelegateHandler! {get set} mutating func showActionSheet() func showMailController() } class FCActionProtocolDelegateHandler : NSObject, MFMailComposeViewControllerDelegate { var delegate: FCActionProtocol! init(delegate: FCActionProtocol) { super.init() self.delegate = delegate } func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) { controller.dismissViewControllerAnimated(true, completion: nil) } } extension FCActionProtocol { mutating func showActionSheet() { delegateHandler = FCActionProtocolDelegateHandler(delegate: self) let actionController = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet) actionController.addAction(UIAlertAction(title: NSLocalizedString("ActionClear", comment: ""), style: .Default) { (action) in }) actionController.addAction(UIAlertAction(title: NSLocalizedString("ActionWriteBack", comment: ""), style: .Default) { (action) in self.showMailController() }) if let controller = self as? UIViewController { controller.presentViewController(actionController, animated: true) {} } } func showMailController() { if MFMailComposeViewController.canSendMail() { let controller = MFMailComposeViewController() controller.mailComposeDelegate = delegateHandler (self as! UIViewController).navigationController!.presentViewController(controller, animated: true, completion: nil) } } } 

Attention! The idea of ​​the code is that there is the FCActionProtocol protocol, which includes a button, ( actionButton ) by clicking on which a sheet with actions is shown ( showActionSheet ). Inside, by clicking on the sheet item, the mail client ( showMailController ) should appear. To ensure that the logic and processing of this call is not implemented in a class that inherits our protocol, we do default implementation inside using some delegate delegate delegate , which is created inside our extension, and the delegate methods of the email client are processed by an instance of the FCActionProtocolDelegateHandler class.

As a result, the complexity of adding this reusable action list is as follows:

 class FCMyController: FCActionProtocol { var actionButton: UIButton! // convenience FCActionProtocol var delegateHandler: FCActionProtocolDelegateHandler! // convenience FCActionProtocol } 

All the logic inside. We only need to initialize and add a button. In my opinion, it turned out beautifully and succinctly.

Protocols and enum - it can be convenient


Life case number 3: our team did the online ticket sales service. The mobile client communicates closely with the server and there are different scenarios in which calls are made to the API. Let's divide them conditionally into search, ticket booking and payment. In each of these processes, an error may occur (on the server side, client, communication protocol, data validation, and so on). If while booking or searching the 500th from the server still does not bear anything terrible, then, for example, when paying, the data from the internal server could already go to the payment gateway and the client cannot simply show an error, while his money could be debited from the banking cards.
Here the protocols can allow you to create quite elegant code:

 protocol Critical { func criticalStatus() -> (critical: Bool, message: String) } enum Error { case Search(code: Int) case Booking(code: Int) case Payment(code: Int) } extension Error : Critical { func criticalStatus() -> (critical: Bool, message: String) { switch self { case .Payment(let code) where code == 500: return (true, "Please contact us, because your payment could proceed") default: return (false, "Something went wrong. Please try later.") } } } 

Now we pull our code and estimate how bad everything is:

 let error = Error.Payment(code: 500) if error.criticalStatus().critical { print("callcenter will solve it") } 

It is a pity that in reality the project represented a huge reservoir of objective-c with a bunch of hacks for compatibility with Swift.
I hope that the following projects can be implemented using all the features of the language.

Ps I hope someone who is a beginner will be interested in Swift and the development approach using protocols. Maybe someone from the middle will mark for himself a couple of techniques that he did not use. And seniors will not criticize and share in the comments a couple of their secrets and developments. Thanks to all.

Source: https://habr.com/ru/post/272005/


All Articles