I'm trying to update an NSError object with more information. For example, an api call may fail and I want to update the error object returned from the api class with view controller information (method name that caused error, client message, any additional info). There isn't a setter method for the UserInfo dictionary and trying to set a value for the dictionary raises an exception (Not key value code compliant I believe). I thought about creating a new NSError object with the updated user info, but I wasn't sure if I might lose information.
Question
What's the best way to update the user info dictionary of an NSError object?
With swift extentions it's easy:
extension NSError {
func addItemsToUserInfo(newUserInfo: Dictionary<String, String>) -> NSError {
var currentUserInfo = userInfo
newUserInfo.forEach { (key, value) in
currentUserInfo[key] = value
}
return NSError(domain: domain, code: code, userInfo: currentUserInfo)
}
}
usage:
var yourError = NSError(domain: "com.app.your", code: 999, userInfo: nil)
yourError = yourError.addItemsToUserInfo(["key1":"value1","key2":"value2"])
The canonical approach would be to make a new NSError all your own and then put the original NSError in the userInfo dictionary under the key NSUnderlyingErrorKey. This is a slightly different result, but as best I can tell NSErrors are quite intentionally immutable.
Easiest way to do this would be to get a mutable copy of the userInfo dictionary and add whatever you like to that. Then you would have to create a new NSError (since there is not setUserInfo: method) with the same domain and code as the original one.
Related
We had an issue with an Error object that crashes in Crashlytics' Objective-c code because it doesn't respond to userInfo (a member of NSError), while investigating I stumbled upon this weird behavior in Swift.
In a playground, I tried creating a class SwiftError implementing the Error protocol:
class SwiftError: Error {
}
let sError = SwiftError()
if sError is NSError { // Generates a warning: 'is' test is always true
print("Success")
} else {
print("Fail")
}
// Prints Fail
let nsError = sError as NSError
//Compiler Error: 'SwiftError' is not convertible to 'NSError'; did you mean to use 'as!' to force downcast?
Checking if the SwiftError is NSError gives a warning that it always succeeds but fails runtime.
Casting SwiftError as an NSError gives a compiler error.
Can someone help explain to me why this happens and how could I know if a class implementing the Error protocol actually is an NSError or not ?
Thanks!
There is something odd about the nature of the bridging between NSError and Error. They are bridged for communication purposes — that is, they can travel back and forth between Swift and Cocoa; and an error that comes from Cocoa is an NSError (and NSError adopts the Error protocol in Swift, to allow for this); but your class that you declare as conforming to Error is itself not an NSError.
because it doesn't respond to userInfo
If you need your Swift Error type to carry userInfo information for Cocoa's benefit, then you were looking for the CustomNSError protocol.
https://developer.apple.com/documentation/foundation/customnserror
This looks like a bug to me, I have filed it as SR-14322.
According to SE-0112 Improved NSError Bridging,
Every type that conforms to the Error protocol is implicitly bridged to NSError.
This works with struct and enum types, but apparently not with class types.
You can replace your class SwiftError: Error by a struct SwiftError: Error to solve the problem. If that is not possible for some reason then the following “trick” worked in my test:
let nsError = sError as Error as NSError
That compiles and gives the expected results:
class SwiftError: Error {}
extension SwiftError: CustomNSError {
public static var errorDomain: String { "MyDomain" }
public var errorCode: Int { 13 }
public var errorUserInfo: [String : Any] { ["Foo" : "Bar" ] }
}
let sError = SwiftError()
let nsError = sError as Error as NSError
print(nsError.userInfo)
// Output: ["Foo": "Bar"]
I've seen that NSUserDefaults do not allow objects other than Array, Dictionary, String, NSData, NSNumber, Bool, NSDate. But
Why it is not allowed to store other objects?
What if I need to store some properties as a single object? If I do it using dictionary, I've to write the keys somewhere which I've used to store the value. So, what could be the other alternatives.
What were the problems will arise if Apple allows the other objects also to store.
What if we use CoreData instead NSUserDefaults. I Know NSUserDefaults is globally available.
What is the best way to make a value available globally even after we relaunched the app if get terminated?
As suggested by #Hoa, previously I forgot mention NSCoding option also
What if we have many properties in a custom class, Do we need to encode and decode all of them using NSCoding methods?
You can save any object to a plist file as long as it is compliant with the NSCoding protocol.
You can use code similar to this:
+(id) readObjectFromFile:(NSString*) sFileName
{
return [NSKeyedUnarchiver unarchiveObjectWithFile:sFileName];
}
+(bool) saveObject:(id <NSCoding>) anObject ToFile:(NSString*) sFileName
{
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:anObject];
NSError * error;
[data writeToFile:sFileName options:NSDataWritingAtomic error:&error];
if (error)
{
NSLog(#"Save Cats Data error: %#", error.description);
return NO;
}
return YES;
}
Swift Version:
func readObjectFromFile(sFileName: String) -> AnyObject {
return NSKeyedUnarchiver.unarchiveObjectWithFile(sFileName)
}
func saveObject(anObject: AnyObject, ToFile sFileName: String) -> Bool {
var data: NSData = NSKeyedArchiver.archivedDataWithRootObject(anObject)
var error: NSError
data.writeToFile(sFileName, options: NSDataWritingAtomic, error: error)
if error != nil {
print("Save Cats Data error: \(error.description)")
return false
}
return true
}
To learn more about the NSCoding protocol you can read:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/
The intention behind NSUserDefaults is to save contextual data relevant to the application state, for example saving the user's preferences, the state of the application when the user stopped using it (so you can return to that state when it fires up), login session data, etc..
Core Data is a more appropriate way to store persistent data, you can map your data model as you like and has a broader variety of options to save datatypes.
Whilst NSUserDefaults is available "everywhere", this should not be a turning point to decide if this is a better option for saving your data.
You can write a singleton class that serves as a data provider so that you can access your data in the same way you access the NSUserDefaults shared instance. You just need to keep in mind that this class or module should do only one thing, serve as an interface between your model and your implementation, so you do not store any objects in this class, just use it to pass over the requests to get and save data to CoreData.
This class could look something like this:
class CoreDataProvider {
static let sharedInstance = SUProvider()
let managedObjectContext : NSManagedObjectContext
let sortDescriptor: NSSortDescriptor
let fetchRequest: NSFetchRequest
private init(){
managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
fetchRequest = NSFetchRequest(entityName: "MyObject")
sortDescriptor = NSSortDescriptor (key:"objectAttribute", ascending:true)
self.fetchRequest.sortDescriptors = [self.sortDescriptor]
}
func getSavedObjects() -> [MyObject]? {
fetchRequest.sortDescriptors = [sortDescriptor]
do {
return try self.managedObjectContext.executeFetchRequest(fetchRequest) as? [MyObject]
} catch {
print("no records found")
}
}
}
Which you would use like this:
func getAllRecords() {
let records = CoreDataProvider.sharedInstance.getSavedObjects()
//- Do whatever you need to do
}
A temporary way to store object is to create json strings of your dictionaries or arrays. I have used it in some low scale apps. You can then store those string in NSUserDefault.., and when you need to use it, you can extract it, and you can use Object Mapper library that will automatically map the json data to object type.
example, you can create a function in your class extension that parses json data to your objects any time you need it.
I would suggest using the above method only for small scale apps. If you are going for high traffic/large scale app, you might wanna look into Core Data or even SQlite3..
Feel free to ask any question
Reference to Object Mapper library is here
I have a scenario where I've to cast an AnyObject to a dynamic type (available only at runtime). Is this possible with Swift? If yes, how would I do it?
To explain the scenario further, I have a function which has a completion block that returns the response or error from a service call. In the block, the success object is in fact an Array of AnyObject where the dynamicType of the object is that of the calling class.
someObject.callService(classObject, withCompletionBlock: {(objectOrNil : [AnyObject]!, errorOrNil:NSError?) -> Void in
if(errorOrNil != nil) { self.delegate.didFailWithError(errorOrNil)
}
else {
// Here objectOrNil is during runTime an Array of class X
// X.response gives the response JSON
// In a particular scenario, I need to intercept the response before the delegate
self.delegate.didReceiveResponse(objectOrNil)
}
}, withProgressBlock: nil)
In the block objectOrNil is an array of a class X ([X]) where the X would be class invoking the service call. All the classes that call the method has a response dictionary object which is populated with the response after the service call is completed and the delegate invoked.
To handle a particular scenario, I need to intercept the service response (X.response) where X is objectOrNil[0] from within this block and do additional processing.
I'm able to see the class name when I do a
po objectOrNil[0].dynamicType
in the console, but am unable to cast the objectOrNil[0] to the right type X to fetch X.response. When I tried
po objectOrNil[0].response
I'm getting an error that response is ambiguous with NSURLResponse response. But
po objectOrNil[0].response as NSDictionary
returns the right JSON response in console during runtime (with breakpoint).
However the below code gives a compile time error that AnyObject does not have response attribute.
let responseDict = objectOrNil[0].response as NSDictionary
Can anyone guide me on how to cast to the dynamicType type and fetch the response dictionary for me to proceed?
Thanks
You could use a protocol
protocol Respondable {
var response : NSDictionary {get}
}
and then constrain the type of objectOrNil to that protocol
someObject.callService(classObject, withCompletionBlock: {(objectOrNil : [Respondable]!, errorOrNil:NSError?) -> Void
I'm stuck on why my transformable classes aren't being called. The following screenshot shows my Entity attribute as a transformable:
According to the documentation, it should automatically call the class "StringEncryptionTransformer" and perform the transformation.
I followed this guide in order to set up the class. I'm using the EncryptionTransformer and StringEncryptionTransformer classes provided, the only change I made was with the encryption to use RNcryptor.
Encryption:
return [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password:[self key] error:&error];
and Decryption:
return [RNDecryptor decryptData:data withPassword:[self key] error:&error];
The saved entity appears never to go through the transformation, is there something I'm missing? I tried adding an initialize to the NSManagedObject, but the results were the same.
you need to register value transformer like below
extension NSValueTransformerName {
static let classNameTransformerName = NSValueTransformerName(rawValue: "ClassNameTransformer")
}
ValueTransformer.setValueTransformer(ClassNameTransformer(), forName: .classNameTransformerName)
I'm trying to unit test a method which has a dependency on another class. The method calls a class method on that class, essentially this:
func myMethod() {
//do stuff
TheirClass.someClassMethod()
}
Using dependency injection technique, I would like to be able to replace "TheirClass" with a mock, but I can't figure out how to do this. Is there some way to pass in a mock class (not instance)?
EDIT: Thanks for the responses. Perhaps I should have provided more detail. The class method I am trying to mock is in an open source library.
Below is my method. I am trying to test it, while mocking out the call to NXOAuth2Request.performMethod. This class method issues a network call to get the authenticated user's info from our backend. In the closure, I am saving this info to the global account store provided by the open source library, and posting notifications for success or failure.
func getUserProfileAndVerifyUserIsAuthenticated() {
//this notification is fired when the refresh token has expired, and as a result, a new access token cannot be obtained
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didFailToGetAccessTokenNotification", name: NXOAuth2AccountDidFailToGetAccessTokenNotification, object: nil)
let accounts = self.accountStore.accountsWithAccountType(UserAuthenticationScheme.sharedInstance.accountType) as Array<NXOAuth2Account>
if accounts.count > 0 {
let account = accounts[0]
let userInfoURL = UserAuthenticationScheme.sharedInstance.userInfoURL
println("getUserProfileAndVerifyUserIsAuthenticated: calling to see if user token is still valid")
NXOAuth2Request.performMethod("GET", onResource: userInfoURL, usingParameters: nil, withAccount: account, sendProgressHandler: nil, responseHandler: { (response, responseData, error) -> Void in
if error != nil {
println("User Info Error: %#", error.localizedDescription);
NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
}
else if let data = responseData {
var errorPointer: NSError?
let userInfo = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &errorPointer) as NSDictionary
println("Retrieved user info")
account.userData = userInfo
NSNotificationCenter.defaultCenter().postNotificationName("UserAuthenticated", object: self)
}
else {
println("Unknown error retrieving user info")
NSNotificationCenter.defaultCenter().postNotificationName("UserCouldNotBeAuthenticated", object: self)
}
})
}
}
In Swift this is better done by passing a function. There are many ways to approach this, but here is one:
func myMethod(completion: () -> Void = TheirClass.someClassMethod) {
//do stuff
completion()
}
Now you can pass a completion handler, while existing code will continue to use the default method. Notice how you can refer to the function itself (TheirClass.someClassMethod). You don't have to wrap it up in a closure.
You may find it better to let the caller just pass this all the time rather than making it a default. That would make this class less bound to TheirClass, but either way is fine.
It's best to integrate this kind of loose coupling, design-for-testability into the code itself rather than coming up with clever ways to mock things. In fact, you should ask yourself if myMethod() should really be calling someClassMethod() at all. Maybe these things should be split up to make them more easily tested, and then tie them together at a higher level. For instance, maybe myMethod should be returning something that you can then pass to someClassMethod(), so that there is no state you need to worry about.