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
Swift 4 allows multiple protocols to be called at once with the help of protocol composition.
In Swift, a protocol defines a blueprint of methods or properties that can then be adopted by classes (or any other types). Here, Greet - name of the protocol. name - a gettable property.
You can create objects from classes, whereas protocols are just type definitions. Try to think of protocols as being abstract definitions, whereas classes and structs are real things you can create.
Swift protocols on their side do not allow optional methods. But if you are making an app for macOS, iOS, tvOS or watchOS you can add the @objc keyword at the beginning of the implementation of your protocol and add @objc follow by optional keyword before each methods you want to be optional.
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:
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.")
}
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With