3

Is it possible to write custom conversion (casting) operator in swift? Especially I'm looking for enums conversion, ex:

enum MyEnum : Int {
    case Case1 = 0
    case Case2
    func __conversion() -> String { // doesn't work since Swift 1.0
        switch self {
        case Case1: return "Case 1"
        case Case2: return "Case 2"
        }
    }
}

let enumStr: String = MyEnum.Case1

Of course, I can convert to String with explicit method, but I would like to have implicit mechanism.

brigadir
  • 6,874
  • 6
  • 46
  • 81
  • 1
    No, custom conversion methods were removed from Swift long ago. – Martin R Jul 20 '16 at 09:54
  • @MartinR, what about `String` initializer with `MyEnum` input param? – brigadir Jul 20 '16 at 10:04
  • Can't he make the enum a string type and use rawValue? – Feldur Jul 20 '16 at 11:02
  • 1
    @Feldur I believe the source of this question is not how to modify the specific `rawValue` type of a given enum, but rather the potential conversion (ideally, for OP: implicit) to other given types (which are not the same type as `rawValue`). – dfrib Jul 20 '16 at 11:08

1 Answers1

4

Disclaimer/TL;DR! This answer pertains to the technical question as to whether we can possibly implement implicit bridging mechanisms between different Swift types ourself. The answer is: for some cases, yes, but only in a limited sense and by means of "hacks": do not use this is production code!

Swift internal protocol abuse: we may implement implicit mechanisms to Obj-C objects (e.g. NSNumber, NSString ...)

As MartinR writes in his comment, custom conversion methods are not present for (native) Swift.

For the technical discussion we can, however, (ab)use the internal protocol _ObjectiveCBridgeable to allow implicit bridging from your enum to Obj-C objects, in this case e.g. NSString. For a more detailed Q&A of the subject of the internal protocol _ObjectiveCBridgeable, see

Before proceeding, I'll quote a disclaimer from my answer in thread above:

... note that _ObjectiveCBridgeable is an internal/hidden protocol (_UnderScorePreFixedProtocol), so solutions based on it might break without warning in upcoming Swift versions.


Example #1: implementing implicit bridging of your enum to NSString

First lets add a failable initializer to your enum, allowing (attempted) initialization by String instances:

import Foundation

enum MyEnum: Int {
    case Case1 = 0
    case Case2

    init?(string: String) {
        switch string {
        case "Case 1": self = .Case1
        case "Case 2": self = .Case2
        default: return nil
        }
    }
}

Next up, let MyEnum conform to _ObjectiveCBridgeable, as described in more detail in the thread linked to above

extension MyEnum: _ObjectiveCBridgeable {

    typealias _ObjectiveCType = NSString

    static func _isBridgedToObjectiveC() -> Bool {
        return true
    }

    static func _getObjectiveCType() -> Any.Type {
        return _ObjectiveCType.self
    }

    func _bridgeToObjectiveC() -> _ObjectiveCType {
        return NSString(string: "Case \(self.rawValue+1)")
    }

    static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) {
        result = MyEnum(string: source as String)
    }

    static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) -> Bool {
        self._forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
}

With the conformance above, we can now make use of implicit bridging from MyEnum instances to NSString

/* example usage */
var myCase: MyEnum = .Case1
var enumNSstr: NSString = myCase // MyEnum -> NSString, implicit

print(enumNSstr) // Case 1

enumNSstr = "Case 2"

// NSString -> MyEnum, by type conversion (castable)
myCase = (enumNSstr as MyEnum) ?? .Case1
print(myCase) // Case 2

Example #2: implementing implicit bridging of your enum to a custom Swift native type

We may even abuse the _ObjectiveCBridgeable protocol further, using its (deep backend) mechanisms to implement implicit bridging between two native Swift types, with the limitation that the type bridged to must be a reference type (specifically: instances of the type must be representable by AnyObject, hence the reference type limitation).

Let MyEnum be as defined above, but additionally, define a reference (class) type Foo, and conform MyEnum to _ObjectiveCBridgeable with the bridged to type, _ObjectiveCType being set to Foo.

class Foo {
    var bar: String
    init(bar: String) { self.bar = bar }
}

extension MyEnum: _ObjectiveCBridgeable {

    typealias _ObjectiveCType = Foo

    static func _isBridgedToObjectiveC() -> Bool {
        return true
    }

    static func _getObjectiveCType() -> Any.Type {
        return _ObjectiveCType.self
    }

    func _bridgeToObjectiveC() -> _ObjectiveCType {
        return Foo(bar: "Case \(self.rawValue+1)")
    }

    static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) {
        result = MyEnum(string: source.bar)
    }

    static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: MyEnum?) -> Bool {
        self._forceBridgeFromObjectiveC(source, result: &result)
        return true
    }
}

We can now make use of implicit bridging from MyEnum instances to Foo

/* example usage */
var myCase: MyEnum = .Case1
var myFoo: Foo = myCase // MyEnum -> Foo, implicit
print(myFoo.bar) // Case 1

myFoo.bar = "Case 2"

// Foo -> MyEnum, by type conversion (castable)
myCase = (myFoo as? MyEnum) ?? .Case1
print(myCase) // Case 2

Finally note that you may, for any given type (say, MyEnum), naturally only implement implicit bridging to a single other (reference) type; since you can only conform to _ObjectiveCType once (for a unique type for the typealias _ObjectiveCType), otherwise yielding a compile time error for redundant protocol conformance.


The above is tested for Swift 2.2.

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • thanks for so detailed explanation. Sad that it's hacking-way approach, but still very helpful. – brigadir Jul 22 '16 at 09:39
  • @brigadir happy to dwell down into this subject! But again, as you also writes, these are hacks and shouldn't really be used in practice, even if it can be interesting playing around with them :) – dfrib Jul 22 '16 at 13:54