How to list all Variables of a class in swift - class

Is there a way to list all Variables of a class in Swift?
For example:
class foo {
var a:Int? = 1
var b:String? = "John"
}
I want to list it like this: [a:1, b:"John"]

How you can do it in Swift 3.0 recursively:
import Foundation
class FirstClass {
var name = ""
var last_name = ""
var age = 0
var other = "abc"
func listPropertiesWithValues(reflect: Mirror? = nil) {
let mirror = reflect ?? Mirror(reflecting: self)
if mirror.superclassMirror != nil {
self.listPropertiesWithValues(reflect: mirror.superclassMirror)
}
for (index, attr) in mirror.children.enumerated() {
if let property_name = attr.label {
//You can represent the results however you want here!!!
print("\(mirror.description) \(index): \(property_name) = \(attr.value)")
}
}
}
}
class SecondClass: FirstClass {
var yetAnother = "YetAnother"
}
var second = SecondClass()
second.name = "Name"
second.last_name = "Last Name"
second.age = 20
second.listPropertiesWithValues()
results:
Mirror for FirstClass 0: name = Name
Mirror for FirstClass 1: last_name = Last Name
Mirror for FirstClass 2: age = 20
Mirror for FirstClass 3: other = abc
Mirror for SecondClass 0: yetAnother = YetAnother

The following should use reflection to generate the list of members and values. See fiddle at http://swiftstub.com/836291913/
class foo {
var a:Int? = 1
var b:String? = "John"
}
let obj = foo()
let reflected = reflect(obj)
var members = [String: String]()
for index in 0..<reflected.count {
members[reflected[index].0] = reflected[index].1.summary
}
println(members)
Output:
[b: John, a: 1]

Maybe a bit late for the party, but this solution using reflection and Mirror is 100% working:
class YourClass : NSObject {
var title:String
var url:String
...something other...
func properties() -> [[String: Any]] {
let mirror = Mirror(reflecting: self)
var retValue = [[String:Any]]()
for (_, attr) in mirror.children.enumerated() {
if let property_name = attr.label as String! {
retValue.append([property_name:attr.value])
}
}
return retValue
}
}
and somewhere in your code...
var example = MoreRow(json: ["title":"aTitle","url":"anURL"])
print(example.listPropertiesWithValues())

I got clue from here. https://medium.com/#YogevSitton/use-auto-describing-objects-with-customstringconvertible-49528b55f446
This is a demo above Swift 4.0.
import Foundation
extension CustomStringConvertible {
var description : String {
var description: String = ""
if self is AnyObject { // unsafeAddressOf((self as! AnyObject))
description = "***** \(type(of: self)) - <\(Unmanaged.passUnretained(self as AnyObject).toOpaque())>***** \n"
} else {
description = "***** \(type(of: self)) *****\n"
}
let selfMirror = Mirror(reflecting: self)
for child in selfMirror.children {
if let propertyName = child.label {
description += "\(propertyName): \(child.value)\n"
}
}
return description
}
}
extension NSObject {
var description: String {
var description: String = ""
if self is AnyObject { // unsafeAddressOf((self as! AnyObject))
description = "***** \(type(of: self)) - <\(Unmanaged.passUnretained(self as AnyObject).toOpaque())>***** \n"
} else {
description = "***** \(type(of: self)) *****\n"
}
let selfMirror = Mirror(reflecting: self)
for child in selfMirror.children {
if let propertyName = child.label {
description += "\(propertyName): \(child.value)\n"
}
}
return description
}
}
class AA: CustomStringConvertible {
var a: String = "aaa"
}
class BB: NSObject {
var b: String = "bbb"
}
let aa = AA()
print(aa)
let bb = BB()
print(bb.description)
Output --
***** AA - <0x00000001001038e0>*****
a: aaa
***** BB - <0x0000000100103310>*****
b: bbb

Related

Swift: checking 2 object from same class have different values?

I'm using swift in my project. I have 2 object from same class(for example object A and object B from Class MyClass):
class MyClass: NSObject {
var someString: String = ""
var someInt: Int = 0
}
...
let A = MyClass()
A.someString = "A object"
A.someInt = 1
let B = MyClass()
B.someString = "B object"
B.someInt = 2
how I can check if same properties have same value, and if not return value and key of the property?
I think we can do this with using Mirror with 2 for loop inside each other, am I write?
I think you're looking for something like this pretty much:
import Foundation
class MyClass {
var someString: String = ""
var someInt: Int = 0
}
let a = MyClass()
a.someString = "A object"
a.someInt = 1
let b = MyClass()
b.someString = "B object"
b.someInt = 2
func compare<T: MyClass>(_ instance: T, with instance2: T) -> [String: AnyHashable] {
let sourceMirror = Mirror(reflecting: instance)
let targetMirror = Mirror(reflecting: instance2)
var output = [String: AnyHashable]()
for sourceChild in sourceMirror.children {
guard let label = sourceChild.label else { continue }
guard let targetChild = (targetMirror.children.first { $0.label! == label }) else {
fatalError("Failed to find target child, since types are same this fatal error should not be fired")
}
guard
let firstValue = sourceChild.value as? AnyHashable,
let secondValue = targetChild.value as? AnyHashable
else {
continue
}
guard firstValue != secondValue else { continue }
output[label] = secondValue
}
return output
}
for result in compare(a, with: b) {
print("label: \(result.key), value: \(result.value)")
}
The downside of this method is all of your fields must be conforming to Hashable protocol if you want to see the difference between these.
The output is:
label: someInt, value: 2
label: someString, value: B object

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

Get a type of Element of an array in Swift (through reflection)

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>

How to create objects from SwiftyJSON

I have a code, that parses JSON's list of questions and I can get every property. How can I iterate through the whole file and for each question create an object ?
class ViewController: UIViewController {
var hoge: JSON?
override func viewDidLoad() {
super.viewDidLoad()
let number = arc4random_uniform(1000)
let url = NSURL(string: "http://www.wirehead.ru/try-en.json?\(number)")
var request = NSURLRequest(URL: url!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
if data != nil {
hoge = JSON(data: data!)
let level = hoge!["pack1"][0]["level"].intValue
let questionText = hoge!["pack1"][0]["questionText"].stringValue
let answer1 = hoge!["pack1"][0]["answer1"].stringValue
let answer2 = hoge!["pack1"][0]["answer2"].stringValue
let answer3 = hoge!["pack1"][0]["answer3"].stringValue
let answer4 = hoge!["pack1"][0]["answer4"].stringValue
let correctAnswer = hoge!["pack1"][0]["correctAnswer"].stringValue
let haveAnswered = hoge!["pack1"][0]["haveAnswered"].boolValue
}
}
}
my model of Question which objects I want to create below
class Question {
var level : Int?
var questionText : String?
var answer1 : String?
var answer2 : String?
var answer3 : String?
var answer4 : String?
var correctAnswer : String?
var haveAnswered : Bool = false
init(level: Int, questionText:String, answer1:String, answer2:String, answer3:String, answer4:String, correctAnswer: String, haveAnswered:Bool) {
self.level = level
self.questionText = questionText
self.answer1 = answer1
self.answer2 = answer2
self.answer3 = answer3
self.answer4 = answer4
self.correctAnswer = correctAnswer
self.haveAnswered = false
}
}
This is how I would approach the problem.
Step 1
Since your init inside Question does receive non optional objects, I had the feeling that the properties of Questions should be non optional too. I also converted the properties from var to let (tell me if I am wrong).
Step 2
This is the refactored Question class. As you can see I added a class method build that receive a JSON (a SwiftyJSON) and returns a Question (if the json contains correct data), nil otherwise.
Right now I cannot do this with a failable initializer.
extension String {
func toBool() -> Bool? {
switch self.lowercaseString {
case "true", "1", "yes" : return true
case "false", "0", "no" : return false
default: return nil
}
}
}
class Question {
let level: Int
let questionText: String
let answer1: String
let answer2: String
let answer3: String
let answer4: String
let correctAnswer: String
let haveAnswered: Bool
init(level: Int, questionText:String, answer1:String, answer2:String, answer3:String, answer4:String, correctAnswer: String, haveAnswered:Bool) {
self.level = level
self.questionText = questionText
self.answer1 = answer1
self.answer2 = answer2
self.answer3 = answer3
self.answer4 = answer4
self.correctAnswer = correctAnswer
self.haveAnswered = false
}
class func build(json:JSON) -> Question? {
if let
level = json["level"].string?.toInt(),
questionText = json["questionText"].string,
answer1 = json["answer1"].string,
answer2 = json["answer2"].string,
answer3 = json["answer3"].string,
answer4 = json["answer4"].string,
correctAnswer = json["correctAnswer"].string,
haveAnswered = json["haveAnswered"].string?.toBool() {
return Question(
level: level,
questionText: questionText,
answer1: answer1,
answer2: answer2,
answer3: answer3,
answer4: answer4,
correctAnswer: correctAnswer,
haveAnswered: haveAnswered)
} else {
debugPrintln("bad json \(json)")
return nil
}
}
}
Step 3
Now let's look at viewDidLoad.
func viewDidLoad() {
super.viewDidLoad()
let number = arc4random_uniform(1000)
if let
url = NSURL(string: "http://www.wirehead.ru/try-en.json?\(number)"),
data = NSURLConnection.sendSynchronousRequest(NSURLRequest(URL: url), returningResponse: nil, error: nil) {
// line #a
let rootJSON = JSON(data: data)
// line #b
if let questions = (rootJSON["pack1"].array?.map { return Question.build($0) }) {
// now you have an array of optional questions [Question?]...
}
}
}
At line #a I put inside rootJSON the whole data received from the connection (converted into JSON).
What happen at line #b?
Well I try to access the array located inside "pack1".
rootJSON["pack1"].array?
If the array exists I run the map method. This will extract each cell of the array and I will be able to refer to it with the $0 parameter name inside the closure.
Inside the closure I use this json block (that should represent a question) to build a Question instance.
The result will be an array of Question?. There could be ill values if some son data was not valid. If you want I can show you how to remove the nil values from this array
I could not try the code with real data, hope this helps.
Step 1. We will create one protocol with one constructor method in it and Model class
protocol JSONable {
init?(parameter: JSON)
}
class Style: JSONable {
let ID :String!
let name :String!
required init(parameter: JSON) {
ID = parameter["id"].stringValue
name = parameter["name"].stringValue
}
/* JSON response format
{
"status": true,
"message": "",
"data": [
{
"id": 1,
"name": "Style 1"
},
{
"id": 2,
"name": "Style 2"
},
{
"id": 3,
"name": "Style 3"
}
]
}
*/
}
Step 2. We will create extension of JSON which will convert JSON to model class type object
extension JSON {
func to<T>(type: T?) -> Any? {
if let baseObj = type as? JSONable.Type {
if self.type == .array {
var arrObject: [Any] = []
for obj in self.arrayValue {
let object = baseObj.init(parameter: obj)
arrObject.append(object!)
}
return arrObject
} else {
let object = baseObj.init(parameter: self)
return object!
}
}
return nil
}
}
Step 3. Use code with Alamofire or other code.
Alamofire.request(.GET, url).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
var styles: [Style] = []
if let styleArr = json["data"].to(type: Style.self) {
styles = styleArr as! [Style]
}
print("styles: \(styles)")
case .failure(let error):
print(error)
}
}
I hope this will be useful.
Please refer to this link for more information on this.
https://github.com/SwiftyJSON/SwiftyJSON/issues/714
You can use SwiftyJSONModel which was specifically designed for this purpose. So in your case the model would be like this:
class Question: JSONObjectInitializable {
enum PropertyKey: String {
case level, questionText
case answer1, answer2, answer3, answer4
case correctAnswer, haveAnswered
}
var level : Int?
var questionText : String?
var answer1 : String?
var answer2 : String?
var answer3 : String?
var answer4 : String?
var correctAnswer : String?
var haveAnswered : Bool = false
required init(object: JSONObject<PropertyKey>) throws {
level = object.value(for: .level)
questionText = object.value(for: .questionText)
answer1 = object.value(for: .answer1)
answer2 = object.value(for: .answer2)
answer3 = object.value(for: .answer3)
answer4 = object.value(for: .answer4)
correctAnswer = object.value(for: .correctAnswer)
haveAnswered = object.value(for: .haveAnswered) ?? false
}
}
And then do like this:
let rootJSON = JSON(data: data)
let questions = rootJSON.arrayValue.flatMap { try? Question(json: $0) }
The framework gives you several nice features:
All the keys are stored in separated enum PropertyKey
No boilerplate as stringValue, intValue etc.
If JSON will be invalid, framework will give a verbose error and you will immediately see what exactly went wrong
for (item, content) in hoge {
let level = content["level"].intValue
}
that should work

Cannot assign to the result of this expression with generics

I have the following generic class where I want to manage a string hash:
class NamedProfile<T> {
private var set = [String:T]()
private var profiles = [String]()
private let userDefaults = NSUserDefaults.standardUserDefaults()
private let profileName:String
var currentSet = ""
init(name:String, set:[String:T]) {
profileName = name
self.set = set
if let existingProfiles = userDefaults.objectForKey(name) as? [String] {
profiles = existingProfiles
}
for key in profiles {
if let existingProfile = userDefaults.objectForKey(profileNamed(name)) as? T {
set[key] = existingProfile // <-- error
}
}
}
private func profileNamed(name:String) -> String { return "\(profileName) \(name)" }
}
Why does the compiler croak in the above assignment?
In
init(name:String, set:[String:T]) {
// ...
set[key] = existingProfile // <-- error
// ...
}
set refers to the (immutable) method parameter.
Use self.set instead to refer to the property.