using NSOperationQueue and blocks - cocoa

I am having a little trouble using addoperationwithblock in Cocoa. Let's say I have a master function
-(IBAction) callthisone {
// Call another function "slave" here and store returned value in result
result = return value from slave
NSLog(#" result is %#",result);
}];
}
-(NSArray *) slave {
[operationQueue addOperationWithBlock: ^{
NSString * result = #"5" ;
}];
return result;
}
I can never get the result value returned in the master. How do I do this ? Is my approach correct ? Thanks

You may try something like this:
-(IBAction) callthisone {
[self slave: ^(NSString* result) {
NSLog(#" result is %#",result);
}
];
}
-(void)slave: (void(^)(NSString*)) callback {
[operationQueue addOperationWithBlock: ^{
NSString* str = [NSString stringWithFormat: #"5]";
callback(str);
}
];
}

Apple's documentation for addOperationWithBlock says:
Parameters
The block to execute from the operation object. The block should take
no parameters and have no return value.
These are meant for self contained block operations.
Could you try something different that does have more flexibility in terms of getting stuff in and out of the queue / thread? Maybe Grand Central Dispatch (I was just looking at this thread).

Related

Lazy `SignalProducer` that fetches more data asynchronously when all data has been consumed

Let's imagine that we can fetch a fixed number of messages asynchronously (one request, containing N elements)
func fetchMessages(max: UInt, from: Offset) -> SignalProducer<Message,NoError>
Now, I'd like to turn this into an unbounded SignalProducer that will lazily call fetchMessages when the previous stream completes.
func stream(from: Offset) -> SignalProducer<Message, NoError> {
// challenge is to implement this function
}
An initial idea that could work, but that would still require pre-computing all the ranges would be to genericize the following code
func lazyFetchFrom(from: Offset) -> SignalProducer<Message,NoError> {
return SignalProducer<Message,NoError> { (observer, disposable) in
fetchMessages(from).start(observer)
}
}
let lazyStream =
fetchMessages(1000, from)
.concat(lazyFetchFrom(from + 1000))
.concat(lazyFetchFrom(from + 2000))
.... // could probably be done generically using a flatMap
Now, I'd like to go one step further and evaluate the next call to lazyFetchFrom once the previous values have been consumed. Is that possible?
Thanks
PS: to be clear, my main concern is to provide some sort of backpressure so that the producer doesn't produce too quickly compared to the consumer
Edit: here is my latest attempt at implementing some backpressure. However, when we observeOn the signal, the backpressure disappears, and everything is queued in memory
- (RACSignal *)allContentFromId:(NSInteger)contentId afterDate:(NSDate *)date fetchSize:(NSInteger)fetchSize {
RACSignal *signalNextPagination = [self nextPaginationAllContentFromId:contentId afterDate:date fetchSize:fetchSize];
//in signalNextPagination will be send next fetch size data and send complete only after downloaded all data
//so we used aggregate
return [signalNextPagination aggregateWithStart:#[] reduce:^id(NSArray *before, NSArray *next) {
return [before arrayByAddingObjectsFromArray:next];
}];
}
- (RACSignal *)nextPaginationAllContentFromId:(NSInteger)contentId afterDate:(NSDate *)date fetchSize:(NSInteger)fetchSize {
//command will be send one fetch request
//after recv data in command need try size fetch
//if size eq fetch size, so need repeat command with new offset
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSDate *date) {
return [self requestContentFromId:contentId afterDate:date limit:fetchSize];
}];
command.allowsConcurrentExecution = YES;
RACSignal *download = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[command.executionSignals flattenMap:^RACStream *(id value) {
return value;
}] subscribeNext:^(NSArray *datas) {
[subscriber sendNext:datas];
if ([datas count] == fetchSize) {
NSDate *date = [[datas firstObject] pubDate];
[command execute:date];
} else {
[subscriber sendCompleted];
}
} error:^(NSError *error) {
[subscriber sendError:error];
[subscriber sendCompleted];
}];
[command execute:date];
return nil;
}];
return download;
}

Wait for asynchronous block to finish - signal (seems) do not work

I am using cryptotokenkit to send/receive data from smart card. The use case is, I must have the response from the card API before I do something else.
In my case I found that this line is always called after kMaxBlockingTimeSmartCardResponse (= 10) seconds.
From
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
it directly goes to
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kMaxBlockingTimeSmartCardResponse * NSEC_PER_SEC));
dispatch_semaphore_wait(sema, timeout);
Then wait for 10 seconds to come to the block call. Its not the block callback delaying. If I set the dispatch time 20 or 30 seconds, it wait for 20 or 30 seconds to execute. The wait call really wait for the specified time then callback block is executed.
What I am doing wrong? I wonder if this is related to adObserver.
- (void) updateSlots {
self.slotNames = [self.manager slotNames];
self.slotModels = [NSMutableArray new];
self.slots = [NSMutableArray new];
if([self.slotNames count] > 0)
{
for (NSString *slotName in self.slotNames)
{
NSLog(#"SmartCard reader found: %#", slotName);
// semaphore BLOCK starts
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self.manager getSlotWithName:slotName reply:^(TKSmartCardSlot *slot)
{
if (slot) {
SCSlotModel *slotModel = [SCSlotModel new];
[self.slotModels addObject:slotModel];
[self.slots addObject:slot];
[slot addObserver:self forKeyPath:#"state"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial
context:nil];
slotModel.suffixedName = slotName; // NOT slot.name; original name is suffixed with 01, 02 etc.
slotModel.slot = slot;
slotModel.cardStatus = [CardUtil mapCardStatus:slot.state];
DLog(#"slot: %#, slotmodel: %#",slot.name, slotModel);
} else {
NSLog(#"Did not find slot with name: %#", slotName);
}
dispatch_semaphore_signal(sema);
}];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kMaxBlockingTimeSmartCardResponse * NSEC_PER_SEC));
dispatch_semaphore_wait(sema, timeout);
// semaphore BLOCK ends
self.errorCode = SCPNoError;
}
} else {
NSLog(#"No slot available.");
self.errorCode = SCPErrorReaderNotFound;
}
}
getSlotWithName is the method from TKSmartCardSlotManager
/// Instantiates smartcard reader slot of specified name. If specified name is not registered, returns nil.
- (void)getSlotWithName:(NSString *)name reply:(void(^)(TKSmartCardSlot *__nullable slot))reply;
But in other places it works as expected, for the same type of asynchronous calls.
- (BOOL) beginSession:(TKSmartCard *)card
{
__block BOOL response = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[card beginSessionWithReply:^(BOOL success, NSError *error) {
response = success;
if (!success) {
NSLog(#"Could not begin session.");
}
if (error) {
NSLog(#"Could not begin session with error %#", error);
}
dispatch_semaphore_signal(sema);
}];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kMaxBlockingTimeSmartCardResponse * NSEC_PER_SEC));
dispatch_semaphore_wait(sema, timeout);
return response;
}
What thread does getSlotWithName:reply: call its reply handler back on?
If for example updateSlots is executed on the main thread and you are doing something like this:
... // some async operation in `getSlotWithName:reply:` has completed
dispatch_async(dispatch_get_main_queue(), ^{
reply(slot)
});
then you would have ended up queueing your reply on the main thread however the main thread is currently locked with your semaphore.
Ensuring you call dispatch_semaphore_signal() from a thread other than the one you have locked should fix your issue.
Side Note: GCD and semaphores do the trick here but there are better ways you could perform actions after an async operation completes rather than letting a thread get locked up.. For example, delegate callbacks.
Not got enough information here to suggest anything exact but there are other options out there :)

How to wait [NSWorkspace recycleURLs] completion synchronously

I need to move files on trash using NSWorkspace recycleURLs but I must wait completion, putting all code in the completionHandler isn't so simple, mainly because the method containing the call to recycleURLs must return a value.
I've found a dirty way to achieve the result but I would know if exists a better solution.
My solution uses a conditional variable and runloop
The code runs in a separated thread, not in main thread
__block BOOL waitCompletion = YES;
void (^myCompletionHandler)(NSDictionary *newURLs, NSError *error) =
^(NSDictionary *newURLs, NSError *recycleError) {
// do some stuff and before exits from method change waitCompletion value
waitCompletion = NO;
};
[[NSWorkspace sharedWorkspace] recycleURLs:myURLToDelete
completionHandler:myCompletionHandler];
// loop until completionHandler finishes
while (waitCompletion && CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2.0, true)) {
; //nop
}
// we can continue after recycleURLs
...
...

Execute Applescript from Cocoa App with params

I would like to know how to execute an applescript from a cocoa application passing parameters.
I have seen how easy is to execute applescripts with no parameters in other questions here at stackoverflow, however the use NSAppleScript class, in which, i haven't seen no method that solve my problem. Does anyone have any idea.
I would like a Cocoa code with the same effect o this shell:
osascript teste.applescript "snow:Users:MyUser:Desktop:MyFolder" "snow:Users:MyUser:Desktop:Example:"
So it may run this AppleScript.
on run argv
set source to (item 1 of argv)
set destiny to (item 2 of argv)
tell application "Finder" to make new alias file at destiny to source
0
end run
Any help is appreciated. Thanks in advance.
Look at my GitHub repository, I have a category of NSAppleEventDescriptor that makes it much easier to create NSAppleEventDescriptor to call different AppleScript procedures with arguments, and coercion to and from many AppleScript typed.
NSAppleEventDescriptor-NDCoercion
I found easier to follow this piece code. I took a code from here and modified it to my purpose.
- (BOOL) executeScriptWithPath:(NSString*)path function:(NSString*)functionName andArguments:(NSArray*)scriptArgumentArray
{
BOOL executionSucceed = NO;
NSAppleScript * appleScript;
NSAppleEventDescriptor * thisApplication, *containerEvent;
NSURL * pathURL = [NSURL fileURLWithPath:path];
NSDictionary * appleScriptCreationError = nil;
appleScript = [[NSAppleScript alloc] initWithContentsOfURL:pathURL error:&appleScriptCreationError];
if (appleScriptCreationError)
{
NSLog([NSString stringWithFormat:#"Could not instantiate applescript %#",appleScriptCreationError]);
}
else
{
if (functionName && [functionName length])
{
/* If we have a functionName (and potentially arguments), we build
* an NSAppleEvent to execute the script. */
//Get a descriptor for ourself
int pid = [[NSProcessInfo processInfo] processIdentifier];
thisApplication = [NSAppleEventDescriptor descriptorWithDescriptorType:typeKernelProcessID
bytes:&pid
length:sizeof(pid)];
//Create the container event
//We need these constants from the Carbon OpenScripting framework, but we don't actually need Carbon.framework...
#define kASAppleScriptSuite 'ascr'
#define kASSubroutineEvent 'psbr'
#define keyASSubroutineName 'snam'
containerEvent = [NSAppleEventDescriptor appleEventWithEventClass:kASAppleScriptSuite
eventID:kASSubroutineEvent
targetDescriptor:thisApplication
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
//Set the target function
[containerEvent setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:functionName]
forKeyword:keyASSubroutineName];
//Pass arguments - arguments is expecting an NSArray with only NSString objects
if ([scriptArgumentArray count])
{
NSAppleEventDescriptor *arguments = [[NSAppleEventDescriptor alloc] initListDescriptor];
NSString *object;
for (object in scriptArgumentArray) {
[arguments insertDescriptor:[NSAppleEventDescriptor descriptorWithString:object]
atIndex:([arguments numberOfItems] + 1)]; //This +1 seems wrong... but it's not
}
[containerEvent setParamDescriptor:arguments forKeyword:keyDirectObject];
[arguments release];
}
//Execute the event
NSDictionary * executionError = nil;
NSAppleEventDescriptor * result = [appleScript executeAppleEvent:containerEvent error:&executionError];
if (executionError != nil)
{
NSLog([NSString stringWithFormat:#"error while executing script. Error %#",executionError]);
}
else
{
NSLog(#"Script execution has succeed. Result(%#)",result);
executionSucceed = YES;
}
}
else
{
NSDictionary * executionError = nil;
NSAppleEventDescriptor * result = [appleScript executeAndReturnError:&executionError];
if (executionError != nil)
{
NSLog([NSString stringWithFormat:#"error while executing script. Error %#",executionError]);
}
else
{
NSLog(#"Script execution has succeed. Result(%#)",result);
executionSucceed = YES;
}
}
}
[appleScript release];
return executionSucceed;
}
Technical Note TN2084
Using AppleScript Scripts in Cocoa Applications
Even though your application is written in Objective-C using Cocoa, you can use AppleScript scripts to perform certain operations. This Technical Note explains how to integrate and execute AppleScripts from within your Cocoa application. It discusses how to leverage the NSAppleScript class and the use of NSAppleEventDescriptor to send data to the receiver.
https://developer.apple.com/library/archive/technotes/tn2084/_index.html
https://applescriptlibrary.files.wordpress.com/2013/11/technical-note-tn2084-using-applescript-scripts-in-cocoa-applications.pdf
Swift 4 version, modified from the code here:
https://gist.github.com/chbeer/3666e4b7b2e71eb47b15eaae63d4192f
import Carbon
static func runAppleScript(_ url: URL) {
var appleScriptError: NSDictionary? = nil
guard let script = NSAppleScript(contentsOf: url, error: &appleScriptError) else {
return
}
let message = NSAppleEventDescriptor(string: "String parameter")
let parameters = NSAppleEventDescriptor(listDescriptor: ())
parameters.insert(message, at: 1)
var psn = ProcessSerialNumber(highLongOfPSN: UInt32(0), lowLongOfPSN: UInt32(kCurrentProcess))
let target = NSAppleEventDescriptor(descriptorType: typeProcessSerialNumber, bytes: &psn, length: MemoryLayout<ProcessSerialNumber>.size)
let handler = NSAppleEventDescriptor(string: "MyMethodName")
let event = NSAppleEventDescriptor.appleEvent(withEventClass: AEEventClass(kASAppleScriptSuite), eventID: AEEventID(kASSubroutineEvent), targetDescriptor: target, returnID: AEReturnID(kAutoGenerateReturnID), transactionID: AETransactionID(kAnyTransactionID))
event.setParam(handler, forKeyword: AEKeyword(keyASSubroutineName))
event.setParam(parameters, forKeyword: AEKeyword(keyDirectObject))
var executeError: NSDictionary? = nil
script.executeAppleEvent(event, error: &executeError)
if let executeError = executeError {
print("ERROR: \(executeError)")
}
}
For running the apple script:
on MyMethodName(theParameter)
display dialog theParameter
end MyMethodName
I'm not all too familiar with AppleScript, but I seem to remember that they are heavily based on (the rather crappy) Apple Events mechanism which dates back to the days where the 56k Modem was the coolest Gadget in your house.
Therefore I'd guess that you're looking for executeAppleEvent:error: which is part of NSAppleScript. Maybe you can find some information on how to encapsulate execution arguments in the instance of NSAppleEventDescriptor that you have to pass along with this function.

Wrong callback in Objective-C when calling SOAP service

I have 2 SOAP services that I want to call from an IPad app.
One is used to Log the user in (SecurityASMX), the other is one that returns the current username (SecuredCalls) once logged in.
I can call the SecurityASMX no problem using the following code. The Async call callback is operation :
- (IBAction) OnButtonClick:(id) sender {
bindingSecurity = [[SecurityASMXSvc SecurityASMXSoapBinding] initWithAddress:#"http://myserver/Azur.IPADTest.Web.Services/public/Security.asmx"];
bindingSecurity.logXMLInOut = YES;
SecurityASMXSvc_Login *requestLogin = [[SecurityASMXSvc_Login alloc] init];
requestLogin.strUsername = #"test";
requestLogin.strPassword = #"testpass";
[bindingSecurity LoginAsyncUsingParameters:requestLogin delegate:self];
[requestLogin release];
self.label.text = #"Login in progress";
}
- (void) operation:(SecurityASMXSoapBindingOperation *)operation completedWithResponse:(SecurityASMXSoapBindingResponse *)response
{
[NSThread sleepForTimeInterval:2.0];
self.label.text = #"Login Done!";
}
This works fine.
However, in the same code file, I have a binding to my second web service to return the username with the following code. The async call callback is operationSecure :
- (IBAction) OnButtonSecureCallClick:(id) sender {
bindingSecuredCalls = [[SecureCallsSvc SecureCallsSoapBinding] initWithAddress:#"http://myserver/Azur.IPADTest.Web.Services/private/SecureCalls.asmx"];
bindingSecuredCalls.logXMLInOut = YES;
SecureCallsSvc_ReturnUserName *requestReturnUserName = [[SecureCallsSvc_ReturnUserName alloc] init];
[bindingSecuredCalls ReturnUserNameAsyncUsingParameters:requestReturnUserName delegate:self];
[requestReturnUserName release];
self.label.text = #"Get UserName In Progress";
}
- (void) operationSecure:(SecureCallsSoapBindingOperation *)operation completedWithResponse:(SecureCallsSoapBindingResponse *)response
{
[NSThread sleepForTimeInterval:2.0];
self.label.text = #"Get Username Done!";
}
The problem is that when the call to ReturnUserName returns, the method that gets called is the one for the login (operation) and not the one I want (operationSecure).
How can I tell my second webservice binding to call the second callback?
Thanks!
First thing would be to check if the API you're using (I assume it's a third party API) allows you to specify the callback method.
If not, you can work with the operation parameter and use isKindOfClass to see what is actually being passed.
- (void) operation:(SecurityASMXSoapBindingOperation *)operation completedWithResponse:(SecurityASMXSoapBindingResponse *)response
{
[NSThread sleepForTimeInterval:2.0];
if([operation isKindOfClass:[SecurityASMXSoapBindingOperation class]])
{
self.label.text = #"Login Done!";
}
else if([operation isKindOfClass:[SecureCallsSoapBindingOperation class]])
{
self.label.text = #"Get Username Done!";
}
}
Ideally you'd set the type of operation and response parameters to be the superclass of the respective objects returned.

Resources