- (void)applicationDidEnterBackground:(UIApplication *)application {
NSOutputStream *outputStream;
NSInputStream *inputStream;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
});
}
Input,OutputStream delegate didn't call.What i am doing wrong.
Did you run the current run loop by following way ?
do {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
} while (done == TRUE);
You seem to have omitted the streams' initialization, e.g.
NSInputStream *inputStream = [NSInputStream inputStreamWithURL: myURL];
NSOutputStream *outputStream = [NSOutputStream outputStreamToMemory];
Related
I am trying to implement a singelton to read / write from a simple chat server. Im using a singelton model so that i can access the data and write from all of my viewControllers.
My singelton code:
#import "ChatDataController.h"
#implementation ChatDataController
{
ChatDataController * anotherSingles;
}
#synthesize enString;
#synthesize enInt;
+(ChatDataController *) singlOjb {
static ChatDataController * single=nil;
#synchronized(self)
{
if(!single)
{
single = [[ChatDataController alloc] init];
}
}
return single;
}
// We can still have a regular init method, that will get called the first time the Singleton is used.
- (id)init
{
self = [super init];
if (self) {
// Work your initialising magic here as you normally would
[self initNetworkCommunication];
}
return self;
}
// Open connection to server
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", 8080, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void) sendMsg {
NSString *response = [NSString stringWithFormat:#"iam:TestString"];
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
}
#end
The problem is that when i run [self initNetworkCommunication]; from my init method my app crashes with the error: (lldb)
A break is detected at the line:
inputStream = (__bridge NSInputStream *)readStream;
Any ideas what i am doing wrong?
EDIT: Is this the way i should be doing it?
First, your singleton code should be like this...
+ (ChatDataController *)sharedInstance
{
static dispatch_once_t once;
static ChatDataController *chatDataController;
dispatch_once(&once, ^ { chatDataController = [[ChatDataController alloc] init];});
return chatDataController;
}
Next, and I'm not sure is this will fix as I'm not sure of the exact problem but I would do it this way...
In your AppDelegate (or wherever you need to start the stream) do this...
[[ChatDataController sharedInstance] initNetworkCommunication];
This makes sure the singleton object is fully instantiated before calling its instance methods.
I want to do an Asynchrous NSURLConnection inside of an NSOperation on a background thread.
it is because I'm doing some very expensive operations on the data as they come back.
This is a very similar question to what they asked here:
How do I do an Asynchronous NSURLConnection inside an NSOperation?
but the difference is that I run the connection in another class.
Here is my first attempt:
In my MainViewController:
#property (nonatomic, strong) NSOperationQueue *requestQueue;
#pragma mark - Lazy initialization
- (NSOperationQueue *)requestQueue
{
if (!_requestQueue) {
_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.name = #"Request Start Application Queue";
_requestQueue.maxConcurrentOperationCount = 1;
}
return _requestQueue;
}
-(void)callToServer
{
URLJsonRequest *request = [URLRequestFactory createRequest:REQUEST_INTERFACE_CLIENT_VERSION
delegate:self];
RequestSender *requestSender = [[RequestSender alloc]initWithPhotoRecord:request delegate:self];
[self.requestQueue addOperation:requestSender];
}
Here is my operation:
- (id)initWithPhotoRecord:(URLJsonRequest *)request
delegate:(id<RequestSenderDelegate>) theDelegate{
if (self = [super init])
{
self.delegate = theDelegate;
self.jsonRequest = request;
}
return self;
}
- (void)main {
//Apple recommends using #autoreleasepool block instead of alloc and init NSAutoreleasePool, because blocks are more efficient. You might use NSAuoreleasePool instead and that would be fine.
#autoreleasepool
{
if (self.isCancelled)
return;
[self.jsonRequest start];
}
}
Here is my Request start function:
-(void) start
{
NSURL *url = [NSURL URLWithString:#"http://google.com"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
urlConnection = [[[NSURLConnection alloc] initWithRequest:theRequest delegate:self]autorelease];
[urlConnection start];
[theRequest release]
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"Received reponse from connection");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
}
I do not get a response from the server.
A couple of approaches:
Schedule the NSURLConnection in the main run loop, by using the startImmediately parameter of NO, set the run loop, and only then should you start the connection, e.g.:
urlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:NO];
[urlConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[urlConnection start];
Create a dedicated thread for the the connection and schedule the connection in the run loop you create for that thread. See AFURLConnectionOperation.m in AFNetworking source for an example of this.
Actually use AFNetworking, which gives you NSOperation based operations that you can add to your queue, and takes care of this run loop stuff for you.
So, AFNetworking does something like:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
#autoreleasepool {
[[NSThread currentThread] setName:#"NetworkingThread"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self
selector:#selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
So I do something like the following. First I have a few private properties:
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, weak) NSURLConnection *connection;
Then the network operation can then do something like:
#synthesize executing = _executing;
#synthesize finished = _finished;
- (instancetype)init {
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void)start {
if (self.isCancelled) {
[self completeOperation];
return;
}
self.executing = YES;
[self performSelector:#selector(startInNetworkRequestThread)
onThread:[[self class] networkRequestThread]
withObject:nil
waitUntilDone:NO];
}
- (void)startInNetworkRequestThread {
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:self.request
delegate:self
startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
self.connection = connection;
}
- (void)completeOperation {
self.executing = NO;
self.finished = YES;
}
- (void)setFinished:(BOOL)finished {
if (finished != _finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)setExecuting:(BOOL)executing {
if (executing != _executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isAsynchronous {
return YES;
}
// all of my NSURLConnectionDataDelegate stuff here, for example, upon completion:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// I call the appropriate completion blocks here, do cleanup, etc. and then, when done:
[self completeOperation];
}
-(void)start
{
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSURL* url = [[NSURL alloc] initWithString:#"http://url.to/feed.xml"];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; // ivar
[request release];
[url release];
// Here is the trick
NSPort* port = [NSPort port];
NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop
[rl addPort:port forMode:NSDefaultRunLoopMode];
[_connection scheduleInRunLoop:rl forMode:NSDefaultRunLoopMode];
[_connection start];
[rl run];
}
More details can be found here: link
I know this post is over a year old, but I wanted to add some suggestions for people who may run the same problem trying to create own async network operation.
You need to add runloop to operation that runs in background and you should stop it when the operations has finished.
There actually 2 simple options:
Option 1 - using NSRunLoop
NSPort *port = [NSPort port];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:port forMode:NSDefaultRunLoopMode];
[self.connection scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[self.connection start];
[runLoop run];
and you need to stop when the operation is finished:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSDate *date = [NSDate distantFuture];
while (!runLoopIsStopped && [runLoop runMode:NSDefaultRunLoopMode beforeDate:date]);
Option 2 - using CF
You need to add
CFRunLoopRun();
when you start operation
and call
CFRunLoopStop(CFRunLoopGetCurrent());
when you finish operation.
Read the following post: CFRunLoopRun() vs [NSRunLoop run]
I created a NSObject, which downloads some data from a php web service. The problem is that when i run the app in the simulator works well, but in the device the delegate methods are not called. I donĀ“t know why. I write all the delegates methods but none of them are called. This is a part of the NSObject code:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:hostStr]];
NSLog(#"%# URL Engine",hostStr);
dispatch_async(dispatch_get_main_queue(), ^{
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
// while(!finished) {
// [[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];
//}
[_connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_connection start];
});
It's tricky about call dispatch_async to get the connection run on the main thread queue.
Try this if you want to let your delegates get called on the main thread:
[self performSelectorOnMainThread:#selector(startConnection)
withObject:Nil
waitUntilDone:NO];
If you want to let your delegates get called on the other thread, try:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[connection scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
[connection start];
[currentRunLoop run];
});
OK. So there is an exceptional case I have to handle for NSStreamDelegate implementation.
The process is the following:
1- a COMMAND data is written to the output stream to trigger the other end
2- The other end, starts putting data for the input stream BUT asynchronously, meaning that the first part of the data is received at the beginning then a couple of seconds later, the rest of the data comes in.
3- After all the data is received, the server will NOT send any data but the connection remains open. (input stream status = Open (2))
4- After couple of minutes, the server ends the connection (input stream status = 5) and NSStreamEventEndEncountered is triggered.
SendRequest class
- (void)sendSecondRequest {
CFReadStreamRef readStream = nil;
CFWriteStreamRef writeStream = nil;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)self.SOMEServer, self.SOMEPort, &readStream, &writeStream);
self.inputStream = (__bridge NSInputStream *)readStream;
self.outputStream = (__bridge NSOutputStream *)writeStream;
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
NSData *data = [NSData dataWithData:[kSecondCall dataUsingEncoding:NSASCIIStringEncoding]];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
[self.outputStream write:[data bytes] maxLength:[data length]];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
if (aStream == self.inputStream) {
uint8_t buffer[1024];
NSInteger len = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
[gOutput appendString:output];
}
}
break;
case NSStreamEventEndEncountered:
NSLog(#"Can not connect to the host");
[aStream close];
[aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
aStream = nil;
break;
default:
break;
}
}
- (void)disconnectSocket {
[self.inputStream close];
[self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream close];
[self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream setDelegate:nil];
[self.outputStream setDelegate:nil];
self.inputStream = nil;
self.outputStream = nil;
}
The gOutput variable is a global variable that I use to read and store input stream.
The problem is that since the data's first part comes immediately and then the rest comes in asynchronously, I can not handle the moment when there is no more data available.
In other words, how can I know if the server is done sending data and I should start processing it?
Server sends a terminator sequence at the end. when the client detects this terminator, it starts to process....
I have an iPhone app that is a client for an existing server application.
I am using the following code to connect, and this code was working fine. Today I started work on the project, and something odd is happening.
When I click "Connect" NSHost takes about 30-45 seconds to resolve and connect to the host. Once the connection is established, data transfer speeds are normal and fast.
I have the original client software (PC Application) connecting to the same server, and the connection is handled immediately!!
Perhaps someone can shed some light on the subject...
-(IBAction)tryConnection{
[self showLogoffButtons];
iPhone_PNPAppDelegate *mainDelegate = (iPhone_PNPAppDelegate *)[[UIApplication sharedApplication] delegate];
settings=mainDelegate.settings;
[activityindicator startAnimating];
sendState=0;
NSHost* host;
//30-45 second pause happens here (since I am using an IP Address)
host = [NSHost hostWithAddress:settings.masterLocation];
//NSHost returns (eventually) with the correct connection.
if (!host) {
host = [NSHost hostWithName:settings.masterLocation];
}
if( host ) {
[NSStream getStreamsToHost:host port:[settings.masterPort intValue] inputStream:&iStream outputStream:&oStream] ;
if( nil != iStream && nil != oStream ) {
[iStream retain];
[oStream retain];
[iStream setDelegate:self];
[oStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
[oStream open];
}else{
[self resetConnectionAndScreen];
self.navigationItem.prompt=#"Connection Error! Please check connection settings.";
}
}else{
[self resetConnectionAndScreen];
self.navigationItem.prompt=#"Connection Error! Please check connection settings.";
}
}
//I am also now getting these warnings.**.. wth?! :)
warning: no '+hostWithAddress:' method found
warning: (Messages without a matching method signature
warning: no '+hostWithName:' method found
warning: 'NSStream' may not respond to '+getStreamsToHost:port:inputStream:outputStream:'
I have updated my code to the following, which has resolved my issue.
-(IBAction)tryConnection{
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)settings.masterLocation, [settings.masterPort intValue], &readStream, &writeStream);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
iStream = (NSInputStream *)readStream;
[iStream retain];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
oStream = (NSOutputStream *)writeStream;
[oStream retain];
[oStream setDelegate:self];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[oStream open];
}
if (readStream) CFRelease(readStream);
if (writeStream) CFRelease(writeStream);
}
here is another way of networking using cfsockets
http://randomsnippetsofusefulstuff.blogspot.com/