Unwrapping an Optional value error in Swift - objective-c

According to the question subject it is very frequent question and lot many answers there on stacks but i am confuse with behaviour of the error assurance cases. I am little new to Swift and i have some codes. In this code some times i getting and error of Unwrapping an Optional value
Bellow is the code
let operation = AFHTTPRequestOperation(request: request)
operation.setCompletionBlockWithSuccess({ (operation:AFHTTPRequestOperation!, responseObject:AnyObject!) -> Void in
var error:NSError?
//Bellow line gives me an error
let responseDict = NSJSONSerialization.JSONObjectWithData(responseObject as! NSData, options:NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
Error: fatal error: unexpectedly found nil while unwrapping an Optional value
Note: This line gives me and error only when my internet connectivity is down. When internet is totally disconnect then this error not comes up and it goes to Error block of AFNetworking. And when internet is fully working then also i did not get this error as well.
Please suggest some modification or replacement in code.

Your responseDict might not be valid JSON in this case. Whenever you get an error like this, look carefully at all your forced unwrapped optionals. In this case:
let responseDict = NSJSONSerialization.JSONObjectWithData(responseObject as! NSData, options:NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
Instead use:
if let responseDict = NSJSONSerialization.JSONObjectWithData(responseObject as! NSData, options:NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary {
// Response was valid JSON
}

You need to unwrap the responseObject which is an optional. Most likely it is nil when the connection is down (which makes perfect sense).
if let response = responseObject as? NSData {
let responseDict = NSJSONSerialization.JSONObjectWithData(response, ...
}

Related

Swift perform selector with inout argument

I have an objc method I'd like to call from swift, but the method exists in a third party framework that may or may not exist at runtime.
What I'd like to do is call it dynamically, so I'm looking into interacting with it using selectors.
The method signature looks like this in ObjC
- (NSString * _Nullable)buildData:(NSError * _Nullable __autoreleasing * _Nullable)error;
Removing the nullable annotations, it's the bog standard "return a thing, or error" pattern that ObjC has had forever.
- (NSString*)buildData:(NSError*)error;
If swift can load this at compile time, then it quite happily translates into
func buildData() throws -> String
However, I'd like to call it dynamically. I've worked out I can do this:
let _target:NSObject = // obtain a reference to the underlying value
var error: NSError? = nil
_target.perform(NSSelectorFromString("buildData:"), with: &error)
The problem is, I can't pass a reference to the perform selector method. XCode gives me a compile error of
'&' used with non-inout argument of type 'Any?'
So my question is, how can I call this method using selectors?
Try this:
var error: NSError? = nil
let _target: NSObject = // obtain a reference to the underlying value
withUnsafeMutablePointer(to: &error) {
let selector: Selector = NSSelectorFromString("buildData:")
let methodIMP: IMP! = _target.method(for: selector)
unsafeBitCast(methodIMP,to:(#convention(c)(Any?,Selector,OpaquePointer)->Void).self)(_target,selector,OpaquePointer($0))
}
More info on using convention(c) for invoking selectors in Swift in my answer here

Swift 3 Data not conforming to NSCoding

I tried posting this on Apple's forums but for some reason it's been waiting for moderator approval for a day.
So I want to give my app's users the ability to change the background color, so I have a default value which can be changed, and any changes to that color will be stored as a UserDefault, so I have a computed propety which upon request either reads or writes the value stored in UserDefaults.standard like this:
var homeColor: UIColor? {
get {
guard let data = Defaults.getValue(for: .HomeColor) as? Data else {
return #colorLiteral(red: 0.4119389951, green: 0.8247622848, blue: 0.9853010774, alpha: 1)
}
return NSKeyedUnarchiver.unarchiveObject(with: data) as? UIColor ?? #colorLiteral(red: 0.4119389951, green: 0.8247622848, blue: 0.9853010774, alpha: 1)
}
set {
let data = NSKeyedArchiver.archivedData(withRootObject: newValue)
Defaults.set(value: data, for: .HomeColor)
}
}
BTW Defaults is an enum I created to manage UserDefaults more easily, so please don't confuse them.
I know I should convert an UIColor to Data and back when dealing with UserDefaults so I use NSKeyedArchiver and NSKeyedUnarchiver to do that but I still get an exeption saying:
'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x6000002439c0'
I can't tell which object is "instance 0x60000005d250" because the inspector is empty, and the debugging info is useless because it just says that my AppDelegate is responsible for that, instead of the actual function that raises the exception. I traced the exeption "manually" using breakpoints and found out it happens whenever I call NSKeyedArchiver.archiveData(withRootObject:), I suspect then that instance 0x60000005d250 is my newValue which is a UIColor, yet the documentation says that UIColor implements NSSecureCoding, which has an encode(withCoder:) function requirement, but there is no other object that could be because that exception only happens when I call NSKeyedArchiver.archiveData(withRootObject:) and that is the only value I pass to it, if I don't do that I get a different exception (which is expectd since UIColor is not compatible with a plist) but there is no
"unrecognized selector sent to instance"
which is the puzzling part.
I also tested the archiving function itself using a value compatible with plist and no exception was raised, so I guess it is not a bug (or at least not an obvious one) caused by some internal variable of that function.
Update
I kept on tracking and actually found out there's nothing wrong with NSKeyedArchiver.encodeData(withRootObject:) it actually returns a Data object, the actual exception is raised when I call UserDaults.standard.set(value:forKey:)(which is not shown in my code directly). So that is the function sending the wrong selector. NSKeyedArchiver.encodeData(withRootObject:) returns swift's own Data instead of NSData. And I searched the documentation and Data does not conform to NSCoding directly and I guess it does not do so through any another protocol. If that was the case it would still be odd since Data is supposed to bridge directly to NSData, so I am not sure what is happening here.

(iOS 10, Swift 3) Reading `userInfo` dictionary from a CloudKit notification: How do I cast `[AnyHashable : Any]` to `[String : NSObject]`?

Background
I'm trying to load the userInfo dictionary from application:didReceiveRemoteNotification:userInfo:fetchCompletionHandler in my app delegate.
I then need to cast userInfo from [AnyHashable:Any] to [String:NSObject] so I can use it in CloudKit's CKNotification:fromRemoteNotificationDictionary.
Question
When I do:
let ui = userInfo as! [String : NSObject]
I get the error:
'[AnyHashable:Any]' is not convertible to '[String:NSObject]'
Is there a better way to convert userInfo to the appropriate type, or am I on a completely wrong track?
You just need to cast it first to NSDictionary and then you can cast it to [String: NSObject].
Try like this:
CKNotification(fromRemoteNotificationDictionary: userInfo as NSDictionary as! [String: NSObject])
NSString.localizedStringWithFormat("%#",(notification.userInfo as! [String: AnyObject] as! [String : NSObject])["theKeyValue"]!)
This is how I got the string. Feel free to correct it.
As suggested, it is enough to take away all casts and use userInfo as such for Swift 3 to handle the thing correctly.

Is it possible to pass a NULL NSError** to a Swift function that throws?

In Objective-C, it's expected that you can pass in NULL to any NSError** parameter to ignore the error. However, when I try to pass NULL to a Swift method that throws an error, it generates a runtime error.
// Thrower.swift
class Thrower: NSObject {
static func throwError() throws {
throw NSError(domain: "bla", code: 0, userInfo: nil)
}
}
...
// AppDelegate.m
BOOL success = [Thrower throwErrorAndReturnError:NULL];
This generates an EXC_BAD_INSTRUCTION error, with this stack:
I'm a little surprised at this behavior. I would expect this to either work, or the compiler to generate a warning when you pass NULL to one of these methods.
Here's what the generated header of the Swift method looks like:
+ (BOOL)throwErrorAndReturnError:(NSError * __nullable * __null_unspecified)error;
If this was not supposed to work, why wouldn't they generate NSError * __nullable * __nonnull, so that a compiler warning is generated when you try to pass in a nullable NSError*?
Is there something I'm missing here, or is this just expected behavior?
On WWDC'15 Session 401 (Swift and Objective-C Interoperability) Doug Gregor said:
This means, 'I thought about it, I couldn't come to an answer.' The best thing to do is keep it implicitly unwrapped optional in Swift, keep it null-unspecified here.
So, basically, your null is mapped to ImplicitlyUnwrappedOptional<ErrorType>.None, that pretty much explains the crash.
On the session above they mention NSError ** is assumed to be nullable on both pointers. Apparently they've changed their mind or the mapping is not symmetrical, anyway it looks wrong to me.
Considering that behavior and not being NSError * __nullable * __nonnull I'd say it's a bug, I'd open a radar. If you do please let us know so we can dupe it.

'NSError' is not convertible to 'AutoreleasingUnsafePointer<NSError?>'

In my iOS application I need to play sound. I have the following code:
var audioFileLocationUrl = NSBundle.mainBundle().pathForResource("midnight", ofType: "m4a")
var error: NSError
var audioPlayer = AVAudioPlayer()
audioPlayer = AVAudioPlayer(contentsOfURL: audioFileLocationUrl, error: error)
audioPlayer.prepareToPlay()
On line:
audioPlayer = AVAudioPlayer(contentsOfURL: audioFileLocationUrl, error: error)
I have error: Could not find overload for 'init' that accepts the supplied arguments
Edit (it was pointed out to me that there is a clear difference between when to use NSErrorPointer and when to use NSError?. Here is the official word:
From “Using Swift with Cocoa and Objective-C.”
“When you need to report the reason for the error, you can add to the function an NSError out parameter of type NSErrorPointer. This type is roughly equivalent to Objective-C’s NSError **, with additional memory safety and optional typing. You can use the prefix & operator to pass in a reference to an optional NSError type as an NSErrorPointer object, as shown in the code listing below.
var writeError : NSError?
AVAudioPlayer(contentsOfURL: audioFileLocationUrl, error: &writeError)
Try
var error :NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: audioFileLocationUrl, error: &error)
Failing that check you're calling the right method and passing in the right object types.
The reason you get this warning is because the compiler can't find that init method with those parameters.
Update: Error handling has changed drastically in Swift 2. See the documentation for the latest info.
For me, this code produces the error 'NSError' is not convertible to 'AutoreleasingUnsafePointer<NSError?>'. This is because the method is trying to take an error by reference so it can "write back" to your local variable. In Obj-C this looks like (NSError **). (Fun fact: AutoreleasingUnsafePointer<NSError?> is aliased to NSErrorPointer in Swift.)
In fact, if you ⌘-click on AVAudioPlayer to see its declaration, you'll find the init method is defined as
init(contentsOfURL url: NSURL!, error outError: AutoreleasingUnsafePointer<NSError?>)
For this reason, you must:
use an NSError? (i.e. Optional<NSError>) variable, since the error may be nil.
Pass the error in by reference with the prefix & operator.
var error: NSError?
...
audioPlayer = AVAudioPlayer(contentsOfURL: audioFileLocationUrl, error: &error)
See Error Reporting in "Using Swift with Cocoa and Objective-C" for more information.