I am new to ReactiveCocoa and trying to understand , how I can model the following example using reactive cocoa commands: I am trying to read bytes from stream and this is done in the function "readStream". I want to prevent further calls to readStream , until I am done with the first batch of reading. As shown, I am using the "reading" flag, which is set when reading starts and is reset when reading is done. I would like to avoid using the flag "reading", can this be coded in a better way using reactive cocoa.
- (void) stream:(NSStream *) aStream handleEvent:(NSStreamEvent)eventCode {
..
case NSStreamEventHasBytesAvailable:
if (!self.reading){
self.reading = YES; //
[self readStream]; // Reads bytes from the stream
}
break;
- (void) readStream {
// Read data
self.reading = NO;
}
RACCommands enables button until the returned signal complete, I'm not sure the context with your code, if it locates in lower level global singleton component, you still need keep the flag.
Related
I would like to make a function that only run once and cancel if is still running.
I tried it using a simple lock boolean on start/end, but sometimes it's "overlapping".
There's a better and secure way to do that?
#property (assign) BOOL lock;
- (void)myFuntion
{
if (self.lock) {
NSLog(#"(Canceled) Syncing is already running...");
return;
}
self.lock = YES;
// My Code
self.lock = NO;
}
The NSLock class should be able to help you here. I have not tried this example directly, but something like:
NSLock *myFunctionLock=[NSLock new]; // this should be a class data member/property/etc.
- (void)myFuntion
{
if (![myFunctionLock tryLock])
return; /* already running */
// My Synchronized Code
[myFunctionLock unlock];
}
We are all assuming that you are talking about concurrent programming, where you are running the same code on different threads. If that's not what you mean then you would need to explain what you DO mean, since code that runs on the same thread can only execute a function once at any particular moment.
Take a look at NSLock's tryLock function. The first caller to assert the lock gets back a TRUE, and can proceed to access the critical resource. Other callers get back FALSE and should not access the critical resource, but won't block.
Basic task
I have some multiplatform library which is using some C++ stream interface. I have to use this stream interface to upload data by NSURLSession. My implementation should work on OS X and iOS (currently I'm testing on OS X)
What I did
Task looks quite simple and I was sure I will implement this quite fast.
I have configured NSURLSession which is working fine if I'm using NSURLRequest with simple NSData.
I'm trying to use stream like this:
NSURLSessionDataTask *dataTask = [m_Private.session uploadTaskWithStreamedRequest: request];
HTTPDownoadTaskProxy *dataTaskProxy = [HTTPDownoadTaskProxy new];
// store data to properly handle delegate
dataTaskProxy.coreTask = dataTask;
dataTaskProxy.cppRequest= req;
dataTaskProxy.cppResponseHandler = handler;
dataTaskProxy.cppErrorHandler = errorHandler;
m_Private.streamedDataTasks[dataTask] = dataTaskProxy;
[dataTask resume];
So far so good. According to documentation of uploadTaskWithStreamedRequest I should receive notification from delegate and I do receive it:
- (void)URLSession: (NSURLSession *)session
task: (NSURLSessionTask *)task
needNewBodyStream: (void (^)(NSInputStream *bodyStream))completionHandler
{
HTTPDownoadTaskProxy *proxyTask = self.streamedDataTasks[task];
CppInputStreamWrapper *objcInputStream = [[CppInputStreamWrapper alloc] initWithCppInputStream:proxyTask.cppRequest.GetDataStream()];
completionHandler(objcInputStream);
}
Now I should receive calls in subclass of NSInputStream which is in my case CppInputStreamWrapper, and also it is quite simple:
#implementation CppInputStreamWrapper
- (void)dealloc {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
- (instancetype)initWithCppInputStream: (const std::tr1::shared_ptr<IInputStream>&) cppInputStream
{
if (self = [super init]) {
_cppInputStream = cppInputStream;
}
return self;
}
#pragma mark - overrides for NSInputStream
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
return (NSInteger)self.cppInputStream->Read(buffer, len);
}
- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len {
return NO;
}
- (BOOL)hasBytesAvailable {
return !self.cppInputStream->IsEOF();
}
#pragma mark - this methods are need to be overridden to make stream working
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
#pragma mark - Undocumented CFReadStream Bridged Methods
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop
forMode:(__unused CFStringRef)aMode
{}
- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags
callback:(__unused CFReadStreamClientCallBack)inCallback
context:(__unused CFStreamClientContext *)inContext {
return NO;
}
#end
So I'm using workaround needed when subclassing NSInputStream.
Problem
Now this should work. But I'm not receiving any call of methods of CppInputStreamWrapper (except for my call when construction object).
No errors no warning are reported, nothing!
When I've added exception breakpoint I'm catching
thread #8: tid = 0x155cb3, 0x00007fff8b770743 libobjc.A.dylib`objc_exception_throw, name = 'com.apple.NSURLConnectionLoader', stop reason = breakpoint 1.1
This comes from thread com.apple.NSURLConnectionLoader which I didn't create.
I'm totally puzzled and have no idea what else I can do.
Update
I've used code form link in comment which is hosted on github.
Now at least some parts of my class are invoked by framework, but I see strange crash.
Crash is located in this method:
- (BOOL)_setCFClientFlags:(CFOptionFlags)inFlags
callback:(CFReadStreamClientCallBack)inCallback
context:(CFStreamClientContext *)inContext {
if (inCallback != NULL) {
requestedEvents = inFlags;
copiedCallback = inCallback;
memcpy(&copiedContext, inContext, sizeof(CFStreamClientContext));
if (copiedContext.info && copiedContext.retain) {
copiedContext.retain(copiedContext.info);
}
copiedCallback((__bridge CFReadStreamRef)self, kCFStreamEventHasBytesAvailable, &copiedContext); // CRASH HERE
} else {
requestedEvents = kCFStreamEventNone;
copiedCallback = NULL;
if (copiedContext.info && copiedContext.release) {
copiedContext.release(copiedContext.info);
}
memset(&copiedContext, 0, sizeof(CFStreamClientContext));
}
return YES;
}
Crash is EXC_BAD_ACCESS (when running tests on OS X). when I see this code everything looks fine. It should work! self is pointing to proper object with retain count 3 so I have no idea why it is crashing.
Undocumented private bridging API is not the only problem in custom NSInputStream implementation especially in the context of CFNetworking integration. I'd like to recommend to use my POSInputStreamLibrary as basic building block. Instead of implementing a lot of NSInputStream methods and supporting async notifications you should implement much simpler POSBlobInputStreamDataSource interface. At least you can look at POSBlobInputStream to consult what kind of functionality you should implement to support NSInputStream contract completely.
POSInputStreamLibrary is used in the most popular Russian cloud storage service Cloud Mail.Ru and uploads >1M files per day without any crashes.
Good luck and feel free to ask any questions.
I see you do have implementations of the undocumented CFReadStream bridge methods -- that is one of the more common issues. However... note the comment in the NSStream.h header for the NSStream class:
// NSStream is an abstract class encapsulating the common API to NSInputStream and NSOutputStream.
// Subclassers of NSInputStream and NSOutputStream must also implement these methods.
That means you also need to implement -open, -close, -propertyForKey:, -streamStatus, etc. -- every method that is declared on NSStream and NSInputStream, basically. Try calling -open yourself in your code (which NSURLConnection will eventually do) -- you will get the idea since it should crash right there. You will probably need at least some minimal status handling so that -streamStatus does not return NSStreamStatusNotOpen after -open is called, for example. Basically, every concrete subclass needs to implement the entirety of the API. It's not like a normal class cluster where just a couple of core methods need to be overridden -- even the -delegate and -setDelegate: methods must be implemented (the superclass does not have instance variable storage for it, I'm pretty sure).
AFNetworking has an internal AFMultipartBodyStream which has the minimal implementations needed -- you can see that example inside AFURLRequestSerialization.m. Another code example is HSCountingInputStream.
I'am sending chunks of UIImage data over MCSession with an NSStream.
When I get bytes
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
if (eventCode == NSStreamEventHasBytesAvailable) {
// read data and append to self.data
// how to know that self.data can be used to create UIImage
}
}
I append them to a mutable data instance. The problem is how to know that the accumulated data represents a full image, so I can use -[UIImage initWithData:] to create it?
You should watch for NSStreamEventEndEncountered
The stream has no knowledge of its contents. If you can't rely on the stream ending to tell you that the data is complete, then you either need to use/create some protocol for the transmission that includes a "finished" signal, or just try to create the image and take appropriate action if that fails.
This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}
I'm trying to print a label with a Star Micronics TSP650II printer in a monotouch app.
The problem is that session.OutputStream.HasSpaceAvailable() always returns false. What am I missing?
the C# code I have goes something like this (cut for simplicity):
var manager = EAAccessoryManager.SharedAccessoryManager;
var starPrinter = manager.ConnectedAccessories.FirstOrDefault (p => p.Name.IndexOf ("Star") >= 0); // this does find the EAAccessory correctly
var session = new EASession (starPrinter, starPrinter.ProtocolStrings [0]); // the second parameter resolves to "jp.star-m.starpro"
session.OutputStream.Schedule (NSRunLoop.Current, "kCFRunLoopDefaultMode");
session.OutputStream.Open ();
byte[] toSend = GetInitData(); // this comes from another project where the same printer with ethernet cable was used in a windows environment and worked, not null for sure
if (session.OutputStream.HasSpaceAvailable()) {
int bytesWritten = session.OutputStream.Write (toSend, (uint)stillToSend.Length);
if (bytesWritten < 0) {
Debug.WriteLine ("ERROR WRITING DATA");
} else {
Debug.WriteLine("Some data written, ignoring the rest, just a test");
}
} else
Debug.WriteLine ("NO SPACE"); // THIS ALWAYS PRINTS, the output stream is never ready to take any output
UPDATE:
I was able to work-around this problem by binding Star Micronics iOS SDK to my project, but that's less than ideal as it adds 700K to the package for something that should work without that binding.
UPDATE 2:
I've been getting requests for the binding code. I still strongly recommend you try to figure out the bluetooth connectivity and not use the binding but for those who are brave enough, here it is.
This is Kale Evans, Software Integration Engineer at Star Micronics.
Although Apple's EADemo doesn't show this, the following piece of code below is important for printing to EAAccessory.(Note, below code is Objective-C example).
if ([[_session outputStream] hasSpaceAvailable] == NO)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
This gives OS time to process all input sources.
You say this does find the EAAccessory correctly
Could this be the reason the OutputStream returns false if the session is actually null?
Best Regards,
Star Support