I got this weird problem in implanting simple NSURLConnection...
The method didReceiveData get call and I'm happily trying to append the receive data but... nada!
There's some data for sure (as the length indicate but appendData do NOT append the data!
I start to bang my head on this one and I need some help before it's to late :-)
Here some code to look at :
My header...
#interface ActionViewController : UITableViewController {
Site *site;
NSURLConnection *siteConnection;
NSMutableData *receivedData;
UIView *waitView;
UIActivityIndicatorView *activityIndicator;
int nConnections;
BOOL fail;
}
My implementation..
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
NSLog(#"Received %d bytes of data",[data length]);
[receivedData appendData:data];
NSLog(#"Received %d bytes of data",[receivedData length]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[activityIndicator stopAnimating];
waitView.hidden = YES;
// release the connection
[connection release];
}
The console output...
[Session started at 2010-08-21 21:27:55 -0400.]
2010-08-21 21:28:19.263 myApp[2042:207] Received 108 bytes of data
2010-08-21 21:28:19.263 myApp[2042:207] Received 0 bytes of data
2010-08-21 21:28:19.263 myApp[2042:207] Succeeded! Received 0 bytes of data
I don't get it! HELP!!!
BTW, the data is a simple xml result that look like this...
<donnee>0</donnee><donnee>0</donnee><donnee>0</donnee><donnee>1</donnee><donnee>0</donnee><donnee>0</donnee>
Well, got my answer in an another question on this site about NSMutableData... forgot to initialize the thing! (code 18 or a remainder of sending message to nil is a NICE feature of Objective-C)
receivedData = [[NSMutableData alloc] init];
Related
Background:
Please don't mark this as duplicate. I have tried every other related post and they didn't work for me. I have looked at countless examples (StackOverflow/MBProgressHUD Demo/etc.) trying to get this to work. I feel like most examples are outdated as some methods are deprecated. This is the closest I have got.
What I want the code to do:
The MBProgress HUD should display the default loading screen with "Preparing" before I start connecting to JSON. When I connect, I want the mode to change to AnnularDeterminate when I receive a response. Once it's finished loading, I want the progress bar to display my progress of adding the data to a dictionary. Once that is done, change the mode to CustomView displaying a checkmark image and "Completed".
The Issue:
The MBProgress HUD will display. However, it only shows the default loading screen with "Preparing..." underneath it and then it will say "Completed" with the checkmark custom view after progress is at 1.0. There is no Determinate view with my updated progress in between.
My Code
- (void)viewDidLoad
{
buildingsDataArray = [[NSMutableArray alloc] init];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.delegate = self;
HUD.label.text = NSLocalizedString(#"Preparing...", #"HUD preparing title");
[self connect];
NSLog(#"End of setup");
}
- (void)connect
{
NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:buildingList]];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
NSLog(#"End of connect");
}
// Connection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"connection received response");
[MBProgressHUD HUDForView:self.view].mode = MBProgressHUDModeAnnularDeterminate;
NSLog(#"Changed mode");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[webData appendData:data];
NSLog(#"connection received data");
[MBProgressHUD HUDForView:self.view].progress = 0.0f;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Fail with error - %# %#", [error localizedDescription], [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
if ([[error localizedDescription] isEqualToString:#"The Internet connection appears to be offline."]) {
//do something
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSArray *allDataArray = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
float count = [allDataArray count];
float c=1;
if ([buildingsDataArray count]!=0)
[buildingsDataArray removeAllObjects];
for (NSDictionary *dataDict in allDataArray){ //from 1 to 6
if ([[dataDict objectForKeyedSubscript:#"id"] intValue] != 0)
[buildingsDataArray addObject:dataDict];
usleep(200000);
float p = c/count;
[MBProgressHUD HUDForView:self.view].progress = p;
NSLog(#"Progress: %f", [MBProgressHUD HUDForView:self.view].progress);
});
c++;
}
[MBProgressHUD HUDForView:self.view].customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Checkmark.png"]];
[MBProgressHUD HUDForView:self.view].label.text = NSLocalizedString(#"Completed", #"HUD done title");
[MBProgressHUD HUDForView:self.view].mode = MBProgressHUDModeCustomView;
[[MBProgressHUD HUDForView:self.view] hideAnimated:YES afterDelay:1.0f];
}
And here is my console output
End of connect
End of setup
connection received response
Changed mode
connection received data
connection finished loading
Progress: 0.166667
Progress: 0.333333
Progress: 0.500000
Progress: 0.666667
Progress: 0.833333
Progress: 1.000000
Additional comments
When I try adding
dispatch_async(dispatch_get_main_queue()
outside all of the MBProgress method calls inside my connection delegate,
I get the default loading screen again. But after progress reaches 1.0, the determinate loading screen view shows (fully loaded) with "Completed" and then disappears after delay 1.0. There is still no loading process in between. Also, the checkmark image never shows. I don't think this is the way to do it.
The output when I do this is:
End of connect
End of doSetup
connection received response
connection received data
connection finished loading
Changed mode
Progress: 0.166667
Progress: 0.333333
Progress: 0.500000
Progress: 0.666667
Progress: 0.833333
Progress: 1.000000
Here is a useful reference that touches base on UICalls on different threads:
Is there a way to make drawRect work right NOW?
This is what worked:
NSURLConnection was being called on the main thread. So all of my UI updates weren't occurring until after the connection delegate calls were finished.
So to fix this problem, I put the connection in a background thread.
- (void)viewDidLoad{
//...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSURL *URL = [NSURL URLWithString:buildingList];
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:URL];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection setDelegateQueue:[[NSOperationQueue alloc] init]];
[connection start];
});
}
Inside the connection delegate methods, I surrounded each MBProgress call with
dispatch_async(dispatch_get_main_queue(), ^{
//...
}
Hope this helps others. I spent all day on this and it was such a simple misunderstanding...
I was successful in creating XPC service and communicating with XPC service by sending messages from main application. But what I want to know is, whether its possible to initiate a communication from XPC service to the main application. The Apple documentation says XPC is bidirectional. It would be much appreciated if someone can point me in right direction with an example.
Please note,
I want to launch the XPC from main application.
communicate with XPC from main application.
when some events occur, XPC should send a message to main application.
I succeeded in first two, but couldn't find any resource on the third one.
Thanks. :)
Figured everything out. The following should be a decent example:
ProcessorListener.h (included in both client and server):
#protocol Processor
- (void) doProcessing: (void (^)(NSString *response))reply;
#end
#protocol Progress
- (void) updateProgress: (double) currentProgress;
- (void) finished;
#end
#interface ProcessorListener : NSObject<NSXPCListenerDelegate, Processor>
#property (weak) NSXPCConnection *xpcConnection;
#end
ProcessorListener.m: (Included in just the server)
#import "ProcessorListener.h"
#implementation ProcessorListener
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
[newConnection setExportedInterface: [NSXPCInterface interfaceWithProtocol:#protocol(Processor)]];
[newConnection setExportedObject: self];
self.xpcConnection = newConnection;
newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol: #protocol(Progress)];
// connections start suspended by default, so resume and start receiving them
[newConnection resume];
return YES;
}
- (void) doProcessing: (void (^)(NSString *g))reply
{
dispatch_async(dispatch_get_global_queue(0,0), ^{
for(int index = 0; index < 60; ++index)
{
[NSThread sleepWithTimeInterval: 1];
[[self.xpcConnection remoteObjectProxy] updateProgress: (double)index / (double)60 * 100];
}
[[self.xpcConnection remoteObjectProxy] finished];
}
// nil is a valid return value.
reply(#"This is a reply!");
}
#end
MainApplication.m (your main app):
#import "ProcessorListener.h"
- (void) executeRemoteProcess
{
// Create our connection
NSXPCInterface * myCookieInterface = [NSXPCInterface interfaceWithProtocol: #protocol(Processor)];
NSXPCConnection * connection = [[NSXPCConnection alloc] initWithServiceName: kServiceName];
[connection setRemoteObjectInterface: myCookieInterface];
connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(Progress)];
connection.exportedObject = self;
[connection resume];
id<Processor> theProcessor = [connection remoteObjectProxyWithErrorHandler:^(NSError *err)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle: #"OK"];
[alert setMessageText: err.localizedDescription];
[alert setAlertStyle: NSWarningAlertStyle];
[alert performSelectorOnMainThread: #selector(runModal) withObject: nil waitUntilDone: YES];
}];
[theProcessor doProcessing: ^(NSString * response)
{
NSLog(#"Received response: %#", response);
}];
}
#pragma mark -
#pragma mark Progress
- (void) updateProgress: (double) currentProgress
{
NSLog(#"In progress: %f", currentProgress);
}
- (void) finished
{
NSLog(#"Has finished!");
}
#end
Note that this code is is a condensed version based on my working code. It may not compile 100% but shows the key concepts used. In the example, the doProcessing runs async to show that the callbacks defined in the Progress protocol still get executed even after the initial method has return and the Received response: This is a reply! has been logged.
I am trying to make a app that can connect to a device and ask for a query. To be able to have the a reply, I need to send an NSString command first. I have it in a loop with NSTimer.
I normally have like this in Interface Builder
Read:
SIDDA1999AP
then after some time changed to
Read:
#REL10345
and so on until the last string send #"H\r\n" then back to the first one.
Part of my code base in Asyncsocket
- (void)viewDidLoad
{
[super viewDidLoad];
// Read Data as loaded
NSTimer *t;
t = [NSTimer scheduledTimerWithTimeInterval: 0.5
target: self
selector:#selector(onTick:)
userInfo: nil repeats:YES];
}
-(void)onTick:(NSTimer *)timer {
sendStr =
#"S\r"
#"#\r\n"
#"P\r\n"
#"H\r\n"
;
NSData *sendData = [sendStr dataUsingEncoding:NSUTF8StringEncoding];
[asyncSocket writeData:sendData withTimeout:-1.0 tag:0];
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
DDLogVerbose(#"socket:didReadData:withTag:");
NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
componentsSeparatedByString:#"L"]objectAtIndex:6];
[self debugPrint2:[NSString stringWithFormat:#"Read: \n%#",response]];
[response release];
[asyncSocket readDataWithTimeout:-1 tag:0];
}
debugPrint2 where I display my text.
I want to extract string from debugPrint and display it individually. Because its in a loop data would be dynamic. I also need to extract a substring (shorten from SIDDA1999AP to 1999). In other words, remove the prefix and suffix.
My xib would have like
Data1: 1999
Data2: 10345
....
Here's an image of my application in progress:
I hope somebody could help.
I am attempting to update a "status label", NSTextField, with the current (X) of total (Y) when downloading files from an NSURLConnection. Below is some code that is working, but not 100%, or the way I would like.
X = runningCurrent
Y = runningTotal
The following code updates the (Y) or ofTotal correctly, however, the (X) or current jumps all over the place and does not increment 1, 2, 3 .. etc.
ApplicationController
- (void) updateLabelWithCurrent:(int)current ofTotal:(int)total
{
[txtStatus setStringValue:[NSString stringWithFormat:#"Downloading %i of %i",current,total]];
[txtStatus setNeedsDisplay:YES];
}
XML Data Source
for (int x = 0; x < [catArray count]; x++)
{
/* download each file to the corresponding category sub-directory */
[[WCSWallpaperDownloader alloc]
initWithWallpaperURL: [NSURL URLWithString:[[catArray objectAtIndex:x] objectForKey:#"imageUrl"]]
andFileOutput: [NSString stringWithFormat:#"%#/%#_0%i.jpg",cat,catName,x] withCurrent:x ofTotal:[catArray count]];
}
WCSWallpaperDownloader
- (id)initWithWallpaperURL:(NSURL *)imageUrl andFileOutput:(NSString*)fileOutput withCurrent:(int)current ofTotal:(int)total
{
self = [super init];
if (self)
{
appController = [[ApplicationController alloc] init];
self.fileOut = fileOutput;
NSURLRequest *imageRequest =
[NSURLRequest requestWithURL:imageUrl cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:1800.0];
[[NSURLConnection alloc] initWithRequest:imageRequest delegate:self];
runningCurrent = current;
runningTotal = total;
}
return self;
}
#pragma mark NSURLConenction
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
receivedData = [[NSMutableData data] retain];
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
/* release the connection, and the data object */
[connection release];
[receivedData release];
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
/* updates the status label with the current download of total objects being downloaded */
[appController updateLabelWithCurrent: runningCurrent ofTotal: runningTotal];
/* skip existing files */
if ( ! [MANAGER fileExistsAtPath:fileOut] )
{
[receivedData writeToFile:fileOut atomically:YES];
[receivedData release];
}
[[appController txtStatus] setStringValue:#""];
}
Solution
The following code correctly increments the download status as each object finishes.
- (void) incrementStatusLabelWithTotal:(int)total
{
runningCurrent++;
[txtStatus setStringValue:[NSString stringWithFormat:#"Downloading %i of %i",runningCurrent,total]];
}
It looks like you are setting off your downloads one by one, but they are not finishing in the same order - so you create each object telling it that it is loading item X of Y, but if the object downloading item 6 finishes before the object downloading item 4, your X is going to go, as you say, all over the place.
Each wallpaper downloader should just tell the appController that it has finished, and let the appController hold the number of items that have been downloaded so far, and the total number.
In fact, the wallpaper downloaders don't really need to know how many downloads are happening, or which particular number they are. Your XML data source should be telling your "app controller" the total number of downloads, and then each downloader, as it finishes, should tell the controller that it is done.
So, your current init method would just be:
- (id)initWithWallpaperURL:(NSURL *)imageUrl andFileOutput:(NSString*)fileOutput
I'm not sure you should be allocating a new instance of appController each time in this method - the rest of the code looks like there should be a single one of these which is displaying one label, effectively a delegate for the downloader? Perhaps this should be assigned by the XML data source when it creates each object?
After the download is complete, your connectionDidFinishLoading method would be something like this:
[appController downloaderDidFinishDownloading:self];
Which would call a method in your appController that looks something like this:
-(void)downloaderDidFinishDownloading:(WCSWallpaperDownloader*)downloader
{
completedDownloads++;
[txtStatus setStringValue:[NSString stringWithFormat:#"Downloaded %i of %i",completedDownloads,totalDownloads]];
}
Where completedDownloads and totalDownloads are ivars in your app controller class.
When trying to compile an infinite while loop in xcode iphone view based application, it gives me an error that reads expected identifier or '(' before 'while'. I made it as simple as possible. Sorry about the picture. Code block was not working. If you want to see an image of the code, here is the link. http://www.freeimagehosting.net/uploads/931d5d8788.gif
#import "Lockerz_NotifierViewController.h"
#implementation Lockerz_NotifierViewController
NSMutableData *responseData;
while (1) {
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://amicionline.me"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create a connection
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if(theConnection) {
// create the datum
responseData=[[NSMutableData data] retain];
} else {
// code this later
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[responseData setLength:0];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// make it work
NSLog(#"Succeeded! Received %d bytes of data:",[responseData length]);
// release it
[connection release];
[responseData release];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
What are you trying to do? You can't write code directly in an #implementation. You must put it inside a method, for example:
-(void)start {
while (1) {
...
}
}