Is reflection on Metatype? - swift

While it's possible to get a list of properties from an instance: it requires an instance which is troublesome if the Type isn't trivial and doesn't have a straightforward .init(). Similarly, can't we test the existence of a nested type?
struct SomeStruct: Codable {
var x: Int
var y: Int
}
class CoolClass: Codable {
var thing: SomeStruct
var name: String
enum CodingKeys: String, CodingKey {
case name = "title", name = "stuff"
}
}
func testA(_ type: Any.Type) {
if type is Codable.Protocol {
print("is Codable")
} else {
print("is NOT Codable")
}
let mirror = Mirror(reflecting: type)
mirror.children // << empty collection!!
}
func testB<T>(_ type: T.Type) where T: Codable {
// test if `type` defines `CodingKeys` or if it is synthesized.
}
testA(SomeStruct.self) // "is NOT Codable", can't get list of ["x", "y"]
// , nor can't get CodingKeys...
If there is no reflection for metatypes, my attempts at piggybacking off of Codable's CodingKeys (explicit or synthesized) have all failed. Is this possible?

I don't know how to solve your main problem, but you are not checking correctly if type is codable. Since type is already a structure/class type (not an object), you should try to cast it from Any.Type to Codable.Type, which will only succeed if type implements Codable:
if type as? Codable.Type != nil {
print("is Codable")
} else {
print("is NOT Codable")
}

Related

Problem of return class type with Enums - Swift

I want to make an enum that returns a class type that conforms to the codable protocol for Networking Layer.
enum SearchQueryType : String, CaseIterable{
case podcast
case episode
var classType: Any {
switch self {
case .podcast:
return PodcastSearchResponse.self
case .episode:
return EpisodeSearchResponse.self
}
}
static func objectForType <T> (type: String) -> T {
return self.allCases.first{$0.rawValue == type}!.classType as! T
}
}
the problem is when assigning the generic type from objectForType function I get an error says "Generic parameter 'T' could not be inferred"
func search (for query: String, type: String, sortByDate: Bool) {
let endpoint = SearchResultRequest.searchFor(query: query, type: type, sortByDate: sortByDate)
var objectType = SearchQueryType.ObjectForType(type: type)
NetworkManger.shared.callRequest(objectType: objectType , endpoint: endpoint) { (result) in
switch result{
}
}
}
I tried to return a type that conforms to the codable protocol, but I get an error from the search function that says the expected T.type is not codable.
is there any way to make a function or enum that returns a class type that works with my case?

Get all keys in the Codable (or Decodable)

Given the following codable struct:
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey {
case b = "B"
case z = "ZOOM"
}
}
How can i get an array of strings like so: ["B", "ZOOM"] without an instance of MyBanana?
Additionally, is there a way to get this from auto-synthesised CodingKeys, e.g.
struct MyBanana: Codable {
var b: String?
var z: String?
}
would return ["b","z"]
I tried the following for the first question:
func whatever<T, Key>(keyedBy: Key.Type) -> [T] where T: Codable, Key: CodingKey
{
let allKeys: [Key] = [Key]()
for k in keyedBy {
allKeys.append(k)
}
return [T]()
}
But i get
Type 'Key.Type' does not conform to protocol 'Sequence'
Update
For declared CodingKeys #Cameron Porter's answer works fine (add CaseIterable to the enum)
For synthesized ones this works, albeit pretty manual still:
extension Decodable {
func getAllCodingKeys(_ activator: (String) -> String) -> [String] {
let mirror = Mirror(reflecting: self)
return mirror.children.filter({ $0.label != nil }).map({ activator($0.label!) })
}
}
Then in your Codable, add
func getAllKeys() -> [String] {
return self.getAllCodingKeys { CodingKeys(stringValue: $0)!.stringValue }
}
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case b = "B"
case z = "ZOOM"
}
static var allCases: [String] {
return CodingKeys.allCases.map { $0.rawValue }
}
}
And then you can get your array of coding keys as
MyBanana.allCases
Doesn't work for auto-synthesised coding keys however

In Swift, how can you use generic enum types in protocol extensions?

I want to share functionality between CatZoo and DogZoo since they're storing similar data under the hood but I also want them to know what they are so they can act on their specific data, as shown in the DogZoo.makeNoise() method.
What is the correct type for AnimalStorageProtocol.storage?
enum CatType: String {
case lion
case tiger
case panther
}
enum DogType: String {
case coyote
case domestic
case wolf
}
struct CatZoo: AnimalStorageProtocol {
private var storage: [CatType: Int] // it's a bonus if they can stay private
}
struct DogZoo: AnimalStorageProtocol {
private var storage: [DogType: Int]
func makeNoise() {
for (key, value) in storage {
switch key {
case .coyote:
print("\(value) yips!")
case .domestic:
print("\(value) barks!")
case .wolf:
print("\(value) howls!")
}
}
}
}
I thought I could define a generic enum type in the protocol but I haven't been able to get it to work.
protocol AnimalStorageProtocol {
// var storage: <RawRepresentable where RawRepresentable.RawValue == String: Int> { get set }
var storage: [RawRepresentable: Int] { get set }
}
extension AnimalStorageProtocol {
var isEmpty: Bool {
get {
for (_, value) in storage {
if value != 0 {
return false
}
}
return true
}
}
}
There are two different way you could do it depending on what your requirements are.
If you don't require the type to be a enum, you can simply do
protocol AnimalStorageProtocol {
associatedtype AnimalType: Hashable
var storage: [AnimalType: Int] { get set }
}
This will allow any hashable type to be used.
If you require that the types can only be RawRepresentable where the RawValue is a String you'll have to define another protocol that your animal types will have to conform to.
protocol AnimalType: Hashable, RawRepresentable {
var rawValue: String { get }
}
protocol AnimalStorageProtocol {
associatedtype Animal: AnimalType
var storage: [Animal: Int] { get set }
}
Then you just have to set your enum types to conform to the AnimalType protocol.
enum CatType: String, AnimalType { ... }
enum DogType: String, AnimalType { ... }

if-let Any to RawRepresentable<String>

Let's assume this:
enum MyEnum: String { case value }
let possibleEnum: Any = MyEnum.value
if let str = stringFromPossibleEnum(possibleEnum: possibleEnum)
What's my best bet of implementing stringFromPossibleEnum without knowing enum type name?
func stringFromPossibleEnum(possibleEnum: Any) -> String? {
// how should this be implemented without knowing enum type name?
}
UPD: ok, it's getting better, with this I can tell if possibleEnum is an enum:
if Mirror(reflecting: possibleEnum).displayStyle == .enum { print("yes!") }
But how to tell if that's a String-based enum?
UPD: this tweet suggests that you can get rawValue as Any from Enum. You can probably then check if that rawValue is String. But how to get rawValue from Mirror?
Ok, so this is basically not doable currently out of the box, as you can't as?-cast to RawRepresentable, and Mirror does not provide rawValue for enums.
I'd say the best bet is to make own protocol, provide default implementation for String-based RawRepresentable and conform all enums manually like so:
Assuming these are the enums:
enum E1: String { case one }
enum E2: String { case two }
enum E3: String { case three }
StringRawRepresentable protocol and default implementation:
protocol StringRawRepresentable {
var stringRawValue: String { get }
}
extension StringRawRepresentable
where Self: RawRepresentable, Self.RawValue == String {
var stringRawValue: String { return rawValue }
}
Conform all needed existing enums to the protocol:
extension E1: StringRawRepresentable {}
extension E2: StringRawRepresentable {}
extension E3: StringRawRepresentable {}
And now we can cast to StringRawRepresentable:
func stringFromPossibleEnum(possibleEnum: Any) -> String? {
if let e = possibleEnum as? StringRawRepresentable { return e.stringRawValue }
return nil
}
stringFromPossibleEnum(possibleEnum: E2.two as Any)
Not sure what you're really trying to achieve here, but here it is:
enum MyEnum: String {
case A
case B
case C
}
func stringFromEnum<T: RawRepresentable>(_ value: T) -> String
where T.RawValue == String {
return value.rawValue
}
print(stringFromEnum(MyEnum.A))
print(stringFromEnum(MyEnum.B))
print(stringFromEnum(MyEnum.C))

How to pass type (e.g. String) as function argument?

I'm using "JSON to my own class" and I have real headache with type conversions. Typical conversion is looks like:
I need: "55" (JSON) -> 55 (Int)
My path: AnyObject? -> String -> Int
Swift isn't converting AnyObject("55") to Int("55") directly, Swift thinks that "55" is always String. So I've written helper method:
internal func jsonDirectToInt(from: AnyObject?) -> Int? {
guard let string = from as? String,
let int = Int(string) else { return nil }
return int
}
My question: can I pass class type to function as argument, so I can write something like that:
internal func jsonDirectToInt(from: AnyObject?, type: <T>) -> Int? {
guard let string = from as? String,
let int = <T>(string) else { return nil }
return int
}
Possibly I'm misunderstanding what you want to achieve, but it sounds from your question and the third code block that follows that you want a "jsonDirectToAnything" generic function; attempting to convert the JSON from parameter to the type passed via type parameter, using a JSON (AnyObject) -> String -> SomeType pipeline. In your code example above (last code block) your return type is set to Int even when showing that you want some generic <T>(string) conversion (which is assigned to return type), so I will assume that using return type Int is not what you're after, but rather using a generic return type (if not; the question don't really make much sense for me).
Anyway, for the technical discussion: you can create such a function with the generic return type constrained to types that conform to a protocol---say StringInitializable---that includes a blueprint for an (failable) initializer by String instances. You extend the types you want to be able to use the "generic" jsonDirect method to StringInitializable, and, if needed, implement the String initializer blueprinted by the protocol. In the example below, I've blueprinted initializer init?(_ text: String) in StringInitializable. This failable initializer is readily natively available for e.g. Double and String types, but needs to be implemented (just as a wrapper) for e.g. extending Int to the protocol.
Finally note, before we proceed, that there exist several existing tools for handling conversion of JSON data to native Swift types, e.g.
SwiftyJSON
Example solution for your specific question:
/* Protocol with blueprint for failable initializer by String */
protocol StringInitializable {
init?(_ text: String)
}
/* Extend type types you want to use "generically" in 'jsonDirect' method */
extension Double : StringInitializable { } // already has a 'init?(_ text: String)', OK
extension String : StringInitializable { } // already has a 'init?(_ text: String)', OK
extension Int : StringInitializable { // point to 'init(_:radix:)' initializer
init?(_ text: String) {
guard let foo = Int.init(text, radix: 10) else {
return nil
}
self = foo
}
}
/* Your own class */
class MyClass: StringInitializable {
let foo : Int?
required init?(_ text: String) {
foo = Int(text)
}
}
/* jsonDirect for (attempted) type conversion from AnyObject to
generic type, where the latter is constrained to types conforming
to protocol 'StringInitializable' */
func jsonDirect<T: StringInitializable>(from: AnyObject?, toType _ : T.Type) -> T? {
guard let foo = from as? String, let bar = T(foo) else {
return nil
}
return bar
}
Example usage for JSON conversion into Int, Double and String as well as a the custom class MyClass:
/* Example usage */
var myJSONInt : AnyObject = "55"
var myJSONInvalidInt : AnyObject = "foo"
var myJSONDouble : AnyObject = "55.3"
var myJSONString : AnyObject = "Foo"
/* Attempt json -> specified type conversions */
let fooInt = jsonDirect(myJSONInt, toType: Int.self)
let fooMyClass = jsonDirect(myJSONInt, toType: MyClass.self)
let fooInvalidInt = jsonDirect(myJSONInvalidInt, toType: Int.self)
let fooDouble = jsonDirect(myJSONDouble, toType: Double.self)
let fooIntString = jsonDirect(myJSONInt, toType: String.self)
/* Results */
print(fooInt.dynamicType, ": value =", fooInt ?? "nil")
// Optional<Int> : value = 55
print(fooMyClass.dynamicType, ": value =", fooMyClass?.foo ?? "nil")
// Optional<MyClass> : value = 55
print(fooInvalidInt.dynamicType, ": value =", fooInvalidInt ?? "nil")
// Optional<Int> : value = nil
print(fooDouble.dynamicType, ": value =", fooDouble ?? "nil")
// Optional<Double> : value = 55.3
print(fooIntString.dynamicType, ": value =", fooIntString ?? "nil")
// Optional<String> : value = 55