I have a generic dictionary, and built a generic function to access that dictionary (to prevent concurrency access problems).
My (singleton) data class looks like:
class AppData {
static let genericDict: Dictionary<String, Any> = [:]
static func genericGet<T: Any>(_ objID: String) -> T {
let retVal: T
mySemaphore.wait()
retVal = genericDict[objID] as! T
mySemaphore.signal()
return retVal
}
}
However, when I call my function like so:
class SomeClass {
let product: SomeObj = AppData.genericGet(objID) as! SomeObj
}
I get the error:
Generic parameter 'T' could not be inferred
I both explicitly and implicitly cast the type to my desired value. Not sure what else I can do to fix this issue.
I've tried restarting XCode, does not help.
The real code:
public class AppData: ObservableObject {
static private var dataDirectory: Dictionary<String, Any> = Dictionary(minimumCapacity: 10000)
static let dataGetLock = DispatchSemaphore(value: 1)
static func get<T: Any>(_ objID: String) -> T? {
let retVal: T
dataGetLock.wait()
retVal = dataDirectory[objID] as! T
dataGetLock.signal()
return retVal
}
}
I get the error on the following two lines:
class StoreViewEnvironment: ObservableObject {
func initProductLoad(storeID: String) {
...
let liveStore: Store = AppData.get(storeID) as! Store
let liveMenu: Menu = AppData.get(liveStore.menuID) as! Menu
...
}
}
I don't approve of the way you're doing this (on the general grounds that Any is just about always a bad smell), but here's a version that compiles (just delete the Any constraint):
class AppData {
static let genericDict: Dictionary<String, Any> = [:]
static func get<T>(_ objID: String) -> T {
let retVal: T
retVal = genericDict[objID] as! T
return retVal
}
}
class SomeClass {
let product : String = AppData.get("yoho")
}
Related
I'm very new in Swift so i might be missing some basics.
I have struct:
struct MyStruct {
var a: Int
var b: String
var c: Bool
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
}
and function, that should iterate through given struct properties and return their types in json:
func structProps(){
let elm = MyStruct()
let mirror = Mirror(reflecting: elm)
var exampleDict: [String: String] = [:]
for child in mirror.children {
exampleDict[child.label!] = String(describing:type(of: child.value)) as String
}
if let theJSONData = try? JSONSerialization.data(
withJSONObject: exampleDict,
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
}
}
it kinda return what i need:
JSON string = {"a":"Int","b":"String","c":"Bool"}
Because i'm having a lot of structs and i want to export json from all of them, i'm wondering if there is a way to have generic initializer. Without passing default values.
It means without
init() {
a: Int = 1,
b: String? = "",
c: Bool? = false
}
If I understand correctly , you can create a base protocol and add as an extension to your structures like
protocol BaseFunction {
func getElements() -> [String : String]
func getDict() -> String
}
extension BaseFunction {
func getElements() -> [String : String] {
let mirror = Mirror(reflecting: self)
let propertiesRemoveNil = mirror.children.filter({!(($0.value as AnyObject) is NSNull)})
let properties = propertiesRemoveNil.compactMap({$0.label})
var types = [String]()
_ = mirror.children.forEach({
types.append(String(describing:type(of: $0.value)))
})
return Dictionary(uniqueKeysWithValues: zip(properties, types))
}
func getDict() -> String{
if let theJSONData = try? JSONSerialization.data(
withJSONObject: getElements(),
options: []) {
let theJSONText = String(data: theJSONData, encoding: .ascii)
return theJSONText ?? ""
}
return ""
}
}
And usage :
func structProps(){
let elm = MyStruct()
print(elm.getDict())
}
OUTPUT :
{"a":"Int","b":"String","c":"Bool"}
I was going to write a comment about using a protocol but I thought it would be easier to understand as an answer with some code.
To make the usage more generic so you don't need specific code for each type of struct you should use a protocol. Instead of having an init that might clash with already existing init in the struct I prefer a static method that returns a new object, also known as a factory method
protocol PropertyExtract {
static func createEmpty() -> PropertyExtract
}
Then we can make use of the default init for the struct or any supplied to create an object with some initial values by letting the struct conform to the protocol in an extension
extension MyStruct: PropertyExtract {
static func createEmpty() -> PropertyExtract {
MyStruct(a: 0, b: "", c: false)
}
}
And instead of hardcoding or passing a specific type of object to the encoding function we pass the type of it
func structProps(for structType: PropertyExtract.Type)
and use the protocol method to get an instance of the type
let object = structType.createEmpty()
The whole function (with some additional changes)
func structProps(for structType: PropertyExtract.Type) -> String? {
let object = structType.createEmpty()
let mirror = Mirror(reflecting: object)
let exampleDict = mirror.children.reduce(into: [String:String]()) {
guard let label = $1.label else { return }
$0[label] = String(describing:type(of: $1.value))
}
if let data = try? JSONEncoder().encode(exampleDict) {
return String(data: data, encoding: .utf8)
}
return nil
}
And this is then called with the type of the struct
let jsonString = structProps(for: MyStruct.self)
According to Creating a Swift Runtime Library, there is a way to access meta data types without initializer.
Solution for what i was asking for is possible with Runtime library.
Mirror(reflecting:) expects an instance of a type and not a type itself, ref.
One idea is to have a generic function that works with all types conforming to a protocol that provides an empty init. Something like:
protocol EmptyInitializable {
init()
}
struct StructOne {
let a: Bool
let b: String
}
struct StructTwo {
let c: Int
let d: Float
}
extension StructOne: EmptyInitializable {
init() {
a = false
b = ""
}
}
extension StructTwo: EmptyInitializable {
init() {
c = 1
d = 1.0
}
}
func print(subject: EmptyInitializable) -> String? {
let dictionary = Dictionary(uniqueKeysWithValues:
Mirror(reflecting: subject).children.map {
($0.label!, String(describing: type(of: $0.value)))
}
)
return (try? JSONSerialization.data(withJSONObject: dictionary)).flatMap {
String(data: $0, encoding: .utf8)
}
}
print(subject: StructOne()) // "{"a":"Bool","b":"String"}"
print(subject: StructTwo()) // "{"d":"Float","c":"Int"}"
You still have to implement the empty init and assign some values and I'm afraid there's no way currently to avoid that.
I am working with Realm and ObjectMapper and would like to create some extensions to make my life easier when backing up some data to JSON. I have the following extensions defined:
extension Mappable where Self:Object {
func getCompleteJSONDictionary() throws -> [String: Any]? {
var returnValue: [String: Any]?
if self.isManaged, let realm = self.realm, !realm.isInWriteTransaction {
try realm.write {
var data = self.toJSON()
data["id"] = self.getPrimaryKeyValue()
returnValue = data
}
} else {
var data = self.toJSON()
data["id"] = self.getPrimaryKeyValue()
returnValue = data
}
return returnValue
}
}
extension Results where Element:Object, Element:Mappable {
func getAllCompleteJSONDictionaries() throws -> Array<[String:Any]>? {
var array: Array<[String:Any]> = Array()
for element in self {
if let dictionary = try? element.getCompleteJSONDictionary(), let data = dictionary {
array.append(data)
}
}
if array.count > 0 {
return array
} else {
return nil
}
}
}
extension Realm {
func getJSONBackupData<T>(forTypes types: [T.Type]) throws -> [String: Any] where T:Object, T:Mappable {
var data: [String: Any] = [:]
try self.write {
for type in types {
let entities = self.objects(type)
if let entityJsonData = try entities.getAllCompleteJSONDictionaries() {
data[String(describing: type)] = entityJsonData
}
}
}
return data
}
}
The first two extensions work fine, but as soon as I try to use the last, the country class conforms to both Object and Mappable:
var finalData = realm.getJSONBackupData(forTypes:[Country.self])
I get an error that T cannot be inferred. I still get myself hopelessly muddled when it comes to generics in Swift so am guessing i am just not understanding the problem correctly. Is there an easy fix here or have I asked too much of the compiler?
The problem is that the Realm Objects does not conform to Mappable Protocol. So the Country Object is not Mappable and thus the complier is saying that Type Country.self cannot be inferred as Object and Mappable.
I'm trying to get all the members of a generic class T, I can get the properties based on a specific class.
But, how I can do it using Mirror ?
let mirrored_object = Mirror(reflecting: user)
for (index, attr) in mirrored_object.children.enumerated() {
if let propertyName = attr.label as String! {
print("Attr \(index): \(propertyName) = \(attr.value)")
}
}
I added this as extension
extension NSObject {
public func GetAsJson() -> [[String:Any?]] {
var result:[[String: Any?]] = [[String: Any?]]()
for item in self {
var dict: [String: Any?] = [:]
for property in Mirror(reflecting: self).children {
dict[property.label!] = property.value
}
result.append(dict)
}
return result
}
}
Let say I have following code
class Foo {
}
var fooArray : Array<Foo> = Array<Foo>()
// This is important because in my code I will get Any (vs Array<Foo)
var fooArrayAny : Any = foo
I want to be able to get a Type Foo out of variable fooArrayAny.
If I had fooArray, I would do something like that:
let type = fooArray.dynamicType.Element().dynamicType
However, this doesn't work with fooArrayAny. It says that it has no member Element()
If you set NSObject as the base class of Foo, then you could use the following code:
class EVReflectionTests: XCTestCase {
func testArrayInstance() {
let fooArray : Array<Foo> = Array<Foo>()
let fooArrayAny : Any = fooArray
if let arr = fooArray as? Array {
let i = arr.getArrayTypeInstance(arr)
print("i = \(i)")
}
}
}
class Foo: NSObject {
}
extension Array {
public func getArrayTypeInstance<T>(arr:Array<T>) -> T {
return arr.getTypeInstance()
}
public func getTypeInstance<T>() -> T {
let nsobjectype : NSObject.Type = T.self as! NSObject.Type
let nsobject: NSObject = nsobjectype.init()
return nsobject as! T
}
}
This code is a snippet of my library EVReflection
Update:
I noticed a mistake in the code above. I used fooArray instead of fooArrayAny. When changing that to fooArrayAny I get the same error as you that the compiler does not have the Element. After playing around with this, I found out a solution that does work. Again it has parts of code of my EVReflection library.
class EVReflectionTests: XCTestCase {
func testArrayInstance() {
let fooArray : Array<Foo> = Array<Foo>()
let fooArrayAny : Any = fooArray
if let _ = fooArrayAny as? NSArray {
var subtype: String = "\(Mirror(reflecting: fooArrayAny))"
subtype = subtype.substringFromIndex((subtype.componentsSeparatedByString("<") [0] + "<").endIndex)
subtype = subtype.substringToIndex(subtype.endIndex.predecessor())
print("The type of the array elements = \(subtype)")
if let instance = swiftClassFromString(subtype) {
print("An instance of the array element = \(instance)")
let type = instance.dynamicType
print("An instance of the array element = \(type)")
}
}
}
// All code below is a copy from the EVReflection library.
func swiftClassFromString(className: String) -> NSObject? {
var result: NSObject? = nil
if className == "NSObject" {
return NSObject()
}
if let anyobjectype : AnyObject.Type = swiftClassTypeFromString(className) {
if let nsobjectype : NSObject.Type = anyobjectype as? NSObject.Type {
let nsobject: NSObject = nsobjectype.init()
result = nsobject
}
}
return result
}
func swiftClassTypeFromString(className: String) -> AnyClass! {
if className.hasPrefix("_Tt") {
return NSClassFromString(className)
}
var classStringName = className
if className.rangeOfString(".", options: NSStringCompareOptions.CaseInsensitiveSearch) == nil {
let appName = getCleanAppName()
classStringName = "\(appName).\(className)"
}
return NSClassFromString(classStringName)
}
func getCleanAppName(forObject: NSObject? = nil)-> String {
var bundle = NSBundle.mainBundle()
if forObject != nil {
bundle = NSBundle(forClass: forObject!.dynamicType)
}
var appName = bundle.infoDictionary?["CFBundleName"] as? String ?? ""
if appName == "" {
if bundle.bundleIdentifier == nil {
bundle = NSBundle(forClass: EVReflection().dynamicType)
}
appName = (bundle.bundleIdentifier!).characters.split(isSeparator: {$0 == "."}).map({ String($0) }).last ?? ""
}
let cleanAppName = appName
.stringByReplacingOccurrencesOfString(" ", withString: "_", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
.stringByReplacingOccurrencesOfString("-", withString: "_", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
return cleanAppName
}
}
class Foo: NSObject {
}
The output of this code will be:
The type of the array elements = Foo
An instance of the array element = <EVReflection_iOS_Tests.Foo: 0x7fd6c20173d0>
An instance of the array element = Foo
Swift 5
Its old but I want to share my version if someone needs it.
I use ModelProtocol and I suggests you use protocol so we can do operation to model via protocol (ex: static instantiating).
protocol ModelProtocol {}
class Foo: ModelProtocol {
}
Since I can't check type is Array, I use CollectionProtocol and create Array extension to get Element via protocol.
protocol CollectionProtocol {
static func getElement() -> Any.Type
}
extension Array: CollectionProtocol {
static func getElement() -> Any.Type {
return Element.self
}
}
Testing.
var fooArray: Array<Foo> = Array<Foo>()
var fooArrayAny: Any = fooArray
let arrayMirrorType = type(of: fooArrayAny)
String(describing: "arrayMirrorType: \(arrayMirrorType)")
if arrayMirrorType is CollectionProtocol.Type {
let collectionType = arrayMirrorType as! CollectionProtocol.Type
String(describing: "collectionType: \(collectionType)")
let elementType = collectionType.getElement()
String(describing: "elementType: \(elementType)")
let modelType = elementType as! ModelProtocol.Type
String(describing: "modelType: \(modelType)")
// ... now you can do operation to modelType via ModelProtocol
}
Printing.
arrayMirrorType: Array<Foo>
collectionType: Array<Foo>
elementType: Foo
modelType: Foo
class Foo {
var foo: Int = 1
}
struct Boo {
var boo: String = "alfa"
}
func f(array: Any) {
let mirror = Mirror(reflecting: array)
let arraytype = mirror.subjectType
switch arraytype {
case is Array<Foo>.Type:
let fooArray = array as! Array<Foo>
print(fooArray)
case is Array<Boo>.Type:
let booArray = array as! Array<Boo>
print(booArray)
default:
print("array is not Array<Foo> nor Array<Boo>")
break
}
}
var fooArray : Array<Foo> = []
fooArray.append(Foo())
var anyArray : Any = fooArray // cast as Any
f(anyArray) // [Foo]
var booArray : Array<Boo> = []
booArray.append(Boo())
anyArray = booArray // cast as Any
f(anyArray) // [Boo(boo: "alfa")]
var intArray : Array<Int> = []
anyArray = intArray // cast as Any
f(anyArray) // array is not Array<Foo> nor Array<Boo>
I'm just starting swift and am trying to replicate functionality I've used with obj-c. basically it's a json to object model mapping. the code I'm using looks like it should work, but I'm getting the error in the title and am not sure why or how to fix it.
Here's some sample code:
struct foo {
let modelMap = ["DataModelBase" : DataModelBase.self,
"ManifestDataModel" : ManifestDataModel.self]
func createModel(classString: String, json: Dictionary<String, AnyObject>) -> AnyObject {
if let modelClass: DataModelBase = modelMap[classString] {
let model = modelClass.initWithDictionary(json)
return model
}
return json
}
The error is being triggered on the line:
if let modelClass: DataModelBase = modelMap[classString]
DataModelBase is a base class and ManifestDataModel subclasses DataModelBase
Can someone shed some light on why the error message is happening and how I can fix it?
What you want in that if let statement is probably
if let modelClass: DataModelBase = modelMap[classString] as? DataModelBase {
this will only run if modelMap[classString] is a DataModelBase
EDIT:
class DataModelBase {
required init(json: [String:AnyObject]) {
}
}
class ManifestDataModel: DataModelBase {
required init(json: [String:AnyObject]) {
super.init(json: json)
}
}
struct foo {
let modelMap = ["DataModelBase" : DataModelBase.self,
"ManifestDataModel" : ManifestDataModel.self]
func createModel(classString: String, json: [String: AnyObject]) -> AnyObject {
if modelMap[classString] != nil {
if let modelType = modelMap[classString] {
let model = modelType.init(json: json)
return model
}
}
return json
}
}