Swift: unrecognized selector sent to instance, how to detect null value? - ios

I am trying to use Microsoft Graph iOS SDK and followed the official code sample.
The following code snippet is to get basic profile of all users in the organization:
private func getContactInfo(){
self.graphClient.users().request().getWithCompletion{
(collection: MSCollection?, request:MSGraphUsersCollectionRequest?, error: NSError?) in
if let users = collection {
for user: MSGraphUser in users.value as! [MSGraphUser] {
print(user)
print(String(user.surname.dynamicType))
print(user.mobilePhone)
}
}
}
}
Output Result:
{
businessPhones = (
);
displayName = "Boss";
givenName = "Jack";
id = "30fb78ff-522f-45e7-a9cd-75ba8ee2eca6";
jobTitle = "<null>";
mail = "boss#abc.net";
mobilePhone = "<null>";
officeLocation = "<null>";
preferredLanguage = "<null>";
surname = "\U8a79";
userPrincipalName = "boss#abc.net";
}
ImplicitlyUnwrappedOptional<String>
And runtime exception happened
2016-06-05 17:02:55.302 ABC[76976:915052] -[NSNull _fastCStringContents:]: unrecognized selector sent to instance 0xb59398
2016-06-05 17:02:55.305 ABC[76976:915052] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull _fastCStringContents:]: unrecognized selector sent to instance 0xb59398'
*** First throw call stack:
(
0 CoreFoundation 0x0092c494 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x02640e02 objc_exception_throw + 50
...
When I try to print the null value like mobilePhone, runtime exception occurred. How to detect null value to avoid crashing? Thank you.
Add: MSGraphUser.m snippet used in this case,
#interface MSGraphUser()
{
...
NSString* _mobilePhone;
...
- (NSString*) mobilePhone
{
return self.dictionary[#"mobilePhone"];
}
...

There must be a better swift support, but for now, you could extend the class like what Casey has said.
If you did this, you can check
if user.optMobilePhone is NSNull
The actual extension would look something like this:
extension MSGraphUser {
var optMobilePhone: AnyObject? {
return dictionaryFromItem()["mobilePhone"]
}
}

You can use one way to filter null objects
- (NSString* __nullable) mobilePhone
{
if ([self.dictionary isKindOfClass:[NSString class]])
{
return self.dictionary[#"mobilePhone"];
}
return nil;
}
or
- (NSString* __nullable) mobilePhone
{
if (![self.dictionary isKindOfClass:[NSNull class]]) {
return self.dictionary[#"mobilePhone"];
}
return nil;
}

Many JSON APIs will return null in the JSON document for values that they want to mark explicitely as not present. This is different from keys that will not be reported at all.
The first thing you do is check with the creator of the API or with the documentation how that kind of value should be interpreted. For example, if you ask for the key "user", you might find that the value isn't present at all, that the value is null, or that the value is an empty string. Find out how each value should be interpreted, or if they should be treated the same.
Then since you will need this all the time, you add a function to NSDictionary that will return what you want, and logs things that you didn't expect. You check that a value isn't there at all by checking with if let ... . You check whether a value is null by checking value == NSNull (). And then you make that function return either an optional string or a string.

It looks like mobilePhone can actually return a NSString or NSNull so it should be declared as - (id) mobilePhone. Since it's not, we can extend MSGraphUser
extension MSGraphUser {
var optMobilePhone : String? { return self.dictionary["mobilePhone"] as? String }
}
And then use that computed property to safely access the value:
for obj in users.value {
if let user = obj as? MSGraphUser {
print(user)
print(String(user.surname.dynamicType))
print(user.optMobilePhone ?? "no phone")
}
}

Related

Comparing two dictionary values gives me an NSInvalidArgumentException

I have a for loop that iterates through dictionaries within a dictionary, trying to find one that has a key that matches a key from a completely separate dictionary.
for (id rDCKey in rootDictCopy)
{
tempHouseNumber = rDCKey;
if ([[[rootDictCopy objectForKey:rDCKey] objectForKey:#"RandomUniqueIdentifier"] isEqual:[[routePathRootDictCopy objectForKey:houseNumber] objectForKey:#"RandomUniqueIdentifier"]])
{
NSLog(#"done");
goto DONE;
}
}
When both values are equal to nothing, it's fine, and everything passes. But the moment they have a value (which is always a 256 character long NSString), it crashes, giving me this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa000000333939394'
I have no clue what's up, and any help would be appreciated.
I can give more code if necessary.
Thanks.
Update: I updated my for loop to check for types, but the same problem occurs.
Update 2: Changed || to &&
for (id rDCKey in rootDictCopy)
{
tempHouseNumber = rDCKey;
if ([[rootDictCopy objectForKey:rDCKey] isKindOfClass:[NSMutableDictionary class]] && [[routePathRootDictCopy objectForKey:houseNumber] isKindOfClass:[NSMutableDictionary class]])
{
if ([[[rootDictCopy objectForKey:rDCKey] objectForKey:#"RandomUniqueIdentifier"] isEqual:[[routePathRootDictCopy objectForKey:houseNumber] objectForKey:#"RandomUniqueIdentifier"]])
{
NSLog(#"done");
goto DONE;
}
}
else
{
NSLog(#"ERROR");
}
}
reason: '-[NSTaggedPointerString objectForKey:]: unrecognized
selector sent to instance 0xa000000333939394'
Exception occurred because you called objectForKey method with an object type NSTaggedPointerString
Before compare you should check type of your data.
You can do as below:
if ( [obj isKindOfClass:[NSDictionary class]] ) {
// is a NSDictionary
// do further action like get objectForKey, compare ..
} else {
// you don't got what you want -> print error log or something like that
}
And your code should be like below:
for (id rDCKey in rootDictCopy)
{
tempHouseNumber = rDCKey;
// TODO:
// check if rootDictCopy is a NSDictionary (if needed)
// check if routePathRootDictCopy is a NSDictionary (if needed)
// check if [rootDictCopy objectForKey:rDCKey] is a NSDictionary
// check if [routePathRootDictCopy objectForKey:houseNumber] is a NSDictionary
if ([[[rootDictCopy objectForKey:rDCKey] objectForKey:#"RandomUniqueIdentifier"] isEqual:[[routePathRootDictCopy objectForKey:houseNumber] objectForKey:#"RandomUniqueIdentifier"]])
{
NSLog(#"done");
goto DONE;
}
}
Note: My answer will help your code work without crash but your should find why you got unexpected object type here. And how to sure you alway recevied NSMutableDictionary object, that is the best solution!
As users Nhat Dinh, Amit Tandel and Larme suggested, some dictionaries within rootDictCopy had been transformed into an NSString earlier in the code. I fixed that, making those dictionaries remain dictionaries, and I no longer receive that error. Thanks for everyone's help!

NSData properties cannot be queried over an object link

I have model like this:
class Session: Object {
dynamic var token: NSData?
}
class SessionsPool: Object {
let sessions = List<Session>()
}
I can request all sessions (normally there is one or zero) with required token
let myToken: NSData = ...
let sessions = self.realm.objects(Session).filter("token == %#", myToken)
It works well.
And I would like to request all pools that has at least one session with required token. And I would like to "observe" this request for updates.
let myToken: NSData = ...
var pools = self.realm.objects(SessionsPool).filter("ANY sessions.token == %#", myToken)
pools.addNotificationBlock { (change) in
// Some code
}
But I can't do it due to error:
*** Terminating app due to uncaught exception 'Unsupported operator',
reason: 'NSData properties cannot be queried over an object link.'
So I can use predicates like token == *someNSData* or like ANY sessions.stringToken == *someNSString*, but not like ANY sessions.token == *someNSData*.
What's wrong with NSData?
That particular query type hasn't been implemented yet: https://github.com/realm/realm-cocoa/issues/4222
However, your query can be refactored to something that Realm does support:
Replace this: ANY sessions.token == %#
With this: SUBQUERY(sessions, $session, $session.token == %#).#count > 0

CNContactVCardSerialization.dataWithContacts giving exception

I'm trying to convert a CNContact array to vCard using the method CNContactVCardSerialization.dataWithContacts(). But it is giving me the following error.
2016-07-25 14:05:00.115 AddressBook-ios9[902:28918] Exception writing contacts to vCard (data): A property was not requested when contact was fetched.
I made sure that I'm passing an valid array of CNContacts, but still it is giving this exception. Can anybody guide to me to what I've done wrong?
I'm attaching the source code below.
func getVcardFromSearchingName(name: String) -> NSData? {
do {
if let contacts = searchMultiContacts(name) {
print(contacts)
let vCard = try CNContactVCardSerialization.dataWithContacts(contacts)
return vCard
} else {
return nil
}
} catch {
return nil
}
}
I found out my mistake. On the keys to fetch contact, I was missing CNContactVCardSerialization.descriptorForRequiredKeys(). After adding it, the code is working flawlessly.

API - Microsoft Cognitive Services Face, how to return attributes

In swift I'm using the Microsoft Cognitive Services Face API function detectWithData and trying to use returnFaceAttributes which calls for [AnyObject]!. I need help with what to enter into the Array.
According to this link I assumed ["age", "gender"] would work but I receive an error saying:
unrecognized selector sent to instance 0x7f9b96043df0
And using [MPOFaceAttributeTypeAge, MPOFaceAttributeTypeGender] gives an error:
Value of type 'MPOFaceAttributeTypeAge' does not conform to expected element type 'AnyObject'
For some reason typing "true" in the array give me the age attribute but all other attributes show as nil.
I can't find any examples using swift online. Any advice or pointing me in the right direction would be appreciated.
#IBAction func battleBtn(sender: UIButton){
if !hasChoosenTop || !hasChoosenBottom{
showErrorAlert()
} else{
if let firstImg = topImg.image, let firstImgData = UIImageJPEGRepresentation(firstImg, 0.8), let secondImg = bottomImg.image, let secondImgData = UIImageJPEGRepresentation(secondImg, 0.8){
FaceService.instance.client.detectWithData(firstImgData, returnFaceId: true, returnFaceLandmarks: false, returnFaceAttributes: [MPOFaceAttributeTypeAge, MPOFaceAttributeTypeGender], completionBlock: { (face: [MPOFace]!, err: NSError!) in
if err == nil {
var topFace: String?
topFace = face[0].faceId
var top = face[0].attributes.age
print("my faceId: \(topFace)")
print("my faceId: \(top)")
}
})
}
}
}
screenshot of error
Include an array of Int for [AnyObject]!. For example: [1,4] returns age and smile.

SOAP usage in iOS with touchXML

I'm using a sudz-c generated SOAP framework, my service calls seem to work fine, but when I try doing anything with the data, iOS (emulator) crashes.
This is the service call...
[service hentOpgaveliste:self action:#selector(handleToDoList:) userid:userNameTxt.text pdaid:[pdaIdTxt.text intValue]];
For the handleToDoList: I am using the standard method provided in the examples, which successfully NSLogs my result.
....
CXMLNode *xmlResult = (CXMLNode*)value;
NSLog(#"HentToDo: %#", [xmlResult description]);
....
From here, I get the log you see below.
{
hentOpgavelisteResult = {
diffgram = "<null>";
schema = {
element = {
complexType = {
choice = {
element = {
complexType = {
sequence = {
element = "<null>";
};
};
};
};
};
};
};
};
When I attempt to NSLog the child count as seen below, or any of the other CXMLNode instance method for that matter, I get the following exception.
....
NSLog(#"Children %#", [xmlResult childCount]);
....
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary childCount]: unrecognized selector sent to instance
Not sure where to go from here. I've seen blogs such as this talking about issues with touchXML and namespaces, but it appears to me that I have a namespace.
Any ideas would be appreciated, when it comes to SOAP I'm noob class.
This is a common mistake; when logging integers you should use %i instead of %#, like this:
NSLog(#"Children %i", [xmlResult childCount]);
%# is only for logging objects. If you try to log an integer as an object, you get a crash because it thinks it's a pointer to a random place in memory, and tries to call the description method on it.
Also, from the exception that you're getting, xmlResult is an NSDictionary (CFDictionary is the same thing), in which case the method you should be calling is count, not childCount:
NSLog(#"Children %i", [xmlResult count]);
For printing count..
You should use '%d'
ex:
NSLog(#"Children count = %d", [xmlResult childCount]);

Resources