I have about 15 URLs for XML Parsing simultaneously. I am using KMXML Parser for XML parsing.
How can i do Parsing simultaneously using thread. How to handle the responses form that parsing. How can i store those responses in Sq-lite Database.
My code for parsing is as follow:
NSArray* yourURLs = [NSArray arrayWithObjects:#"http://www.designworldonline.com/rss/",#"http://www.3dcadtips.com/feed/",#"http://feeds.feedburner.com/MakePartsFast",#"http://www.designworldonline.com/category/technologies/electricalelectronic/feed", nil];
for(NSString* url in yourURLs) {
[self performSelectorInBackground:#selector(parse:) withObject:url];
}
-(void)parse:(NSString*)link {
KMXMLParser *parser = [[KMXMLParser alloc] initWithURL:link delegate:self];
_parseResults = [parser posts];
[self performSelectorOnMainThread:#selector(update) withObject:_parseResults waitUntilDone:NO];
}
-(void)update
{
[pd addObject:_parseResults];
}
But with this coding,
_parseresults=[parser posts]; is called only for last link rather than for each link.
and i got response in array pd is only from last link & 9 times. i don't know why parsing method is only called last time only.
try to use framework will help you very will
https://github.com/AFNetworking/AFNetworking
https://github.com/pokeb/asi-http-request
Use Operation Queue and then add all your operations inside Operation Queue.
Check out this link ...
http://developer.apple.com/library/mac/#samplecode/NSOperationSample/Introduction/Intro.html%23//apple_ref/doc/uid/DTS10004184
hope it will help
Happy Coding.
Related
I need to configure RestKit to interact with a server API that expects the following:
All requests are in multipart/form-data
There is a JSON payload. However the client must collapse the payload
into a form-data string and prepend it with "json="
Authentication is done via hashing the contents of JSON payload with
the URL including any GET parameters. And appending this hash to URL (Yes I know this is bad. But I
have no control over the API)
I'm trying to get RestKit append its serialization output to a json=, then modify the RKObjectLoader to computer and append the correct signature hash.
I've tried subclassing RKObjectManager and overriding sendObject:toResourcePath:usingBlock: to override the HTTPBody; but can't get this to work. I've also tried subclassing RKClient's and configureRequest: but it seems like this method gets called before object serialization.
This is my first day working with RestKit. Help would be much appreciated! Thanks!
Update: I've worked out a different solution than my original described bottom. I've created a subclasses of RKObjectLoader and RKObjectManager. The custom object manager simply has one change to return the custom object loader. The object loader's send message was overridden to perform custom construction.
Here's a solution I've worked out.
I use a subclass of RKClient where I override
- (void)configureRequest:(RKRequest *)request {
[super configureRequest:request];
request.delegate = self;
}
And do all my encoding/signing in the delegate.
- (void)requestWillPrepareForSend:(RKRequest *)request {
[self encodeJSONPayloadForRequest:request];
[self signRequest:request];
}
- (void)encodeJSONPayloadForRequest:(RKRequest*)request {
NSDictionary * bodyDict = (NSDictionary*)request.params;
NSError * error = nil;
NSString * jsonString = [bodyDict JSONStringWithOptions:JKSerializeOptionNone error:&error];
if (error) NSAssert(false, #"error serializing into JSON");
NSDictionary * dictionary = [NSDictionary dictionaryWithObject:jsonString forKey:#"json"];
request.params = dictionary;
}
I'll need to implement a mechanism for passing through to any pre-existing delegate. However, this approach seems to do the trick.
I've been using RestKit 0.10.0 for a while now and up until this point, I only posted serialized objects to my server:
[[RKObjectManager sharedManager] postObject:serializedObject
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
loader.targetObject = nil;
}];
So far, so good. But I now need to make a GET request to the server with a few query parameters. The first natural thing that came in mind was to do the same as I did for posting objects:
create a serialization mapping for the object encapsulating the query parameters
create a response mapping for the object being received from the server
define and use a router for RKRequestMethodGET (instead of RKRequestMethodPOST)
make the request using getObject:usingBlock (instead of postObject:usingBlock)
I soon found out this is not the way to do it, so after searching the available resources (RestKit Wiki, RestKit Google group) I now know of two solutions considered as valid:
Appending the query parameters to the resource path.
This works perfectly.
NSDictionary *queryParams = [NSDictionary dictionaryWithObjectsAndKeys:
token, #"accessToken",
[NSNumber numberWithInt:level], #"level",
[NSNumber numberWithInt:count], #"count",
nil];
NSString* resourcePath = [PEER_SUGGESTIONS_CONTROLLER_PATH stringByAppendingQueryParameters:queryParams];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
}];
Setting the query parameters in the loader block.
This does not send the query parameters.
RKParams *params = [RKParams params];
[params setValue:token forParam:#"accessToken"];
[params setValue:[NSNumber numberWithInt:level] forParam:#"level"];
[params setValue:[NSNumber numberWithInt:count] forParam:#"count"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:PEER_SUGGESTIONS_CONTROLLER_PATH
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.params = params;
}];
My questions are:
Why doesn't the second solution work?
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path in the JSON response?
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST parameters, but I do not see the point since I can wrap the parameters in the serialized object that is being sent with the method postObject:usingBlock.
Thanks.
[LATER EDIT]
Regarding the answer to my second question: I've been setting the targetObject to nil in the loader block when making POST requests beacause otherwise RestKit will try use the send object mapping for the response (check this link for a related discussion). But since I am using loadObjectsAtResourcePath:usingBlock:, there is no object being sent, therefore the response will naturally map on the response mapping without having to the set targetObject to nil.
Why doesn't the second solution work?
params is used to create a HTTP body, which is not used in a GET/HEAD request.
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path
in the JSON response?
I think targetObject is nil by default. You normally don't set it, the request will create it if needed. The only time I use it is when requesting objects without primary keys or other weird problems.
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
This is a convenience method so you don't have to remember all the correct syntax. Internally it just sends an object load request using GET.
EDIT:
Use this if you have an object you want to update.
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST
parameters, but I do not see the point since I can wrap the parameters
in the serialized object that is being sent with the method
postObject:usingBlock.
Whatever you put in params will be serialized to an HTTP body (or body stream). Again, postObject:usingBlock: is just a convenience method so you don't have to remember everything.
RestKit is open source. If you are not sure how it works you are free to follow the parameters internally. If you app and web service is well designed, you should be able to use the convenience methods. Sometimes you can not, and then you can use the raw forms like you have done.
EDIT:
Q Hrm, quoting your bullet points messed up the numbers...
I solved adding a Category to RKObjectLoader, that is:
for method
-(void)getObject:(id<NSObject>)object usingBlock:(RKObjectLoaderBlock)block;
I added into the Category a modified method:
-(void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
Here it is the listing fpr file "RKObjectManager+QueryParameters":
//
// RKObjectManager+QueryParameters.h
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import <RestKit/RestKit.h>
#interface RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block;
#end
Here is the listing for file "RKObjectManager+QueryParameters.m":
//
// RKObjectManager+QueryParameters.m
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import "RKObjectManager+QueryParameters.h"
#implementation RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *loader))block {
[self sendObject:object queryParameters:queryParameters method:RKRequestMethodGET usingBlock:block];
}
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block {
NSString *resourcePath = [self.router resourcePathForObject:object method:method];
[self sendObject:object toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = method;
// need to transform the original URL because when method is GET the additional paramentes don't get added
RKURL *originalBaseURL = [RKURL URLWithBaseURL:[loader.URL baseURL]];
NSString *resourcePath = [self.router resourcePathForObject:object method:RKRequestMethodGET];
RKURL *authTokenURL = [originalBaseURL URLByAppendingResourcePath:resourcePath queryParameters:queryParameters];
[loader setURL:authTokenURL];
block(loader);
}];
}
#end
One more step is to add #import "RKObjectManager+QueryParameters.h" in your implementation file.
In this new method it is assumed that the router property of RKObjectManager has been defined before making a call to it.
Hey guys I have done lots of work with ASIHTTPRequest so far and use it several times in my ios application, however in one area of my app where I have added another asihttprequest method its not working properly.
First of all it must be said that the reason I'm trying to spread the load of downloading and parsing the data.. If I do this on the subview it takes a good 2-3 second to go through the second xml sheet and get all of the related values out.. where as If I do it on this mainview where people are not seeing anything load etc then when the go back to the subview it should look almost instant. I don't know how correct this is but I figure its a okay thing to do to make the app feel abit snappier.
So I am setting it the asihttprequest methods identically as the other ones that work minus caching.
What happens is I select a table cell from the main view that loads the second view and parses a bunch of info to the tableview. the User then selects a value which is passed back to the main view and displayed.
I then parse another lot of xml data checking the selected valueID against everything in the second xml sheet. so that when the user selects the second cell I pass all the data that was just parsed over to the second view to make it look as though its loaded alot faster.
Heres a flow chart of that will explain what I'm trying to do abit better
This is what the parser code looks like in the main view which is the one thats working in the emulator but not on the iphone.
This is my protocol that I call from the subview and pass all the values I need to over and fire off the request to the mainviews ASIHTTPRequest.
- (void) setManufactureSearchFields:(NSArray *)arrayValues withIndexPath:(NSIndexPath *)myIndexPath
{
manufactureSearchObjectString = [[arrayValues valueForKey:#"MANUFACTURER"] objectAtIndex:0];
manufactureIdString = [[arrayValues valueForKey:#"MANUID"] objectAtIndex:0]; //Restricts Models dataset
manufactureResultIndexPath = myIndexPath;
[self.tableView reloadData]; //reloads the tabels so you can see the value in the tableViewCell.
//need some sort of if statment here so that if the back button is pressed modelSearchObjectString is not changed..
if (oldManufactureSearchObjectString != manufactureSearchObjectString) {
modelResultIndexPath = NULL;
modelSearchObjectString = #"empty";
oldManufactureSearchObjectString = [[NSString alloc] initWithFormat:manufactureSearchObjectString];
}
//These two lines below are what execute ASIHTTPRequest and set up my parser etc
dataSetToParse = #"ICMod"; // This sets the if statment inside parserDidEndDocument
[self setRequestString:#"ICMod.xml"]; //Sets the urlstring for XML inside setRequestString
}
This then fires the ASIHTTPRequest delegate methods.
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
//NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http://127.0.0.1:8888/codeData/"]; // imac development
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http://127.0.0.1:8888/codeData/"]; // iphone development
//PHP file name is being set from the parent view
[databaseURL appendString:string];
NSLog(#"%#", databaseURL);
//call ASIHTTP delegates (Used to connect to database)
NSURL *url = [NSURL URLWithString:databaseURL];
//This sets up all other request
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
When I run this through debug with break points while testing on the iphone this is where the app falls over.. but On the emulator it has no problems.
This next method never gets called when testing on the iphone but workds sweet on the emulator.
- (void)requestFinished:(ASIHTTPRequest *)request
{
responseString = [request responseString]; //Pass requested text from server over to NSString
capturedResponseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
[self startTheParsingProcess:capturedResponseData];
}
This is the only other delegate that is fired when testing on the iphone, sends me an alret saying the connection has timed out.
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
I don't think you need to see all the parser delegates as I don't think they are the issue as this is where the app falls over...
here is what gets printed to the log...
2011-11-29 14:38:08.348 code[1641:707] http://000.000.000.000:0000/codeData/second.xml
2011-11-29 14:38:18.470 code[1641:707] Error Domain=ASIHTTPRequestErrorDomain Code=2 "The request timed out" UserInfo=0x1e83a0 {NSLocalizedDescription=The request timed out}
If you need more of my code let me know.. but I'm at abit of a loss here as like I say there is no difference to how Im doing this ASIHTTPRequest to other views other than I'm initializing it from the protocol that I'm setting up from the second view.. maybe I should set the values before I reload the table or something... I'm not sure though hopefully someone can help me out with this one and spot the issue I cannot see.
Can you view 'http://127.0.0.1:8888/codeData/' with Safari on the iPhone? Chances are that server isn't available from whatever networks the iPhone is connected to.
If your iMac is using DCHP it is possible that the address has changed since you originally set the value.
Are you sure you have the case correct on the URL? The simulator is much more forgiving on case-sensitivity than the actual device.
Do not fly blind but use an HTTP Proxy like Charles for making sure your requests are actually fired and result into what you expect.
I am trying to use Json in my iphone projects ,
but i didnt get how can I start using json in my project.
help me out from this condition.
Thanks in advance.
Well if you want you can get started by this
http://iosdevelopertips.com/networking/iphone-json-flickr-tutorial-part-1.html
this tutorial will help you understand what json does, but if you want to started on using it in your code than you should use the following example:
April 26, 2009
Dealing with JSON on iPhone
You can easily use the JSON (JavaScript Object Notation) data format in client-server communications
when writing an iPhone app. This blog is not suggesting that JSON is a more superior format for data
exchange than its counterparts such as XML. In fact, we have many projects that don't use JSON.
However, handling JSON is relatively straight forward in ObjectiveC.
Unfortunately, Apple iPhone SDK (as of this writing, the latest is iPhone 2.2.1) doesn't come with
a built-in JSON parser. But I found out a good one called json-framework. It is both a generator
and a parser. As a generator, json-framework can create JSON data from an NSDictionary. As a parser,
you can pass to json-framework an NSString that consists of JSON data and it will return a
NSDictionary that encapsulates the parsed data.
Next, I'm going to show you several examples. Before you proceed, download the library and make
sure you add it to your SDK path list (see INSTALL file that comes with it). If setup properly,
you should be able to start using the library by importing its header file:
#import "JSON/JSON.h"
Consider the following code:
NSDictionary *requestData = [NSDictionary dictionaryWithObjectsAndKeys:
#"grio", #"username",
#"hellogrio", #"password",
nil];
This instantiates a new dictionary which we'll turn into a JSON string. To do so, you'll need to
use a function called JSONRepresentation. This function is added as a category to NSObject. It
means that as long as there's an import of JSON.h file, you can call the function on any NSObject
object.
NSString* jsonString = [requestData JSONRepresentation];
And this is what you got when you print (NSLog(#"%#", jsonString);):
{"username":"grio","password":"hellogrio"}
Parsing is just as simple. Consider the following JSON data:
{
"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{
"value": "New",
"onclick": "CreateNewDoc()"
},
{
"value": "Open",
"onclick": "OpenDoc()"
},
{
"value": "Close",
"onclick": "CloseDoc()"
}
]
}
}
}
Assume that this is the data that you received from a web service called and is currently stored
in an NSString called jsonResult. To parse it, you need to create SBJSON object and call one of
its initialization method, objectWithString.
SBJSON *json = [[SBJSON new] autorelease];
NSError *jsonError;
NSDictionary *parsedJSON = [json objectWithString:jsonResult error:&jsonError];
If parsing fails for reasons such as invalid construct of JSON format, jsonError variable will
be filled with the error info. If it is successful, parsedJSON will contain keys whose values
are either an NSString or NSDictionary. Let's look at the inside of parsedJSON:
NSDictionary* menu = [parsedJSON objectForKey:#"menu"];
NSLog(#"Menu id: %#", [menu objectForKey:#"id"]);
NSLog(#"Menu value: %#", [menu objectForKey:#"value"]);
And here's the output:
Menu id: file
Menu value: File
Observe the JSON data again. popup is an NSDictionary which has an array of menuitem.
NSDictionary* popup = [menu objectForKey:#"popup"];
NSArray* menuItems = [popup objectForKey:#"menuitem"];
NSEnumerator *enumerator = [menuItems objectEnumerator];
NSDictionary* item;
while (item = (NSDictionary*)[enumerator nextObject]) {
NSLog(#"menuitem:value = %#", [item objectForKey:#"value"]);
}
And this is the output:
menuitem:value = New
menuitem:value = Open
menuitem:value = Close
Sorry i forgot the link to this website
json-framework is also good if you don't like TouchJson
I use ASIHttpRequest to make an Asynchronous call to my Web Service. In my requestFinished method I parse the JSON I received from my call and from there you can do pretty much anything with the JSON you received.
I have a strange issue, when it comes to parsing XML with NSXMLParser on the iPhone. When starting the app, I want to preload 4 table-views, that are populated by RSS-Feeds in the background.
When I init the table-views one-by-one, than loading, parsing and displaying all works like a charm. But when I try to init all view at once (at the same time), than it seems, that the XML-parser-instances are disturbing each other. Somehow data from one XML-Feed are "broadcasted" into other xml-parser instances, where they do not belong. Example: there is a "teammember" item, with "This is my name". When this bug occurs, there is a string from another xml-feed added, i.e. resulting in: "This is my name58", where 58 is the chart-position of something from the other view. "58" seems to miss then on the other instance.
It looks to me, that this bug occurs because of the NSXMLParser-delegate method:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentStringValue) {
currentStringValue = [[NSMutableString alloc] initWithCapacity:50];
}
[currentStringValue appendString:string];
}
In this case "by coincidence" bytes are appended to strings, where they do not belong to.
The strange thing is, that every instance of NSXMLParser is unique, got its own unique delegates, that are attached to their own ViewController. Every parsing-requests spawns it own background-task, with its own (also also unique named) Autorelease-pool.
I am calling the NSXMLParser like this in the ViewController:
// prepare XML saving and parsing
currentStringValue = [[[NSMutableString alloc] initWithCapacity:50] retain];
charts = [[NSMutableArray alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://(SOME XML URL)"];
xmlParser = [[[NSXMLParser alloc] initWithContentsOfURL:url] retain];
//Set delegate
[xmlParser setDelegate:self];
//loading indicator
progressWheel = [[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(150.0,170.0,20.0,20.0)] autorelease];
progressWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self.view addSubview:progressWheel];
[progressWheel startAnimating];
// start loading and parsing the xml-feed in the background
//[self performSelectorInBackground:#selector(parse:) withObject:xmlParser]; -> I also tried this
[NSThread detachNewThreadSelector:#selector(parse:) toTarget:self withObject:xmlParser];
And this is one of the background-tasks, parsing the feed:
-(void)parse:(NSXMLParser*)myParser {
NSAutoreleasePool *schedulePool = [[NSAutoreleasePool alloc] init];
BOOL success = [myParser parse];
if(success) {
NSLog(#"No Errors. xmlParser got: %#", myParser);
(POST-PROCESSING DETAILS OF THE DATA RETURNED)
[self.tableView reloadData];
} else {
NSLog(#"Couldn't initalize XMLparser");
}
[progressWheel stopAnimating];
[schedulePool drain];
[myParser release];
}
What could cause this issue? Am I calling the background-task in the right way? Why is this bug approaching, since every XML-Parser got its own, unique instance?
You should not be updating UI elements (like progressWheel) from inside a background thread. UI updates should be done on the main thread.
Use -performSelectorOnMainThread:withObject:waitUntilDone: to update UI elements from within a background thread.
I've released an open source RSS/Atom Parser for iPhone and it makes reading and parsing web feeds extremely easy.
You can set it to download the data asynchronously, or you could run it in a background thread synchronously to collect the feed data.
Hope this helps!