Mapping XML succeeds but object has empty values - restkit

I'm using RKXMLReaderSerialization and trying to map an XML response from my server to an object. It succeeds, but the object from the mapping result just has empty values.
Here's the text/xml response I'm trying to map from my server:
<Provision version="1.0">
<FileInfoWrapper>
<FileUrl>SomeFile.zip</FileUrl>
<FileName>SomeFile.zip</FileName>
<FileSha1>oi7NK/rFLL6dXqcu7ahaNfKsGkE=</FileSha1>
<FileSize>52220448</FileSize>
<Version>13</Version>
<Vital>true</Vital>
</FileInfoWrapper>
</Provision>
Here's my model object:
#interface FileInfoWrapper : NSObject
#property NSString *fileUrl;
#property NSString *fileName;
#property NSString *fileSha1;
#property long fileSize;
#property NSString *version;
#property BOOL vital;
#end
I've added RKXMLReaderSerialization:
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:RKMIMETypeTextXML];
[[self objectManager] setAcceptHeaderWithMIMEType:#"application/json,text/xml"];
I think I have the mapping and response descriptor setup correctly:
RKObjectMapping *fileInfoMapping = [RKObjectMapping mappingForClass:[FileInfoWrapper class]];
[fileInfoMapping addAttributeMappingsFromDictionary:#{
#"FileUrl": #"fileUrl",
#"FileName": #"fileName",
#"FileSha1": #"fileSha1",
#"FileSize": #"fileSize",
#"Version": #"version",
#"Vital": #"vital"}];
RKResponseDescriptor *fileInfoResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:fileInfoMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"Provision.FileInfoWrapper"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[self objectManager] addResponseDescriptor:fileInfoResponseDescriptor];
But when I call:
[[self objectManager] getObjectsAtPath:#"static/download/Installer.info.xml"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"****Success!****");
NSLog(#"mappingResult: %#", mappingResult);
FileInfoWrapper *fileInfo = [mappingResult firstObject];
NSLog(#"URL: %#", [fileInfo fileUrl]);
NSLog(#"Name: %#", [fileInfo fileName]);
NSLog(#"SHA1: %#", [fileInfo fileSha1]);
NSLog(#"Size: %lx", [fileInfo fileSize]);
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"****Failure!****");
}];
All the values are null:
I restkit.network:RKObjectRequestOperation.m:180 GET 'http://example.com/static/download/Installer.info.xml'
I restkit.network:RKObjectRequestOperation.m:250 GET 'http://example.com/static/download/Installer.info.xml' (200 OK / 1 objects) [request=0.1080s mapping=0.0060s total=0.1226s]
****Success!****
mappingResult: <RKMappingResult: 0x10064b190, results={
"Provision.FileInfoWrapper" = "<FileInfoWrapper: 0x10065f780>";
}>
URL: (null)
Name: (null)
SHA1: (null)
Size: 0
What am I doing wrong?
UPDATE: I turned on RestKit/ObjectMapping logging and got some additional info:
D restkit.object_mapping:RKMapperOperation.m:377 Executing mapping operation for representation: {
Provision = {
FileInfoWrapper = {
FileName = {
text = "SomeFile.zip";
};
FileSha1 = {
text = "oi7NK/rFLL6dXqcu7ahaNfKsGkE=";
};
…
}
and targetObject: (null)
T restkit.object_mapping:RKMapperOperation.m:320 Examining keyPath 'Provision.FileInfoWrapper' for mappable content...
D restkit.object_mapping:RKMapperOperation.m:300 Found mappable data at keyPath 'Provision.FileInfoWrapper': {
FileName = {
text = "SomeFile.zip";
};
FileSha1 = {
text = "oi7NK/rFLL6dXqcu7ahaNfKsGkE=";
};
…
}
D restkit.object_mapping:RKMapperOperation.m:231 Asked to map source object {
FileName = {
text = "SomeFile.zip";
};
FileSha1 = {
text = "oi7NK/rFLL6dXqcu7ahaNfKsGkE=";
};
…
} with mapping <RKObjectMapping:0x10025e6b0 objectClass=FileInfoWrapper propertyMappings=(
"<RKAttributeMapping: 0x10025ebc0 FileSha1 => fileSha1>",
"<RKAttributeMapping: 0x10025ed90 FileName => fileName>"
…
)>
D restkit.object_mapping:RKMappingOperation.m:952 Starting mapping operation...
T restkit.object_mapping:RKMappingOperation.m:953 Performing mapping operation: <RKMappingOperation 0x10068f730> for 'FileInfoWrapper' object. Mapping values from object {
FileName = {
text = "SomeFile.zip";
};
FileSha1 = {
text = "oi7NK/rFLL6dXqcu7ahaNfKsGkE=";
};
…
} to object <FileInfoWrapper: 0x10068f290> with object mapping (null)
T restkit.object_mapping:RKMappingOperation.m:550 Mapping attribute value keyPath 'FileUrl' to 'fileUrl'
T restkit.object_mapping:RKMappingOperation.m:431 Found transformable value at keyPath 'FileUrl'. Transforming from type '__NSDictionaryM' to 'NSString'
D restkit.object_mapping:RKPropertyInspector.m:130 Cached property inspection for Class 'FileInfoWrapper': {
fileName = {
isPrimitive = 0;
keyValueCodingClass = NSString;
name = fileName;
};
fileSha1 = {
isPrimitive = 0;
keyValueCodingClass = NSString;
name = fileSha1;
};
…
}
T restkit.object_mapping:RKMappingOperation.m:583 Skipped mapping of attribute value from keyPath 'FileUrl to keyPath 'fileUrl' -- value is unchanged ((null))
T restkit.object_mapping:RKMappingOperation.m:550 Mapping attribute value keyPath 'FileSha1' to 'fileSha1'
T restkit.object_mapping:RKMappingOperation.m:431 Found transformable value at keyPath 'FileSha1'. Transforming from type '__NSDictionaryM' to 'NSString'
T restkit.object_mapping:RKMappingOperation.m:583 Skipped mapping of attribute value from keyPath 'FileSha1 to keyPath 'fileSha1' -- value is unchanged ((null))
…

It appears that you have to map from the text node. This post had the same problem:
RestKit 0.20-pre3 with RKXMLReaderSerialization and XMLReader
Changing the mapping to:
[fileInfoMapping addAttributeMappingsFromDictionary:#{
#"FileUrl.text": #"fileUrl",
#"FileName.text": #"fileName",
#"FileSha1.text": #"fileSha1",
#"FileSize.text": #"fileSize",
#"Version.text": #"version",
#"Vital.text": #"vital",}];
did it.

You shouldn't set the accept header to JSON if you're expecting XML. I also don't think you can set a comma separated list like that (not 100% sure on that though). If the server sends JSON as a result of your accept header then RestKit will be happy with the response but will likely fail to actually do any mapping (which appears to be what you see).
Turn on trace logging to get more info about the response and the mapping:
RKLogConfigureByName("RestKit/ObjectMapping", RKLogLevelTrace);
Your mapping code does look correct.

Related

How to get an ALAsset URL from a PHAsset?

You can do it sneakily† using the undocumented PHAsset.ALAssetURL property, but I'm looking for something documented.
† In Objective-C, this will help
#interface PHAsset (Sneaky)
#property (nonatomic, readonly) NSURL *ALAssetURL;
#end
Create the assetURL by leveraging the localidentifier of the PHAsset.
Example:
PHAsset.localidentifier returns 91B1C271-C617-49CE-A074-E391BA7F843F/L0/001
Now take the 32 first characters to build the assetURL, like:
assets-library://asset/asset.JPG?id=91B1C271-C617-49CE-A074-E391BA7F843F&ext=JPG
You might change the extension JPG depending on the UTI of the asset (requestImageDataForAsset returns the UTI), but in my testing the extensions of the assetURL seems to be ignored anyhow.
I wanted to be able to get a URL for an asset too. However, I have realised that the localIdentifier can be persisted instead and used to recover the PHAsset.
PHAsset* asset = [PHAsset fetchAssetsWithLocalIdentifiers:#[localIdentifier] options:nil].firstObject;
Legacy asset URLs can be converted using:
PHAsset* legacyAsset = [PHAsset fetchAssetsWithALAssetUrls:#[assetUrl] options:nil].firstObject;
NSString* convertedIdentifier = legacyAsset.localIdentifier;
(before that method gets obsoleted...)
(Thanks holtmann - localIdentifier is hidden away in PHObject.)
Here is working code tested on iOS 11 both simulator and device
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
[result enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
PHAsset *asset = (PHAsset *)obj;
[asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
NSLog(#"URL:%#", contentEditingInput.fullSizeImageURL.absoluteString);
NSString* path = [contentEditingInput.fullSizeImageURL.absoluteString substringFromIndex:7];//screw all the crap of file://
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isExist = [fileManager fileExistsAtPath:path];
if (isExist)
NSLog(#"oh yeah");
else {
NSLog(#"damn");
}
}];
}];
Read the bottom!
The resultHandler for PHImageManager.requestImage returns 2 objects: result and info.
You can get the original filename for the PHAsset (like IMG_1043.JPG) as well as its full path on the filesystem with:
let url = info?["PHImageFileURLKey"] as! URL
This should work right, but for some reason it doesn't. So basically, you have to copy your image to a file then access that then delete it.
The PHImageFileURLKey is usable to get the original file name, but you cannot actually access that file. It probably has to do with the fact that code in the background can access the file while other apps can delete it.
Here is a PHAsset extension written in Swift that will retrieve the URL.
extension PHAsset {
func getURL(completionHandler : #escaping ((_ responseURL : URL?) -> Void)){
if self.mediaType == .image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
self.requestContentEditingInput(with: options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) -> Void in
completionHandler(contentEditingInput!.fullSizeImageURL as URL?)
})
} else if self.mediaType == .video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable : Any]?) -> Void in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl: URL = urlAsset.url as URL
completionHandler(localVideoUrl)
} else {
completionHandler(nil)
}
})
}
}
}

unable to get objects using NSFetchedResultsController with multiple context

I'm trying to use a NSFetchedResultsController with an additional NSManagedObjectContext different from the main MOC that I use in my App. Although I'm able to save and retrieve data with that additional MOC, every time that I try to create a NSFetchedResultsController it returns an object with nil fetchedObjects.
I'm using MagicalRecord, and this is the code that I'm using:
- (id) init
{
if(SINGLETON){
return SINGLETON;
}
if (isFirstAccess) {
[self doesNotRecognizeSelector:_cmd];
}
self = [super init];
if (self) {
NSManagedObjectModel *model = [NSManagedObjectModel MR_newManagedObjectModelNamed:#"ConnectorCache.momd"];
[NSManagedObjectModel MR_setDefaultManagedObjectModel:model];
storeCoordinator = [NSPersistentStoreCoordinator MR_coordinatorWithAutoMigratingSqliteStoreNamed:#"ConnectorCache.sqlite"];
cacheContext = [NSManagedObjectContext MR_contextWithStoreCoordinator:storeCoordinator];
[MagicalRecord setShouldAutoCreateManagedObjectModel:YES];
}
return self;
}
and the code for getting the fetchedResultsController is:
- (NSFetchedResultsController *) fetchedResultsController {
if (_fetchedResultController != nil) {
return _fetchedResultController;
}
NSFetchRequest *request = [Appointment MR_requestAllSortedBy:#"start" ascending:YES inContext:cacheContext];
_fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:cacheContext sectionNameKeyPath:#"day" cacheName:nil];
return _fetchedResultController;
}
If I use the request for fetching the data, it works correctly:
(lldb) po [cacheContext executeFetchRequest:request error:nil];
<_PFBatchFaultingArray 0x7fd9ca576710>(
<Appointment: 0x7fd9ca4c7220> (entity: Appointment; id: 0xd00000000004000a <x-coredata://36BACA76-7C2B-413C-8782-F92BBC7C1AA7/Appointment/p1> ; data: {
attendees = "<relationship fault: 0x7fd9ca518400 'attendees'>";
attendeesOmitted = nil;
created = "2015-03-10 16:41:31 +0000";
creator = nil;
end = "2015-03-18 18:45:00 +0000";
eventDescription = "Doctor Garc\U00eda Villaran";
eventID = "_8gs3ecpi8h0jab9i6op3ib9k6gp3iba18kojiba68d1j8c2260pj4e1o64";
lastUpdated = "2015-03-10 16:42:13 +0000";
location = "Centro M\U00e9dico Quir\U00f3n Sevilla Este, Sevilla, ES";
start = "2015-03-18 17:45:00 +0000";
status = 0;
title = "Cita otorrino";
})
)
I cannot see what I'm missing…
Kind regards
I was my fault, what I missed is the fact that I'm using MagicalRecord which actually provides a method for getting a NSFetchedResultsController.
NSManageObject MR_fetchAll… or friends will do the job. In my case I used:
_fetchedResultController = [Appointment MR_fetchAllSortedBy:#"start"
ascending:YES
withPredicate:nil
groupBy:#"day"
delegate:nil
inContext:cacheContext];

Error with 'getProfilePicture'

I have a profile view controller where the user can set or change his or her profile picture but am getting constant errors that do not make sense to me.
-(void)getProfilePicture
{
PFUser *user = [PFUser currentUser];
NSLog(#"file--%#",[user objectForKey:PF_USER_PICTURE]);
userName = user[PF_USER_USERNAME];
status = user[PF_USER_STATUS];
if ([user objectForKey:PF_USER_PICTURE]) {
[imageUser setFile:[user objectForKey:PF_USER_PICTURE]];
[imageUser loadInBackground];
}
else{
imageUser.image = [UIImage imageNamed:#"blank_profile#2x.png"];
}
// fieldName.text = user[PF_USER_FULLNAME];
}
#pragma mark - UIActionSheetDelegate
I receive the following errors portrayed in my ProfileViewController.m (I can provide/add .h if needed):
"No visible #interface for 'UIImageView' declares the selector 'setFile:'
"No visible #interface for 'UIImageView' declares the selector 'loadInBackground'
Any help would be much appreciated or any supporting code thats needed, thanks.
Try this and see what it does for you:
-(void)getProfilePicture
{
PFUser *user = [PFUser currentUser];
NSLog(#"file--%#",[user objectForKey:PF_USER_PICTURE]);
userName = user[PF_USER_USERNAME];
status = user[PF_USER_STATUS];
if ([user objectForKey:PF_USER_PICTURE]) {
userImage.file = [user objectForKey:PF_USER_PICTURE];
[userImage loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
imageUser.image = image;
[imageUser setNeedsDisplay];
} else {
NSLog(#"%#", error);
}
}];
}
else{
imageUser.image = [UIImage imageNamed:#"blank_profile#2x.png"];
}
// fieldName.text = user[PF_USER_FULLNAME];
}
#pragma mark - UIActionSheetDelegate
This is assuming your userImage is a PFImageView and [user objectForKey:PF_USER_PICTURE] is a PFFile.
EDIT
Actually, looking at your error it seems that userImage is just a UIImageView not a PFImageView, which might be the source of all your problems. Try making the userImage a PFImageView

NSFormatter show error

I made a custom NSFormatter subclass and want to return an error message
however assigning the error and returning valid = NO doesn't do the trick (no error is shown)
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr
proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
originalString:(NSString *)origString
originalSelectedRange:(NSRange)origSelRange
errorDescription:(NSString **)error
{
BOOL valid = YES;
NSString *proposedString = *partialStringPtr;
if ([proposedString length] < self.minLength) {
*error = #"TOO SHORT";
valid = NO;
}
return valid;
}
I don't use bindings
I think that you problem because NSControl (which validates user input) don't has delegate with implemented method (dcumentation):
control:didFailToValidatePartialString:errorDescription:
If you want that #"TOO SHORT" displays in text field instead of user inputed string you can return #"TOO SHORT" in newString of method:
- (BOOL)isPartialStringValid:(NSString *)partialString
newEditingString:(NSString **)newString
errorDescription:(NSString **)error

Get the Username(s) stored in Keychain, using only the ServiceName? OR: Where are you supposed to store the Username?

So the OS X Keychain has three pieces of information:
ServiceName (the name of my app)
Username
Password
I obviously always know the ServiceName. Is there a way to find any saved Username(s) for that ServiceName? (Finding the password is easy once you know the Username.)
I would much prefer to use a nice Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires the UserName to get any keychain item!
+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;
How are you expected to fully utilize saving credentials in the Keychain, if you need the Username to find the credentials? Is the best practice to save the Username in the .plist file or something?
SecKeychainFindGenericPassword only returns a single keychain item. To find all generic passwords for a specific service, you need to run a query on the keychain. There are several ways to do this, based on what version of OS X you target.
If you need to run on 10.5 or below, you'll need to use SecKeychainSearchCreateFromAttributes. It's a rather horrible API. Here is a rough cut of a method that returns a dictionary mapping usernames to passwords.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
OSStatus status;
// Construct a query.
const char *utf8Service = [service UTF8String];
SecKeychainAttribute attr = { .tag = kSecServiceItemAttr,
.length = strlen(utf8Service),
.data = (void *)utf8Service };
SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
SecKeychainSearchRef *search = NULL;
status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
if (status) {
report(status);
return nil;
}
// Enumerate results.
NSMutableDictionary *result = [NSMutableDictionary dictionary];
while (1) {
SecKeychainItemRef item = NULL;
status = SecKeychainSearchCopyNext(search, &item);
if (status)
break;
// Find 'account' attribute and password value.
UInt32 tag = kSecAccountItemAttr;
UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
SecKeychainAttributeList *attrList = NULL;
UInt32 length = 0;
void *data = NULL;
status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
if (status) {
CFRelease(item);
continue;
}
NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, #"SecKeychainItemCopyAttributesAndData is messing with us");
NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
[result setObject:password forKey:account];
SecKeychainItemFreeAttributesAndData(attrList, data);
CFRelease(item);
}
CFRelease(search);
return result;
}
For 10.6 and later, you can use the somewhat less inconvenient SecItemCopyMatching API:
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
(id)kCFBooleanTrue, kSecReturnData,
(id)kCFBooleanTrue, kSecReturnAttributes,
kSecMatchLimitAll, kSecMatchLimit,
service, kSecAttrService,
nil];
NSArray *itemDicts = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
if (status) {
report(status);
return nil;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSDictionary *itemDict in itemDicts) {
NSData *data = [itemDict objectForKey:kSecValueData];
NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *account = [itemDict objectForKey:kSecAttrAccount];
[result setObject:password forKey:account];
}
[itemDicts release];
return result;
}
For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It doesn't support building attribute-based queries, but you can simply list all passwords and filter out the ones you don't need.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (LKKCGenericPassword *item in [keychain genericPasswords]) {
if ([service isEqualToString:item.service]) {
[result setObject:item.password forKey:item.account];
}
}
return result;
}
(I didn't try running, or even compiling any of the above code samples; sorry for any typos.)
You don't need the username. You do with EMKeychain, but that's an artificial distinction that that class imposes; the underlying Keychain Services function does not require a username to find a keychain item.
When using SecKeychainFindGenericPassword directly, pass 0 and NULL for the username parameters. It will return a keychain item that exists on that service.
However, that will return only one item. If the user has multiple keychain items on the same service, you won't know that, or which one you got (the documentation says it returns the “first” matching item, with no specification of what it considers “first”). If you want any and all items for that service, you should create a search and use that.
Generic passwords have a unique key of the service name and the username. Thus, to fetch a single generic keychain entry, you will need to provide both. However, you can iterate over all generic keychain entries for your given service using the SecKeychainFindGenericPassword function.
(Disclaimer: I don't know anything about doing this in EMKeychain.)

Resources