How can you mirror the design of the Codable/CodableKeys protocols? - swift

I'm trying to achieve something similar to how Swift utilizes the CodableKeys protocol set on an enumeration defined within a class that implements Codable. In my case, the class is CommandHandler and the enumeration is CommandIds and it doesn't require on code-gen from the compiler as the enum will always be explicitly specified.
Here's a simplified version of what I'm after...
protocol CommandId{}
protocol CommandHandler{
associatedtype CommandIds : CommandId, RawRepresentable
}
class HandlerA : CommandHandler{
enum CommandIds : String, CommandId{
case commandA1
case commandA2
}
}
class HandlerB : CommandHandler{
enum CommandIds : String, CommandId{
case commandB1
case commandB2
case commandB3
}
}
func processHandler<T:CommandHandler>(_ handler:T){
// Logic to iterate over CommandIds. <-- This is where I get stumped
}
let handlerA = HandlerA()
processHandler(handlerA)
I'm struggling with the code inside processHandler here because I'm not sure how to reach the enumeration's values from a handler instance.
So what am I missing? What would be the code to get the values of the associated enumeration?

Ok, I believe I have all of the pieces in place to show how you can do this in Swift. Turns out my revised question was right at the edge of being correct in how to do it.
Here's my example written in Swift 4...
First, here's how you define the protocols needed to make this work. From the design standpoint, these are synonymous with CodableKeys and Codable respectively.
protocol CommandId : EnumerableEnum, RawRepresentable {}
protocol CommandHandler{
associatedtype CommandIds : CommandId
}
Here's a protocol and its associated extension to make the 'case' values of enums enumerable. You simply make your enums adhere to the EnumerableEnum protocol and you get a 'values' array.
Since the CommandId protocol above will already be applied to the enums in question, we simplify things by making it also apply the EnumerableEnum protocol in its own definition. This way we only need to apply CommandId to our enums and we get both.
public protocol EnumerableEnum : Hashable {
static var values: [Self] { get }
}
public extension EnumerableEnum {
public static var values: [Self] {
let valuesSequence = AnySequence { () -> AnyIterator<Self> in
var caseIndex = 0
return AnyIterator {
let currentCase: Self = withUnsafePointer(to: &caseIndex){
$0.withMemoryRebound(to: self, capacity: 1){
$0.pointee
}
}
guard currentCase.hashValue == caseIndex else {
return nil
}
caseIndex += 1
return currentCase
}
}
return Array(valuesSequence)
}
}
Here are two classes that implement my CommandHandler/CommandId protocols
class HandlerA : CommandHandler{
enum CommandIds : Int, CommandId{
case commandA1
case commandA2
}
}
class HandlerB : CommandHandler{
enum CommandIds : String, CommandId{
case commandB1 = "Command B1"
case commandB2
case commandB3 = "Yet another command"
}
}
Here's a test function which accepts a CommandHandler type
func enumerateCommandIds<T:CommandHandler>(_ commandHandlerType:T.Type){
for value in commandHandlerType.CommandIds.values{
let caseName = String(describing:value)
let caseRawValue = value.rawValue
print("\(caseName) = '\(caseRawValue)'")
}
}
And finally, here's the results of running that test
enumerateCommandIds(HandlerA.self)
// Outputs
// commandA1 = '0'
// commandA2 = '1'
enumerateCommandIds(HandlerB.self)
// Outputs
// commandB1 = 'Command B1'
// commandB2 = 'commandB2'
// commandB3 = 'Yet another command'
It was a long, windy road to get here, but we did! Thanks to everyone for their help!

You can do this easily using Swift's CaseIterable protocol.
protocol CommandId: CaseIterable {
func handle()
}
protocol CommandHandler {
associatedtype CommandIds: CommandId, RawRepresentable
}
class HandlerA: CommandHandler {
enum CommandIds: String, CommandId {
case commandA1
case commandA2
func handle() {
print("\(rawValue) is handled")
}
}
}
class HandlerB: CommandHandler {
enum CommandIds: String, CommandId {
case commandB1
case commandB2
case commandB3
func handle() {
print("\(rawValue) is handled")
}
}
}
func processHandler<T: CommandHandler>(_ handler: T) {
// Logic to iterate over CommandIds. <-- This is where I get stumped
T.CommandIds.allCases.forEach({ $0.handle() })
}
let handlerA = HandlerA()
processHandler(handlerA)

Having enums in protocols is not allowed in swift. If it were possible, the protocol would not be allowed to refer to the enumerated cases. You'd have to as cast eventually breaking protocol ideals.
Maybe associated types best serve your purpose?
enum Commands:String {
case compliaceType = ""
}
protocol CommandDef {
associatedtype Commands
}
class MyClassA : CommandDef {
enum Commands : String {
case commandA1 = "hi"
case commandA2 = "Explicit A2"
}
}
class MyClassB : CommandDef {
enum Commands : String {
case commandB2 = "Explicit B2"
}
}
print(MyClassA.Commands.commandA1)

The only solution come up in my mind is using associatedtype and moving the enum outside the protocol, doing something like:
enum Commands:String {
case default_command = ""
}
protocol CommandDef {
associatedtype Commands
}
class MyClassA : CommandDef {
enum Commands : String {
case commandA1
case commandA2 = "Explicit A2"
}
}
class MyClassB : CommandDef {
enum Commands : String {
case commandB1
case commandB2 = "Explicit B2"
case commandB3
}
}

There isn’t a way with the Swift language itself to mimic Codable, because Codable’s implementation relies on the compiler generating special-case code. Specifically, there is no protocol extension that creates the default CodingKeys enum, the compiler creates that enum inside a type that conforms to Codable automatically, unless you specify it yourself.
This is similar to how the Swift compiler will automatically create an initializer for structs (the “memberwise initializer”) unless you specify your own initializer. In that case as well, there is no protocol extension or Swift language feature you can use to replicate the auto-generated struct initializer, because it is based on metaprogramming / code generation, in this case, by the compiler.
There are tools, such as Sourcery (https://github.com/krzysztofzablocki/Sourcery), which allow you to implement your own metaprogramming and code generation. With Sourcery, you could run a script in your build phase that would automatically generate the code for the Command enum you want, and add it to any type that conforms toCommandHandler.
This would essentially mimic how Codable works via the Swift compiler generating needed code. But in neither case is it accomplished via Swift language features like protocol extensions, etc. Rather, it is boilerplate source code that gets written by a script rather than having to be written by hand.
UPDATE FOR REVISED QUESTION
If simply ensuring there is a way to enumerate all the cases of the CommandIds enum is all that you need, you can always add a protocol requirement to the CommandId protocol like this:
protocol CommandId {
static var all: [Self] { get }
}
Then implementations would need to look like:
class HandlerA : CommandHandler {
enum CommandIds : String, CommandId {
case commandA1
case commandA2
static var all: [CommandIds] { return [.commandA1, .commandA2] }
}
}
And your process function could look like:
func processHandler<T:CommandHandler>(_ handler:T){
T.CommandIds.all.forEach { // Do something with each command case }
}
It's worth continuing to note though, that for Codable, Swift does not have or use any language functionality to enumerate all cases. Instead, the compiler uses knowledge of all the properties of the Codable-conforming type to generate a specific implementation of the init(from decoder: Decoder) for that type, including a line for each case, based on the known property names and types, e.g.
// This is all the code a developer had to write
struct Example: Codable {
let name: String
let number: Int
}
// This is all source code generated by the compiler using compiler reflection into the type's properties, including their names and types
extension Example {
enum CodingKeys: String, CodingKey {
case name, number
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
number = try values.decode(Int.self, forKey: .number)
}
}
Since Swift is a mostly static language with extremely limited runtime reflection (for now), there is no way to do these types of tasks at runtime using language features.
But there is nothing stopping you or any developer from using code generation the same way the Swift compiler does to accomplish similar conveniences. In fact, a well-known member of the Swift core team at Apple even encouraged me to do so when I presented him some challenges I was facing at WWDC.
It's also worth noting that features that are now part of the Swift compiler or have open pull requests to be added to the Swift compiler (like Codable, and automatic conformance to Equatable and Hashable) were first created and implemented in real-world Swift projects using Sourcery, before they were added to Swift itself.

Related

Use a supertype of an enum as a protocol or generic in Swift

I'm writing a class that I'd like be able to work with a "generic int enum", with the conditions that the enum in question provides one or more given definitions.
If I write 'pseudo-swift' to demonstrate what I mean (which doesn't compile!), it looks like:
protocol enum ConformingEnumSupertype: Int {
case a_required_case = 1,
case another_required_case
}
enum ConformingEnum: Int, ConformingEnumSupertype {
case a_required_case = 1,
case another_required_case,
case some_random_case
}
class ClassThatUsesConformingEnum {
let myEnumValue: ConformingEnumSupertype
init(aConformingEnumValue: ConformingEnumSupertype) {
myEnumValue = aConformingEnumValue
}
func compareSomething(someEnumValue: ConformingEnumSupertype) {
if someEnumValue == myEnumValue {
// ...etc...
}
}
}
I'm afraid don't have any compiling sample code for an attempt to put here, because nothing I write that looks like it might work compiles. I've tried using protocol associated types and typealiases, but I've not managed yet.

Sub enumeration rawValue

Consider the following code, where I declared an enum with sub enums inside of it.
enum LocalizeKey {
case message(Messages)
case buttons(Buttons)
enum Buttons: String {
case remove = "Remove"
case add = "Add"
}
enum Messages: String {
case success = "Success"
case failure = "Failure"
}
}
In a normal enum with no sub enums we can easily access .rawValue property and get the raw value of whatever case we picked.
For this case, i created a function like this just to check out what am i getting .
func keyString(for type: LocalizeKey) {
print(type)
}
keyString(for: .message(.failure)) // usage
Problem : there are no other properties than .self to access for this LocalizeKey enum .
What I am trying to achieve: perhaps you can relate by the naming, i am trying to wrap my localized keys, so i can access them easily based on the key type etc, and the rawValue that is refering to the actual key will go into the getLocalizedValue function .
Playground Output : using the function above the playground output was
message(__lldb_expr_21.LocalizeKey.Messages.failure)
Edit: without having to create a variable that switches self on every case, imagine if we had +400 key that would be a huge mess probably.
You need to switch on the type parameter and do pattern matching:
switch type {
case .message(let messages): return messages.rawValue
case .buttons(let buttons): return buttons.rawValue
}
You can also make this an extension of LocalizeKey:
extension LocalizeKey {
var keyString: String {
switch self {
case .message(let messages): return messages.rawValue
case .buttons(let buttons): return buttons.rawValue
}
}
}
You are going to have to switch somewhere. If there are only a handful of "sub-enums", it is probably the easiest to just write a switch manually:
func keyString(for type: LocalizeKey) {
switch type {
case .message(let message):
print(message.rawValue)
case .buttons(let button):
print(button.rawValue)
}
}
If you don't want to write this manually, you either have to change your data structure so it is not needed, or use a code generation tool that generates the boilerplate for you.
Although The mentioned answers do provide the solution, I'd mention the issue of the approach itself:
At this point, each new case (key) has to be added in your switch statement with an associated value, which seems to be undesired boilerplate coding; I assume that you could imagine how it will look like when having many cases in the enums.
Therefore, I'd recommend to follow an approach to be more dynamic instead of adding the value of each case manually in a switch statement. Example:
protocol Localizable {
var value: String { get }
}
extension RawRepresentable where Self: Localizable, Self.RawValue == String {
var value: String { return rawValue }
}
extension CustomStringConvertible where Self: RawRepresentable, Self.RawValue == String {
var description: String { return rawValue }
}
struct LocalizeKey {
enum Buttons: String, Localizable, CustomStringConvertible {
case remove = "Remove"
case add = "Add"
}
enum Messages: String, Localizable, CustomStringConvertible {
case success = "Success"
case failure = "Failure"
}
}
We are applying the same logic for your code, with some improvements to make it easier to maintain.
Based on that, you still able to implement your function as:
func keyString(for type: Localizable) {
print(type)
}
Usage:
keyString(for: LocalizeKey.Buttons.add) // Add
keyString(for: LocalizeKey.Messages.success) // Success
IMO, I find calling it this way seems to be more readable, straightforward rather than the proposed approach (keyString(for: .message(.failure))).

Nested enums which conform to Equatable protocol produce error

I have a protocol named TableViewItem. This protocol enforces that conforming objects implement a type property, which has the protocol TableViewCellIdentifiable as its type. TableViewCellIdentifiable is used to group three nested enums together, as shown below:
internal protocol TableViewCellIdentifiable: Equatable { }
internal enum TableViewCellType {
internal enum PortfolioSelection: String, TableViewCellIdentifiable {
case portfolio = "portfolioTableViewCell"
case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
case addPortfolio = "actionTableViewCell"
}
internal enum EditPortfolio: String, TableViewCellIdentifiable {
case editPortfolioName = "editPortfolioNameTableViewCell"
case deletePortfolio = "deletePortfolioTableViewCell"
}
internal enum Portfolio: String, TableViewCellIdentifiable {
case portfolioAsset = "portfolioAssetTableViewCell"
case addAsset = "actionTableViewCell"
}
}
Here is an example of how this is being used:
internal final class EditPortfolioNameTableViewItem: TableViewItem {
// MARK: - Internal Properties
internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName
internal let viewModel: TableViewCellModel
// MARK: - Initialization
internal init(viewModel: EditPortfolioNameTableViewCellModel) {
self.viewModel = viewModel
}
}
Unfortunately, on the line that I am declaring the type property, I receive the following error:
Protocol 'TableViewCellIdentifiable' can only be used as a generic constraint because it has Self or associated type requirements
I have read through other questions/answers from others who have encountered this error but I can't quite understand why this particular implementation is problematic, and what the solution would be. I know that Equatable is the source of the problem, however this is crucial to the functionality, as the enums serve two purposes:
To provide reuse identifiers for the table view cells (the raw values).
To allow types to be compared - i.e:
self.tableViewItems.contains(where: { $0.type == item.type })
Any suggestions would be much appreciated, even if it means taking an alternative approach.
In your head, should the following code compile?
var x : Equatable
It shouldn't. Why?
Because if you had:
var x : Equatable
var y : Equatable
Then the compiler can't ensure that x & y are of the same type. x can be "John", because "John"/Strings are Equatable...all while y can be 10, because 10/integers are equatable.
and the compiler would suspect that a few lines below you might want to do
if x == y { print ("equal" }
which it can't process. So it just stops you from ever doing it in the beginning.
The following line of your code will trigger the same error because of the reason above.
internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName
As explained by Honey's answer, TableViewCellIdentifiable doesn't provide enough type information for the compiler to work with. You could potentially adopt a different approach which changes the structure a bit (and is potentially overkill), but provides the functionality you're looking for:
internal protocol ValueAssociated { }
internal extension ValueAssociated {
fileprivate var association: (label: String, value: Any?)? {
get {
let mirror = Mirror(reflecting: self)
if let association = mirror.children.first, let label = association.label {
return (label, association.value)
}
return nil
}
}
}
internal protocol CellIdentifiable {
var rawValue: String { get }
}
internal enum CellType: Equatable, ValueAssociated {
case portfolio(PortfolioIdentifier)
case portfolioSelection(PortfolioSelectionIdentifier)
case editPortfolio(EditPortfolioIdentifier)
internal var identifier: String? {
return (self.association?.value as? CellIdentifiable)?.rawValue
}
internal enum PortfolioIdentifier: String, Equatable, CellIdentifiable {
case portfolioAsset = "portfolioAssetTableViewCell"
case addAsset = "actionTableViewCell"
}
internal enum PortfolioSelectionIdentifier: String, Equatable, CellIdentifiable {
case portfolio = "portfolioTableViewCell"
case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
case addPortfolio = "actionTableViewCell"
}
internal enum EditPortfolioIdentifier: String, Equatable, CellIdentifiable {
case editPortfolioName = "editPortfolioNameTableViewCell"
case deletePortfolio = "deletePortfolioTableViewCell"
}
}
This can be used as follows:
internal let cellType: CellType = .portfolio(.portfolioAsset)
print(cellType.identifier!) // Prints "portfolioAssetTableViewCell"
Hope this helps.

Can I get the name of a type conforming to a protocol from that protocol?

I would like to know if I can find the name of a type conforming to a given protocol, from that protocol. I was thinking of protocol extension to avoid repetition in every type conforming to that protocol. I tried this:
protocol T {
var type: String { get }
}
extension T {
var type: String {
return String(describing: T.self)
}
}
struct S: T {}
let s = S()
print(s.type)
But this is showing T instead of S.
Is there any way I can do this?
Naturally it's printing T, that's what you asked for with String(describing: T.self). T is always the protocol itself.
Inside the protocol extension Self (capital 'S') is how you refer to the conforming type.
So the extension should be:
extension T {
var typeName: String {
return String(describing: Self.self)
}
}
Aside, the built-in type(of:) function already gives you the dynamic type of any object, so it's not clear that you really need to duplicate this functionality on your own.

How can I call a static function on a protocol in a generic way?

Is there a point to declaring a static function on a protocol? The client using the protocol has to call the function on a type conforming to the protocol anyway right? That breaks the idea of not having to know the type conforming to the protocol IMO. Is there a way to call the static function on the protocol in a way where I don't have to know the actual type conforming to my protocol?
Nice question. Here is my humble point of view:
Is there a point to declaring a static function on a protocol?
Pretty much the same as having instance methods declared in a protocol.
The client using the protocol has to call the function on a type conforming to the protocol anyway right?
Yes, exactly like instance functions.
That breaks the idea of not having to know the type conforming to the protocol IMO.
Nope. Look at the following code:
protocol Feline {
var name: String { get }
static func createRandomFeline() -> Feline
init()
}
extension Feline {
static func createRandomFeline() -> Feline {
return arc4random_uniform(2) > 0 ? Tiger() : Leopard()
}
}
class Tiger: Feline {
let name = "Tiger"
required init() {}
}
class Leopard: Feline {
let name = "Leopard"
required init() {}
}
let feline: Feline = arc4random_uniform(2) > 0 ? Tiger() : Leopard()
let anotherFeline = feline.dynamicType.createRandomFeline()
I don't know the real type inside the variable feline. I just know that it does conform to Feline. However I am invoking a static protocol method.
Is there a better way to do this?
I see, you would like to call a static method/function declared in a protocol without creating a value that conforms to the protocol.
Something like this:
Feline.createRandomFeline() // DANGER: compiler is not happy now
Honestly I don't know the reason why this is not possible.
yes this is possible:
Swift 3
protocol Thing {
static func genericFunction()
}
//... in another file
var things:[Thing] = []
for thing in things {
type(of: thing).genericFunction()
}
Thank you #appzYourLife for the help! Your answer inspired my answer.
#appzYourLife answered my question. I had an underlying issue I was trying to resolve and the following code resolves my issue, so I'll post this here, maybe it helps someone with my same underlying question:
protocol MyProtocol {
static func aStaticFunc()
}
class SomeClassThatUsesMyProtocolButDoesntConformToIt {
var myProtocolType: MyProtocol.Type
init(protocolType: MyProtocol.Type) {
myProtocolType = protocolType
}
func aFunction() {
myProtocolType.aStaticFunc()
}
}
I created another solution for this case. IMHO this is quite clean and simple.
First, create a protocol for accessing instance type.
protocol TypeAccessible {
func type() -> Self.Type
}
extension TypeAccessible {
func type() -> Self.Type {
return Swift.type(of: self)
}
}
then create your concrete class as here. The point is your protocol should conform to TypeAccessible protocol.
protocol FooProtocol: TypeAccessible {
static func bar()
}
class Foo: FooProtocol {
static func bar() { }
}
On call site use it as
let instance: FooProtocol = Foo()
instance.type().bar()
For further use cases, just make sure your protocols conform to TypeAccessible and that's all.
A little late to the party on this one.
Here's my solution for "adding" static properties/functions/types to a protocol using typealias.
For example:
enum PropertyScope {
case all
case none
}
struct PropertyNotifications {
static var propertyDidChange =
Notification.Name("propertyDidChangeNotification")
}
protocol Property {
typealias Scope = PropertyScope
typealias Notifications = PropertyNotifications
var scope: Scope { get set }
}
Then you can do this anywhere in your code:
func postNotification() {
let scope: Property.Scope = .all
NotificationCenter.post(name: Property.Notifications.propertyDidChange,
object: scope)
}
Using protocols like Java interfaces is rarely a good idea. They are meta types, meant for defining contracts, which is an entirely different kind of thing.
That being said, just for the point of understanding, I find the most simple and effective way for creating the equivalent of a static factory method of a protocol to write a free function.
It should contain the protocol's name, hoping that that will prevent name clashes, and improve discoverability.
In other languages, createP would be a static member of P, named create and be called as P.create(...), which would drastically improve discoverability and guarantee to prevent name clashes.
In swift, though, this is not an option for protocols, so if protocols are for some reason really actually used as a replacement for interfaces, at least including the protocol's name in the function's name is an ugly workaround that's still slightly better than nothing.
P.S. in case the goal is actually to achieve something like an inheritance hierarchy with structs, union style enums are the tool that's meant to serve that purpose :)
protocol P
{
var x: Int { get }
}
func createP() -> P
{
if (todayIsMonday())
{
return A()
}
else
{
return B()
}
}
class A: P
{
var x = 5
}
class B: P
{
var x = 7
}
This isn't an answer so much as it is an extension to the question. Say I have:
#objc public protocol InteractivelyNameable: Nameable {
static func alertViewForNaming(completion:#escaping((_ success: Bool, _ didCancel: Bool, _ error: Error?) -> Void)) -> UIAlertController?
}
And I have a generic view controller that manages various types (generic type is .fetchableObjectType... basically NSFetchResult). I need to check if a specific object type conforms to the protocol, and if so, invoke it.
something like:
// valid swift code
if self.dataSource.fetchableObjectType is InteractivelyNameable {
// not valid swift code
if let alert = (self.dataSource.fetchableObjectType as InteractivelyNameable).alertViewForNaming(....)
}
I had a situation where I need to create same DomainModel object from 2 different response. so this (static method in protocol helped me) approach helped me.
protocol BaseResponseKeyList: CodingKey {
static func getNameKey()->Self
}
enum FirstResponseKeyList: String, BaseResponseKeyList {
case name
func getNameKey()->FirstResponseKeyList {
return .name
}
}
enum SecondResponseKeyList: String, BaseResponseKeyList {
case userName
func getNameKey()->SecondResponseKeyList {
return .userName
}
}
struct MyDomainModel<T:BaseResponseKeyList> : Decodable {
var name:String?
required init(from d:Decoder) {
do {
let container = try d.container(keyedBy:T.self)
name = try container.decode(String.self, forKey:T.getNameKey())
}catch(_) {
print("error")
}
}
}
let myDomainModel = try JSONDecoder().decode(MyDomainModel <FirstResponseKeyList>.self, from: data)
let myDomainModel2 = try JSONDecoder().decode(MyDomainModel <SecondResponseKeyList>.self, from: data2)