I am trying to get the property list of a swift class. There is a similar question here and here. I have everything working except for certain types do not get returned from class_copyPropertyList. The ones I have tested it with so far are Int? and enums. I have an example class below of what I am trying.
enum PKApiErrorCode: Int {
case None = 0
case InvalidSignature = 1
case MissingRequired = 2
case NotLoggedIn = 3
case InvalidApiKey = 4
case InvalidLogin = 5
case RegisterFailed = 6
}
class ApiError: Serializable {
let code: PKApiErrorCode?
let message: String?
let userMessage: String?
init(error: JSONDictionary) {
code = error["errorCode"] >>> { (object: JSON) -> PKApiErrorCode? in
if let c = object >>> JSONInt {
return PKApiErrorCode.fromRaw(c)
}
return nil
}
message = error["message"] >>> JSONString
userMessage = error["userMessage"] >>> JSONString
}
}
And the Serializable class (with some help from https://gist.github.com/turowicz/e7746a9c035356f9483d is
public class Serializable: NSObject, Printable {
override public var description: String {
return "\(self.toDictionary())"
}
}
extension Serializable {
public func toDictionary() -> NSDictionary {
var aClass : AnyClass? = self.dynamicType
var propertiesCount : CUnsignedInt = 0
let propertiesInAClass : UnsafeMutablePointer<objc_property_t> = class_copyPropertyList(aClass, &propertiesCount)
var propertiesDictionary : NSMutableDictionary = NSMutableDictionary()
for var i = 0; i < Int(propertiesCount); i++ {
var property = propertiesInAClass[i]
var propName = NSString(CString: property_getName(property), encoding: NSUTF8StringEncoding)
var propType = property_getAttributes(property)
var propValue : AnyObject! = self.valueForKey(propName);
if propValue is Serializable {
propertiesDictionary.setValue((propValue as Serializable).toDictionary(), forKey: propName)
} else if propValue is Array<Serializable> {
var subArray = Array<NSDictionary>()
for item in (propValue as Array<Serializable>) {
subArray.append(item.toDictionary())
}
propertiesDictionary.setValue(subArray, forKey: propName)
} else if propValue is NSData {
propertiesDictionary.setValue((propValue as NSData).base64EncodedStringWithOptions(nil), forKey: propName)
} else if propValue is Bool {
propertiesDictionary.setValue((propValue as Bool).boolValue, forKey: propName)
} else if propValue is NSDate {
var date = propValue as NSDate
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "Z"
var dateString = NSString(format: "/Date(%.0f000%#)/", date.timeIntervalSince1970, dateFormatter.stringFromDate(date))
propertiesDictionary.setValue(dateString, forKey: propName)
} else {
propertiesDictionary.setValue(propValue, forKey: propName)
}
}
return propertiesDictionary
}
public func toJSON() -> NSData! {
var dictionary = self.toDictionary()
var err: NSError?
return NSJSONSerialization.dataWithJSONObject(dictionary, options:NSJSONWritingOptions(0), error: &err)
}
public func toJSONString() -> NSString! {
return NSString(data: self.toJSON(), encoding: NSUTF8StringEncoding)
}
}
The String seem to only appear if the Optionals are valid values and the code never appears on the object if it is an enum or Int unless the Int has a default value.
Thanks for any advice I can get for getting all properties of a class no matter what they are.
I got a response on the apple developer forums regarding this issue:
"class_copyPropertyList only shows properties that are exposed to the Objective-C runtime. Objective-C can't represent Swift enums or optionals of non-reference types, so those properties are not exposed to the Objective-C runtime."
Source
So, in summary, serialization to JSON using this approach is not currently possible. You might have to look into using other patterns to accomplish this, such as giving the task of serialization to each object, or potentially by using the reflect() method to serialize an object to JSON.
Related
Getting error message
Cannot convert value of type 'UnsafeMutablePointer<objc_property_t>?' (aka 'Optional<UnsafeMutablePointer>') to specified type 'UnsafeMutablePointer<objc_property_t?>' (aka 'UnsafeMutablePointer<Optional<OpaquePointer>>')
On this line
let properties : UnsafeMutablePointer <objc_property_t?> = class_copyPropertyList(self.classForCoder, &count)
Full code here
var count = UInt32()
let properties : UnsafeMutablePointer <objc_property_t?> = class_copyPropertyList(self.classForCoder, &count)
var propertyNames = [String]()
let intCount = Int(count)
for i in 0..<intCount {
let property : objc_property_t = properties[i]!
guard let propertyName = NSString(utf8String: property_getName(property)) as? String else {
debugPrint("Couldn't unwrap property name for \(property)")
break
}
propertyNames.append(propertyName)
}
You are getting the error because the return type of class_copyPropertyList is not UnsafeMutablePointer<objc_property_t?>.
Your line should read
let properties : UnsafeMutablePointer <objc_property_t> = class_copyPropertyList(self.classForCoder, &count)
class_copyPropertyList() returns a UnsafeMutablePointer<objc_property_t>? and not a UnsafeMutablePointer<objc_property_t?>. It is usually better to avoid explicit type annotations and just write
let properties = class_copyPropertyList(self.classForCoder, &count)
and let the compiler infer the type. The optional must then be unwrapped, for example with guard:
guard let properties = class_copyPropertyList(self.classForCoder, &count) else {
return // Handle error ...
}
The Swift String creation can also be simplified, leading to
var count = UInt32()
guard let properties = class_copyPropertyList(self.classForCoder, &count) else {
return
}
var propertyNames = [String]()
for i in 0..<Int(count) {
let propertyName = String(cString: property_getName(properties[i]))
propertyNames.append(propertyName)
}
You can remove type annotation, like so:
var count = UInt32()
let properties = class_copyPropertyList(self.classForCoder, &count)
Now the properties can also be mapped:
if let properties = class_copyPropertyList(self.classForCoder, &count) {
let range = 0..<Int(count)
let names = range.map {
String(cString: property_getName(properties[$0]))
}
}
I have the following Objective-C code I'm trying to convert to swift:
-(id)initWithBook:(NSString*)bookTitle author:(NSString*)author description:(NSString*)description{
self = [super init];
if (self) {
self.bookTitle = [bookTitle copy];
self.author = [author copy];
self.description = [uri description];
}
return self;
}
+(NSArray*)listOfBooks:(NSArray*)jsonWithBooks{
NSMutableArray *elements = [NSMutableArray new];
for (NSDictionary *dictElment in jsonRespnse){
Books *booksData = [[Books alloc] initWithBook:[dictElment objectForKey:#"bookTitle"]
title:[dictElment objectForKey:#"author"]
description:[dictElment objectForKey:#"description"]];
[elements addObject:booksData];
}
return [NSArray arrayWithArray:elements];
}
In my Objective-C code I'm calling super class "+(NSArray*)listOfBooks:(NSArray*)jsonWithBooks" to generate NSArray of objects. But I haven't found an equivalente on Swift. Any of you knows what would be the best alternative to do something like this?
I'm trying to use #Alexander example but my project crash in the following line:
let inventoryBooks = Book.books(fromDictArray: json .object(forKey: "books") as! [[String : String]] )
I check the type for this:
json .object(forKey: "books")
As follow:
let arrayOfBooks = json .object(forKey: "books")
if arrayOfBooks is NSArray {
print("nsarray")
}
if arrayOfBooks is [[String:String]] {
print("string:string")
}
if arrayOfBooks is NSDictionary {
print("NSDic")
}
And is printing nsarray
My question. What I'm doing wrong or do I need to change the signature on this function:
static func books(fromDictArray array: [[String: String]]) -> [Book?] {
return array.map(Book.init)
}
This sample of the json response:
{
books = (
{
caption = "";
"display_sizes" =(
{
name = thumb;
uri = "https://someUrl.com/img.jpg";
}
);
id = 123;
"max_dimensions" = {
height = 4912;
width = 7360;
};
title = "Learning Swift";
author = "Some guy"
}
{
caption = "";
"display_sizes" =(
{
name = thumb;
uri = "https://someUrl.com/img.jpg";
}
);
id = 123;
"max_dimensions" = {
height = 4912;
width = 7360;
};
title = "Swift";
author = "me meme"
}
)
}
Here is how I would write this code in idiomatic Swift:
struct Book {
let title: String
let author: String
let description: String
/* an implicit member wise initializer is generated,
which would otherwise look something like this:
init(title: String, author: String, description: String) {
self.title = title
self.author = author
self.description = description
} */
}
// Initialization from Dictionaries
extension Book {
init?(fromDict dict: [String: Any]) {
guard
let title = dict["bookTitle"] as? String,
let author = dict["author"] as? String,
let description = dict["description"] as? String
else { return nil }
self.init(
title: title,
author: author,
description: description
)
}
static func books(fromDictArray array: [[String: Any]]) -> [Book?] {
return array.map(Book.init)
}
}
Here are some notable points:
Book is a struct. Such a broad description of a book doesn't need to support the notion of identity. I.e., your book named "Harry Potter", by "J.K. Rowling" with the description "Some description" can be considered to be the same as my book with the same values. There's no apparent need (yet) to distinguish the identity of your book vs the identity of mine.
Book has an implicit memberwise initializer init(title:author:description:) which simply initializes its fields to the given parameters.
An extension is made which compartmentalizes all dictionary related tasks into a single unit.
A failable initializer, init?(fromDictArray:) is made, which returns a new book based off the given dict (presumably created from your JSON). This initializer is fault tolerant. If the dict provided is invalid, then the initializer will simply return nil, without crashing your program.
A static method is made on the Book struct, books(fromDictArray:), which will create an array of optional books ([Book?], a.k.a Array<Optional<Book>> out of the given dict. It is then the job of the consumer of this method to deal with the nil values, those resulting from invalid dicts, as they please.
They could ignore the nil books:
let books = Book.books(fromDictArray: myDictArray).flatMap{$0}
They could crash if a nil book is found:
let books = Book.books(fromDictArray: myDictArray) as! [Book]
Or they can handle the nil cases in some unique way:
let books = Book.books(fromDictArray: myDictArray).map{ book in
if book == nil {
print("A nil book was found")
}
}
As already mentioed by Dan, the Swift equivalent would be a class func:
class func listOfBooks(jsonWithBooks: [NSDictionary]) -> [Book] {
var books = [Book]()
for json in jsonWithBooks {
let book = Book(
book: json["bookTitle"]!,
author: json["author"]!,
description: json["description"]!
)
books.append(book)
}
return books
}
I'm converting a swift project to objective c, but i get some trouble because i don't know how to convert follow code. Please help me. Thanks!
public enum UPCarouselFlowLayoutSpacingMode {
case fixed(spacing: CGFloat)
case overlap(visibleOffset: CGFloat)
}
and
fileprivate var currentPage: Int = 0 {
didSet {
let character = self.items[self.currentPage]
self.infoLabel.text = character.name.uppercased()
self.detailLabel.text = character.movie.uppercased()
}
}
The first (an enum with associated values) has no direct equivalent in Objective-C. For your particular example, you could use something like this:
typedef NS_ENUM(NSInteger, UPCarouselFlowLayoutSpacingMode) {
UPCarouselFlowLayoutSpacingModeFixed,
UPCarouselFlowLayoutSpacingModeOverlap
};
typedef struct {
UPCarouselFlowLayoutSpacingMode mode;
CGFloat amount;
} UPCarouselFlowLayoutSpacing;
You would just pass around values of type UPCarouselFlowLayoutSpacing. You could create helper functions to make these easier to create, e.g.
UPCarouselFlowLayoutSpacing UPCarouselFlowLayoutSpacingMakeFixed(CGFloat spacing) {
UPCarouselFlowLayoutSpacing value;
value.mode = UPCarouselFlowLayoutSpacingModeFixed;
value.amount = spacing;
return value;
}
UPCarouselFlowLayoutSpacing UPCarouselFlowLayoutSpacingMakeOverlap(CGFloat visibleOffset) {
UPCarouselFlowLayoutSpacing value;
value.mode = UPCarouselFlowLayoutSpacingModeOverlap;
value.amount = visibleOffset;
return value;
}
For the second, you can override the setter method of your Objective-C class's currentPage property:
- (void)setCurrentPage:(NSInteger)page {
_page = page;
MovieCharacter *character = self.items[page];
self.infoLabel.text = character.name.localizedUppercaseString;
self.detailLabel.text = character.movie.localizedUppercaseString;
}
Guys i am new to iOS development hardly 2 weeks, i want to convert custom object to NSDictionary or NSData. I want to pass that complex object to web API to get and save.
class customClass{
var firstName:String?
var lastName:String?
var services:Services
}
class Services{
var list:[String] = [String]()
}
can somebody help me to resolve this please.
A bit late... try this in playground. Please note that it will not handle NSData correctly. Also, for the code to work, you need to make sure that your custom class inherits from NSObject and adheres to protocol Parsable. That is because you need to make use of the Objective C runtime for this solution to work with classes. Furthermore all your objects need to be subclasses of NSObject (e.g. NSArray instead of Swift type Array).
import UIKit
protocol Parsable {
}
// Define classes as NSObjects
class customClass: NSObject, Parsable {
var firstName:String?
var lastName:String?
var services:Services?
}
class Services: NSObject, Parsable {
var list:NSMutableArray = NSMutableArray()
}
// Create objects
var obj = customClass()
obj.firstName = "My"
obj.lastName = "Name"
var serv = Services()
serv.list = NSMutableArray()
serv.list.addObject("AAA")
serv.list.addObject("BBB")
obj.services = serv
// The magic
func parse(o: NSObject) -> AnyObject {
let aClass : AnyClass? = o.dynamicType
var propertiesCount : CUnsignedInt = 0
let propertiesInAClass : UnsafeMutablePointer<objc_property_t> = class_copyPropertyList(aClass, &propertiesCount)
let propertiesDictionary : NSMutableDictionary = NSMutableDictionary()
if propertiesCount == 0 {
if let a = o as? [NSObject] {
return a.map( {parse($0)} )
} else {
// Take into account NSData here!!!
return o
}
} else {
for var i = 0; i < Int(propertiesCount); i++ {
let property = propertiesInAClass[i]
let propertyName = NSString(CString: property_getName(property), encoding: NSUTF8StringEncoding) as! String
let propertyValue : AnyObject = o.valueForKey(propertyName)!;
if propertyValue is NSObject && propertyValue is protocol<Parsable> {
propertiesDictionary.setValue(parse(propertyValue as! NSObject), forKey: propertyName)
} else {
propertiesDictionary.setValue(propertyValue, forKey: propertyName)
}
}
return propertiesDictionary
}
}
print(parse(obj))
//print(parse(obj.services!))
//print(parse(obj.services!.list))
I'm going through Jastor's documentation:
There's an Objective-C implementation for returning arrays:
+ (Class)categories_class {
return [ProductCategory class];
}
This is my attempt at converting it to Swift, however it ends up not returning anything so I don't think it's implemented correctly:
#<_TtC4TestApp4Room: id = (null) {
resultCount = 50; // 50 is returning fine
results = ( // results is not
);
}>
NSDictionary response:
{
"resultCount" : 50,
"results" : [
{
"collectionExplicitness" : "notExplicit",
"discCount" : 1,
"artworkUrl60" : "http:\/\/a4.mzstatic.com\/us\/r30\/Features\/2a\/b7\/da\/dj.kkirmfzh.60x60-50.jpg",
"collectionCensoredName" : "Changes in Latitudes, Changes in Attitudes (Ultmate Master Disk Gold CD Reissue)"
}
]
}
Music.swift (not quite sure how to implement the results_class() method)
class Music : Jastor {
var resultCount: NSNumber = 0
var results: NSArray = []
class func results_class() -> AnyClass {
return Author.self
}
}
Author.swift
class Author {
var collectionExplicitness: NSString = ""
var discCount: NSNumber = 0
var artworkUrl60: NSString = ""
var collectionCensoredName: NSString = ""
}
I'm using the following syntax (adapted to your example):
static let results_class = Author.self
and everything works for me.
Other differences that may or may not have an effect:
I'm using Int instead of NSNumber and String instead of NSString (except for arrays).
I'm using implicitly wrapped optionals rather than assigning a default value to each field