I Appreciate the apology! A lot of this stuff can become hand-wavey pretty quickly, so I think a lot of it comes down to trying to see the other side of things and giving people the benefit of the doubt. Which we can all improve on.
To address the general issue, as a concept gets more complex I find it's increasingly hard to come up with meaningful, concise examples. Especially when starting from the feature rather than a concrete use case. Although this has come up for me, it's likely < 10 times with none I can recall in detail.
There's also the complication of being a static-level feature where use cases tend towards meta-programming which has it's own additional complexities.
I think this results in it seeming like an obvious win for those who have come across an instance where they want the feature, but it being difficult to explain find a concise use case.
To give a couple more specific examples...
*note* as I don't have a build with the feature there may be minor mistakes due to not being able to compile
First, a Generic Encoder Type that provides a consistent API between different encodings. (I would still want to play with the structure more before it would be something in production). A Decoder and Combined version could be easily imagined, but the basic pattern lends itself to some Constructor patterns and really anything where you want to do the same action with multiple things.
protocol EncoderProvider {
static func json(_ dateEncodingStrategy: JSONEncoder.DateEncodingStrategy): Self
static var plist: Self { get }
static var xml: Self { get }
static var yaml: Self { get }
func encode<T: Encodable>(_ item: T) throws -> Data
}
enum BasicEncoderProvider: EncoderProvider {
case json(JSONEncoder.DateEncodingStrategy)
case plist
case xml
case yaml
func encode<T: Encodable>(_ item: T) throws -> Data {
switch self {
case .json(let dateEncoding):
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = dateEncoding
return try encoder.encode(item)
case .plist:
return try PropertyListEncoder().encode(item)
case .xml:
return try SwiftyXMLEncoder().encode(item)
case .yaml:
return try YAMLEncoder().encode(item)
}
}
}
struct GenericEncoder<Provider: EncoderProvider> {
// Generic
static func encode<T: Encodable>(_ item: T, with parser: Provider, completion: @escaping (Result<Data, Error>) -> ()) {
DispatchQueue.global().async {
func finishedAction(_ result: Result<Data, Error>) {
DispatchQueue.main.async { completion(result) }
}
do {
let data = try parser.encode(item)
finishedAction(.success(data))
} catch {
finishedAction(.failure(error))
}
}
}
//Specific
static encodeJSON<T: Encodable>(_ item: T, completion: @escaping (Result<Data, Error>) -> ()) {
encode(item, with: .json(.secondsSince1970), completion: completion)
}
static encodePList<T: Encodable>(_ item: T, completion: @escaping (Result<Data, Error>) -> ()) {
encode(item, with: .plist, completion: completion)
}
// Etc.
}
One could argue (probably even me) that this may be better expressed in a class/struct, but part of the point of a protocol is to leave it up to the user how a construct is best expressed in their codebase. That's where this proposal improves the ergonomics, readability, and maintainability when adhering to a protocol with an enum.
Here's another idea that allows setup of a Button's different states:
protocol ButtonState: Equatable {
static var disabled: Self { get }
static var normal: Self { get }
static var selected: Self { get }
var title: String { get }
var titleColor: UIColor { get }
}
extension ButtonState {
var controlState: UIControl.State {
switch self {
case .normal: return .normal
case .selected: return .selected
case .disabled: return .disabled
default: return .disabled
}
}
}
class ModelBackedButton<State: ButtonState>: UIButton {
required init?(coder: NSCoder) {
super.init(coder: coder)
doSetup()
}
private func doSetup() {
Array<State>(arrayLiteral: .normal, .selected, .disabled).forEach(updateValues)
updateViews()
}
private func updateValues(for buttonState: State) {
setTitle(buttonState.title, for: buttonState.controlState)
setTitleColor(buttonState.titleColor, for: buttonState.controlState)
}
}
enum OKButtonState: String, ButtonState {
case disabled = "--"
case normal = "OK"
case selected = "ok"
var title: String { rawValue }
var titleColor: UIColor {
switch self {
case .disabled: return .gray
case .normal: return .black
case .selected: return .darkGray
}
}
}
enum LocalizedOKButtonState: String, ButtonState {
case disabled = "--"
case normal = "OK"
case selected = "ok"
var title: String { NSLocalizedString(rawValue, comment: "...") }
var titleColor: UIColor {
switch self {
case .disabled: return .gray
case .normal: return .black
case .selected: return .darkGray
}
}
}
// Using a subclass here rather than a typealias to avoid any issues when used in InterfaceBuilder
class OKButton: ModelBackedButton<OKButtonState> { }
class LocalizedOKButton: ModelBackedButton<LocalizedOKButtonState> { }
Again, this would need to be more fleshed out for a production, but the basic concept could be used for a variety of UI controls. (radio buttons, Switches, etc.)