initWithDictionary: in Objective-C and Swift - swift

In Objective-C we can use object mapping with json response like this way
PolicyData *policyData = [[PolicyData alloc] initWithDictionary:responseObject error:&err];
Here we can map responseObject with PolicyData class properties.
How i can do the same in Swift?

It should be as easy as adding a bridging header (because PolicyData is likely written in Objective-C). Instructions on how to do this can be seen in this Apple documentation.
Then you can create that PolicyData object as easily as doing:
do {
let newPolicyDataObject = try PolicyData(responseObject)
} catch error as NSError {
print("error from PolicyData object - \(error.localizedDescription)")
}
This assumes your responseObject is a NSDictionary. And Swift 2 helpfully (?) turns error parameters into try/catch blocks.
That is, PolicyData's
- (instancetype) initWithDictionary:(NSDictionary *)responseObject error:(NSError *)err;
declaration magically becomes
func initWithDictionary(responseObject : NSDictionary) throws
as described in the "Error Handling" section of this Apple Objective-C/Swift interoperability doc.

You can add a
convenience init?(dictionary: NSDictionary)
to any object you want to initialize from a dictionary and initialize it's properties there.
Yet, as swift does no dynamic dispatching (sooner or later), you may not generalize that to expect the properties' names to be keys in the dictionary for any object.

Related

How to convert Swift optional into Objective-C

I have read up on Swift optionals, specifically if let, but cannot make sense of this code. The tutorial is a swift project, but I am trying to use it to update an old Obj-C project. I am not trying to create an if let optional in objective-c, but rather just figure out how to make an Obj-C version do what he is doing here with Swift. The underlying code doesn't return a value.
if let user = user << Obj alternative
Following a tutorial and here is the code:
[[FIRAuth auth] signInWithEmail:userEmail
password:userPass
completion:^(FIRUser * _Nullable user, NSError * _Nullable error) {
if(error == nil) { // No ERROR so already so user already in Firebase
if let user = user{
NSDictionary *userData = #{ #"provider": user.providerID};
[[DataService instance] createFirebaseDBUserWithUid:user.uid
userData:userData
isDriver:NO];
}
DLog(#"Email user Auth successfully in Firebase");
Here is the createFirebaseDBUserWithUid:user code (possible returns a Firebase id value)
#objc func createFirebaseDBUser(uid: String, userData: Dictionary<String, Any>, isDriver: Bool) {
if isDriver {
REF_DRIVERS.child(uid).updateChildValues(userData)
} else {
REF_USERS.child(uid).updateChildValues(userData)
}
}
Would an obj-c version just be:
if( user != nil ){
NSDictionary *userData = #{ #"provider": user.providerID};
[[DataService instance] createFirebaseDBUserWithUid:user.uid userData:userData isDriver:NO];
}
The trick with Objective-C of course is its behaviour if you send any message to anil reference.
At the time of its invention (1986) it was actually progress to have any method sent to a nil reference (which is 'faulty' in an if statement since it is 0 (the number) at the same time) simply returning nil (or 0 if you interpret it as a number) and again 'faulty'.
In this sense you are 'mostly' correct if you do
if (user != nil)
in Objective-C wherever you see
if let user = user {
...
}
in Swift. Technically speaking it is not entirely the same, since the let-expression will give you a non-nullable reference to the same content shadowing the optional object for the following scope. So you do not have to do any more unwrapping of the user object in the scope of the if. Note however, that the user will again be optional after the if statement. This is adequately covered by the nil check in the Objective-C counterpart.
There may be "unusual" edge cases you could miss if the author of the Objective-C code relied on the "strange" interpretation of method passing in Objective-C. Nowadays it is considered bad practice to rely on these, but there used to be a time when we had chains of method calls that relied on said behaviour. Swift was invented partly to get rid of those sometimes unintended effects in method chains.
Objective-C Type is equivalent to Type! in Swift. for example: Objective-C NSDictionary would be NSDictionary! in Swift
Thus, Objective-C types are optionals by default and reading a Swift Type in Objective-C won't be affected by being an optional or not.
back to your question, as the intention of the code is not clear for me, generally, the code you wrote will generate a dictionary that contains one pair of (key, value) with the providerID which is not related to being optional or not.
the issue that you would face is if the User object was created in Swift, and you want to read it in Objective-C, then you have set it up for that

Prevent NSKeyedArchiver throwing exception without Objective-C

On iOS version lower than 11 the throwing archivedData(withRootObject:requiringSecureCoding:) is unavailable, so I have tried to do the equivalent on versions less than iOS 11:
let archiveData: NSData
if #available(iOS 11.0, *) {
archiveData = try NSKeyedArchiver.archivedData(
withRootObject: rootObject,
requiringSecureCoding: true
) as NSData
} else {
NSKeyedArchiver.archivedData(withRootObject: userActivity)
let mutableData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: mutableData)
archiver.requiresSecureCoding = true
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
if let error = archiver.error {
throw error
}
archiver.finishEncoding()
archiveData = mutableData
}
However, when the rootObject calls NSCoder.failWithError(_:) in the encode(with:) function an NSInvalidUnarchiveOperationException exception is raised.
If I subclass NSKeyedArchiver as such:
final class KeyedArchiver: NSKeyedArchiver {
override var decodingFailurePolicy: NSCoder.DecodingFailurePolicy {
return .setErrorAndReturn
}
}
It instead raises an NSInternalInconsistencyException exception with the message Attempting to set decode error on throwing NSCoder.
Is there a way to do this kind of archiving without throwing an exception, short of writing an Objective-C function to catch the exception and throwing it as an error?
The reason you're still getting the exception at encode time is that the .decodingFailurePolicy is effective only when decoding (i.e., unarchiving via NSKeyedUnarchiver), not encoding. Any object that calls .failWithError(_:) on encode will still produce the exception.
Calling .failWithError(_:) at encode-time is relatively rare: usually, once you have a fully constructed object at runtime, it's not terribly likely that it should be in a state that's not encodable. There are of course cases where this is possible, so you really have two options:
If you're working with objects you know and can check ahead of time whether they're in a valid state to encode, you should do that and avoid encoding invalid objects altogether
If you're working with arbitrary objects which you can't validate up-front, you're going to have to wrap your callout to NSKeyedArchiver via an Objective-C function which can catch the exception (and ideally throw an Error containing that exception, like the newer NSKeyedArchiver API does on your behalf)
Based on your comment above, option 2 is your best bet.
As an aside, you can shorten up your fallback code to avoid having to construct an intermediate NSMutableData instance:
let archiver = NSKeyedArchiver()
archiver.encode(rootObject, forKey: NSKeyedArchiveRootObjectKey)
archiveData = archiver.encodedData
The default initializer on NSKeyedArchiver constructs a new archiver with an internal mutable data instance to use, and NSKeyedArchiver.encodedData property automatically calls -finishEncoding on your behalf.

Understanding Swift + NSError - All paths through this function will call itself

After working with Objective-C for a long time I have just started to work with Swift as well.
I know that Swift uses the Error protocol instead of NSError and in future I will use custom Error implementations as well. However, currently I need to work with NSError in Swift and I found the following code to easily access NSError properties on Error:
extension Error {
var code: Int { return (self as NSError).code }
var userInfo: [AnyHashable? : Any?] { return (self as NSError).userInfo }
}
While I found an example for the code part in an answer here, I added the userInfo part myself. It is now problem to access someError.code using this extension. However, when using someError.userInfo the app chrashes after calling the getting over and over again.
Xcode shows a warning which explains the source of the problem:
All paths through this function will call itself
Why is this?
As far as I know Error is just a protocol which can be implemented by any class. Error it self does not have code or userInfo properties, so (self as NSError).userInfo should not callError.userInfo`, should it?
So I do not understand why this is a problem. Additionally I do not understand why this is a problem for userInfo but not for code...
Any idea what's the reason for this?

Dictionary cannot be bridged from Objective-C -> Issues in Swift

I have been porting some objective-c code over into swift and I am trying to get the result set as a dictionary and then pack each dictionary (equivalent to a row from the db) into an array. But I am getting this error message "Dictionary cannot be bridged from Objective-C". I have read this from apple but still I am no further along towards a solution. Any ideas? Thanks.
This is the line where the error is:
resultsArray.append(resultSet!.resultDictionary() as Dictionary<String,String>)
From the awesome robertmryan reposted here for convenience:
This will happen if your database has an null values (which return [NSNull null] objects) or numeric values (which return NSNumber objects). You can fix this by defining resultsArray as:
var resultsArray = Array<Dictionary<String,AnyObject>>()
Or, I personally prefer:
var resultsArray = [[String: AnyObject]]()
And then when adding objects, I'd
resultsArray.append(resultSet!.resultDictionary() as Dictionary<String, AnyObject>)
or
resultsArray.append(resultSet!.resultDictionary() as [String: AnyObject])

'NSSecureCoding!' is not a subtype of 'NSURL' using Swift when trying to replace closure argument type

I'm using the
loadItemForTypeIdentifier:options:completionHandler: method on an NSItemProvider object to extract a url from Safari via a Share extension in iOS 8.
In Objective-C, this code compiles and works:
[itemProvider loadItemForTypeIdentifier:(#"public.url" options:nil completionHandler:^(NSURL *url, NSError *error) {
//My code
}];
In Swift however, I'm getting "NSSecureCoding!' is not a subtype of 'NSURL" compile error when I try to do something similar:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (urlItem:NSURL, error:NSError!) in
//My code
})
If I add the bang to NSURL argument type as in NSURL! I get "Cannot convert the expression's type 'Void' to type 'Void'" compile error. And if I leave the default argument typed as NSSecureCoding!, it compiles, but the block/closure doesn't run.
What am I doing wrong?
You don't need to specify the types for urlItem or error as they can be inferred from the declaration of loadItemForTypeIdentifier:options:completionHandler. Just do the following:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: {
(urlItem, error) in
//My code
})
Even better, you can move the closure outside of the method call:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) {
(urlItem, error) in
//My code
}
This API makes use of reflection internally: it looks at the type of the block’s first parameter to decide what to return. This is dependent on Objective-C’s looser enforcement of the block signature compared to Swift — and therefore does not work in Swift. (Yes, still.) See this discussion on the developer forums.
I recommend filing a bug report with Apple and writing small Objective-C wrapper methods to read each type of data you need.
It’s possible I’ve overlooked something. If someone has found a neat way to make this work in Swift I’m keen to hear it.
The new API to use is canLoadObject and loadObject, use it as this:
if (itemProvider.canLoadObject(ofClass: NSURL.self)) {
print("==== attachment is URL")
itemProvider.loadObject(ofClass: NSURL.self, completionHandler:
{
(data, error) in
print("==== url object = \(data)")
})
}
The same can be used for UIImage
https://developer.apple.com/documentation/uikit/drag_and_drop/data_delivery_with_drag_and_drop
Just in case you or other ones still need a solution, it is simple. Just add a side variable with a cast to NSURL. Here it is:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) {
(urlItem, error) in
let url : NSURL = urlItem : NSURL
// Whatever you like to do with your new url
}