Get the real type of a protocol to compare with type stored in a variable - swift

I would like to be able to check the object's type of an object that implements a certain protocol.
In the following method, I would like to be able to loop over all the objects from my array of IObject objects and find the one that has as its real type, the type passed in parameter.
func findObject(forType type: Any) -> IObject? {
for (key, value) in self.objects {
if value.Type == type {
return value
}
}
return nil
}
When I try to compile the above code, I've an error message saying: 'IObject' does not have a member named 'Type'.
Any suggestion?

I am using Swift 2 with Xcode 7 beta 6.
I found this question interesting. The only way I have managed to get this to work is by string comparison - i.e. if "\(object.dynamicType)" == "\(forType)".
Also - if I have properly understood the question then I think that the function possibly needs to return an optional array of IObject - because the array of objects may contain more than one of the given type.
protocol IObject: class {}
class SomeObject1: IObject {}
class SomeObject2: IObject {}
let myIObject01 = SomeObject1()
let myIObject02 = SomeObject2()
let myIObject03 = SomeObject1()
let myIObject04 = SomeObject1()
let myIObject05 = SomeObject2()
var objects: [IObject] = [myIObject01,myIObject02,myIObject03,myIObject04,myIObject05]
func findObject(forType: Any) -> [IObject]? {
var result = [IObject]()
for object in objects {
if "\(object.dynamicType)" == "\(forType)" {
result.append(object)
}
}
return result
}
let result = findObject(SomeObject1)

Try to replace this code:
if value.Type == type
to this:
if value.dynamicType == type
If it still doesn't work, try this:
if value.dynamicType == type.self

Related

Why can't Swift infer this return type properly?

I'm trying to do something I think should be pretty simple, but I'm running into trouble with Swift's type inference. I really don't understand why it's falling down here.
I have a type Cocktail, which has other properties, but the only one important here is the name:
struct Cocktail {
// ... other stuff
let name: String
}
Then I have two protocols:
protocol ScrollIndexable {
var scrollIndexTitle: String { get }
}
protocol ScrollIndexProviding {
var scrollIndices: [any ScrollIndexable] { get }
}
along with a simple conformance on String to ScrollIndexable:
extension String: ScrollIndexable {
var scrollIndexTitle: String { self }
}
I want to make it so that I can use an array of Cocktails as a ScrollIndexProviding:
extension Array: ScrollIndexProviding where Element == Cocktail {
var scrollIndices: [any ScrollIndexable] {
let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
guard let firstCharacter = cocktail.name.first else {
return
}
partialResult.insert(String(firstCharacter))
}
// The return line here has two errors:
// Cannot convert return expression of type 'Array<Cocktail>' to return type '[any ScrollIndexable]'
// No exact matches in call to initializer
return Array(firstCharacters)
}
}
This extension fails to build, with two errors:
Cannot convert return expression of type 'Array' to return type '[any ScrollIndexable]'
No exact matches in call to initializer
The second error seems like noise to me, since Set conforms to Sequence, so I should be able to use that init method.
The first error is confusing to me since the firstCharacters array is of type Set<String>, so the error message just doesn't seem to make any sense. Is there something I'm misunderstanding about the any keyword here? What's going on?
The issue is that you're inside an extension of Array where the Element is Cocktail, so when you try to create an array without specifying the element type the compiler will assume you mean for the element type to be Cocktail.
extension Array where Element: Cocktail {
func someMethod() {
// This array is of type `Array<Cocktail>` since the compiler
// assumes the array's element type should be the same as
// Self's element type, which (from the extension) is `Cocktail`.
let array = Array()
}
}
So, to fix this, just explicitly tell the compiler that the array's element type is String, as in:
extension Array: ScrollIndexProviding where Element == Cocktail {
var scrollIndices: [any ScrollIndexable] {
let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
guard let firstCharacter = cocktail.name.first else {
return
}
partialResult.insert(String(firstCharacter))
}
return Array<String>(firstCharacters)
// ^^^^^^^^ add this
}
}

Swift : how to list fields in a struct from Type object?

I want to extract a list of the fields in a struct, having at my disposal just the Type object.
public struct CoordinateData: Codable {
var longitude: Double?
var latitude: Double?
}
I receive Codable.Type (at this stage, I do not know the object is CoordinateData and I want to keep my solution generic for any Codable)
I want to extract ["longitude", "latitude"] ([String])
I tried to use Mirror and it works when I know the exact type of the object and when the type as an empty initializer:
let metatype: CoordinateData.Type = CoordinateData.self
let c3 = metatype.init()
let m3 = Mirror(reflecting: c3)
for (property, value) in m3.children {
guard let property = property else {
print("no proeprty")
continue
}
print(property)
}
But this solution does not work when all I have is Codable.Type because it has only one .init() method that expect a decoder .init(from: Decoder) which I don't have.
Any suggestion ?
This could work, But you need to consider a few things.
func mirror<T: Any>(object: T) {
let c3 = object
let m3 = Mirror(reflecting: c3)
for (property, _) in m3.children {
guard let property = property else {
print("no proeprty")
continue
}
print(property)
}
}
Use: mirror(object: CoordinateData())
mirror(object: Foo())
You always need to pass an initialized object.
That would remove the init responsibility from the function because as far as i know, you can't initialize the generic objects because they're generic.
So bypassing the object itself this would work for you.
Update: since there is no explicit struct type parameter this could be misused also but passing any argument that's not an object it wouldn't give any result, this could be solved by changing the structs to classes and pass <T> as <T: AnyObject>.
You can alternatively do it this way too, since Mirror parameter is already Any type, so we can extension the Mirror it self to make a getProperties() function check below.
extension Mirror {
func getProperties() -> [String] {
self.children.compactMap { child in
guard let property = child.label else {
return nil
}
return property
}
}
}
Usage: var list = Mirror(reflecting: Foo()).getProperties()
This will always give you an array of strings, however if there were no properties it would return an empty array.

Determine Swift Array Type

I'm trying to determine the type of a collection using Swift. That is my goal. It does not appear to be supported by the new language so I tried to fall back on Objective-C. Collection generics is a new feature and I believe was only added for Swift interoperability.
Is anyone aware of a feature that will allow me to inspect the array type of d?
class SuperClass : NSObject { }
class SubClass: SuperClass { }
let a = SubClass()
if a.isKindOfClass(SuperClass) {
// this works as expected like objective-c
print("yes")
}
let b = Array<SubClass>()
if b.isKindOfClass(Array<SuperClass>) {
// error: value type of 'Array<SubClass>' has no member isKindOfClass
print("yes")
}
if b is Array<SuperClass> {
// error: 'SuperClass' is not a subtype of 'SubClass'
print("yes")
}
You can simply test it with Array<String>() is Array<String> but If you wanna know which type have any variable you can look at d.dynamicType
#crashmstr is right
In your case b is an Array literal (a value type), not a reference type. So it can't call isKindOfClass.
try this
if let array = b as? Array<SuperClass>
{
//perform some action
}

Using a Type Variable in a Generic

I have this question except for Swift. How do I use a Type variable in a generic?
I tried this:
func intType() -> Int.Type {
return Int.self
}
func test() {
var t = self.intType()
var arr = Array<t>() // Error: "'t' is not a type". Uh... yeah, it is.
}
This didn't work either:
var arr = Array<t.Type>() // Error: "'t' is not a type"
var arr = Array<t.self>() // Swift doesn't seem to even understand this syntax at all.
Is there a way to do this? I get the feeling that Swift just doesn't support it and is giving me somewhat ambiguous error messages.
Edit: Here's a more complex example where the problem can't be circumvented using a generic function header. Of course it doesn't make sense, but I have a sensible use for this kind of functionality somewhere in my code and would rather post a clean example instead of my actual code:
func someTypes() -> [Any.Type] {
var ret = [Any.Type]()
for (var i = 0; i<rand()%10; i++) {
if (rand()%2 == 0){ ret.append(Int.self) }
else {ret.append(String.self) }
}
return ret
}
func test() {
var ts = self.someTypes()
for t in ts {
var arr = Array<t>()
}
}
Swift's static typing means the type of a variable must be known at compile time.
In the context of a generic function func foo<T>() { ... }, T looks like a variable, but its type is actually known at compile time based on where the function is called from. The behavior of Array<T>() depends on T, but this information is known at compile time.
When using protocols, Swift employs dynamic dispatch, so you can write Array<MyProtocol>(), and the array simply stores references to things which implement MyProtocol — so when you get something out of the array, you have access to all functions/variables/typealiases required by MyProtocol.
But if t is actually a variable of kind Any.Type, Array<t>() is meaningless since its type is actually not known at compile time. (Since Array is a generic struct, the compiler needs know which type to use as the generic parameter, but this is not possible.)
I would recommend watching some videos from WWDC this year:
Protocol-Oriented Programming in Swift
Building Better Apps with Value Types in Swift
I found this slide particularly helpful for understanding protocols and dynamic dispatch:
There is a way and it's called generics. You could do something like that.
class func foo() {
test(Int.self)
}
class func test<T>(t: T.Type) {
var arr = Array<T>()
}
You will need to hint the compiler at the type you want to specialize the function with, one way or another. Another way is with return param (discarded in that case):
class func foo() {
let _:Int = test()
}
class func test<T>() -> T {
var arr = Array<T>()
}
And using generics on a class (or struct) you don't need the extra param:
class Whatever<T> {
var array = [T]() // another way to init the array.
}
let we = Whatever<Int>()
jtbandes' answer - that you can't use your current approach because Swift is statically typed - is correct.
However, if you're willing to create a whitelist of allowable types in your array, for example in an enum, you can dynamically initialize different types at runtime.
First, create an enum of allowable types:
enum Types {
case Int
case String
}
Create an Example class. Implement your someTypes() function to use these enum values. (You could easily transform a JSON array of strings into an array of this enum.)
class Example {
func someTypes() -> [Types] {
var ret = [Types]()
for _ in 1...rand()%10 {
if (rand()%2 == 0){ ret.append(.Int) }
else {ret.append(.String) }
}
return ret
}
Now implement your test function, using switch to scope arr for each allowable type:
func test() {
let types = self.someTypes()
for type in types {
switch type {
case .Int:
var arr = [Int]()
arr += [4]
case .String:
var arr = [String]()
arr += ["hi"]
}
}
}
}
As you may know, you could alternatively declare arr as [Any] to mix types (the "heterogenous" case in jtbandes' answer):
var arr = [Any]()
for type in types {
switch type {
case .Int:
arr += [4]
case .String:
arr += ["hi"]
}
}
print(arr)
I would break it down with the things you already learned from the first answer. I took the liberty to refactor some code. Here it is:
func someTypes<T>(t: T.Type) -> [Any.Type] {
var ret = [Any.Type]()
for _ in 0..<rand()%10 {
if (rand()%2 == 0){ ret.append(T.self) }
else {
ret.append(String.self)
}
}
return ret
}
func makeArray<T>(t: T) -> [T] {
return [T]()
}
func test() {
let ts = someTypes(Int.self)
for t in ts {
print(t)
}
}
This is somewhat working but I believe the way of doing this is very unorthodox. Could you use reflection (mirroring) instead?
Its possible so long as you can provide "a hint" to the compiler about the type of... T. So in the example below one must use : String?.
func cast<T>(_ value: Any) -> T? {
return value as? T
}
let inputValue: Any = "this is a test"
let casted: String? = cast(inputValue)
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
Why Swift doesn't just allow us to let casted = cast<String>(inputValue) I'll never know.
One annoying scenerio is when your func has no return value. Then its not always straightford to provide the necessary "hint". Lets look at this example...
func asyncCast<T>(_ value: Any, completion: (T?) -> Void) {
completion(value as? T)
}
The following client code DOES NOT COMPILE. It gives a "Generic parameter 'T' could not be inferred" error.
let inputValue: Any = "this is a test"
asyncCast(inputValue) { casted in
print(casted)
print(type(of: casted))
}
But you can solve this by providing a "hint" to compiler as follows:
asyncCast(inputValue) { (casted: String?) in
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
}

'AnyObject' does not have a member named xxx

There is a method which receives an array of 'AnyObject'. Then inside the method based on some condition cast that AnyObject to specific object. There is no issue till this point. After cast the AnyObject into specific object, if i try to access it's properties then it throws error. I understand the issue for what reason the error is coming. But is there any other way to obtain the same in any other logic. Here is the code.
func downloadImage(#list: Array<AnyObject>, forControler: String) {
var xxxList: Array<AnyObject>
if forControler == "A" {
xxxList = list as! Array<A>
} else if forControler == "B" {
xxxList = list as! Array<B>
} else {
xxxList = list as! Array<C>
}
for (index, url) in enumerate(xxxList) {
url.A
}
}
Error throws in url.A
Thanks
You have multiple errors in your code.
First, when declaring a var in Swift (here xxxList), you have to initialize it just after. Otherwise, this var needs to be flagged as optional.
You get that error for the compiler is "dumb". It does not know what the real type of your objects wrapped into your array is. It only sees AnyObject objects, which do not have any A field or property. You have to cast your list into the desired type before iterating over it.
Your code should be like this instead:
func downloadImage(#list: [AnyObject], forController: String) {
if forController == "A" {
var aList = list as! [A]
// for loop iterating over A objects
} else if forController == "B" {
var bList = list as! [B]
// for loop iterating over B objects
} else {
var cList = list as! [C]
// for loop iterating over C objects
}
}
You must define a loop for each type. Well, you can also define a single loop and perform the casting into that one (moving the if statements into that loop).