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

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?

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

Correct way to catch NSInvalidArgumentException when using NSExpression [duplicate]

This question already has answers here:
Catch an exception for invalid user input in swift
(3 answers)
Closed 5 years ago.
I want to validate user created expressions (like "2+2", "5+7" or more complex). I use NSExpression class to parse and calculate this expressions. This is my Playground code:
import UIKit
let string = "2+2"
var ex:NSExpression?
do {
ex = NSExpression(format: string)
}
catch {
print("String is not valid expression")
}
if let result = ex?.expressionValue(with: nil, context: nil) as! NSNumber? {
print("result is \(result)")
}
When I use valid expression ("2+2") - I get the result. But sometime user can provide wrong string ("2+" for example). With this string my app crashes with this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "2+ == 1"'
I don't understand how I can catch this exception and why code above don't do this. Now I use Objective C class (with same logic), calling this method from my swift code, and in that class I really can catch such exception:
+(NSNumber *)solveExpression:(NSString *)string
{
id value;
#try {
NSExpression *ex = [NSExpression expressionWithFormat:string];
value = [ex expressionValueWithObject:nil context:nil];
}
#catch (NSException *e) { }
return value;
}
This works and I can get correct parse state (nil means problems with expression) and result (NSNumber), but I really want to understand how to do all this things correct and entirely in Swift.
NSInvalidArgumentException is not a catchable error in the sense of Java exceptions. Apple doesn't guarantee that your program will be in a correct state when you catch this exception and chances are that something will go wrong.
You should probably use some other mechanism to check if the string is valid before you pass it to the method.
This is what the book Using Swift with Cocoa and Objective-C has to say:
Although Swift error handling resembles exception handling in Objective-C, it is entirely separate functionality. If an Objective-C method throws an exception during runtime, Swift triggers a runtime error. There is no way to recover from Objective-C exceptions directly in Swift. Any exception handling behavior must be implemented in Objective-C code used by Swift.
[My bold]
Having just skimmed the reference for NSExpression I can't see a simple way around the issue. The quote above recommends writing a a bit of Objective-C code to do it. The simplest way is probably to create a C function:
Declaration:
extern NSExpression* _Nullable makeExpression(NSString* format _Nonnull);
Definition
NSExpression* _Nullable makeExpression(NSString* format _Nonnull)
{
NSExpression* ret = nil;
#try
{
// create and assign the expression to ret
}
#catch(NSException* e)
{
// ignore
}
return ret;
}
The function returns nil for expressions that are in error.
You could probably add an NSError** parameter to be used when there is a failure. You could also probably make this a method in a category on NSExpression and then the return nil for error/fill in NSError pattern would probably be imported to Swift as a Swift throwing method.
I should say, by the way, that an Objective-C exception is not really guaranteed to leave your program in a consistent state. Fingers crossed it is OK in this case.

initWithDictionary: in Objective-C and 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.

NSURLSession crashing from Swift

I've got a simple class that uses an NSURLSession.
class test {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration());
func f() {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0), { () -> Void in
var task = self.session.dataTaskWithRequest(request, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if error != nil {
// cry
return;
}
var error: NSError? = nil;
var dict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: &error) as! Dictionary<String, String>;
// use dict
});
task.resume();
});
}
When I try to deserialize the data as JSON, the application crashes.
I've determined that the data seems to be of the right length but the content looks like garbage in the debugger, so it seems to me that the data object passed in is broken. Furthermore, I suspect some stack smashing as I can step through this completion handler and see that it's the attempt to deserialize the data that's crashing, but when the actual crash occurs, the stack in the debugger mentions nothing about the completion handler or any of my code.
I've seen several samples of using NSURLSession that look pretty much exactly like mine that just work. I tried using the shared session instead of making a new one, but that did not help either.
What is causing this crash?
Seems that the JSON was not actually all strings- I brainfarted and one of them was actually a number.
The real problem in the question is that Swift is completely worthless when handling the problem of force casts failing. Not only do you not get any kind of useful error at runtime that you could handle or recover from, but the debugging information presented when it occurs is completely misleading and points to totally the wrong place. You simply get a trap in a function without symbols with the wrong callstack.

'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
}