Artem Stepanenko
Artem Stepanenko

Reputation: 3501

Type casting in Swift 3

I want to check whether a generic type conforms to a protocol. If it is, I need to cast the type itself and call a static method against it.

func log<T>(object: T) {
    if let C = T as? Loggable {    // this line doesn't compile
        print("\(C.description(of: object))")
    } else {
        print("\(object)")
    }
}

Anybody knows if it's doable?

UPDATE

I confused you with my first code snippet. Hopefully, second makes more sense. I've made some changes thanks to the @portella's answer, but it's still not enough.

func decodeObject<T>() -> T? {
    guard let object = objects.first else {
        return nil
    }
    objects.removeFirst()

    if object is NSNull {
        return nil
    }

    if T.self is Coding.Type {    // the condition is fixed, thanks to @Hiron
        if let data = object as? Data {
            return type(of: (T.self as! Coding)).from(data: data) as? T
        } else {
            return nil
        }
    }

    return object as? T
}

Upvotes: 0

Views: 3307

Answers (3)

Artem Stepanenko
Artem Stepanenko

Reputation: 3501

Let's say you have a protocol with a static method and a class which implements it:

protocol Something {
    static func doSomething()
}

class SomethingConcrete: Something {
    static func doSomething() {
        // does something concrete
    }
}

Also there's a generic function. If it gets the type Something it calls it's static method (there's no object involved).

There's two ways of doing this: overloading and casting.

Overloading (a credit to @portella)

func someFunction<T: Something>() {
    T.doSomething()
}

func someFunction<T>() {
    // does something else
}

Casting (uses a type check mentioned by @Hiron)

func someFunction<T>() {
    if T.self is Something.Type {
        (T.self as! Something.Type).doSomething()
    } else {
        // does something else
    }
}

Upvotes: 0

portella
portella

Reputation: 853

Does this fit your needs:

protocol Loggable {
    func log()
    static func staticLog()
}

func log<L: Loggable>(object: L) {
    object.log()
    L.staticLog()
}

You ensure that all the objects sent into the function conforms to the protocol Loggable

For me, i don't like this, but hope the following solution can help (i strongly recommend the first one):

protocol Loggable {
    func log()
    static func staticLog()
}

func log<L>(object: L) {
    guard let loggableObject = object as? Loggable else {
        print("💩 Not a loggable object")

        return
    }

    loggableObject.log()
    type(of: loggableObject).staticLog()
}


final class NotLoggableClass {}

final class LoggableClass: Loggable {
    func log() {
        print("👏")
    }

    static func staticLog() {
        print("😕")
    }
}

log(object: NotLoggableClass())

log(object: LoggableClass())

The call log(object: NotLoggableClass()) will return: 💩 Not a loggable object

The call log(object: LoggableClass()) will return: 👏😕

EDIT: regarding the update, do you want to check the argument type or the argument type? You are not sending any argument into your function, which seems odd. I think you would like to validate it, not the return, i guess. Is this what you are trying to achieve:

protocol Coding {
    associatedtype T

    static func decodeObject<T>(data: Data) -> T?
}

extension UIImage: Coding {
    typealias T = UIImage

    static func decodeObject<T>(data: Data) -> T? {
        return UIImage(data: data) as? T
    }
}

Regarding your solution, i don't understand the objects or where that came from. If you want the generic type to conform with the Coding protocol you can make that with a constraint in the function declaration.

Example:

protocol Coding {
    associatedtype T

    static func from(data: Data) -> T?
}

func decodeObject<T: Coding>(_ objects: [Data]) -> T? {
    guard let object = objects.first else {
        return nil
    }

    var objects = objects
    objects.removeFirst()

    return T.from(data: object) as? T
}

extension String: Coding {
    static func from(data: Data) -> String? {
        return String(data: data, encoding: .utf8)
    }
}

Although, i don't understand the objects, this should be something to be done by the type itself, not by a common helper or something, which seems to me what you are trying.

Try to avoid dynamic cast and type with Swift 😉

Hope that helps 🍻

Upvotes: 1

Hiron
Hiron

Reputation: 1487

Maybe you should write

if T.self is Coding.Type

instead of

if T.self is Coding

Upvotes: 1

Related Questions