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
...
...
Related
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 would i go about stopping my timer whenever I try to do [myTimer invalidate]; I get an error.
My Timer:
NSTimer *MyTimer;
MyTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(handleTimer)
userInfo: nil
repeats: YES];
Here's the if statement I have set up for the timer.
-(void)handleTimer{
if (count >= 0) {
[secondsLeft setText:[NSString stringWithFormat:#"%ld",(long)count]]; //displays current value of count to UI
count--; //Incrementally decreases count
} else if(count <= 0) { //if the timer runs out then...
self.secondsLeft.text = #"Times's Up!";
[answerButtonOutlet setEnabled:NO];
self.secondsLeftToAnswer.text = #"Answer is 30";
} else if([self.secondsLeftToAnswer.text isEqual: #"Correct!"]) { //else if statement if user gets answer Correct.
[myTimer invalidate]; // error occurs here. "Unknown receiver 'myTimer': did you mean NSTimer?
}
}
How would I make it so that I don't get the error in the second else if statement? I'm brand new to coding so I'm sure its probably pretty obvious but I can't figure it out for the life of me. Thanks!
Since NSTimer *MyTimer;
[myTimer invalidate]; should be [MyTimer invalidate];
You're better off just using the timer argument that gets passed to the handler method. In other words, you need an extra colon in the selector to indicate that the handler takes an argument
selector: #selector(handleTimer:) // note the colon at the end
Then your handler looks like this
-(void)handleTimer:(NSTimer *)theTimer
{
...
else if([self.secondsLeftToAnswer.text isEqual: #"Correct!"]) {
[theTimer invalidate];
}
}
You have [myTimer invalidate], when you should really have:
[MyTimer invalidate];
MyTimer = nil;
Try this change and let me know what happens. Also, throw an NSLog in that else if to make sure it's firing.
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).
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.
I have an NSDocument based app in which I want to limit the number of documents open at the same time (for a Lite version). I just want to have n documents, and if the user tries to open more than n, show a message with a link to the full app download.
I have managed to count the number of documents using NSDocumentController and, inside readFromFileWrapper, I can return FALSE. That prevents the new doc from opening, but it shows a standard error message. I don't know how to avoid that. I would like to open a new window from a nib.
Is there any way to prevent NSDocument showing the standard error message when returning FALSE from readFromFileWrapper? Or is there any other way to prevent the document from opening before readFromFileWrapper is called?
Try the init method, which is called both when creating a new document and when opening a saved document. You simply return nil if the limit has been reached. However, I have not tried this, and it might cause the same error to be displayed.
- (id)init {
if([[NSDocumentController documents] count] >= DOCUMENT_LIMIT) {
[self release];
return nil;
}
...
}
In case the same error is displayed, you could use a custom NSDocumentController. Your implementations would check the number of open documents, display the message at the limit, and call the normal implementation otherwise.
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError {
if([[self documents] count] >= DOCUMENT_LIMIT) {
// fill outError
return nil;
}
return [super openUntitledDocumentAndDisplay:displayDocument error:outError];
}
- (id)openDocumentWithContentsOfURL:(NSURL *)absoluteURL display:(BOOL)displayDocument error:(NSError **)outError {
NSDocument *doc = [self documentForURL:absoluteURL];
if(doc) { // already open, just show it
[doc showWindows];
return doc;
}
if([[self documents] count] >= DOCUMENT_LIMIT) {
// fill outError
return nil;
}
return [super openDocumentWithContentsOfURL:absoluteURL display:displayDocument];
}