Tips for Protocol-Oriented Programming (POP) in Swift

Tips for Protocol-Oriented Programming (POP) in Swift

1. Entrustment Model

1. Usage process

The most common usage of protocols is to pass values ​​through agents, which is the delegation pattern. Common application scenarios include: a view is customized in the controller, and a custom view is added to the view. If some functions or properties in the customized view need to be called in the controller, the delegation pattern is to specify a protocol, let the controller comply with a protocol and provide an implementation, so that the methods in the protocol can be used in the customized view.

For example, if you want to add a custom view to a controller, you can click a button in the view to change the value of a label in the controller. The simple code is as follows:

Custom views

  1. //SelectTabbar.swift
  2. @objc protocol SelectTabbarDelegate {
  3. func changeLabel(_ str: String)
  4. }
  1. //SelectTabbar.swift
  2. class SelectTabbar: UIView {
  3. var keywords : [String]?
  4. var buttons : [UIButton]?
  5. weak public var delegate : SelectTabbarDelegate?
  6.       
  7. init(frame: CGRect,keywords:[String]) {
  8. super.init(frame: frame)
  9. self.keywords = keywords
  10. renderView()
  11. }
  12.       
  13. required init?(coder aDecoder: NSCoder) {
  14. fatalError( "init(coder:) has not been implemented" )
  15. }
  16.       
  17. override func layoutSubviews() {
  18. super.layoutSubviews()
  19. }
  20.       
  21. private func renderView(){
  22. buttons = keywords?.enumerated().map({ ( index , key ) ->UIButton in  
  23. let buttonWidth = kScreenWidth/CGFloat((keywords?. count )!)
  24. let button = UIButton.init(frame: CGRect.init(x: CGFloat( index )*buttonWidth, y: 0, width: buttonWidth, height: 50))
  25. button.setTitle( key , for : .normal)
  26. button.setTitleColor(UIColor.blue, for : .normal)
  27. button.backgroundColor = UIColor.gray
  28. button.tag = index  
  29. button.addTarget(self, action : #selector(tapButton(sender:)), for : .touchUpInside)
  30. addSubview(button)
  31. return button
  32. })
  33. }
  34.       
  35. @objc func tapButton(sender: UIButton){
  36. delegate?.changeLabel(keywords![sender.tag])
  37. }
  38.       
  39. }

controller:

  1. class TestViewController: UIViewController,SelectTabbarDelegate {
  2. lazy var label : UILabel = {
  3. var label = UILabel(frame: CGRect.init(x: 50, y: 200, width: 100, height: 30))
  4. label.text = labelStr
  5. label.backgroundColor = UIColor.red
  6. return label
  7. }()
  8.       
  9. private var labelStr : String? {
  10. didSet{
  11. label.text = labelStr
  12. }
  13. }
  14.       
  15. override func viewDidLoad() {
  16. super.viewDidLoad()
  17. view .backgroundColor = .white
  18. view .addSubview(label)
  19. setupSelectTabbar()
  20. }
  21.       
  22. func setupSelectTabbar(){
  23. let selectTabbar = SelectTabbar(frame: CGRect.init(x: 0, y: kNavigationHeightAndStatuBarHeight, width: kScreenWidth, height: 50),keywords:[ "aa" , "bb" ])
  24. selectTabbar.delegate = self
  25. view .addSubview(selectTabbar)
  26. }
  27. func changeLabel(_ str: String) {
  28. labelStr = str
  29. }
  30.       
  31. }

This way, you can clearly express your logic. Otherwise, if you want to operate the content of the controller in the view, you need to operate the instance of the controller externally, which causes a problem that you cannot operate the private properties and private methods in the instance (although iOS is a dynamic language and there is no absolute privacy, but who would always use the runtime to operate).

2. Note

In ARC, for general delegates, we will specify them as weak in the declaration. When the actual object of this delegate is released, it will be reset to nil. This ensures that even if the delegate no longer exists, we will not crash due to accessing the reclaimed memory. This feature of ARC eliminates a very common crash error in Cocoa development, and it is no exaggeration to say that it has saved thousands of programmers from danger.

Of course we would want to do this in Swift. But when we try to write code like this, the compiler won’t let us pass:

  1. 'weak' cannot be applied to non-class type

Reason: This is because Swift's protocol can be followed by types other than class. Types like struct or enum do not manage memory through reference counting, so it is impossible to modify them with ARC concepts such as weak.

Two workarounds:

  1. Using @objc
  2. Declare a class-specific protocol. By adding the class keyword, you can restrict the protocol to be followed only by class types, and structures or enumerations cannot follow the protocol. The class keyword must appear first in the protocol inheritance list, before other inherited protocols.
  • protocol SelectTabbarDelegate : class

2. Application of AOP programming ideas

First, let us understand the meaning of AOP.

  • In computing, aspect-oriented programming (AOP) is a paradigm programming that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification, such as “log all function calls when the function’s name begins with ‘set’”. This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development.

To put it simply, in Swift, protocols are used to cut into certain codes, separating out additional functions without generating coupling, and putting those codes that are not closely related to the main logic together.

Common scenarios: logging, performance statistics, security control, transaction processing, exception handling, etc.

Continuing with the above example, we need to count once when opening TestViewController and also count when clicking two buttons. The counted content is distinguished by identifer.

Let's first create a Statistician.swift to store our statistical logic. (Simulation implementation)

Declare a StatisticianProtocal protocol and provide its default implementation.

  1. import Foundation
  2. enum LogIdentifer:String {
  3. case button1 = "button1"  
  4. case button2 = "button2"  
  5. case testViewController = "testViewController"  
  6. }
  7.   
  8. protocol StatisticianProtocal {
  9. func statisticianLog(fromClass:AnyObject, identifer:LogIdentifer)
  10. func statisticianUpload(fromClass:AnyObject, identifer:LogIdentifer)
  11. // Extend functionality with a trailing closure
  12. func statisticianExtension(fromClass:AnyObject, identifer:LogIdentifer, extra:()->())
  13. }
  14.   
  15. extension StatisticianProtocal{
  16. func statisticianLog(fromClass:AnyObject, identifer:LogIdentifer) {
  17. print( "statisticianLog--class:\(fromClass) from:\(identifer.rawValue)" )
  18. }
  19.       
  20. func statisticianUpload(fromClass:AnyObject, identifer:LogIdentifer) {
  21. print( "statisticianUpload--class:\(fromClass) from:\(identifer.rawValue)" )
  22. }
  23.       
  24. func statisticianExtension(fromClass:AnyObject, identifer:LogIdentifer, extra:()->()){
  25. extra()
  26. }
  27. }
  28.   
  29. class Statistician: NSObject {
  30.   
  31. }

Next, in any class that needs statistics, we make the class conform to this protocol and call the method in the protocol where needed. If the method that needs to be called in a particular class is slightly different, just rewrite the method in the protocol.

  1. class SelectTabbar: UIView,StatisticianProtocal {
  2. var keywords : [String]?
  3. var buttons : [UIButton]?
  4. weak public var delegate : SelectTabbarDelegate?
  5.       
  6. init(frame: CGRect,keywords:[String]) {
  7. super.init(frame: frame)
  8. self.keywords = keywords
  9. renderView()
  10. //Perform a statistics
  11. operateStatistician(identifer: .testViewController)
  12. }
  13.       
  14. required init?(coder aDecoder: NSCoder) {
  15. fatalError( "init(coder:) has not been implemented" )
  16. }
  17.       
  18.       
  19. override func layoutSubviews() {
  20. super.layoutSubviews()
  21. }
  22.       
  23. private func renderView(){
  24. buttons = keywords?.enumerated().map({ ( index , key ) ->UIButton in  
  25. let buttonWidth = kScreenWidth/CGFloat((keywords?. count )!)
  26. let button = UIButton.init(frame: CGRect.init(x: CGFloat( index )*buttonWidth, y: 0, width: buttonWidth, height: 50))
  27. button.setTitle( key , for : .normal)
  28. button.setTitleColor(UIColor.blue, for : .normal)
  29. button.backgroundColor = UIColor.gray
  30. button.tag = index  
  31. button.addTarget(self, action : #selector(tapButton(sender:)), for : .touchUpInside)
  32. addSubview(button)
  33. return button
  34. })
  35. }
  36.       
  37. @objc func tapButton(sender: UIButton){
  38. //Perform a statistics
  39. switch sender.tag {
  40. case 0:operateStatistician(identifer: .button1)
  41. default :operateStatistician(identifer: .button2)
  42. }
  43. delegate?.changeLabel(keywords![sender.tag])
  44. }
  45.       
  46. func operateStatistician(identifer:LogIdentifer){
  47. statisticianLog(fromClass: self, identifer: identifer)
  48. statisticianUpload(fromClass: self, identifer: identifer)
  49. statisticianExtension(fromClass: self, identifer: identifer) {
  50. print( "extra: in SelectTabbar class" )
  51. }
  52. }
  53.       
  54. }

The above code implements the logic of three statistics without writing the statistical logic into the controller file, which reduces the functional coupling.

3. Used to replace extension and enhance code readability

Extensions can easily add functions to subclasses that inherit them. This brings up a problem, that is, all subclasses have this method, but the method itself may not be clear, or you only want a few subclasses to use this method. In this case, you can use protocols instead of extensions.

  1. // Defines a Shakable protocol. Classes that comply with this protocol can use the methods in it and provide a default implementation for the method
  2. // where Self:UIView indicates that only subclasses of UIView can comply with this protocol
  3. protocol Shakable
  4. func shakeView()
  5. }
  6.   
  7. extension Shakable where Self:UIView{
  8. func shakeView(){
  9. print(Self.self)
  10. }
  11. }

At this time, you can let a subclass comply with the protocol, such as the example above.

  1. class SelectTabbar: UIView,Shakable

If you do not reimplement this method in the class, you can implement the default method. This means that the subclass of the SelectTabbar class complies with the Shakable protocol, which is indirectly equal to SelectTabbar():Shakable?. In this way, we can happily let the SelectTabbar object use this method. (The Self keyword can only be used in protocols or classes, indicating the current class, and can be used as a return value).

If you don't want a subclass to use the shakeView() method, it's very simple. Just remove the Shakable protocol in class SelectTabbar: UIView,Shakable.

Other practices:

By using AOP to separate the data source and event source of the tableview, you can process the logic inside separately, making the tableview's proxy method less redundant.

Summarize

There are many other uses for protocols. The above are the most common scenarios. If you find that the protocol has better applications in other places during future development, you will update this article.

<<:  Is iOS jailbreaking still your thing? Talking about iOS jailbreaking and its future

>>:  This time, I'm not going to stand on Google

Recommend

Toutiao | Advertising Optimization Guide (Practical)

As one of the mainstream forms of mobile Internet...

It’s too hot! “Too hot to death” is not a joke. How to prevent it?

Review expert: Wu Xinsheng, deputy chief physicia...

20-year gaming veteran: Why should I buy a new computer?

In 1992, when I was in the second grade of elemen...

Mr. K's Grid System Guide: How to Design Fast and Easily

Course catalog: ├──Video| ├──01 Why is the typeset...

How to implement Touch ID verification in iOS 8 using Swift

iOS8 opens many APIs, including HomeKit, HealthKi...

How to master festival brand marketing?

Taking Master Kong’s Father’s Day marketing campa...

Community operation strategies at different stages

Infancy: protection period, developing cognitive ...