子类化时避免LSP违规

时间:2016-10-03 08:26:46

标签: ios swift inheritance

在objective-C中,我可以将视图控制器子类化如下。

class KeyboardObserverViewController: UIViewController {

    var tableView: UITableView?

    init() {
        super.init(nibName: nil, bundle: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(KeyboardObserverViewController.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(KeyboardObserverViewController.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func keyboardDidShow(_ notification: Notification) {
        let rect = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        if let tableView = tableView {
            let insets = UIEdgeInsetsMake(tableView.contentInset.top, 0, rect.height, 0)
            tableView.contentInset = insets
            tableView.scrollIndicatorInsets = insets
        }
    }

    func keyboardWillHide(_ notification: Notification) {
        if let tableView = tableView {
            let insets = UIEdgeInsetsMake(tableView.contentInset.top, 0, 0, 0)
            UIView.animate(withDuration: 0.3, animations: {
                tableView.contentInset = insets
                tableView.scrollIndicatorInsets = insets
            })
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

覆盖表视图变量并返回更专业的表视图(即UITableView的子类)。然后,我可以在需要时将表视图变量转换为。在Swift中,这有点棘手,如this post中所述。

那么你将如何继承这个视图控制器,创建一个具有更多专业性的类,同时避免使用LSP violation。或者是继承视图控制器(并对其变量进行子类化),这太棘手了?

编辑:关于我的帖子可能与this post类似的建议 - 我更关注处理代码重复而不是类与结构。

澄清:我特意在Swift中寻找一种方法(或最佳实践),允许我编写一次这个代码,并在各种视图控制器子类中使用它,这些子类使用他们的CustomTableView实例自己的。

4 个答案:

答案 0 :(得分:2)

以下内容如何:

1获取UITableView子类的一些通用协议。

protocol TableViewContainer {
  associatedtype T : UITableView
  var tableView : T? { get }
}

2然后是Observer的协议:

protocol KeyboardEventsObserver {
  func registerKeyboardEvents()
  func keyboardDidShow(_ notification: Notification)
  func keyboardWillHide(_ notification: Notification)
}

3然后是观察者也是表视图容器的扩展名。所以我们可以重用代码:

extension KeyboardEventsObserver where Self : TableViewContainer {

  func registerKeyboardEvents() {
    NotificationCenter.default.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) {
      notification in
      self.keyboardDidShow(notification)
    }
    NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) {
      notification in
      self.keyboardWillHide(notification)
    }
  }

  func keyboardDidShow(_ notification: Notification) {
    let rect = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
    if let tableView = tableView {
      let insets = UIEdgeInsetsMake(tableView.contentInset.top, 0, rect.height, 0)
      tableView.contentInset = insets
      tableView.scrollIndicatorInsets = insets
      tableView.backgroundColor = UIColor.red
    }
  }

  func keyboardWillHide(_ notification: Notification) {
    if let tableView = tableView {
      let insets = UIEdgeInsetsMake(tableView.contentInset.top, 0, 0, 0)
      UIView.animate(withDuration: 0.3, animations: {
        tableView.contentInset = insets
        tableView.scrollIndicatorInsets = insets
      })
      tableView.backgroundColor = UIColor.green
    }
  }
}

4最后,我们只是将UIViewController子类化为我们想要的功能。请注意,tableView可以是UITableView的任何子类。

class MyCustomTableView : UITableView {

}

class SomeController : UIViewController, KeyboardEventsObserver, TableViewContainer {

  @IBOutlet var tableView: MyCustomTableView?

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    registerKeyboardEvents()
  }

  override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    NotificationCenter.default.removeObserver(self)
  }
}

答案 1 :(得分:1)

如果你很高兴在Objective-C中的控制器子类中转换表视图,你可以在Swift中同样这样做:

import Foundation

class Table : NSObject {

    var inset: CGFloat = 0
}

class NotifiedController : NSObject {

    var table: Table?

    override init() {}

    func didGetNotification() {

        self.table?.inset = 10
    }
}

class WishingTable : Table {

    var twinklingStarCount: Int = 0
}

class WishingController : NotifiedController {

    override init() {

        super.init()

        self.table = WishingTable()
    }

    func makeAWish() {

        if let wishingTable = self.table as? WishingTable {
            wishingTable.twinklingStarCount += 1
        }
    }
}

这根本不需要覆盖属性。

答案 2 :(得分:0)

要支持子类化,请尝试以下方法:

class KeyboardObserverViewController: UIViewController {
    func keyboardAvoidingTableView() -> UITableView? {
        return nil
    }
    ...
}

class SomeViewController: KeyboardObserverViewController {
    var tableView: MyTableView? // <- your table view as usual
    override func keyboardAvoidingTableView() -> UITableView? {
        return self.tableView
    }
}

在一般情况下,你可以反过来做,我更喜欢这个作为你提到的子类化问题的一般解决方法:

class KeyboardObserverViewController: UIViewController {
    var tableView: UITableView?
    ...
}

class SomeViewController: KeyboardObserverViewController {
    init() {
        ....
        self.tableView = MyTableView()
    }
    var myTableView: MyTableView? {
        return self.tableView as? MyTableView
    }
}

虽然在这种特殊情况下,我看到keyboardAvoidingTableView(或代码中的tableView)不是作为存储视图引用的变量,而是作为&#34;用键盘移动的表视图&#34; 。更重要的是,您可以将其设为UIScrollView,而不是在您的应用中假设更广泛的情况:

func keyboardAvoidingScrollView() -> UIScrollView?

答案 3 :(得分:0)

作为使用通用协议的替代方法,您可以直接在类定义中使用泛型。您仍然无法覆盖view属性:与协议一样,您必须在基类中提供额外的,通用类型的属性,但这实际上是唯一的额外开销代码。

这些是示例基类(从我的其他答案重用)。控制器类是参数化的,第二个(计算的)属性提供表作为正确的子类类型。

import Foundation

class Table : NSObject {

    var inset: CGFloat = 0
}

class NotifiedController<SpecializedTable : Table> : NSObject {

    var table: Table?

    // Giving this a good name is a bit tricky...
    var specializedTable: SpecializedTable? {
        return self.table as? SpecializedTable
    }

    func didGetNotification() {

        self.table?.inset = 10
    }
}

现在可以定义子类:

class WishingTable : Table {

    var twinklingStarCount: Int = 0
}

class WishingController : NotifiedController<WishingTable> {

    override init() {

        super.init()

        self.table = WishingTable()
    }

    func makeAWish() {

        self.specializedTable?.twinklingStarCount += 1
    }
}

请注意,控制器子类在继承子句中声明其专门化。

一个警告是,这些类,因为它们使用Swift泛型,现在在Objective-C代码中不可用。但是通用协议解决方案也是如此。

相关问题