Create Swift optionals from cast NSDictionary object - objective-c

I'm trying to cast an Objective-C NSDictionary's values to 2 Swift optionals.
These values may be missing from the original NSDictionary, in which case the function can safely be called with nil arguments.
Is there a better way of doing this?
#objc
track(_ data: NSDictionary) {
// Cast NSDictionary to Swift dictionary
guard let dataDictionary = data as? [String: Any] else {
return
}
// Extract (optional) values
let value = dataDictionary["value"]
let name = dataDictionary["name"]
// Cast Objective-C optionals if possible, otherwise assign nil
let floatValue: Float? = (value as? CGFloat != nil) ? Float(value as! CGFloat) : nil
let stringName: String? = (name as? NSString != nil) ? name as! String : nil
// Function arguments:
// name: String? = nil
// value: Float? = nil
Tracker.track(name: stringName, value: floatValue)
}

Firstly
let stringName: String? = (name as? NSString != nil) ? name as! String : nil
Can be simplified as
let stringName: String? = name as? String
But if we want to make the modification earlier, we can avoid the [String: Any] from the start:
let floatValue = (data["value"] as? NSNumber)?.floatValue
let nameValue = data["name"] as? String
If data["value"] doesn't exist, it will be nil. If it's not a NSNumber (since it's the Objective-C basic way to encapsulate Float into a Object) it will be nil. In the end, the floatValue will be nil or have the real value.
Same logic for the nameValue.

Related

Casting Objective C blocks in Swift

There is a particular case where I can't find the correct solution on how to cast a block defined in Objective C to a closure in Swift, so that I can execute it in Swift. For other cases using #escaping helped when having another block/closure being passed to a closure. But unfortunately not in this case.
Block type definitions in Objective C:
typedef void (^ViewModelUpdatedBlock)(GetValueForKeyPathBlock);
typedef id (^GetValueForKeyPathBlock)(NSString * keyPath);
Declaration of a block in Objective C:
someNSMutableDictionary[kViewModelUpdatedBlockKey] = ^(GetValueForKeyPathBlock getValueBlock) { ... }
When I try to use one of these blocks in Swift I can't find the correct way to cast it. I tried:
let viewModelUpdatedBlock = someNSMutableDictionary[kViewModelUpdatedBlockKey] as? ViewModelUpdatedBlock // = nil
let viewModelUpdatedBlock = someNSMutableDictionary[kViewModelUpdatedBlockKey] as? (GetValueForKeyPathBlock?)->() // = nil
let viewModelUpdatedBlock = someNSMutableDictionary[kViewModelUpdatedBlockKey] as? (GetValueForKeyPathBlock)->() // = nil
let viewModelUpdatedBlock = someNSMutableDictionary[kViewModelUpdatedBlockKey] as? (#escaping GetValueForKeyPathBlock)->() // = nil
Please help!

Cannot convert value of type to expected argument type

So I'm converting this line of code
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(TidyFeed * _Nonnull item, NSDictionary<NSString *,id> * _Nullable bindings) {
return ![item.feedEntryId isEqualToString:feedEntryId];
}];
to swift code
var predicate = NSPredicate { (item: TidyFeed, bindings: NSDictionary) in
return !(item.feedEntryId == feedEntryId)
}
but I get this error
Cannot convert value of type '(TidyFeed, NSDictionary) -> Bool' to expected argument type '(AnyObject, [String : AnyObject]?) -> Bool'
has anyone encounter this error when using blocks?
Additional Info
Swift code
apiClient.dismissFeed(feedEntryId, completion: {(responseObject: AnyObject, error: NSError) -> Void in
})
Error
Cannot convert value of type '(AnyObject, NSError) -> Void' to expected argument type 'AFAPIOperationCompletionBlock!'
and here's the completion block code of AFAPIOperationCompletionBlock
- (void) completion:(id)responseObject error:(NSError *)error
In Objective-C, the first parameter id evaluatedBlock of the closure
can be substituted by any object pointer, like TidyFeed *, and that
is a known method to avoid additional pointer conversion if you know for sure that the evaluated objects are in fact of that type.
Swift however does not allow this, and you have to cast the pointer
inside the block:
let predicate = NSPredicate { (item: AnyObject, _) -> Bool in
return (item as! TidyFeed).feedEntryId != feedEntryId
}
This will crash if the block is called with an object which is not
of the type TidyFeed. A safe version is
let predicate = NSPredicate { (item: AnyObject, _) -> Bool in
if let feed = item as? TidyFeed {
return feed.feedEntryId != feedEntryId
} else {
return false
}
}
with an optional cast. This can be written more compactly as:
let predicate = NSPredicate { (item: AnyObject, _) -> Bool in
return (item as? TidyFeed)?.feedEntryId != feedEntryId
}
Note also how you can use the wildcard pattern _ for the unused
bindings parameter.

Comparing AnyObject in Swift

I'm having trouble porting this Objective-C code to Swift. The code must sort the contents of a directory by a given property.
NSArray *contents = [fileManager contentsOfDirectoryAtURL:directoryURL includingPropertiesForKeys:#[property] options:kNilOptions error:&error];
if (!contents) return nil;
contents = [contents sortedArrayUsingComparator:^NSComparisonResult(NSURL *url1, NSURL *url2) {
id value1;
if ([url1 getResourceValue:&value1 forKey:property error:nil]) return NSOrderedAscending;
id value2;
if ([url2 getResourceValue:&value2 forKey:property error:nil]) return NSOrderedDescending;
return [value1 compare:value2];
}];
return contents
My Swift version so far:
if let contents = fileManager.contentsOfDirectoryAtURL(directoryURL, includingPropertiesForKeys: [property], options: NSDirectoryEnumerationOptions.allZeros, error: &error) as? [NSURL] {
let sortedContents = contents.sorted({(URL1 : NSURL, URL2 : NSURL) -> Bool in
var value1 : AnyObject?
if !URL1.getResourceValue(&value1, forKey: property, error: nil) { return true }
var value2 : AnyObject?
if !URL2.getResourceValue(&value2, forKey: property, error: nil) { return false }
// How do I compare AnyObject?
return false
})
return sortedContents
} else {
return nil
}
Particularly, I don't know how to compare two AnyObject objects. I can't downcast to the Comparable protocol because it's not marked as #objc and apparently I can't call compare with optional chaining syntax (error: Operand of postfix '?' should have optional type; type is 'NSComparisonResult').
Of course, there's always the brute-force approach of downcasting the values to String, NSDate and NSNumber:
if let string1 = value1 as? String {
if let string2 = value2 as? String {
return string1 < string2
}
}
if let date1 = value1 as? NSDate {
if let date2 = value2 as? NSDate {
return date1.compare(date2) == NSComparisonResult.OrderedAscending
}
}
// And so on...
return false
Is there a better way?
Unless they really are AnyObject (ie., they can be swift things) declare them (or cast them) to NSObject instead.
The problem is not with the receiver type of AnyObject (that is fine; you can call any known objc method with AnyObject); but with the parameter type. There is no single signature of compare:. There's one that takes NSString; one that takes NSNumber; one that takes NSDate, etc., but none that is general, none that takes AnyObject.

What is wrong with the implementation of NSCoding protocol in Swift

I thought I'd be cautious and try out Swift on an existing Obj-C project by converting one class. And a small, simple one at that. Oh dear.
Transliterating the original obj-c into Swift should be straightforward and so it seemed. Unfortunately, whilst the encoder to persistent store seems to work, it crashes with an EXC_BREAKPOINT error at the first line of the init coder.
IF (and the caps are intentional) NSCoding/Swift gives the same persistent content as NSCoding/ObjC, then my all obj-c version should be able to read what is encoded by Swift and vice versa. This proves not to be the case - and my perfectly-functioning obj-c version crashes out when it tries to read the persistent store from the Swift version. Surely, if NSCoding is implemented correctly, it ought to generate something in one that is readable in t'other? Otherwise, there ought to be separate NSCodingSwift and NSCodingObjC protocols?
So, to summarise, I can read/write in obj-c. I can't write/obj-c and read/swift and I can write/swift read/obj-c and I can't read/write in swift.
Here are the two versions:
let keyBeaconItemNameKey = "name"
let keyBeaconItemUUIDKey = "uuid"
let keyBeaconItemMajorValueKey = "major"
let keyBeaconItemMinorValueKey = "minor"
import UIKit
import CoreLocation
class SMBeaconItem : NSObject, NSCoding
{
var name : String!
var uuid : NSUUID!
var major : NSNumber!
var minor : NSNumber!
init(newName : String, newUUID : NSUUID, newMajor : NSNumber, newMinor : NSNumber )
{
name = newName
uuid = newUUID
major = newMajor
minor = newMinor
}
init( coder decoder : NSCoder!)
{
name = decoder.decodeObjectForKey(keyBeaconItemNameKey) as String
uuid = decoder.decodeObjectForKey(keyBeaconItemUUIDKey) as NSUUID
major = decoder.decodeObjectForKey(keyBeaconItemMajorValueKey) as NSNumber
minor = decoder.decodeObjectForKey(keyBeaconItemMinorValueKey) as NSNumber
}
func encodeWithCoder( encoder: NSCoder!)
{
encoder.encodeObject(name, forKey:keyBeaconItemNameKey)
encoder.encodeObject(uuid, forKey:keyBeaconItemUUIDKey)
encoder.encodeObject(major, forKey:keyBeaconItemMajorValueKey)
encoder.encodeObject(minor, forKey:keyBeaconItemMinorValueKey)
}
}
And the working original:
#implementation SMBeaconItem
- (instancetype)initWithName:(NSString *)name uuid:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor
{
self = [super init];
if (!self)
{
return nil;
}
_name = name;
_uuid = uuid;
_majorValue = major;
_minorValue = minor;
return self;
}
#pragma mark - Persistence
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (!self)
{
return nil;
}
_name = [aDecoder decodeObjectForKey:keyBeaconItemNameKey];
_uuid = [aDecoder decodeObjectForKey:keyBeaconItemUUIDKey];
_majorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMajorValueKey] unsignedIntegerValue];
_minorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMinorValueKey] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:keyBeaconItemNameKey];
[aCoder encodeObject:self.uuid forKey:keyBeaconItemUUIDKey];
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.majorValue] forKey:keyBeaconItemMajorValueKey];
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.minorValue] forKey:keyBeaconItemMinorValueKey];
}
#end
Thanks for any help you can give.
The only thing that stands out to me is that you're using String instead of NSString as the name's type. In the betas Apple has been putting out, String (strangely) is not a one-for-one replacement of NSString. Namely, some methods are missing and require calling .bridgeToObjectiveC() to get the NSString version. Using that type instead will probably conform to what NSCoder is expecting, though that difference should not be so.
I didn't actually test this claim since I'm not on my dev machine. But that's my gut instinct. Try it and see what happens! If nothing changes, try switching the order of how you're setting the vars and see if it's a problem related to the name field or simply the first line of the init function.
Below code working is swift for save retrieve NSCoding value in UserDefaults
import UIKit
import Foundation
class ViewController: UIViewController {
var employees: Employees?
let static_key = "nscdeing_data_saved"
override func viewDidLoad() {
super.viewDidLoad()
var request = URLRequest(url: URL(string: "http://dummy.restapiexample.com/api/v1/employees")!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 60)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let status = (response as? HTTPURLResponse)?.statusCode, status == 200, let data = data{
do {
guard let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] else { return }
self.employees = Employees.init(fromDictionary: dic)
let archiveData = try NSKeyedArchiver.archivedData(withRootObject: self.employees as Any, requiringSecureCoding: true)
UserDefaults.standard.set(archiveData, forKey: self.static_key)
} catch let error {
fatalError(error.localizedDescription)
}
}
}.resume()
}
#IBAction func printAction(_ sender: Any) {
if let data = UserDefaults.standard.data(forKey: static_key){
do {
let value = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
print(value as Any)
} catch let error {
fatalError(error.localizedDescription)
}
}
}
}
class Employees : NSObject, NSCoding, NSSecureCoding{
static var supportsSecureCoding: Bool{
return true
}
var data : [Datum]!
var status : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
status = dictionary["status"] as? String
data = [Datum]()
if let dataArray = dictionary["data"] as? [[String:Any]]{
for dic in dataArray{
let value = Datum(fromDictionary: dic)
data.append(value)
}
}
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]{
var dictionary = [String:Any]()
if status != nil{
dictionary["status"] = status
}
if data != nil{
var dictionaryElements = [[String:Any]]()
for dataElement in data {
dictionaryElements.append(dataElement.toDictionary())
}
dictionary["data"] = dictionaryElements
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder){
data = aDecoder.decodeObject(forKey: "data") as? [Datum]
status = aDecoder.decodeObject(forKey: "status") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder){
if data != nil{
aCoder.encode(data, forKey: "data")
}
if status != nil{
aCoder.encode(status, forKey: "status")
}
}
}
class Datum : NSObject, NSCoding, NSSecureCoding{
static var supportsSecureCoding: Bool{
return true
}
var employeeAge : String!
var employeeName : String!
var employeeSalary : String!
var id : String!
var profileImage : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
employeeAge = dictionary["employee_age"] as? String
employeeName = dictionary["employee_name"] as? String
employeeSalary = dictionary["employee_salary"] as? String
id = dictionary["id"] as? String
profileImage = dictionary["profile_image"] as? String
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]{
var dictionary = [String:Any]()
if employeeAge != nil{
dictionary["employee_age"] = employeeAge
}
if employeeName != nil{
dictionary["employee_name"] = employeeName
}
if employeeSalary != nil{
dictionary["employee_salary"] = employeeSalary
}
if id != nil{
dictionary["id"] = id
}
if profileImage != nil{
dictionary["profile_image"] = profileImage
}
return dictionary
}
/**
* NSCoding required initializer.
* Fills the data from the passed decoder
*/
#objc required init(coder aDecoder: NSCoder){
employeeAge = aDecoder.decodeObject(forKey: "employee_age") as? String
employeeName = aDecoder.decodeObject(forKey: "employee_name") as? String
employeeSalary = aDecoder.decodeObject(forKey: "employee_salary") as? String
id = aDecoder.decodeObject(forKey: "id") as? String
profileImage = aDecoder.decodeObject(forKey: "profile_image") as? String
}
/**
* NSCoding required method.
* Encodes mode properties into the decoder
*/
#objc func encode(with aCoder: NSCoder){
if employeeAge != nil{
aCoder.encode(employeeAge, forKey: "employee_age")
}
if employeeName != nil{
aCoder.encode(employeeName, forKey: "employee_name")
}
if employeeSalary != nil{
aCoder.encode(employeeSalary, forKey: "employee_salary")
}
if id != nil{
aCoder.encode(id, forKey: "id")
}
if profileImage != nil{
aCoder.encode(profileImage, forKey: "profile_image")
}
}
}

property type or class using reflection

I was wondering if it's possible to determine the class or primitive type of an Objects properties. Getting all properties names and values is pretty easy. SO answer
So is there any way to get the properties class type while the property hast no value or nil value?
Example Code
#interface MyObject : NSObject
#property (nonatomic, copy) NSString *aString;
#property (nonatomic, copy) NSDate *aDate;
#property NSInteger aPrimitive;
#end
#implementation MyObject
#synthesize aString;
#synthesize aDate;
#synthesize aPrimitive;
- (void)getTheTypesOfMyProperties {
unsigned int count;
objc_property_t* props = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
// Here I can easy get the name or value
const char * name = property_getName(property);
// But is there any magic function that can tell me the type?
// the property can be nil at this time
Class cls = magicFunction(property);
}
free(props);
}
#end
After searching through Apples Documentation about objc Runtime and according to this SO answer I finally got it working. I just want to share my results.
unsigned int count;
objc_property_t* props = class_copyPropertyList([MyObject class], &count);
for (int i = 0; i < count; i++) {
objc_property_t property = props[i];
const char * name = property_getName(property);
NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
const char * type = property_getAttributes(property);
NSString *attr = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString componentsSeparatedByString:#","];
NSString * typeAttribute = [attributes objectAtIndex:0];
NSString * propertyType = [typeAttribute substringFromIndex:1];
const char * rawPropertyType = [propertyType UTF8String];
if (strcmp(rawPropertyType, #encode(float)) == 0) {
//it's a float
} else if (strcmp(rawPropertyType, #encode(int)) == 0) {
//it's an int
} else if (strcmp(rawPropertyType, #encode(id)) == 0) {
//it's some sort of object
} else {
// According to Apples Documentation you can determine the corresponding encoding values
}
if ([typeAttribute hasPrefix:#"T#"]) {
NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)]; //turns #"NSDate" into NSDate
Class typeClass = NSClassFromString(typeClassName);
if (typeClass != nil) {
// Here is the corresponding class even for nil values
}
}
}
free(props);
Inspired by the ObjC answer by #arndt-bieberstein I have written a solution in Swift 3 (probably very similar - if not same - in earlier versions of Swift). You can find it on Github I am trying to make a pod of it but I am having issues getting pob lib lintto work with the Swift 3 code (CLI xcodebuild or Xcode 8 related problem probably.) Anyhow, the class method func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? can extract the name and types of any Swift class that inherits from NSObject.
The work horse of the project are these methods, but checkout the full code on Github:
func getTypesOfProperties(in clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}
func getTypeOf(property: objc_property_t) -> Any {
guard let attributesAsNSString: NSString = NSString(utf8String: property_getAttributes(property)) else { return Any.self }
let attributes = attributesAsNSString as String
let slices = attributes.components(separatedBy: "\"")
guard slices.count > 1 else { return getPrimitiveDataType(withAttributes: attributes) }
let objectClassName = slices[1]
let objectClass = NSClassFromString(objectClassName) as! NSObject.Type
return objectClass
}
func getPrimitiveDataType(withAttributes attributes: String) -> Any {
guard let letter = attributes.substring(from: 1, to: 2), let type = primitiveDataTypes[letter] else { return Any.self }
return type
}
func getNameOf(property: objc_property_t) -> String? {
guard let name: NSString = NSString(utf8String: property_getName(property)) else { return nil }
return name as String
}
It can extract the NSObject.Type of all properties which class type inherits from NSObject such as NSDate (Swift3: Date), NSString(Swift3: String?) and NSNumber, however it is store in the type Any (as you can see as the type of the value of the Dictionary returned by the method). This is due to the limitations of value types such as Int, Int32, Bool. Since those types do not inherit from NSObject, calling .self on e.g. an Int - Int.self does not return NSObject.Type, but rather the type Any. Thus the method returns Dictionary<String, Any>? and not Dictionary<String, NSObject.Type>?.
You can use this method like this:
class Book: NSObject {
let title: String
let author: String?
let numberOfPages: Int
let released: Date
let isPocket: Bool
init(title: String, author: String?, numberOfPages: Int, released: Date, isPocket: Bool) {
self.title = title
self.author = author
self.numberOfPages = numberOfPages
self.released = released
self.isPocket = isPocket
}
}
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
print("'\(name)' has type '\(type)'")
}
// Prints:
// 'title' has type 'NSString'
// 'numberOfPages' has type 'Int'
// 'author' has type 'NSString'
// 'released' has type 'NSDate'
// 'isPocket' has type 'Bool'
You can also try to cast the Any to NSObject.Type, which will succeed for all properties inheriting from NSObject, then you can check the type using standard == operator:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if let objectType = type as? NSObject.Type {
if objectType == NSDate.self {
print("Property named '\(name)' has type 'NSDate'")
} else if objectType == NSString.self {
print("Property named '\(name)' has type 'NSString'")
}
}
}
}
If you declare this custom == operator:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
You can then even check the type of value types like this:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(inClass: Book.self) else { return }
for (name, type) in types {
if type == Int.self {
print("Property named '\(name)' has type 'Int'")
} else if type == Bool.self {
print("Property named '\(name)' has type 'Bool'")
}
}
}
LIMITATIONS
I have not yet been able to give this project support for when the value types are optionals. If you have declared a property in you NSObject subclass like this: var myOptionalInt: Int? my solution will not work, because the method class_copyPropertyList can't find those properties.
Does anyone have a solution for this?