Manually load objects using RestKit into Core Data using JSON string - iphone

Using the development branch of restkit (0.20), is there a way to perform mapping on a JSON string into the core data object store?
I know in 0.10, the method described at the bottom of this page worked but how is this operation performed in restkit 0.20? Any guidance appreciated! Thanks!
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:MIMEType];
id parsedData = [parser objectFromString:JSONString error:&error];
RKObjectMappingProvider* mappingProvider = [RKObjectManager sharedManager].mappingProvider;
RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
RKObjectMappingResult* result = [mapper performMapping];

I installed RESTKit v0.20.0pre4 yesterday and had the same need for one particular case.
Here is an example of the JSON String I want to map:
{"info":"the sun is shining","detail":"in Bordeaux ~ 29 °C"}
This is how I perform the mapping.
- (InfoRESTMapped *)mapInfoFromJSONString:(NSString *) JSONString{
NSString* MIMEType = #"application/json";
NSError* error;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&error];
if (parsedData == nil && error) {
//deal with error
}
RKObjectMapping *infoMapping = [RKObjectMapping requestMapping];
[infoMapping addAttributeMappingsFromDictionary:#{
#"info": #"myInfo",
#"detail": #"myDetail",
}];
InfoRESTMapped *infoMapped = [[InfoRESTMapped alloc] init];
RKMappingOperation* mapper = [[RKMappingOperation alloc] initWithSourceObject:parsedData destinationObject:infoMapped mapping:infoMapping];
[mapper performMapping:&error];
return infoMapped;
}
These posts led me to the solution I have suggested:
https://github.com/RestKit/RestKit/issues/999
https://groups.google.com/d/topic/restkit/M1d7WwcFBDA/discussion
I hope it helps. I guess some other will give better solutions when the final version will be released (with better documentation too...).

For Restkit 0.22, You can use this code. This returns an RKMappingResult wherein you can enumerate the objects after mapping using the property .array.
- (RKMappingResult *)mapJSONStringWithString:(NSString *)jsonString
{
RKMappingResult *result = nil;
NSError* error;
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
if (parsedData == nil && error) {
NSLog(#"json mapping error");
}
NSDictionary *mappingsDictionary = #{#"":[CustomMappingClass getMappingForUsers]};
ObjectClass *obj = [ObjectClass new];
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
NSError *mappingError = nil;
mapper.targetObject = obj;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) {
result = [mapper mappingResult];
}
return result;
}

The most straightforward way to import JSON into your Core Data store using RestKit is documented in the Read Me "Generate a Seed Database"
Here is what I wrote in Swift:
if let persistentStore = RKManagedObjectStore.default().persistentStoreCoordinator.persistentStores.first {
if let objectImporter = RKManagedObjectImporter.init(persistentStore: persistentStore) {
let path = Bundle.main.path(forResource: jsonFile, ofType: "json")
let mapping = SLTour.responseMapping()
var error:NSError?
objectImporter.importObjectsFromItem(atPath: path, with: mapping, keyPath: "data", error: &error)
try objectImporter.finishImporting()
objectImporter.logSeedingInfo()
}
}
Hopefully, this will save somebody from several hours of trying to solve this in the future.

Related

google places api - json return correct data and then an exception occurs

I'm using google places API,
The json return valid results, In my async methods I'm setting markers per each result, the method ends and all of a sudden i get the following thread 1: EXC_BAD_ACCESS code:2 address:0x9243e8, in the output window i get the following: [71539:c07] -[__NSCFString CGImage]: unrecognized selector sent to instance 0xb19dd10
I have no clue how to debug this, or where to look for the problem, all my parameters look good.
thanks
I suspect what you are doing is as a commenter says, treating the returned JSON object as a type it is not. Specifically, from your error, you are treating a string like an image. Generally, JSON doesn't not contain actually images, so when you deserialize it, you will not get images. In the Google Places API, it does not send you images. Instead, it send you URLs to images. You will have to download the images using the URLs you get from the Places API.
Here is some code I use. This method gets the place list from the API based on a CLLocation and returns it as a much more easily digested NSArray of NSDictionary objects each containing the important met-data pertaining to a place:
-(NSArray*)getPlaceListForLocation:(CLLocation*)inLocation {
NSString* requestStr = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=%#&location=%f,%f&radius=100&sensor=true",self.googleAPIKey,inLocation.coordinate.latitude,inLocation.coordinate.longitude];
NSURL* requestURL = [NSURL URLWithString:requestStr];
NSLog(#"Fetching place data with URL = %#", requestURL);
NSData* requestData = [NSData dataWithContentsOfURL:requestURL];
if ( nil != requestData ) {
NSError* jsonError = nil;
id resultsObj = [NSJSONSerialization JSONObjectWithData:requestData options:0 error:&jsonError];
if ( nil == resultsObj ) {
if ( nil != jsonError ) {
NSLog(#"%#",jsonError.description);
}
return nil;
}
NSArray* itemList = [resultsObj objectForKey:#"results"];
if ( nil == itemList || itemList.count == 0) {
return nil;
}
NSMutableArray* placeList = [NSMutableArray arrayWithCapacity:itemList.count];
for ( NSDictionary* item in itemList ) {
NSString* name = [item objectForKey:#"name"];
NSString* iconURL = [item objectForKey:#"icon"];
NSString* vicinity = [item objectForKey:#"vicinity"];
NSDictionary* geometryDict = [item objectForKey:#"geometry"];
NSDictionary* locationDict = [geometryDict objectForKey:#"location"];
NSMutableDictionary* itemDict = [NSMutableDictionary dictionaryWithCapacity:2];
if ( nil != name ) {
[itemDict setObject:name forKey:#"name"];
}
if (nil != iconURL ) {
[itemDict setObject:iconURL forKey:#"iconurl"];
}
if ( nil != vicinity ) {
[itemDict setObject:vicinity forKey:#"vicinity"];
}
if ( nil != locationDict ) {
NSString* latitudeStr = [locationDict objectForKey:#"lat"];
NSString* longitudeStr = [locationDict objectForKey:#"lng"];
double latitude = [latitudeStr doubleValue];
double longitude = [longitudeStr doubleValue];
CLLocation* placeLoc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
CLLocationDistance distance = [inLocation distanceFromLocation:placeLoc];
[itemDict setObject:[NSNumber numberWithDouble:distance] forKey:#"distance"];
}
[placeList addObject:itemDict];
}
return placeList;
}
return nil;
}
Then I display these in a table view using the returned NSArray as the data source. In order to display the icon, I use something like MKNetworkImageView in order to fetch the icon based on the URL.

Core Data optimization

I have a large list of Users in an NSDictionary that has a structure like this:
97 = {
birthday = "";
gender = Unspecified;
image = {
status = Prepared;
type = Image;
};
"name_display" = "Facebook User";
"name_first" = Facebook;
"name_last" = User;
type = Contact;
"user_id" = 97;
};
98 = {
birthday = "";
gender = Unspecified;
image = {
status = Prepared;
type = Image;
};
"name_display" = "Facebook User";
"name_first" = Facebook;
"name_last" = User;
type = Contact;
"user_id" = 98
}
I want to input this data into Core Data. First I must check if the user already exists in core data. If so, update that user. Otherwise create a new user. The way I am doing it works, but it is extremely slow. Here's my code:
NSDictionary *users = [responseData objectForKey:#"users"];
if (users) {
for (id userKey in [users allKeys]) {
NSDictionary *contactDictionary = [users objectForKey:userKey];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userID == %#", userKey];
NSUInteger count = [CoreDataHelper countForEntity:#"Contact" withPredicate:predicate andContext:[FSAppDelegate managedObjectContext]];
if (count > 0) {
NSMutableArray *existingContactArray = [CoreDataHelper searchObjectsForEntity:#"Contact" withPredicate:predicate andSortKey:nil andSortAscending:NO andContext:[FSAppDelegate managedObjectContext]];
Contact *existingContact = [existingContactArray objectAtIndex:0];
[CoreDataHelper updateContactWithDictionary:contactDictionary forContactObject:existingContact];
}
else {
Contact *newContact = (Contact*)[NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:[FSAppDelegate managedObjectContext]];
[CoreDataHelper updateContactWithDictionary:contactDictionary forContactObject:newContact];
}
}
NSError *error;
if (![[FSAppDelegate managedObjectContext] save:&error]) {
// Handle the error.
NSLog(#"error saving to db - fsrequest class.");
}
}
And here is my method to update the contact
+(BOOL)updateContactWithDictionary:(NSDictionary*)changedContact forContactObject:(Contact*)contact {
NSString *bday = [changedContact valueForKey:#"birthday"];
NSString *gender = [changedContact valueForKey:#"gender"];
NSString *nameDisplay = [changedContact valueForKey:#"name_display"];
NSString *nameFirst = [changedContact valueForKey:#"name_first"];
NSString *nameLast = [changedContact valueForKey:#"name_last"];
NSString *type = [changedContact valueForKey:#"type"];
NSString *userID = [NSString stringWithFormat:#"%#",[changedContact valueForKey:#"user_id"]];
NSString *imageStatus = [[changedContact objectForKey:#"image"]objectForKey:#"status"];
NSString *imageType = [[changedContact objectForKey:#"image"]objectForKey:#"type"];
NSString *imageURL = [[changedContact objectForKey:#"image"]objectForKey:#"url"];
NSString *imageThumb = [[changedContact objectForKey:#"image"]objectForKey:#"url_thumb"];
NSString *locationName = [[changedContact objectForKey:#"location"]objectForKey:#"name"];
[contact setBirthday:bday];
[contact setGender:gender];
[contact setNameDisplay:nameDisplay];
[contact setNameFirst:nameFirst];
[contact setNameLast:nameLast];
[contact setType:type];
[contact setUserID:userID];
[contact setImageStatus:imageStatus];
[contact setImageType:imageType];
if (imageURL && !((NSNull *)imageURL == [NSNull null])) {
[contact setImageURL:imageURL];
}
if (imageThumb && !((NSNull *)imageThumb == [NSNull null])) {
[contact setImageThumbURL:imageThumb];
}
if (locationName && !((NSNull *)locationName == [NSNull null])) {
[contact setLocationName:locationName];
}
return YES;
}
Can someone give me an example of how I would do this in a much faster way? Some people have mentioned some ideas, but I need to see it to understand. Thanks!
First of all I'd move save: outside the loop. Replace:
// save core data
NSError *error;
if (![[FSAppDelegate managedObjectContext] save:&error]) {
// Handle the error.
NSLog(#"error saving to db - fsrequest class.");
}
}
}
with
}
// save core data
NSError *error;
if (![[FSAppDelegate managedObjectContext] save:&error]) {
// Handle the error.
NSLog(#"error saving to db - fsrequest class.");
}
}
Also, do you have some default values for imageURL, imageThumb and locationName defined in Core Data model? If no, why do you check for nulls (twice)?
Bonus:
It may be a good idea to eliminate countForEntity:withPredicate:andContext: call, like this:
NSMutableArray *existingContactArray = [CoreDataHelper searchObjectsForEntity:#"Contact" withPredicate:predicate andSortKey:nil andSortAscending:NO andContext:[FSAppDelegate managedObjectContext]];
if ([existingContactArray count] > 0)
{
Contact *existingContact = [existingContactArray objectAtIndex:0];
[CoreDataHelper updateContactWithDictionary:contactDictionary forContactObject:existingContact];
}
You need to understand that a fetch request is expensive (it needs to run SQL and do I/O). I'm not sure where your CoreDataHelper comes from, but it will do some sort of NSFetchRequest which is expensive. You can construct a single NSFetchRequest which will tell you which objects already exists. That will reduce the cost from O(N) to O(1).
[request setPredicate:[NSPredicate predicateWithFormat:#"userID IN %#", allKeys]];
And, as noted above, move the saves out of the loop. But if you're updating adding a many objects, you might want to save every now and then.

Deserializing local NSString of JSON into objects via RestKit (no network download)

Is it possible to deserialize an NSString of JSON into objects via RestKit? I checked the API list here and could not find something that would serve for this purpose. The closest I could find are the various parser classes that return NSDictionary after parsing the input. I assume RestKit uses these parsers after downloading the response so my thinking is that the functionality is available somewhere in RestKit but not exposed publicly.
If I am not missing anything and this functionality is not exposed, what would be the alternatives? Two obvious ones do not look very promising: Get the resulting NSDictionary and try to deserialize myself (effectively reimplementing RestKit) or try to dive into RestKit source and see if this can be somehow exposed (looks tedious and error prone).
Thanks in advance for any help.
PS: The idea is that a string property on a deserialized object is actually the JSON representation of another set of objects (embedded JSON in a sense) and it is deserialized on demand during runtime.
Pretty "simple":
NSString *stringJSON;
...
RKJSONParserJSONKit *parser;
NSError *error= nil;
parser= [[[RKJSONParserJSONKit alloc] init] autorelease];
MyManagedObject *target;
target= [MyManagedObject object];
NSDictionary *objectAsDictionary;
RKObjectMapper* mapper;
objectAsDictionary= [parser objectFromString:stringJSON error:&error];
mapper = [RKObjectMapper mapperWithObject:objectAsDictionary
mappingProvider:[RKObjectManager sharedManager].mappingProvider];
mapper.targetObject = target;
RKObjectMappingResult* result = [mapper performMapping];
NSLog(#"%#", [result asObject]);
As of RestKit 0.20.0-pre2
NSString* JSONString = #"{ \"name\": \"The name\", \"number\": 12345}";
NSString* MIMEType = #"application/json";
NSError* error;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&error];
if (parsedData == nil && error) {
// Parser error...
}
AppUser *appUser = [[AppUser alloc] init];
NSDictionary *mappingsDictionary = #{ #"someKeyPath": someMapping };
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
mapper.targetObject = appUser;
NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) {
// Yay! Mapping finished successfully
NSLog(#"mapper: %#", [mapper representation]);
NSLog(#"firstname is %#", appUser.firstName);
}
This works for Restkit 0.21.0:
NSString* jsonFilePath = [[NSBundle mainBundle] pathForResource:#"fileName"
ofType:#"json"];
NSString* JSONString = [NSString stringWithContentsOfFile:jsonFilePath
encoding:NSUTF8StringEncoding
error:NULL];
NSError* error;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
if (parsedData == nil && error) {
// Parser error...
}
//_objectManager is RKObjectManager instance
NSMutableDictionary *mappingsDictionary = [[NSMutableDictionary alloc] init];
for (RKResponseDescriptor *descriptor in _objectManager.responseDescriptors) {
[mappingsDictionary setObject:descriptor.mapping forKey:descriptor.keyPath];
}
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) {
NSLog(#"result %#",[mapper mappingResult]);
}
This works for Restkit 0.20, using Core Data Entities. It is based in the solution given by #innerself
NSString* jsonFilePath = [[NSBundle mainBundle] pathForResource:#"info-base"
ofType:#"json"];
NSString* JSONString = [NSString stringWithContentsOfFile:jsonFilePath
encoding:NSUTF8StringEncoding
error:NULL];
NSError *error = nil;
NSData *data = [JSONString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
if (parsedData == nil && error) {
// Parser error...
NSLog(#"parse error");
}
//_objectManager is RKObjectManager instance
NSMutableDictionary *mappingsDictionary = [[NSMutableDictionary alloc] init];
for (RKResponseDescriptor *descriptor in [RKObjectManager sharedManager].responseDescriptors) {
[mappingsDictionary setObject:descriptor.mapping forKey:descriptor.keyPath];
}
RKManagedObjectMappingOperationDataSource *datasource = [[RKManagedObjectMappingOperationDataSource alloc]
initWithManagedObjectContext:[RKManagedObjectStore defaultStore].persistentStoreManagedObjectContext
cache:[RKManagedObjectStore defaultStore].managedObjectCache];
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData
mappingsDictionary:mappingsDictionary];
[mapper setMappingOperationDataSource:datasource];
NSError *mappingError = nil;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) {
// data is in [mapper mappingResult]
}
You can see how RestKit does this internally in the RKManagedObjectResponseMapperOperation class.
There are three stages of this operation.
The first is to parse the JSON string into NSDictionarys, NSArrays, etc. This is the easiest part.
id parsedData = [RKMIMETypeSerialization objectFromData:data
MIMEType:RKMIMETypeJSON
error:error];
Next you need to run a mapping operation to convert this data into your NSManagedObjects. This is a bit more involved.
__block NSError *blockError = nil;
__block RKMappingResult *mappingResult = nil;
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
operationQueue.maxConcurrentOperationCount = 1;
[[RKObjectManager sharedManager].managedObjectStore.persistentStoreManagedObjectContext performBlockAndWait:^{
Remember to replace this dictionary with your own mappings. The key [NSNull null] maps this object from the root.
NSDictionary *mappings = #{[NSNull null]: [jotOfflineRequestStatus mapping]};
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData
mappingsDictionary:mappings];
RKManagedObjectMappingOperationDataSource *dataSource = [[RKManagedObjectMappingOperationDataSource alloc]
initWithManagedObjectContext:[RKManagedObjectStore defaultStore].persistentStoreManagedObjectContext
cache:[RKManagedObjectStore defaultStore].managedObjectCache];
dataSource.operationQueue = operationQueue;
dataSource.parentOperation = mapper;
mapper.mappingOperationDataSource = dataSource;
[mapper start];
blockError = mapper.error;
mappingResult = mapper.mappingResult;
}];
You now need to run the tasks that have been put into the operationQueue we created. It is at this stage that connections to existing NSManagedObjects are made.
if ([operationQueue operationCount]) {
[operationQueue waitUntilAllOperationsAreFinished];
}
A more iOS 5+ oriented answer:
NSString* JSONString = jsonString;
NSString* MIMEType = #"application/json";
NSError* error = nil;
id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType:MIMEType];
id parsedData = [parser objectFromString:JSONString error:&error];
if (parsedData == nil && error) {
NSLog(#"ERROR: JSON parsing error");
}
RKObjectMappingProvider* mappingProvider = [RKObjectManager sharedManager].mappingProvider;
RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
RKObjectMappingResult* result = [mapper performMapping];
if (result) {
NSArray *resultArray = result.asCollection;
MyObject *object = [resultArray lastObject];
NSLog(#"My Object: %#", object);
}
For Restkit 0.22, You can use this code. This returns an RKMappingResult wherein you can enumerate the objects after mapping using the property .array.
- (RKMappingResult *)mapJSONStringWithString:(NSString *)jsonString
{
RKMappingResult *result = nil;
NSError* error;
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:RKMIMETypeJSON error:&error];
if (parsedData == nil && error) {
NSLog(#"json mapping error");
}
NSDictionary *mappingsDictionary = #{#"":[CustomMappingClass getMappingForUsers]};
ObjectClass *obj = [ObjectClass new];
RKMapperOperation *mapper = [[RKMapperOperation alloc] initWithRepresentation:parsedData mappingsDictionary:mappingsDictionary];
NSError *mappingError = nil;
mapper.targetObject = obj;
BOOL isMapped = [mapper execute:&mappingError];
if (isMapped && !mappingError) {
result = [mapper mappingResult];
}
return result;
}
Is this not what you're looking for? http://restkit.org/api/0.10.0/Classes/RKJSONParserJSONKit.html
Judging by the views without any answers, it seems this facility does not exist in RestKit yet. Instead of spending more time trying to figure out how to do the mapping, I wrote my own mapper using the output of JsonKit parser and removed the dependency on RestKit (used the builtin classes for network activity). Right now my mapper is not generic (it has a few dependencies on how the objects are laid out and their names in json) but it works for the purposes of the project. I might come back later and turn it into a more generic object mapping library later on.
EDIT: This was selected answer because there was no other answer as of this answer's date (Jan 21, 2012). Since then, I stopped working on iOS and never visited this question again. Now I am selecting Ludovic's answer because of another user's comment and the upvotes for that answer.

Getting video from ALAsset

Using the new asset library framework available in iOS 4 i see that I can get the url for a given video using the UIImagePickerControllerReferenceURL. The url returned is in the following format:
assets-library://asset/asset.M4V?id=1000000004&ext=M4V
I am trying to upload this video to a website so as a quick proof of concept I am trying the following
NSData *data = [NSData dataWithContentsOfURL:videourl];
[data writeToFile:tmpfile atomically:NO];
Data is never initialized in this case. Has anyone managed to access the url directly via the new assets library? Thanks for your help.
I use the following category on ALAsset:
static const NSUInteger BufferSize = 1024*1024;
#implementation ALAsset (Export)
- (BOOL) exportDataToURL: (NSURL*) fileURL error: (NSError**) error
{
[[NSFileManager defaultManager] createFileAtPath:[fileURL path] contents:nil attributes:nil];
NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:fileURL error:error];
if (!handle) {
return NO;
}
ALAssetRepresentation *rep = [self defaultRepresentation];
uint8_t *buffer = calloc(BufferSize, sizeof(*buffer));
NSUInteger offset = 0, bytesRead = 0;
do {
#try {
bytesRead = [rep getBytes:buffer fromOffset:offset length:BufferSize error:error];
[handle writeData:[NSData dataWithBytesNoCopy:buffer length:bytesRead freeWhenDone:NO]];
offset += bytesRead;
} #catch (NSException *exception) {
free(buffer);
return NO;
}
} while (bytesRead > 0);
free(buffer);
return YES;
}
#end
This is not the best way to do this. I am answering this question in case another SO user comes across the same issue.
Basically my need was to be able to spool the video file to a tmp file so I can upload it to a website using ASIHTTPFormDataRequest. There is probably a way of streaming from the asset url to the ASIHTTPFormDataRequest upload but I could not figure it out. Instead I wrote the following function to drop the file to a tmp file to add to ASIHTTPFormDataRequest.
+(NSString*) videoAssetURLToTempFile:(NSURL*)url
{
NSString * surl = [url absoluteString];
NSString * ext = [surl substringFromIndex:[surl rangeOfString:#"ext="].location + 4];
NSTimeInterval ti = [[NSDate date]timeIntervalSinceReferenceDate];
NSString * filename = [NSString stringWithFormat: #"%f.%#",ti,ext];
NSString * tmpfile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation * rep = [myasset defaultRepresentation];
NSUInteger size = [rep size];
const int bufferSize = 8192;
NSLog(#"Writing to %#",tmpfile);
FILE* f = fopen([tmpfile cStringUsingEncoding:1], "wb+");
if (f == NULL) {
NSLog(#"Can not create tmp file.");
return;
}
Byte * buffer = (Byte*)malloc(bufferSize);
int read = 0, offset = 0, written = 0;
NSError* err;
if (size != 0) {
do {
read = [rep getBytes:buffer
fromOffset:offset
length:bufferSize
error:&err];
written = fwrite(buffer, sizeof(char), read, f);
offset += read;
} while (read != 0);
}
fclose(f);
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can not get asset - %#",[myerror localizedDescription]);
};
if(url)
{
ALAssetsLibrary* assetslibrary = [[[ALAssetsLibrary alloc] init] autorelease];
[assetslibrary assetForURL:url
resultBlock:resultblock
failureBlock:failureblock];
}
return tmpfile;
}
Here is a clean swift solution to get videos as NSData.
It uses the Photos framework as ALAssetLibrary is deprecated as of iOS9:
IMPORTANT
The Assets Library framework is deprecated as of iOS 9.0. Instead, use the Photos framework instead, which in iOS 8.0 and later provides more features and better performance for working with a user’s photo library. For more information, see Photos Framework Reference.
import Photos
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
self.dismissViewControllerAnimated(true, completion: nil)
if let referenceURL = info[UIImagePickerControllerReferenceURL] as? NSURL {
let fetchResult = PHAsset.fetchAssetsWithALAssetURLs([referenceURL], options: nil)
if let phAsset = fetchResult.firstObject as? PHAsset {
PHImageManager.defaultManager().requestAVAssetForVideo(phAsset, options: PHVideoRequestOptions(), resultHandler: { (asset, audioMix, info) -> Void in
if let asset = asset as? AVURLAsset {
let videoData = NSData(contentsOfURL: asset.URL)
// optionally, write the video to the temp directory
let videoPath = NSTemporaryDirectory() + "tmpMovie.MOV"
let videoURL = NSURL(fileURLWithPath: videoPath)
let writeResult = videoData?.writeToURL(videoURL, atomically: true)
if let writeResult = writeResult where writeResult {
print("success")
}
else {
print("failure")
}
}
})
}
}
}
There you go...
AVAssetExportSession* m_session=nil;
-(void)export:(ALAsset*)asset withHandler:(void (^)(NSURL* url, NSError* error))handler
{
ALAssetRepresentation* representation=asset.defaultRepresentation;
m_session=[AVAssetExportSession exportSessionWithAsset:[AVURLAsset URLAssetWithURL:representation.url options:nil] presetName:AVAssetExportPresetPassthrough];
m_session.outputFileType=AVFileTypeQuickTimeMovie;
m_session.outputURL=[NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%f.mov",[NSDate timeIntervalSinceReferenceDate]]]];
[m_session exportAsynchronouslyWithCompletionHandler:^
{
if (m_session.status!=AVAssetExportSessionStatusCompleted)
{
NSError* error=m_session.error;
m_session=nil;
handler(nil,error);
return;
}
NSURL* url=m_session.outputURL;
m_session=nil;
handler(url,nil);
}];
}
You can use a different preset key if you wish to re-encode the movie (AVAssetExportPresetMediumQuality for example)
Here is the Objective C solution of Alonzo answer, Using photos framework
-(NSURL*)createVideoCopyFromReferenceUrl:(NSURL*)inputUrlFromVideoPicker{
NSURL __block *videoURL;
PHFetchResult *phAssetFetchResult = [PHAsset fetchAssetsWithALAssetURLs:#[inputUrlFromVideoPicker ] options:nil];
PHAsset *phAsset = [phAssetFetchResult firstObject];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:nil resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
NSURL *url = [(AVURLAsset *)asset URL];
NSLog(#"Final URL %#",url);
NSData *videoData = [NSData dataWithContentsOfURL:url];
// optionally, write the video to the temp directory
NSString *videoPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"%f.mp4",[NSDate timeIntervalSinceReferenceDate]]];
videoURL = [NSURL fileURLWithPath:videoPath];
BOOL writeResult = [videoData writeToURL:videoURL atomically:true];
if(writeResult) {
NSLog(#"video success");
}
else {
NSLog(#"video failure");
}
dispatch_group_leave(group);
// use URL to get file content
}
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return videoURL;
}
this from Zoul's Answer
thanks
Similar Code in Xamarin C#
Xamarin C# Equivalent
IntPtr buffer = CFAllocator.Malloc.Allocate(representation.Size);
NSError error;
nuint buffered = representation.GetBytes(buffer, Convert.ToInt64(0.0),Convert.ToUInt32(representation.Size),out error);
NSData sourceData = NSData.FromBytesNoCopy(buffer,buffered,true);
NSFileManager fileManager = NSFileManager.DefaultManager;
NSFileAttributes attr = NSFileAttributes.FromDictionary(NSDictionary.FromFile(outputPath));
fileManager.CreateFile(outputPath, sourceData,attr);

Parse NSURL query property

I have a URL like myApp://action/1?parameter=2&secondparameter=3
With the property query I get following part of my URL
parameter=2&secondparameter=3
Is there any way easy to put this in a NSDictionary or an Array?
Thx a lot
You can use queryItems in URLComponents.
When you get this property’s value, the NSURLComponents class parses the query string and returns an array of NSURLQueryItem objects, each of which represents a single key-value pair, in the order in which they appear in the original query string.
Swift
let url = "http://example.com?param1=value1&param2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)
Alternatively, you can add an extension on URL to make things easier.
extension URL {
var queryParameters: QueryParameters { return QueryParameters(url: self) }
}
class QueryParameters {
let queryItems: [URLQueryItem]
init(url: URL?) {
queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
print(queryItems)
}
subscript(name: String) -> String? {
return queryItems.first(where: { $0.name == name })?.value
}
}
You can then access the parameter by its name.
let url = "http://example.com?param1=value1&param2=param2"
print(url.queryParameters["param1"])
I had reason to write some extensions for this behavior that might come in handy. First the header:
#import <Foundation/Foundation.h>
#interface NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat;
- (NSString *)stringByEncodingURLFormat;
- (NSMutableDictionary *)dictionaryFromQueryComponents;
#end
#interface NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents;
#end
#interface NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents;
#end
These methods extend NSString, NSURL, and NSDictionary, to allow you to convert to and from query components strings and dictionary objects containing the results.
Now the related .m code:
#import "XQueryComponents.h"
#implementation NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat
{
NSString *result = [self stringByReplacingOccurrencesOfString:#"+" withString:#" "];
result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
- (NSString *)stringByEncodingURLFormat
{
NSString *result = [self stringByReplacingOccurrencesOfString:#" " withString:#"+"];
result = [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
- (NSMutableDictionary *)dictionaryFromQueryComponents
{
NSMutableDictionary *queryComponents = [NSMutableDictionary dictionary];
for(NSString *keyValuePairString in [self componentsSeparatedByString:#"&"])
{
NSArray *keyValuePairArray = [keyValuePairString componentsSeparatedByString:#"="];
if ([keyValuePairArray count] < 2) continue; // Verify that there is at least one key, and at least one value. Ignore extra = signs
NSString *key = [[keyValuePairArray objectAtIndex:0] stringByDecodingURLFormat];
NSString *value = [[keyValuePairArray objectAtIndex:1] stringByDecodingURLFormat];
NSMutableArray *results = [queryComponents objectForKey:key]; // URL spec says that multiple values are allowed per key
if(!results) // First object
{
results = [NSMutableArray arrayWithCapacity:1];
[queryComponents setObject:results forKey:key];
}
[results addObject:value];
}
return queryComponents;
}
#end
#implementation NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents
{
return [[self query] dictionaryFromQueryComponents];
}
#end
#implementation NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents
{
NSString *result = nil;
for(__strong NSString *key in [self allKeys])
{
key = [key stringByEncodingURLFormat];
NSArray *allValues = [self objectForKey:key];
if([allValues isKindOfClass:[NSArray class]])
for(__strong NSString *value in allValues)
{
value = [[value description] stringByEncodingURLFormat];
if(!result)
result = [NSString stringWithFormat:#"%#=%#",key,value];
else
result = [result stringByAppendingFormat:#"&%#=%#",key,value];
}
else {
NSString *value = [[allValues description] stringByEncodingURLFormat];
if(!result)
result = [NSString stringWithFormat:#"%#=%#",key,value];
else
result = [result stringByAppendingFormat:#"&%#=%#",key,value];
}
}
return result;
}
#end
Something like that:
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:#"&"]) {
NSArray *elts = [param componentsSeparatedByString:#"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
Note : This is sample code. All error cases are not managed.
Try this ;)!
NSString *query = #"parameter=2&secondparameter=3"; // replace this with [url query];
NSArray *components = [query componentsSeparatedByString:#"&"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
for (NSString *component in components) {
NSArray *subcomponents = [component componentsSeparatedByString:#"="];
[parameters setObject:[[subcomponents objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
forKey:[[subcomponents objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
All previous posts do not do the url encoding properly. I would suggest the following methods:
+(NSString*)concatenateQuery:(NSDictionary*)parameters {
if([parameters count]==0) return nil;
NSMutableString* query = [NSMutableString string];
for(NSString* parameter in [parameters allKeys])
[query appendFormat:#"&%#=%#",[parameter stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet],[[parameters objectForKey:parameter] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];
return [[query substringFromIndex:1] copy];
}
+(NSDictionary*)splitQuery:(NSString*)query {
if([query length]==0) return nil;
NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
for(NSString* parameter in [query componentsSeparatedByString:#"&"]) {
NSRange range = [parameter rangeOfString:#"="];
if(range.location!=NSNotFound)
[parameters setObject:[[parameter substringFromIndex:range.location+range.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:[[parameter substringToIndex:range.location] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
else [parameters setObject:[[NSString alloc] init] forKey:[parameter stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
return [parameters copy];
}
According to the already very clean answer of Onato I wrote an extension for NSURL in Swift where you can get a query param like this:
e.g. the URL contains the pair param=some_value
let queryItem = url.queryItemForKey("param")
let value = queryItem.value // would get String "someValue"
The extension looks like:
extension NSURL {
var allQueryItems: [NSURLQueryItem] {
get {
let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
let allQueryItems = components.queryItems!
return allQueryItems as [NSURLQueryItem]
}
}
func queryItemForKey(key: String) -> NSURLQueryItem? {
let predicate = NSPredicate(format: "name=%#", key)!
return (allQueryItems as NSArray).filteredArrayUsingPredicate(predicate).first as? NSURLQueryItem
}
}
Here is the extension in swift:
extension NSURL{
func queryParams() -> [String:AnyObject] {
var info : [String:AnyObject] = [String:AnyObject]()
if let queryString = self.query{
for parameter in queryString.componentsSeparatedByString("&"){
let parts = parameter.componentsSeparatedByString("=")
if parts.count > 1{
let key = (parts[0] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let value = (parts[1] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
if key != nil && value != nil{
info[key!] = value
}
}
}
}
return info
}
}
The preferred way to deal with URLs is now NSURLComponents. In particular the queryItems property which returns an NSArray of params.
If you want the params in a NSDictionary, here's a method:
+(NSDictionary<NSString *, NSString *>*)queryParamsFromURL:(NSURL*)url
{
NSURLComponents* urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSMutableDictionary<NSString *, NSString *>* queryParams = [NSMutableDictionary<NSString *, NSString *> new];
for (NSURLQueryItem* queryItem in [urlComponents queryItems])
{
if (queryItem.value == nil)
{
continue;
}
[queryParams setObject:queryItem.value forKey:queryItem.name];
}
return queryParams;
}
Caveat: URLs can have repeated params, but the dictionary will only contain the last value of any duplicated param. If that is undesirable, use the queryItems array directly.
For those using Bolts Framework you can use:
NSDictionary *parameters = [BFURL URLWithURL:yourURL].inputQueryParameters;
Remember to import:
#import <Bolts/BFURL.h>
If you happen to have Facebook SDK in your project, you also have Bolts. Facebook is using this framework as a dependency.
Swift 2.1
Oneliner:
"p1=v1&p2=v2".componentsSeparatedByString("&").map {
$0.componentsSeparatedByString("=")
}.reduce([:]) {
(var dict: [String:String], p) in
dict[p[0]] = p[1]
return dict
}
// ["p1": "v1", "p2": "v2"]
Used as an extension on NSURL:
extension NSURL {
/**
* URL query string as dictionary. Empty dictionary if query string is nil.
*/
public var queryValues : [String:String] {
get {
if let q = self.query {
return q.componentsSeparatedByString("&").map {
$0.componentsSeparatedByString("=")
}.reduce([:]) {
(var dict: [String:String], p) in
dict[p[0]] = p[1]
return dict
}
} else {
return [:]
}
}
}
}
Example:
let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
let queryDict = url.queryValues
// ["p1": "v1", "p2": "v2"]
Please note, if using OS X 10.10 or iOS 8 (or later), it's probably better to use NSURLComponents and the queryItems property and create the dictionary from the NSURLQueryItems directly.
Here's a NSURLComponents based NSURL extension solution:
extension NSURL {
/// URL query string as a dictionary. Empty dictionary if query string is nil.
public var queryValues : [String:String] {
get {
guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false) else {
return [:]
}
guard let queryItems = components.queryItems else {
return [:]
}
var result:[String:String] = [:]
for q in queryItems {
result[q.name] = q.value
}
return result
}
}
}
A footnote to the NSURL extension is that it's actually possible in Swift to give the property the same name as the existing string property—query. I didn't know until I tried it, but the polymorphism in Swift lets you differ only on the return type. So if the extended NSURL property is public var query: [String:String] it works. I didn't use this in the example as I find it a little bit crazy, but it does work ...
I published a simple class doing the job under MIT:
https://github.com/anegmawad/URLQueryToCocoa
With it you can have arrays and objects in the query, which are collected and glued together
For Example
users[0][firstName]=Amin&users[0][lastName]=Negm&name=Devs&users[1][lastName]=Kienle&users[1][firstName]=Christian
will become:
#{
name : #"Devs",
users :
#[
#{
firstName = #"Amin",
lastName = #"Negm"
},
#{
firstName = #"Christian",
lastName = #"Kienle"
}
]
}
You can think of it as a URL query counterpart of NSJSONSerializer.
It looks that you are using it to process incoming data from another iOS application. If so, this is what I use for the same purpose.
Initial call (e.g. in external application):
UIApplication *application = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:#"myApp://action/1?parameter=2&secondparameter=3"];
if ([application canOpenURL:url]) {
[application openURL:url];
NSLog(#"myApp is installed");
} else {
NSLog(#"myApp is not installed");
}
Method to extract QueryString data from NSURL and save as NSDictionary:
-(NSDictionary *) getNSDictionaryFromQueryString:(NSURL *)url {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
NSRange needle = [url.absoluteString rangeOfString:#"?" options:NSCaseInsensitiveSearch];
NSString *data = nil;
if(needle.location != NSNotFound) {
NSUInteger start = needle.location + 1;
NSUInteger end = [url.absoluteString length] - start;
data = [url.absoluteString substringWithRange:NSMakeRange(start, end)];
}
for (NSString *param in [data componentsSeparatedByString:#"&"]) {
NSArray *keyvalue = [param componentsSeparatedByString:#"="];
if([keyvalue count] == 2){
[result setObject:[keyvalue objectAtIndex:1] forKey:[keyvalue objectAtIndex:0]];
}
}
return result;
}
Usage:
NSDictionary *result = [self getNSDictionaryFromQueryString:url];
This class is a nice solution for url parsing.
.h file
#interface URLParser : NSObject {
NSArray *variables;
}
#property (nonatomic, retain) NSArray *variables;
- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;
#end
.m file
#import "URLParser.h"
#implementation URLParser
#synthesize variables;
- (id) initWithURLString:(NSString *)url{
self = [super init];
if (self != nil) {
NSString *string = url;
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#"&?"]];
NSString *tempString;
NSMutableArray *vars = [NSMutableArray new];
[scanner scanUpToString:#"?" intoString:nil]; //ignore the beginning of the string and skip to the vars
while ([scanner scanUpToString:#"&" intoString:&tempString]) {
[vars addObject:[tempString copy]];
}
self.variables = vars;
}
return self;
}
- (NSString *)valueForVariable:(NSString *)varName {
for (NSString *var in self.variables) {
if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:#"="]]) {
NSString *varValue = [var substringFromIndex:[varName length]+1];
return varValue;
}
}
return nil;
}
#end
Hendrik wrote a nice example for extension in this question, however I had to re-write it to not use any objective-c library methods. Using NSArray in swift is not the correct approach.
This is the result, all swift and a bit more safe. The usage example will be less lines of code with Swift 1.2.
public extension NSURL {
/*
Set an array with all the query items
*/
var allQueryItems: [NSURLQueryItem] {
get {
let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
if let allQueryItems = components.queryItems {
return allQueryItems as [NSURLQueryItem]
} else {
return []
}
}
}
/**
Get a query item form the URL query
:param: key The parameter to fetch from the URL query
:returns: `NSURLQueryItem` the query item
*/
public func queryItemForKey(key: String) -> NSURLQueryItem? {
let filteredArray = filter(allQueryItems) { $0.name == key }
if filteredArray.count > 0 {
return filteredArray.first
} else {
return nil
}
}
}
Usage:
let queryItem = url.queryItemForKey("myItem")
Or, more detailed usage:
if let url = NSURL(string: "http://www.domain.com/?myItem=something") {
if let queryItem = url.queryItemForKey("myItem") {
if let value = queryItem.value {
println("The value of 'myItem' is: \(value)")
}
}
}
try this:
-(NSDictionary *)getUrlParameters:(NSString *)url{
NSArray *justParamsArr = [url componentsSeparatedByString:#"?"];
url = [justParamsArr lastObject];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:#"&"]) {
NSArray *elts = [param componentsSeparatedByString:#"="];
if([elts count] < 2) continue;
[params setObject:[elts lastObject] forKey:[elts firstObject]];
}
return params;
}
Fairly compact approach:
func stringParamsToDict(query: String) -> [String: String] {
let params = query.components(separatedBy: "&").map {
$0.components(separatedBy: "=")
}.reduce(into: [String: String]()) { dict, pair in
if pair.count == 2 {
dict[pair[0]] = pair[1]
}
}
return params
}
Most robust solution if you are using a URL to pass data from the web app to the phone and you want to pass arrays, numbers, strings, ...
JSON encode your object in PHP
header("Location: myAppAction://".urlencode(json_encode($YOUROBJECT)));
And JSON decode the result in iOS
NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];