【问题标题】:Class to protocol conversation in swift类到协议会话在 swift
【发布时间】:2019-01-21 15:44:10
【问题描述】:

我有 UIViewController 的这个沉重的 basicVC 类子类,我正在尝试将其转换为 vcprotocol。

BasicVC 像上帝一样做所有工作。我想作为 vcProtocol 闯入其中。

我正在尝试做的是关注点分离。并非所有 ViewController 都需要显示警报视图或网络未连接消息。

例如,我有在协议扩展中创建的指标视图作为计算属性。没有错误警告,但没有显示任何指示符。当我尝试调试并执行po acticvityIndicator 时,我收到以下错误,这表明从未分配过activityIndi​​cator。

error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x5a1012de027).
The process has been returned to the state before expression evaluation.

代码sn-p:

protocol vcProtocol {
    var activityIndicator: UIActivityIndicatorView { get }
}

协议扩展:

extension vcProtocol where Self: UIViewController {

    var activityIndicator: UIActivityIndicatorView {
        let indicator = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.gray)
        indicator.hidesWhenStopped = true
        indicator.style = .whiteLarge
        indicator.color = .red
        indicator.backgroundColor = UIColor.gray
        indicator.translatesAutoresizingMaskIntoConstraints = false
        return indicator
    }

    func showLoadingIndicator() {
        activityIndicator.startAnimating()
        activityIndicator.isHidden = false
    }

    func hideLoadingIndicator() {
        activityIndicator.stopAnimating()
        activityIndicator.isHidden = true
    }
}

我不知道如何解决这个问题。因为我只能在协议中具有计算属性。 所以我将它们作为仅获取属性。 我的计划是使用协议扩展来提供默认实现。

关于如何解决这个问题的任何想法。

【问题讨论】:

  • 不确定是否相关,但您是否随时将此activityIndicator 添加为subView
  • 你的活动指示器视图是一个计算属性,所以每次你引用它时,你都会得到一个新的!
  • @rob 我不明白。因此错误。至少这是我的假设
  • EXC_BAD_ACCESS in po 并不意味着属性是nil,它发生在协议扩展中定义的属性的情况下,您可以在扩展中添加断点并对其进行验证。您应该在使用它的地方添加代码。

标签: swift swift-protocols protocol-oriented


【解决方案1】:

这个activityIndicator 是一个计算属性,所以每次你引用计算属性时,都会调用get 块。最终结果是,正如所写,每次引用 activityIndicator 时,都会获得 UIActivityIndicatorView 的新实例,这显然不是您的意图。

考虑你的showLoadingIndicator

func showLoadingIndicator() {
    activityIndicator.startAnimating()
    activityIndicator.isHidden = false
}

第一行(startAnimating)将返回一个新的UIActivityIndicatorView,第二行(isHidden)将返回另一个。而且这些都不是您可能添加到子视图中的那个。

这个activityIndicator 真的应该被实例化一次并且只被实例化一次。不幸的是,您无法在扩展中定义存储属性,因此有几种方法:

  1. 您可以让UIViewController 声明存储的属性,只需定义方法方法来配置、显示和隐藏它:

    protocol LoadingIndicatorProtocol: class {
        var loadingActivityIndicator: UIActivityIndicatorView? { get set }
    }
    
    extension LoadingIndicatorProtocol where Self: UIViewController {
    
        func addLoadingIndicator() {
            let indicator = UIActivityIndicatorView(style: .gray)
            indicator.hidesWhenStopped = true
            indicator.style = .whiteLarge
            indicator.color = .red
            indicator.backgroundColor = .gray
            indicator.translatesAutoresizingMaskIntoConstraints = false
    
            view.addSubview(indicator)
            // you might want to add the constraints here, too
    
            loadingActivityIndicator = indicator
        }
    
        func showLoadingIndicator() {
            loadingActivityIndicator?.startAnimating()
            loadingActivityIndicator?.isHidden = false
        }
    
        func hideLoadingIndicator() {
            loadingActivityIndicator?.stopAnimating()
            loadingActivityIndicator?.isHidden = true
        }
    }
    

    然后UIViewController 子类只需为activityIndicator 定义自己的ivar,例如

    class ViewController: UIViewController, LoadingIndicatorProtocol {
        var loadingActivityIndicator: UIActivityIndicatorView?
    
        override viewDidLoad() {
            super.viewDidLoad()
    
            addLoadingIndicator()
        }
    
        ...
    }
    
  2. 另一种方法是通过objc_getAssociatedObjectobjc_setAssociatedObject使用associated objects,实现存储属性行为:

    protocol LoadingIndicatorProtocol {
        var loadingActivityIndicator: UIActivityIndicatorView { get }
    }
    
    private var associatedObjectKey = 0
    
    extension LoadingIndicatorProtocol {
    
        var loadingActivityIndicator: UIActivityIndicatorView {
            if let indicatorView = objc_getAssociatedObject(self, &associatedObjectKey) as? UIActivityIndicatorView {
                return indicatorView
            }
    
            let indicator = UIActivityIndicatorView(style: .gray)
            indicator.hidesWhenStopped = true
            indicator.style = .whiteLarge
            indicator.color = .red
            indicator.backgroundColor = .gray
            indicator.translatesAutoresizingMaskIntoConstraints = false
            objc_setAssociatedObject(self, &associatedObjectKey, indicator, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    
            return indicator
        }
    
        func showLoadingIndicator() {
            loadingActivityIndicator.startAnimating()
            loadingActivityIndicator.isHidden = false
        }
    
        func hideLoadingIndicator() {
            loadingActivityIndicator.stopAnimating()
            loadingActivityIndicator.isHidden = true
        }
    }
    

【讨论】:

  • 我喜欢你的第一种方法,因为我想要快速的方式。快速编译器正在抱怨。关于 loadingActivityIndi​​cator = indicator 自动修复使 addLoadingIndicator 发生变异的问题。但后来我在我的视图控制器中调用此方法时遇到问题。想法?
  • @Alix - 您是否使用 class 限定符定义了您的协议​​?
  • 你是对的。我完全错过了那个。感谢您的指出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 1970-01-01
  • 2016-04-30
  • 2016-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多