Generic Class with NSObject inheritance, self.setValue(, forKey: ) not work - ios

I want to set value direct in NSObject using self.setValue(, forKey: ) that time give me an error. like that
2018-03-08 17:36:36.723485+0530 BaseProject[20631:312969] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_TtGC11BaseProject8DataRootCS_4Root_ 0x600000452840> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key data.'
When I set the value using Generic T like var data: [T] = [] then app is crash, and I set Class name like var data: [Root] = [] then work perfectly.
My Codes is
protocol AGMappable {
init(with responseDict: [String: AnyObject])
}
My Model Class is
class Root: NSObject, AGMappable {
var avatar: String = ""
var id: Int = 0
var first_name: String = ""
var last_name: String = ""
required init(with responseDict: [String: AnyObject]) {
super.init()
avatar = responseDict["avatar"] as? String ?? ""
}
}
Set a value using setValue(,forKey:)
class DataRoot<T: AGMappable>: NSObject, AGMappable{
var page: Int = 0
var per_page: Int = 0
var total: Int = 0
var total_pages: Int = 0
var data: [T] = []
required init(with responseDict: [String: AnyObject]){
super.init()
if let datastr = responseDict["data"] as? [[String : AnyObject]] {
var temp: [T] = []
for v in datastr {
temp.append(T(with: v as [String: AnyObject]))
}
self.setValue(temp, forKey: "data") // Here error
}
}
}

setValue(_:forKey:) can work with #objc properties only, as it relies on runtime features that only #objc properties have. Swift 4 changed the way class members are available to Objective-C: they are no longer available by default, so you either need to declare the class as #objcMembers, or add #objc individually to every member you need to expose to Objective-C.
However in your case the class can't be exported to Objective-C due to the fact that is a generic one, so you need to declare data as #objc var data: [T] = [], or dynamic var data: [T] = []

You can rewrite it like this
let datastr = responseDict["data"] as? [[String : Any]]
self.data = datastr?.map {
T(with: $0)
} ?? [T]()

Related

Saving an object to user defaults

I have below Struct and I am creating a default object using the Struct. After creating the object I am saving to user defaults. but get the error . Please see below code and error. Let me know how this object can be saved to user defaults.
struct FlightSearchObj {
var FlightTripType:Int = 0
var FlightArrObj: [FlightObj]?
var FlightTravellers: [String : Int] = ["Adults": 1, "Child": 0, "Infants": 0]
var FlightClass : String = "Economy"
var PreferredAirline : [String] = []
var FlightFareType : String = "All"
}
struct FlightObj {
var fromAirportObj:AirportDetail?
var toAirportObj:AirportDetail?
var DepartDate: String?
}
In ViewController
var finalOneWayRoundFlightSearchObj : FlightSearchObj?
var finalMultiFlightSearchObj : FlightSearchObj?
//DefaultObject for Flights
finalOneWayRoundFlightSearchObj = FlightSearchObj()
finalMultiFlightSearchObj = FlightSearchObj()
var flightArrObj = [FlightObj]()
let flightObj = FlightObj()
flightArrObj.append(flightObj)
flightArrObj.append(flightObj)
finalOneWayRoundFlightSearchObj?.FlightArrObj = flightArrObj
finalMultiFlightSearchObj?.FlightArrObj = flightArrObj
print(finalOneWayRoundFlightSearchObj?.FlightArrObj?.count ?? 0)
print(finalMultiFlightSearchObj?.FlightArrObj?.count ?? 0)
FetchOnewayRoundTripObjValues(obj:finalOneWayRoundFlightSearchObj!)
FetchMultiCityObjValues(obj:finalMultiFlightSearchObj!)
func FetchOnewayRoundTripObjValues(obj:FlightSearchObj){
if (Constants.sharedInstance.defaults.value(forKey: "flightOnewayRoundTripObj") != nil){
print("OneWay/RoundTrip Obj found - \(obj)")
}else{
print("OneWay/RoundTrip Obj not found")
print(obj)
Constants.sharedInstance.defaults.set(obj, forKey: "flightOnewayRoundTripObj")
}
}
func FetchMultiCityObjValues(obj:FlightSearchObj){
if (Constants.sharedInstance.defaults.value(forKey: "flightMultiFlightObj") != nil){
print("MultiFlight Obj found - \(obj)")
}else{
print("MultiFlight Obj not found")
Constants.sharedInstance.defaults.set(obj, forKey: "flightMultiFlightObj")
}
}
Error
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object BookingApp.FlightSearchObj(FlightTripType: 0, FlightArrObj: Optional([BookingApp.FlightObj(fromAirportObj: nil, toAirportObj: nil, DepartDate: nil), BookingApp.FlightObj(fromAirportObj: nil, toAirportObj: nil, DepartDate: nil)]), FlightTravellers: ["Adults": 1, "Child": 0, "Infants": 0], FlightClass: "Economy", PreferredAirline: [], FlightFareType: "All") for key flightOnewayRoundTripObj'
* First throw call stack:
(0x202978ec4 0x201b49a40 0x20287f594 0x2029ab140 0x2029ab80c 0x2029ab580 0x2029ab930 0x202864408 0x2028f4908 0x2028f425c 0x202860db4 0x202863da4 0x2029b64d8 0x20333b4dc 0x103060038 0x1030644f8 0x102a56db8 0x102adcadc 0x102add8cc 0x22f6936d0 0x22f693b00 0x22f5ec2ec 0x22f600dac 0x22f602280 0x22f5e4470 0x23010373c 0x206f75b74 0x206f7ab2c 0x206ed944c 0x206f07d7c 0x206f08be4 0x2029087cc 0x202903460 0x202903a00 0x2029031f0 0x204b7c584 0x22fc5abc0 0x1030fb894 0x2023c2bb4)
libc++abi.dylib: terminating with uncaught exception of type NSException
Confirm protocol Codable in all your struct which you want to save in UserDefaults.
struct FlightSearchObj: Codable {
var FlightTripType:Int = 0
var FlightArrObj: [FlightObj]?
var FlightTravellers: [String : Int] = ["Adults": 1, "Child": 0, "Infants": 0]
var FlightClass : String = "Economy"
var PreferredAirline : [String] = []
var FlightFareType : String = "All"
}
struct FlightObj: Codable {
var fromAirportObj:AirportDetail?
var toAirportObj:AirportDetail?
var DepartDate: String?
}
struct AirportDetail: Codable {
// Your properties
}
Now you need to save and get that object from UserDefaults using PropertyListEncoder and PropertyListDecoder.
In your case
func FetchOnewayRoundTripObjValues(obj:FlightSearchObj){
if let data = UserDefaults.standard.value(forKey:"flightOnewayRoundTripObj") as? Data {
let res = try? PropertyListDecoder().decode(FlightSearchObj.self, from: data)
print("OneWay/RoundTrip Obj found - \(res)")
}else{
print("OneWay/RoundTrip Obj not found")
print(obj)
UserDefaults.standard.set(try? PropertyListEncoder().encode(obj), forKey:"flightOnewayRoundTripObj")
// Constants.sharedInstance.defaults.set(obj, forKey: "flightOnewayRoundTripObj")
}
}
func FetchMultiCityObjValues(obj:FlightSearchObj){
if let data = UserDefaults.standard.value(forKey:"flightOnewayRoundTripObj") as? Data {
let res = try? PropertyListDecoder().decode(FlightSearchObj.self, from: data)
print("MultiFlight Obj found - \(res)")
}else{
print("MultiFlight Obj not found")
UserDefaults.standard.set(try? PropertyListEncoder().encode(obj), forKey:"flightMultiFlightObj")
// Constants.sharedInstance.defaults.set(obj, forKey: "flightMultiFlightObj")
}
}
In case of Struct you need to Make your struct codable simply by marking it as adopting the Codable protocol:
struct FlightSearchObj:Codable {
var FlightTripType:Int = 0
var FlightArrObj: [FlightObj]?
var FlightTravellers: [String : Int] = ["Adults": 1, "Child": 0, "Infants": 0]
var FlightClass : String = "Economy"
var PreferredAirline : [String] = []
var FlightFareType : String = "All"
}
struct FlightObj:Codable {
var fromAirportObj:AirportDetail?
var toAirportObj:AirportDetail?
var DepartDate: String?
}
Also make AirportDetail Codable...

How to call the correct constructor when using generic T.Type class on Swift4?

I have a database with some tables, each table represents a object of my project. I want write a generic function to read, by SQL, a table and create a object with the records readed. So, the parameters of my function are: Table Name and Object Type. The code below is my func to do this. In the end of func, I tries call what I would like to do, but with a especific object, that's don't the I want.
func readAll<T>(objeto: String, typeClass: T.Type) -> [T] {
var ret : [T] = []
// STATEMENT DATA
let queryString = "SELECT * FROM \(objeto);"
var queryStatement: OpaquePointer? = nil
// STATEMENT DATA TYPE
let queryString2 = "PRAGMA table_info(\(objeto));"
var queryStatement2: OpaquePointer? = nil
// 1
if sqlite3_prepare_v2(db,queryString,-1,&queryStatement,nil) != SQLITE_OK {
print("Error to compile readAll \(objeto) 1")
return ret
}
if sqlite3_prepare_v2(db,queryString2,-1,&queryStatement2,nil) != SQLITE_OK {
print("Error to compile readAll \(objeto) 2")
return ret
}
var listNameColumns : [String] = []
while( sqlite3_step(queryStatement2) == SQLITE_ROW ) {
listNameColumns.append( String(cString: sqlite3_column_text(queryStatement2, 2)!) )
}
// 2
while ( sqlite3_step(queryStatement) == SQLITE_ROW ) {
var dict: [String:Any] = [:]
for i in 0...listNameColumns.count-1 {
let nameColumn = String(cString: sqlite3_column_name(queryStatement,Int32(i))!)
switch (sqlite3_column_type(queryStatement, Int32(i))) {
case SQLITE_TEXT:
dict[nameColumn] = String(cString: sqlite3_column_text(queryStatement, Int32(i))!)
break
case SQLITE_INTEGER:
dict[nameColumn] = sqlite3_column_int(queryStatement, Int32(i))
break
case SQLITE_FLOAT:
dict[nameColumn] = sqlite3_column_double(queryStatement, Int32(i))
break
default:
print("Tipo desconhecido.")
break
}
}
ret.append(ResPartner(dict: dict)) <------ HERE IS MY QUESTION!
}
// 3
sqlite3_finalize(queryStatement2)
sqlite3_finalize(queryStatement)
return ret
}
Here are two objects, They are a bit different, but the builder works the same and the fields as well.
class ResPartner {
static let fieldsResPartner : [String] = ["id","company_type_enum_for_customer","name","contact_address","customer_account_number","customer_group_id","segment_id","subsegment_id","economic_group_id","street","category_id","type_stablishment_id","final_user","final_taxpayer","cnpj_cpf","inscr_est","ccm","cnae","phone","phone_extension","mobile","fax","email","email_extra","website","lang"]
var attributes : [String:Any] = [:]
init(dict : [String:Any]) {
for k in dict.keys {
if(ResPartner.fieldsResPartner.contains(k)) {
attributes[k] = dict[k]
}
}
}
func toString() {
for k in attributes.keys{
print("\(k) - \(attributes[k]!)")
}
}
}
class Product {
static let fieldsProducts : [String] = ["id","name","default_code","display_name","categ_id","company_ax_id","destination_type","fiscal_class_code","multiple","taxes_id","uom_id","uom_po_id","__last_update","active","create_date","create_uid","currency_id","invoice_police","item_ids","list_price","price","pricelist_id","type"]
public var default_code: String!
public var display_name: String!
public var id: Int!
public var name: String!
public var destination_type: String!
public var company_ax_id: Int!
public var categ_id: Int!
public var fiscal_class_code: String!
public var taxes_id: Int!
public var uom_id: Int!
public var uom_po_id: Int!
public var multiple: Int!
public var last_update: String!
public var active: Bool!
public var create_date: String!
public var create_uid: Int!
public var currency_id: Int!
public var invoice_police: String!
public var item_ids: [Int]!
public var list_price: String!
public var price: Float!
public var pricelist_id: Int!
public var type: String!
init() {
}
init( dict : [String:Any] ) {
self.default_code = dict["default_code"] as! String
self.display_name = dict["display_name"] as! String
self.id = dict["id"] as! Int
self.name = dict["name"] as! String
self.destination_type = dict["destination_type"] as! String
self.company_ax_id = dict["company_ax_id"] as! Int
self.categ_id = dict["categ_id"] as! Int
self.fiscal_class_code = dict["fiscal_class_code"] as! String
self.taxes_id = dict["taxes_id"] as! Int
self.uom_id = dict["uom_id"] as! Int
self.uom_po_id = dict["uom_po_id"] as! Int
self.multiple = dict["multiple"] as! Int
self.last_update = dict["last_update"] as! String
self.active = dict["active"] as! Bool
self.create_date = dict["create_date"] as! String
self.create_uid = dict["create_uid"] as! Int
self.currency_id = dict["currency_id"] as! Int
self.invoice_police = dict["invoice_police"] as! String
self.item_ids = dict["item_ids"] as! [Int]
self.list_price = dict["list_price"] as! String
self.price = dict["price"] as! Float
self.pricelist_id = dict["pricelist_id"] as! Int
self.type = dict["type"] as! String
}
}
So, my question is, How I call the constructor of T.Type class passed by parameter? I did read about protocols, extensions, other posts, but not solves my problem.
You can constrain your generic with protocol:
Define a protocol for initializing with a dictionary:
protocol DictionaryInitializable {
init(dict: [String: Any])
}
Make your two types conform to that type (you'll have to add required to your init methods, as prompted by Xcode), e.g.:
class Product: DictionaryInitializable {
...
required init(dict: [String: Any]) {
...
}
}
and
class ResPartner: DictionaryInitializable {
static let fieldsResPartner = ...
var attributes: [String: Any] = [:]
required init(dict: [String: Any]) {
for k in dict.keys where ResPartner.fieldsResPartner.contains(k) {
attributes[k] = dict[k]
}
}
func toString() { ... }
}
Change your method declaration to make it clear that T must conform to your new protocol:
func readAll<T: DictionaryInitializable>(objeto: String, typeClass: T.Type) -> [T] {
var ret: [T] = []
...
// 2
while ( sqlite3_step(queryStatement) == SQLITE_ROW ) {
var dict: [String: Any] = [:]
...
ret.append(T(dict: dict)) // You can now use `T` here
}
return ret
}
And you’d call it like:
let list = db_respartner.readAll(objeto: "res_partner", typeClass: ResPartner.self)
Create a Protocol with init Method
protocol Mappable {
init(dictionary:[String:AnyObject]) // Changed based on your requirement.
}
Conform your protocol in ResPartner.
class ResPartner: Mappable {
required init(dictionary : [String : AnyObject]) {
// initialize here
}
}
Conform your protocol in Product.
class Product: Mappable {
required init(dictionary : [String : AnyObject]) {
// initialize here
}
}
Create a custom objects
func readAll<T:Mappable>(objeto: String, typeClass: T.Type) -> [T] {
var ret : [T] = []
// intialize your variable
let obj = typeClass.init(dictionary: ["":"" as AnyObject])
return ret
}

How to test if an `NSObject` has a property with arbitrary name that can be set?

I want to implement a generic populating of a model from a dictionary. I test if the model has a property using a following test, but the condition always fails:
if (self.responds(to:(NSSelectorFromString(keyName)))){
self.setValue(keyValue, forKey: key )
}
Here is an example code:
import UIKit
class myModel: NSObject {
var userName: String = ""
var phoneNumber: String = ""
init(dict: Dictionary<String, Any>) {
super.init()
for (key, value) in dict {
let keyName = key
let keyValue: String = String(describing: value)
print("key \(key) value \(value)")
if (self.responds(to:(NSSelectorFromString(keyName)))){
self.setValue(keyValue, forKey: key )
}
}
}
}
You are almost done. Add the following at top of your NSObject class - #objcMembers
Such as -
import UIKit
#objcMembers
class myModel: NSObject {
This works as you expect (tested in playgrounds):
import UIKit
class myModel: NSObject {
#objc var userName: String = ""
#objc var phoneNumber: String = ""
init(dict: Dictionary<String, Any>) {
super.init()
for (key, value) in dict {
let keyCapitalized = key.prefix(1).uppercased() + key.dropFirst()
let keyName = "set\(keyCapitalized):"
let keyValue: String = String(describing: value)
print("key \(key) (selector: '\(keyName))' value \(value)")
if self.responds(to: Selector(keyName)) {
self.setValue(keyValue, forKey: key)
}
}
}
}
let m = myModel(dict: ["userName" : "milan", "data" : "data"])
print(">>> \(m.userName)") // prints ">>> milan"
print(">>> \(m.phoneNumber)") // prints ">>> " (number was not provided, and data key was ignored)
Just two points.
First of all, you need to expose those properties to ObjectiveC for responds() to work - therefore I added #objc annotations on both properties.
Second of all, the proper selector syntax to test if you can set the property named userName is NOT Selector("userName"), but Selector("setUserName:") (you are testing a setter).
Dictionaries need to be given a type for their keys and values. What you have in your initialiser is too ambiguous.
Because the result of getting something out of a dictionary can be nil, you need to provide a default value which is what happens after ??
class MyModel: NSObject {
var userName: String = ""
var phoneNumber: String = ""
init(dict: [String : String]) {
self.userName = dict["userName"] ?? ""
self.phoneNumber = dict["phoneNumber"] ?? ""
super.init()
}
}
Below way you can set model values from a dictionary.
class MyModel {
var userName: String?
var phoneNumber: String?
init(dict: [String:Any]?) {
self.userName = dict?["username"] as? String
self.phoneNumber = dict?["phonenumber"] as? String
}
}
Example Usage :
let myModel = MyModel(dict: ["username":"test","phonenumber":"1234567890"])

Unrecongnized Selector name

Unrecognized Selector to instance name.
I want to create partion array from section. I am trying to do this in swift 2, but I am not able to get it to work.
var currentCollation : UILocalizedIndexedCollation!
var sections: [Section] {
let selector: Selector = "name"
let users: [User] = array.map { name in
let a = name["fullName"] as? String
let b = name["email"] as! String
let c = name["mobile"] as! String
let d = name["img"] as! String
let user = User(name: a! )
user.email = b
user.mobile = c
user.img = d
user.section = UILocalizedIndexedCollation.current().section(for: user, collationStringSelector:"name")
return user
}
var sections = [Section]()
for _ in 0..<currentCollation.sectionIndexTitles.count {
sections.append(Section())
}
for user in users {
sections[user.section!].addUser(user: user)
}
print(sections)
for section in sections {
print(section.users)
var user = section.users as? User
print(user?.name)
section.users = self.currentCollation.sortedArray(from: section.users, collationStringSelector: "name") as! [User]
}
return sections
}
#objc class User: NSObject {
let name: String
var section: Int?
var img: String?
var email: String?
var mobile : String?
init(name: String) {
self.name = name
}
}
class Section {
var users: [User] = []
func addUser(user: User) {
self.users.append(user)
}
}
Don't use strings for selectors in Swift. Use the #selector construct:
let selector: Selector = #selector(name)
However, that will only work if the current class is an NSObject and has an Objective-C function "name" that has no parameters:
class Foo: NSObject {
#objc func name() {
print("In \(#function)")
}
}
(Actually, you can create a selector to an object that doesn't inherit from NSObject, but you can't use functions like perform() on such a selector, so it isn't very useful)
You don't appear to have a function named "name" in your class, of type #objc or not.

Casting struct to NSUserDefaults in Swift?

Is there a way I can cast this Swift data set in someway to a form that is acceptable to NSUserDefauts? i.e. NSObject NSSet? (p.s. I realize NSUserDefaults isn't for this type of data, but I'm just testing)
struct Users {
var name: String = ""
var stores: [Store]
}
struct Store {
var name: String = ""
var clothingSizes = [String : String]()
}
init() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let usersPeople = userDefaults.valueForKey("Users") as?
I think you can use Dictionary. You'll need to make method to wrap data to struct and vice versa.
For example:
var users : [String: AnyObject]()
users["name"] = "SomeName"
users["stores"] = yourStoreArray
NSUserDefaults.standardUserDefaults().setObject(users, forKey: "Users")
something like that.
And when you need to get struct
if let myDictionaryFromUD = userDefaults.objectForKey("Users") as? [String:AnyObject]{
self.users = Users(myDictionaryFromUD["name"], stores: myDictionaryFromUD["stores"] as! [Store])
}
I aslo assume that you will save to userDefaults array of Users. In this case, you will save [[String: AnyObject]] but mechanics the same.
I don't know that this is the best way to do this but you can try this.
struct Users {
var name: String = ""
var stores: [Store]
}
struct Store {
var name: String = ""
var clothingSizes = [String : String]()
}
var Advert = [Users]()
init() {
NSUserDefaults.standardUserDefaults().setObject(Advert[0].name, forKey: "NameYouWant")
}

Resources