Краткое описание проблемы
У меня есть подкласс общего вида TintableView<T>: UIView
, который реализует протокол TintStateComputing
с идентичным связанным типом T
. Реализация ограниченного расширения TintStateComputing
не вызывается; вместо этого вызывается его неограниченная реализация расширения.
Протокол TintStateComputing
имеет функцию computeTintState() -> T
, которая делает то, на что это похоже: проверяет свойства локального состояния и возвращает соответствующий экземпляр T
.
Я хочу написать реализации расширений func computeTintState() -> T
для TintStateComputing
, ограниченные типом T
. Например, с перечислением ControlState
:
extension TintStateComputing where T == ControlState {
func computeTintState() -> T {
return self.isEnabled ? .enabled : T.default
}
}
Однако, чтобы завершить согласование протокола, я думаю, что мне нужно учитывать другие значения T
. Итак, я также указал неограниченное расширение TintStateComputing
. Эта реализация расширения без ограничений всегда вызывается вместо ограниченной реализации.
extension TintStateComputing {
func computeTintState() -> T {
return _tintState ?? T.default
}
}
Игровая площадка
import UIKit
// MARK: - Types
public enum ControlState: Int {
case normal, enabled, highlighted, selected, disabled
}
public protocol Defaultable {
static var `default`: Self { get }
}
extension ControlState: Defaultable {
public static let `default`: ControlState = .normal
}
// MARK: - TintStateComputing declaration
public protocol TintStateComputing {
associatedtype TintState: Hashable & Defaultable
func computeTintState() -> TintState
var _tintState: TintState? { get }
var isEnabled: Bool { get }
}
// MARK: - TintableView declaration
class TintableView<T: Hashable & Defaultable>: UIView, TintStateComputing {
// `typealias TintState = T` is implictly supplied by compiler
var _tintState: T?
var isEnabled: Bool = true { didSet { _tintState = nil }}
var tintState: T {
get {
guard _tintState == nil else {
return _tintState!
}
return computeTintState()
}
set {
_tintState = newValue
}
}
}
// MARK: - Unconstrained TintStateComputing extension
extension TintStateComputing {
func computeTintState() -> TintState {
return _tintState ?? TintState.default
}
}
// MARK: - Constrained TintStateComputing extension
extension TintStateComputing where TintState == ControlState {
func computeTintState() -> TintState {
return self.isEnabled ? .enabled : TintState.default
}
}
// MARK: - Test Case
let a = TintableView<ControlState>()
a.isEnabled = true
print("Computed tint state: \(a.tintState); should be .enabled") // .normal
print("finished")
Обход
Сегодня утром я понял, что, поскольку (по крайней мере, на данный момент) я действительно пытаюсь выполнить обработку флага isEnabled: Bool
на представлении, я мог бы следовать той же схеме, что и для Defaultable
, чтобы определить значение по умолчанию «включен» случай.
public protocol Enableable {
static var defaultEnabled: Self { get }
}
extension ControlState: Defaultable, Enableable {
public static let `default`: ControlState = .normal
public static let defaultEnabled: ControlState = .enabled
}
В этот момент я действительно могу исключить протокол TintStateComputing
и обновить реализацию tintState: T
моего представления, чтобы напрямую учесть флаг.
var tintState: T {
get {
guard _tintState == nil else { return _tintState! }
return self.isEnabled ? T.defaultEnabled : T.default
}
set {
_tintState = newValue
}
}
Это не так обобщенно, как помещение реализации в ограниченное расширение, но пока она будет работать. Я думаю, что если у меня появятся будущие подклассы с многомерными типами состояний тинта (например, 'enabled' + 'in-range'), я смогу обратиться через override
.
struct CalendarState: Equatable, Hashable, Defaultable, Enableable {
let x: Int
static let `default`: CalendarState = CalendarState(x: 0)
static let defaultEnabled: CalendarState = CalendarState(x: 1)
}
class ControlTintableView: TintableView<ControlState> {}
class CalendarView: TintableView<CalendarState> {}
let a = ControlTintableView()
a.isEnabled = true
print("ControlTintableView computed tint state: \(a.tintState); should be: .enabled") // .enabled
let b = CalendarView()
b.isEnabled = true
print("CalendarView computed tint state: \(b.tintState); should be: CalendarState(x: 1)") // CalendarState(x: 1)