ReactiveCocoa: sendNext to a subscriber in asynchronous programming - ios

I am trying to use ReactiveCococa for network connection using NSInputStream & NSOutputStream . The connect code looks as follows:
-(RACSignal*) connect: (NSString *) url {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> theSubscriber) {
self.subscriber = theSubscriber;
// create inputStream and outputStream, initialize and open them
[self.inputStream open]
[self.outputStream open];
}];
return nil;
}
-(void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventHasBytesAvailable:
//read from input stream
NSArray * data = read_InputStream;
[subscriber sendNext:data];
break;
}
...
}
I have to store the value of subscriber and call sendNext on it, when I receive data in the callback method.
Is there a better way to handle this in ReactiveCocoa and avoid declaring subscriber property. Besides, this will not work with multiple subscribers.

You can use rac_signalForSelector to turn a delegate callback method into a signal. Then you can subscribe to this signal inside createSignal's didSubscribe block. Something like this:
- (RACSignal*)connect:(NSString *)url
{
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> theSubscriber)
{
// create inputStream and outputStream, initialize and open them
[self.inputStream open];
[self.outputStream open];
[[self rac_signalForSelector:#selector(stream:handleEvent:)
fromProtocol:#protocol(NSStreamDelegate)]
subscribeNext:^(RACTuple *tuple)
{
NSStream *aStream = (NSStream *)tuple.first;
NSStreamEvent *eventCode = (NSStreamEvent *)tuple.second;
// check eventCode here
NSArray *data = read_InputStream;
[theSubscriber sendNext:data];
}];
return nil;
}];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
}
When using rac_signalForSelector, the signal will pass the arguments from the method through as a tuple which you can then look at to decide what action to take.
Depending on what you're trying to achieve here, there are probably more elegant reactive solutions, but rac_signalForSelector at least solves the problem of needing the subscriber property.

Related

Main Thread blocking NSStream when checking if input is available on NSStream

I am working with a package called DSBridge to connect a mobile application (iOS code in this case) to javascript code containing the main logic of my application. This involves running the JavaScript on an invisible WKWebView.
My JavaScript code needs to call a method in the native iOS application which needs to execute and return a value to the JavaScript.
My Objective C function reads:
- (NSString* )read:(NSDictionary *) args{
NSLog(#"In Read");
while(self->connection != NULL) {
if([self->connection isMessageAvailable]) {
[self->connection messageRetrieved];
NSLog(#"Message Received in ViewController");
return [self->connection getMessage];
}
}
return NULL;
}
Which is called from my JavaScript, I need this to return on the main thread so that the return value is sent into my WebView.
The issue is that my the message from IO I am waiting for will never appear as my NSStream is blocked by the while loop.
My connection methods and NSStream look like this:
-(void)readData:(NSData*)receivedData{
NSData *decoded = [self btXOR:receivedData withMask:0x26];
message = [[NSString alloc] initWithData:decoded encoding:NSUTF8StringEncoding];
NSLog(#"message: %#", message);
messageAvailable = true;
}
- (BOOL) isMessageAvailable {
return messageAvailable;
}
- (void) messageRetrieved {
messageAvailable = false;
}
- (NSString*) getMessage {
NSLog(#"Message: %#", message);
return message;
}
#pragma mark StreamDelegate
-(void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode)
{
//Reading in from host done here
case NSStreamEventHasBytesAvailable:
DDLogInfo(#"NSStreamEventHasBytesAvailable");
DDLogInfo(#"stream is input: %i " , (theStream == inputStream));
NSLog(#"NSStreamEventHasBytesAvailable");
NSLog(#"stream is input: %i " , (theStream == inputStream));
if (theStream == inputStream)
{
uint8_t buffer[1024];
long len;
while ([inputStream hasBytesAvailable])
{
len = [inputStream read:buffer maxLength:sizeof(buffer)];
DDLogDebug(#"InputStream still has bytes");
NSLog(#"InputStream still has bytes");
if (len > 0)
{
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes: (const void *)buffer length:(int)len];
[self readData:data];
}
}
}
break;
The 'NSStreamEventHasBytesAvailable' case should occur once the message is ready, and should be picked up by my read function so it can be returned to my WebViews JavaScript.
How can I take the while loop off of the main thread and still return once I have the message? Is it possible to take the NSStream off the main runloop in a way that it will not be blocked by my read function?
I tried making read async and using a callback to the JavaScript as seen here, but this makes my JavaScript codebase rather unwieldy with callbacks.

what is the relationship between external accessory and protocol string

I have to say sorry before, my english's not good.
I'm working on a project related to "external accessory framework".
I need to fetch some data stream from our product through a "USB to lightning" adapter.
I've been spinning my wheel for several days.
My question here is "what's the exact way to use the framework".
I found something on apple dev forum:
pic
Does it mean that if i have a "USB to lightning" adapter and its protocol string(For now, we're not going to make our own adapter, we plan to work with another company), then I plug our product to it, my app would be notified of the attachment or removal of the accessory.
Am i right?
I'm confusing right now, cause a colleague of mine contacted a adapter vendor, and they told her there is NO such thing called "protocol string".
I guess for some reason they just don't want us to know.
thanks!
You have set up these notifications for connection. Make you make connection through BT or USB.
/* Setup notification monitor */
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleAccessoryConnectNotification:) name:EAAccessoryDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleAccessoryDisconnectNotification:) name:EAAccessoryDidDisconnectNotification object:nil];
/* Turn on EA notifications */
[[EAAccessoryManager sharedAccessoryManager] registerForLocalNotifications];
Once connection establishes you will get a call back with accessory info.
- (void) handleAccessoryConnectNotification:(NSNotification*)notification
{
NSDictonary *earInfo = notification.userInfo;
Print(#“Accessory Info: %#”,earInfo);
// Get connected Accessory object.
EAAccessory* currentAccessory = eaInfo[#"EAAccessoryKey"];
// Create Session with base Protocol with Accessory.
EASession* currentEASession = [[EASession alloc] initWithAccessory:currentAccessory forProtocol:“com.accessory.base”];
/* Open input and output steam */
if ( self.currentEASession.inputStream && self.currentEASession.outputStream)
{
[[currentEASession inputStream] setDelegate:self];
[[currentEASession inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[currentEASession inputStream] open];
[[currentEASession outputStream] setDelegate:self];
[[currentEASession outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[currentEASession outputStream] open];
}
}
- (void)handleAccessoryDisconnectNotification:(NSNotification*)notification {
Print(#“Accessory Info: %#”notification.userInfo);
}
# Read and Write data delegate method
- ( void ) stream: ( NSStream * ) aStream handleEvent: ( NSStreamEvent ) eventCode {
#try
{
switch ( eventCode )
{
case NSStreamEventOpenCompleted:{
Print(#“[%s] NSStreamEventOpenCompleted",__PRETTY_FUNCTION__);
break;
}
case NSStreamEventHasSpaceAvailable:
{
/* Write data */
break;
}
case NSStreamEventHasBytesAvailable:
{
//TODO
/* Read data */
static uint8_t buffer[65536];
NSInteger n = 65536;
while (n == 65536)
{
n = [self.currentEASession.inputStream read:buffer maxLength:65536];
}
break;
}
case NSStreamEventErrorOccurred:
{
//TODO
Print(#"[%s] NSStreamEventErrorOccurred",__PRETTY_FUNCTION__);
break;
}
case NSStreamEventEndEncountered:
{
//TODO
Print(#"[%s] NSStreamEventEndEncountered",__PRETTY_FUNCTION__);
break;
}
default:
{
break;
}
}
}
#catch (NSException *exception)
{
}
}
Relation between external accessory and protocol is.
When you connect iOS device with accessory. You have to create a session between accessory and iOS device to read and write data. Protocol string will identifier unique connection between accessory and iOS device for sharing and writing data.
Same time one accessory can be connected with multiple iOS devices. Each device will have a unique connection or session in between and it will identify through protocol.

Not getting more messages after receiving first message from Java server in iOS client using NSInputStream

I've got a Java server (and it's able to correctly read a request from my iOS client -- it even generates a response and appears to send it correctly, though I got First message response from server every time but not getting other messages after receiving first message):
sequence of communication
Step 1-> client send login message to server
Step 2-> server validate the user and sends login info to the clients
Step 3-> Message Packet1
Message Packet2
Message Packet3
Message Packet4
step-4-> I have checked server log and it says server has send 4 string
messages
Step-5 -> On client side I am receiving only first message i.e. Message
Packet1, and there no other packets on NSInputStream showing. or NSStreamEventHasBytesAvailable option in delegate method->
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
Not calling most of the time more than one but sometimes It calls and gets MessagePacket2 or MessagePacket4 data.
Please help me out, I am unable to figure out why I am receiving only first packet from server instead of 4 packets, as server sending 4 packets.
I have used code from the below tutorial ->
http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server#comments
My code is here->
#interface NetworkManager()<NSStreamDelegate>
#property (strong, nonatomic)NSInputStream *objInputStream;
#property (strong, nonatomic)NSOutputStream *objOutputStream;
#end
- (void)initializeNetworkCommunicationToServer
{
self.networkOpened = NO;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)SERVER_HOSTNAME,
SERVER_PORT_ADDR,
&readStream,
&writeStream);
self.objInputStream = (__bridge_transfer NSInputStream *)readStream;
self.objOutputStream = (__bridge_transfer NSOutputStream*)writeStream;
[self.objInputStream setDelegate:self];
[self.objOutputStream setDelegate:self];
[self.objInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objInputStream open];
[self.objOutputStream open];
}
//------------------------------------------------------
pragma NSStreamDelegate delegate method
//------------------------------------------------------
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
{
switch (streamEvent)
{
case NSStreamEventNone:
{
NSLog(#"NSStreamEventNone");
break;
}
case NSStreamEventOpenCompleted:
{
NSLog(#"NSStreamEventOpenCompleted");
}
break;
case NSStreamEventHasBytesAvailable:
{
NSLog(#"NSStreamEventHasBytesAvailable:");
if (theStream == self.objInputStream)
{
while ([self.objInputStream hasBytesAvailable])
{
uint8_t buffer[1024];
unsigned int len = 0;
len = [self.objInputStream read:buffer
maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output =
[[NSString alloc] initWithBytes:buffer
length:len
encoding:NSASCIIStringEncoding];
if (nil != output)
{
NSLog(#"server said: %#", output);
}
}//end of if(len > 0)
}//end of while
} //end of if (theStream == self.objInputStream)
}
break;
case NSStreamEventErrorOccurred:
{
NSLog(#"NSStreamEventErrorOccurred: Can not connect to the host!");
}
break;
case NSStreamEventEndEncountered:
{
NSLog(#"NSStreamEventEndEncountered & network connection ended");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
theStream = nil;
}
break;
// The NSStreamEventHasSpaceAvailable event indicates that you can write (at least one byte!) to the stream without blocking. That does not mean that previously written data is completely delivered to the other endpoint of the connection.
case NSStreamEventHasSpaceAvailable:
{
NSLog(#"NSStreamEventHasSpaceAvailable");
if(NO == self.networkOpened)
{
self.networkOpened = YES;
[self sendMessage:#"login:username,password"];
}
}
break;
default:
{
NSLog(#"Unknown event");
}
}
}
//------------------------------------------------------
#pragma mark - send packet
//------------------------------------------------------
- (void)sendMessage:(NSString*)lstrMessage
{
NSMutableData *data = [[NSMutableData alloc] initWithData:
[lstrMessage dataUsingEncoding:NSASCIIStringEncoding]];
unsigned char suffixBytes[] = {1, 1, 0};
[data appendBytes:suffixBytes length:3];
[self.objOutputStream write:[data bytes] maxLength:[data length]];
NSLog(#"message sent->%#",[NSString stringWithUTF8String:[data bytes]]);
}
I have no idea what is wrong with your code, but my experience with streams tuning tells me that there will be make sence to try another way to subscribe to Core Foundation streams using CF API instead of toll-free-bridging it to NSInputStream. I mean CFReadStreamSetClient and CFReadStreamScheduleWithRunLoop functions. You can see example how to do so in my helper class for testing POSInputStreamLibrary.

How to conditionally buffer RACSignal values?

I'm working on some code that interacts with a remote API via websockets. My data layer is responsible for establishing and monitoring the websocket connection. It also contains methods that can be used by the application to enqueue websocket messages to be sent. The application code should not be responsible for inspecting the state of the websocket connection, aka fire-and-forget.
Ideally, I'd like to data layer to function as follows:
When the data layer does not have a connection to the websocket endpoint (self.isConnected == NO), messages are buffered internally.
When a connection is becomes available (self.isConnected == YES), buffered messages are immediately sent, and any subsequent messages are sent immediately.
Here's what I've been able to come up with:
#import "RACSignal+Buffering.h"
#implementation RACSignal (Buffering)
- (RACSignal*)bufferWithSignal:(RACSignal*)shouldBuffer
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
NSMutableArray* bufferedValues = [[NSMutableArray alloc] init];
__block BOOL buffering = NO;
void (^bufferHandler)() = ^{
if (!buffering)
{
for (id val in bufferedValues)
{
[subscriber sendNext:val];
}
[bufferedValues removeAllObjects];
}
};
RACDisposable* bufferDisposable = [shouldBuffer subscribeNext:^(NSNumber* shouldBuffer) {
buffering = shouldBuffer.boolValue;
bufferHandler();
}];
if (bufferDisposable)
{
[disposable addDisposable:bufferDisposable];
}
RACDisposable* valueDisposable = [self subscribeNext:^(id x) {
[bufferedValues addObject:x];
bufferHandler();
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
if (valueDisposable)
{
[disposable addDisposable:valueDisposable];
}
return disposable;
}];
}
#end
Lastly, this is pseudo-code for how it would be used:
#interface APIManager ()
#property (nonatomic) RACSubject* requests;
#end
#implementation WebsocketDataLayer
- (id)init
{
self = [super init];
if (self) {
RACSignal* connectedSignal = RACObserve(self, connected);
self.requests = [[RACSubject alloc] init];
RACSignal* bufferedApiRequests = [self.requests bufferWithSignal:connectedSignal];
[self rac_liftSelector:#selector(sendRequest:) withSignalsFromArray:#[bufferedApiRequests]];
}
return self;
}
- (void)enqueueRequest:(NSString*)request
{
[self.requests sendNext:request];
}
- (void)sendRequest:(NSString*)request
{
DebugLog(#"Making websocket request: %#", request);
}
#end
My question is: Is this the right approach for buffering values? Is there a more idiomatic RAC way of handling this?
Buffering can be thought of as something that applies to individual requests, which leads to a natural implementation using -flattenMap: and RACObserve:
#weakify(self);
RACSignal *bufferedRequests = [self.requests flattenMap:^(NSString *request) {
#strongify(self);
// Waits for self.connected to be YES, or checks that it already is,
// then forwards the request.
return [[[[RACObserve(self, connected)
ignore:#NO]
take:1]
// Replace the property value with our request.
mapReplace:request];
}];
If ordering is important, you can replace -flattenMap: with -map: plus -concat. These implementations avoid the need for any custom operators, and work without manual subscriptions (which are notoriously messy).
You do almost exactly the same as what is implemented in the bufferWithTime: operation and I can't think of any existing operations that would implement it more idiomatically. (Probably this is the reason why bufferWithTime was implemented in this way.) Reviewing your code using that implementation may reveal some faults you didn't think of.
But to be honest, this should not be so hard. There should exist a buffering operation that buffers the output and spews the contents when the trigger signal fires. Probably most buffering can be implemented in terms of this functionality, so having it would add value to the framework.

Trying to use GCDAsyncSocket for buffered transfer

(UPDATED) I am trying to read a large file ( a video or a picture) and send it to a remote server via a SOAP request. I need to encode the data as a Base64 string. I am trying to do this as follows:
Create a template xml for the SOAP request that will go "around" the base64 encoded data
push the first part of the SOAP xml into a buffer
open the video file and encode it in chunks and push each encoded chunk into the buffer
finally, push the second part of the SOAP xml
To be able to "enqueue" parts as above, I am trying to use GCDAsyncSocket with its buffering capabilities. I figure that since GCDAsyncSocket operates on TCP level, I need to write the HTTP POST header myself. So, there are many moving parts which I only vaguely understand and I might be doing it all incorrectly. But my socket never seems to even take off and I am not even sure how to debug it. Here is my relevant code, try to see if you spot any obvious errors:
NSString *soapBody = ...; //Create the SOAP xml here with the part in the middle reserved for the Base64 encoded data (marked with string "CUT_HERE";
NSArray *soapBodyArray = [soapBody componentsSeparatedByString:#"CUT_HERE"];
self.p_soapBodyPart1 = [soapBodyArray[0] dataUsingEncoding:NSUTF8StringEncoding];
self.p_soapBodyPart2 = [soapBodyArray[1] dataUsingEncoding:NSUTF8StringEncoding];
socketQueue = dispatch_queue_create("socketQueue", NULL);//socketQueue is an ivar
self.p_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];//create the socket
NSError *err = nil;
if (![p_socket connectToHost:myURL onPort:80 error:&err]) // Asynchronous!
{
NSLog(#"I goofed: %#", err);
return;
}
NSString* httpHeader = [NSString stringWithFormat:#"POST %# HTTP/1.1\r\nHost: %#\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: text/xml\r\nAccept-Language: en-us\r\nAccept: */*\r\nSOAPAction: http://tempuri.org/myAction\r\nConnection: keep-alive\r\nUser-Agent: ...\r\n\r\n", webserviceOperationsPath, webserviceHost];//Create the HTTP POST header
[p_socket writeData:[httpHeader dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:1]; //enqueue the HTTP header
[p_socket writeData:self.p_soapBodyPart1 withTimeout:-1 tag:2]; //enqueue the first part of the SOAP xml
[self setUpStreamsForInputFile: [self.p_mediaURL path]];//set up NSInputStream to read from media file and encode it as Base64
The socket seems to always connect all right, which I see using this delegate method:
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(#"Socket Connected");
}
setUpStreamsForInputFile method (that is called in the first code listing above):
- (void)setUpStreamsForInputFile:(NSString *)inpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^ {
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
[[NSRunLoop currentRunLoop] run];
});
}
Now, the NSInputStream setup in the previous method will send events to this delegate:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if (stream == self.p_iStream){
if(!self.p_tempMutableData) {
self.p_tempMutableData = [NSMutableData data];
}
uint8_t buf[24000];
unsigned int len = 0;
len = [p_iStream read:buf maxLength:24000];//read a chunk from the file
if(len) {
[p_tempMutableData appendBytes:(const void *)buf length:len];
NSString* base64encData = [Base64 encodeBase64WithData:self.p_tempMutableData];//encode the chunk
self.p_streamEncData = [base64encData dataUsingEncoding:NSUTF8StringEncoding];
[p_socket writeData:self.p_streamEncData withTimeout:-1 tag:3];//write the encoded chunk to the socket
}
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream = nil;
[p_socket writeData:self.p_soapBodyPart2 withTimeout:-1 tag:4];//write the second part of SOAP xml
break;
}
... //some other events handled here
}
}
The socket is supposed to output things to the log with this delegate
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if (tag == 1)
NSLog(#"HTTP Header Written");
else if (tag == 2)
NSLog(#"Soap Part 1 written");
else if (tag == 3)
NSLog(#"File written");
else if (tag == 4)
NSLog(#"Soap Part 2 written");
}
but this happens kind of randomly. For example, sometimes I see the first 2 if's called and sometimes not. When I do and it reaches the third "if" (the one where I am writing the actual encoded data), it writes it only 2 or 3 times and that's it - too few times, I think, given the size of the file. I never see it reach the last "if", where it should write the last part of SOAP xml.
Would appreciate any help! Thanks in advance.
Further update (3/19/13)
Today testing the socket I am no longer getting the write events at all, which tells me that it is random and I am doing something terribly wrong. Today the connection opens but then times out at some point, as I can see with the following delegate method:
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{ // This method is executed on the socketQueue (not the main thread)
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSLog(#"socketDidDisconnect:withError: \"%#\"", err);
}
});
}
which returns
socketDidDisconnect:withError: "Error Domain=NSPOSIXErrorDomain Code=60 "Operation timed out" UserInfo=0x1cd89b00 {NSLocalizedFailureReason=Error in connect() function, NSLocalizedDescription=Operation timed out}"
while I am still running writes of Base64 data in the stream delegate above.

Resources