简体   繁体   中英

How to write a return type of a method as a class that conforms to a protocol + swift

I am working on a project where I need to replicate the functionality developed with android in iOS using swift.

In the android app, there is a place where the return type of a method inside an abstract class Device is mentioned as,

abstract fun getDT(): Class<out DeviceType>

where DeviceType is itself another abstract class. So I heard from the android developer that, in the actual implementation of this method, it will return a class that inherits the DeviceType as below,

override fun getDT(): Class<out DeviceType> {
    return type1DeviceType::class.java
}

where type1DeviceType actually inherits DeviceType abstract class as below

public class type1DeviceType extends DeviceType {

So in our iOS terms, the equivalent for the abstract class is protocol.

So in iOS, I have written a protocol in place of the abstract class in Android. And for the return type of the abstract function inside it, I need to mention the return type as something that conforms to the DeviceType protocol. Any idea how to achieve this?

I tried with the following code in swift.

 public func getDT() -> DeviceTypeProtocol.Type {
    return type1DeviceType as! DeviceTypeProtocol.Type
}

But during runtime, I get the error,

Swift runtime failure: type cast failed

Did you mean something like this?

protocol DeviceType {
    func getDeviceType() -> DeviceType.Type
}

extension DeviceType {
    func getDeviceType() -> DeviceType.Type { Self.self }
}



class AudioDevice: DeviceType {
    func getDeviceType() -> DeviceType.Type { AudioDevice.self }
}

class Microphone: AudioDevice {
}

class Speaker: AudioDevice {
    override func getDeviceType() -> DeviceType.Type { Speaker.self }
}



class VideoDevice: DeviceType {}

class Camera: VideoDevice {}

class Monitor: VideoDevice {
    func getDeviceType() -> DeviceType.Type { VideoDevice.self }
}


func test() {
    print(AudioDevice().getDeviceType()) // prints AudioDevice
    print(Microphone().getDeviceType()) // prints AudioDevice
    print(Speaker().getDeviceType()) // prints Speaker
    print(VideoDevice().getDeviceType()) // prints VideoDevice
    print(Camera().getDeviceType()) // prints Camera
    print(Monitor().getDeviceType()) // prints VideoDevice
}

A protocol is defined for a DeviceType which has a capability of returning a type with getDeviceType that is also a type of DeviceType .

Extension of a protocol is not needed for what you are describing but I wanted to demonstrate it either way. It is used in VideoDevice .

So the AudioDevice inherits the protocol and explicitly defines a method to get a device type. Since it returns type of AudioDevice that is what it prints out. The Microphone inherits from AudioDevice (not from DeviceType ) and does not override the method so it also returns AudioDevice . And Speaker also inherits from AudioDevice but does override the method and so does return Speaker .

The VideoDevice is a bit more fun. It inherits the protocol but does not explicitly define the method needed. Therefore it uses the extension which has a funny syntax of Self.self . It basically just means "return whatever is a static type of a dynamic self" if that makes more sense... This is only possible because extension of a protocol is defined. Removing the extension will create a compile time error that will let you know that you DO need to define that method. Now because the extension is nicely defined the VideoDevice already prints out itself. Same goes for Camera which inherits from VideoDevice (not from DeviceType ). And then Monitor again overrides the method and prints out VideoDevice instead of Monitor .

Naturally you could define device categories (in this case video and audio) as protocols that inherit device type. And you could also put extensions on those protocols. Take a look at this example:

protocol DeviceType {
    func getDeviceType() -> DeviceType.Type
}


protocol AudioDevice: DeviceType { }

class Microphone: AudioDevice {
    func getDeviceType() -> DeviceType.Type { Microphone.self }
}

class Speaker: AudioDevice {
    func getDeviceType() -> DeviceType.Type { Speaker.self }
}


protocol VideoDevice: DeviceType { }

extension VideoDevice {
    func getDeviceType() -> DeviceType.Type { Self.self }
}

class Camera: VideoDevice {
}
class Monitor: VideoDevice {
    func getDeviceType() -> DeviceType.Type { Camera.self }
}


func test() {
    print(Microphone().getDeviceType()) // prints Microphone
    print(Speaker().getDeviceType()) // prints Speaker
    print(Camera().getDeviceType()) // prints Camera
    print(Monitor().getDeviceType()) // prints Camera
}

Well a Swift protocol is not the same as a Kotlin abstract class. A closer comparison would be Kotlin interface and Swift protocol .

I'm a little confused on what your requirements are here. However, based on what I see here it seems like a good case for Protocol Oriented Programming , which can mitigate the need for multiple nested abstract classes or subclasses.

I'm also a little confused why you would need to get the DeviceType of a DeviceType .... is the DeviceType itself not the DeviceType ?

In my head it seems something like this would be a lot more simple:

Kotlin

See in Kotlin playground

interface Device {
    fun doSomething()
}

interface MobileDevice: Device {
    fun onTap()
}

interface DesktopDevice: Device {
    fun onClick()
}

interface WearableDevice: Device {
    fun onButtonPush()
}

class AndroidPhone: MobileDevice {
    override fun doSomething() {
        println("I'm an Android phone.")
    }
    
    override fun onTap() {
        println("Tap the screen.")
    }
}

class MacDesktop: DesktopDevice {
    override fun doSomething() {
        println("I'm a Mac desktop.")
    }
    
    override fun onClick() {
        println("Click the magic mouse.")
    }
}

class SmartNecklace: WearableDevice {
    override fun doSomething() {
        println("I'm a smart necklace.")
    }
    
    override fun onButtonPush() {
        println("Help! I've fallen and I can't get up!")
    }
}

Which could be used like:

fun exampleFunction() {
    val mobile = AndroidPhone()
    val desktop = MacDesktop()
    val wearable = SmartNecklace()
    mobile.doSomething()
    desktop.doSomething()
    wearable.doSomething()
    
    val devices = listOf(mobile, desktop, wearable)
    
    devices.forEach { device ->
        when (device) {
            is MobileDevice -> device.onTap()
            is DesktopDevice -> device.onClick()
            is WearableDevice -> device.onButtonPush()
            else -> println("Unknown Type.")
        }
    }
}

Swift

In your Swift version you can also add some default behaviors to the protocols (see the protocol extension examples below).

protocol Device {
    func doSomething()
}

protocol MobileDevice: Device {
    func onTap()
}

protocol DesktopDevice: Device {
    func onClick()
}

protocol WearableDevice: Device {
    func onButtonPush()
}

extension Device {
    func doSomething() {
        print("Doing default thing.")
    }
}

extension WearableDevice {
    func onButtonPush() {
        print("Help! I've defaulted and I can't get up!")
    }
}

class AndroidPhone: MobileDevice {
    func onTap() {
        print("Tap the screen.")
    }
}

class MacDesktop: DesktopDevice {
    func doSomething() {
        print("I'm a Mac desktop.")
    }

    func onClick() {
        print("Click the magic mouse.")
    }
}

class SmartNecklace: WearableDevice {
    func doSomething() {
        print("I'm a smart necklace.")
    }
}

Which could be used like:

func exampleFunction() {
    let mobile = AndroidPhone()
    let desktop = MacDesktop()
    let wearable = SmartNecklace()

    mobile.doSomething()
    desktop.doSomething()
    wearable.doSomething()

    let devices: Array<Device> = [mobile, desktop, wearable]

    devices.forEach { device in
        switch (device) {
        case let device as MobileDevice:
            device.onTap()
        case let device as DesktopDevice:
            device.onClick()
        case let device as WearableDevice:
            device.onButtonPush()
        default:
            print("Uknown type")
        }
    }
}

Output

Doing default thing.
I'm a Mac desktop.
I'm a smart necklace.
Tap the screen.
Click the magic mouse.
Help! I've defaulted and I can't get up!

If you did something like this, you would already know the type by nature of the object (as seen in switch/when blocks). You wouldn't need a method to get the device type. You would just have it.

If there is something I'm missing from your question, let me know.

As for your error:

Swift runtime failure: type cast failed

If the cast is failing, I'm guessing type1DeviceType is a DeviceTypeProtocol , not a DeviceTypeProtocol.Type .

public func getDT() -> DeviceTypeProtocol.Type {
    return type1DeviceType as! DeviceTypeProtocol
}

Check what the type shows up as when you try to type out or use type1DeviceType, or check the quick help on it to see the type. What do you see as the actual type when you get to that point?

I would also ask how important is it to do this the exact same way as the Android version? I always recommend proper planning cross-platform to keep things as similar as possible. It always helps, especially when debugging and building things together as a team.

And as you can see from the above code, it can be almost exact. That's how I do things as well. But not at the expense of being able to get the job done.

class type1DeviceType: DeviceTypeProtocol {
  public func getDT() -> DeviceTypeProtocol.Type {
    // return type1DeviceType as! DeviceTypeProtocol.Type
    return type1DeviceType.self
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM