Reputation: 2278
I am quite humbled to admit that I have no clue about blocks.
I am writing a GUI uninstaller app that uses a privileged helper tool delete all of my product's files and directories.
Because the helper is a single file, I embed its Info.plist into the executable by creating a TEXT segment then copying Info.plist into it.
So far I can successfully use SMJobBless()
to install that helper tool in /Library/PrivilegedHelperTools
and its launchd property list in /Library/LaunchDaemons
.
Apple recommends using XPC to ensure that the GUI app and the tool have binary-compatible versions. The EvenBetterAuthorizationSample uses a block to call into the helper tool. I need my getVersion function to pass back an NSMutableString
to verifyVersionCompatibility
.
- (uint32_t)getVersion: (NSMutableString**) helperVersionPtr
// Called when the user clicks the Get Version button.
// This is the simplest form of
// NSXPCConnection request because it doesn't require any authorization.
{
assert( helperVersionPtr != NULL );
assert( *helperVersionPtr != NULL );
[self connectAndExecuteCommandBlock:^(NSError * connectError) {
if (connectError != nil) {
[self logError:connectError];
*helperVersionPtr = NULL;
DBG_MSG(( "%s connectAndExecuteCommandBlock failed connectError: %s\n", __func__, [[connectError description] UTF8String] ));
} else {
[[self.helperToolConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError) {
[self logError:proxyError];
}] getVersionWithReply:^(NSString *version) {
[self logWithFormat:@"version = %@\n", version];
[*helperVersionPtr setString: version]; // Pass the version back
}];
}
}];
return 0;
}
The block gets executed here:
- (void)connectAndExecuteCommandBlock:(void(^)(NSError *))commandBlock
// Connects to the helper tool and then executes
// the supplied command block on the
// main thread, passing it an error indicating
// if the connection was successful.
{
assert([NSThread isMainThread]);
// Ensure that there's a helper tool connection in place.
[self connectToHelperTool];
// Run the command block.
// Note that we never error in this case because, if there is
// an error connecting to the helper tool, it will be delivered
// to the error handler
// passed to -remoteObjectProxyWithErrorHandler:.
// However, I maintain the possibility
// of an error here to allow for future expansion.
commandBlock(nil);
}
The helper tool's getVersionWithReply:
- (void)getVersionWithReply:(void(^)(NSString * version))reply
// Part of the HelperToolProtocol.
// Returns the version number of the tool. Note that never
// requires authorization.
{
// We specifically don't check for authorization here.
// Everyone is always allowed to get
// the version of the helper tool.
reply([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]);
}
Upvotes: 1
Views: 1237
Reputation: 3439
I think what you're asking is how can -getVersion:
synchronously obtain the string value obtained via the XPC reply function and return it to the caller.
The problem is that all XPC messages/handlers execute asynchronously and on random threads.
If you really must have a synchronous call, you can use semaphores to block until the reply is received:
- (NSString*)synchronousExample
{
NSConditionLock* barrierLock = [[NSConditionLock alloc] initWithCondition:NO];
id<YourXPCServiceMessaging> proxy = self.serviceProxy; // get the proxy object of the XPC connection
// Send the XPC message that requests a response
__block NSString* replyString = nil;
[proxy someMessageWithStringReply:^(NSString* string){
// The XPC service has responded with the value; squirrel it away and let the original thread know it's done
replyString = string;
[barrierLock lock];
[barrierLock unlockWithCondition:YES];
}];
// Wait for the reply block to execute
[barrierLock lockWhenCondition:YES];
[barrierLock unlock];
return replyString;
}
A simpler approach, if you can reorganize your code, is to make an asynchronous request and then continue once the reply is received:
- (void)startVerifyCheck
{
id<YourXPCServiceMessaging> proxy = self.serviceProxy; // get the proxy object of the XPC connection
[proxy someMessageWithStringReply:^(NSString* string){
// The XPC service has responded with the value
if ([string isEqualToString:@"the expected value"])
{
// Verify successful: continue with the next step
dispatch_async(dispatch_get_main_queue(), ^{
[self verifyComplete];
});
}
}];
// Reply block will continue once the reply is received
}
- (void)verifyComplete
{
// do the next step here...
}
Upvotes: 2