I've completed my game using Buildbox 2.3.3 and have cleaned up as many warnings as possible on Xcode. However, I've been at this for weeks now on me migrating from OpenGL to Metal which I assume is the reason I have that error in Xcode saying GLKit is deprecated, consider migrating to metal instead.
I attempted to upload the game to App store connect without me solving this error but then I instantly got sent an email saying
ITMS-90809: Deprecated API Usage, New apps no longer use UIWebView,
use WKWebView instead.
I've no clue how to go about converting my code to cater for this I would really appreciate some guidance or if anyone can rewrite my code for me converting the OpenGL to metal.
I'll attach the code I'm using below, I would really appreciate it if anyone can help me. I've been stuck at this final stage for weeks now, its very frustrating.
AppDelegate.h
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>{
}
#property (strong, nonatomic) UIWindow *window;
#end
AppDelegate.mm
#import "AppDelegate.h"
#import <GLKit/GLKit.h>
#include "PTPSettingsController.h"
#include "libs/cocos2dx/include/audio/include/SimpleAudioEngine.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
cocos2d::CCDirector::sharedDirector()->pause();
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
cocos2d::CCApplication::sharedApplication()->applicationDidEnterBackground();
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
cocos2d::CCApplication::sharedApplication()->applicationWillEnterForeground();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
cocos2d::CCDirector::sharedDirector()->resume();
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
- (void)loadingDidComplete{
}
-(void)showCustomFullscreenAd{
}
- (void)screenOnEnter:(const char*) name{
}
- (void)screenOnExit:(const char*) name{
}
#end
GameViewController.h
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
#interface GameViewController : GLKViewController
#end
GameViewController.mm
#import "GameViewController.h"
#import <OpenGLES/ES2/glext.h>
#import "PTModelController.h"
#import "PTModelGeneralSettings.h"
#import "PTPAppDelegate.h"
#import "cocos2d.h"
#import "PTPConfig.h"
#include "PTPSettingsController.h"
#define IOS_MAX_TOUCHES_COUNT 10
static PTPAppDelegate s_sharedApplication;
#interface GameViewController () {
NSString* shareMessage;
bool sheduledForShareWidget;
}
#property (strong, nonatomic) EAGLContext *context;
#end
#implementation GameViewController
- (void)viewDidLoad{
[super viewDidLoad];
sheduledForShareWidget = false;
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.context) {
NSLog(#"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
view.context = self.context;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
[view setMultipleTouchEnabled: YES];
[self setPreferredFramesPerSecond:60];
[EAGLContext setCurrentContext:self.context];
PTModelController *mc = PTModelController::shared();
mc->clean();
unsigned long size = 0;
char* pBuffer = (char*)CCFileUtils::sharedFileUtils()->getFileData("data/data.pkg", "rb", &size);
if (pBuffer != NULL && size > 0){
mc->setUsingDataEncryption( true );
}
mc->loadDataForSplashScreen("data/data.pkg", processor().c_str());
s_sharedApplication.setDataArchiveProcessor(processor());
cocos2d::CCApplication::sharedApplication()->run();
}
- (void)dealloc{
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
}
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
if ([self isViewLoaded] && ([[self view] window] == nil)) {
self.view = nil;
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
// Dispose of any resources that can be recreated.
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
cocos2d::CCDirector::sharedDirector()->setViewport();
cocos2d::CCDirector::sharedDirector()->mainLoop();
}
- (void)update{
if(sheduledForShareWidget == true){
sheduledForShareWidget = false;
GLKView *view = (GLKView *)self.view;
UIImage* screenshot = view.snapshot;
PTLog("Opens Share Widget: screenshot was taken");
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:#[shareMessage, screenshot] applicationActivities:nil];
NSArray *excludeActivities = #[UIActivityTypeSaveToCameraRoll,
UIActivityTypeAssignToContact];
activityVC.excludedActivityTypes = excludeActivities;
float iOSVersion = [[UIDevice currentDevice].systemVersion floatValue];
if(iOSVersion > 8.0){
activityVC.popoverPresentationController.sourceView = self.view;
}
[self presentViewController:activityVC animated:YES completion:nil];
PTLog("opens Share Widget: view controller presented");
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
int ids[IOS_MAX_TOUCHES_COUNT] = {0};
float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f};
float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f};
int i = 0;
for (UITouch *touch in touches) {
ids[i] = (intptr_t)touch;
xs[i] = [touch locationInView: [touch view]].x * self.view.contentScaleFactor;
ys[i] = [touch locationInView: [touch view]].y * self.view.contentScaleFactor;
++i;
}
cocos2d::CCEGLView::sharedOpenGLView()->handleTouchesBegin(i, ids, xs, ys);
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
int ids[IOS_MAX_TOUCHES_COUNT] = {0};
float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f};
float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f};
int i = 0;
for (UITouch *touch in touches) {
ids[i] = (intptr_t)touch;
xs[i] = [touch locationInView: [touch view]].x * self.view.contentScaleFactor;;
ys[i] = [touch locationInView: [touch view]].y * self.view.contentScaleFactor;;
++i;
}
cocos2d::CCEGLView::sharedOpenGLView()->handleTouchesMove(i, ids, xs, ys);
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
int ids[IOS_MAX_TOUCHES_COUNT] = {0};
float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f};
float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f};
int i = 0;
for (UITouch *touch in touches) {
ids[i] = (intptr_t)touch;
xs[i] = [touch locationInView: [touch view]].x * self.view.contentScaleFactor;;
ys[i] = [touch locationInView: [touch view]].y * self.view.contentScaleFactor;;
++i;
}
cocos2d::CCEGLView::sharedOpenGLView()->handleTouchesEnd(i, ids, xs, ys);
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
int ids[IOS_MAX_TOUCHES_COUNT] = {0};
float xs[IOS_MAX_TOUCHES_COUNT] = {0.0f};
float ys[IOS_MAX_TOUCHES_COUNT] = {0.0f};
int i = 0;
for (UITouch *touch in touches) {
ids[i] = (intptr_t)touch;
xs[i] = [touch locationInView: [touch view]].x * self.view.contentScaleFactor;;
ys[i] = [touch locationInView: [touch view]].y * self.view.contentScaleFactor;;
++i;
}
cocos2d::CCEGLView::sharedOpenGLView()->handleTouchesCancel(i, ids, xs, ys);
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
PTModelGeneralSettingsPtr generalSettings = PTModelGeneralSettings::shared();
if(generalSettings->orientation() == PTModelGeneralSettings::LandscapeOrientation){
return UIInterfaceOrientationIsLandscape( interfaceOrientation );
}
else if(generalSettings->orientation() == PTModelGeneralSettings::PortraitOrientation){
return UIInterfaceOrientationIsPortrait( interfaceOrientation );
}
return NO;
}
- (NSUInteger) supportedInterfaceOrientations{
PTModelGeneralSettingsPtr generalSettings = PTModelGeneralSettings::shared();
if(generalSettings->orientation() == PTModelGeneralSettings::LandscapeOrientation){
return UIInterfaceOrientationMaskLandscape;
}
else if(generalSettings->orientation() == PTModelGeneralSettings::PortraitOrientation){
return UIInterfaceOrientationMaskPortrait;
}
return NO;
}
- (BOOL) shouldAutorotate {
return NO;
}
-(void) scheduleOpenShareWidget:(const char*) message{
shareMessage = [NSString stringWithUTF8String:message];
sheduledForShareWidget = true;
}
#end
As you've already been told, your question is a little unsuitable for Stack Overflow. You can start rewriting your project from OpenGL to Metal, and ask questions if anything goes wrong.
Apple documentation is a good starting point:
Migrating OpenGL Code to Metal
Mixing Metal and OpenGL Rendering in a View
You could also watch WWDC 2019 video and learn a step-by-step approach for transitioning OpenGL-based apps to the Metal API.
OpenGL is portable and widely supported, so it's a pity not to be able to use it. Luckily the MetalANGLE framework is an almost perfect drop-in replacement for GLKit. I have started using it in the development branch of my map rendering library, CartoType, and it works correctly: I can't see any difference in the graphics compared to GLKit, and I had to make only one minor change to my code to get it working - and that change was probably caused by an incorrect use of GLKit.
So my advice is: stay with OpenGL and use MetalANGLE.
I want to use PHImagemanager to get all photos on the device.
If I set the targetsize too high the app will crash because of memory warnings. So I tested the request without any use of the returned images and set each image to nil, but still app is crashing. I don't know what I'm doing wrong. Can someone help please?
requestOptions = [[PHImageRequestOptions alloc] init];
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
requestOptions.synchronous = false;
assetsOfPhotos = [PHAsset fetchAssetsWithMediaType: PHAssetMediaTypeImage options: nil];
PHImageManager *manager = [PHImageManager defaultManager];
#autoreleasepool {
for (int i = 0; i <= totalImages - 1; i++) {
PHAsset *asset = assetsOfPhotos[i];
[manager requestImageForAsset: asset
targetSize: CGSizeMake(640, 480)
contentMode: PHImageContentModeDefault
options: requestOptions
resultHandler: ^void(UIImage *image, NSDictionary *info) {
image = nil;
}];
}
}
Setting size to 640x480 crash after about 200 images, 320x240 after about 800 images. As a 640x480 image needs 4 times memory then 320x240 image it seems that the app crashes after the same amount of memory that was allocated. So for me this means that I cannot show more images than 200 imags with 640x480 on the test device, because I cannot free allocated memory.
In order to make your #autoreleasepool work you need to set requestOptions.synchronous to YES, and use your own async queue if you want to make the request operation asynchronously.
Please use #autoreleasepool inside the for loop.
for (int i = 0; i <= totalImages - 1; i++) {
#autoreleasepool {
//Your code
}
}
If you want load all photos that you have in Photos.app and you didn't want iCloud. You can do:
That example works with a collection view.
#interface GalleryViewModel ()
#property (strong, nonatomic) NSMutableArray<PHAsset *> *assets;
#property (strong, nonatomic) PHImageManager *imageManager;
#property (strong, nonatomic) PHImageRequestOptions *requestOptions;
#property (strong, nonatomic) NSMutableArray<UIImage *> *imagesList;
#end
#implementation GalleryViewModel
- (instancetype) initWithContext:(ITXAppContext *)context {
self = [super initWithContext:context];
if (self) {
_assets = [[NSMutableArray alloc] init];
_imageManager = [PHImageManager defaultManager];
_requestOptions = [[PHImageRequestOptions alloc] init];
_imagesList = [[NSMutableArray alloc] init];
}
return self;
}
#pragma mark - Public methods
// ==================================================================================
// Public methods
- (void) viewModelDidLoad {
[self obtainAllPhotos];
}
#pragma mark - Private methods
// ==================================================================================
// Private methods
- (void) obtainAllPhotos {
self.requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
self.requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
self.requestOptions.synchronous = YES;
self.requestOptions.networkAccessAllowed = NO;
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO]];
PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
__weak GalleryViewModel *weakSelf = self;
[result enumerateObjectsUsingBlock:^(PHAsset * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[weakSelf.assets addObject:obj];
if (idx >= ([result count] - 1)) {
[weakSelf.viewDelegate setupView];
}
}];
}
#pragma mark - Get data from object
// ==================================================================================
// Get data from object
- (NSInteger) sizeGallery {
if (self.assets) {
return [self.assets count];
}
return 0;
}
- (UIImage *) imagesFromList:(NSInteger) index {
__block UIImage *imageBlock;
[self.imageManager requestImageForAsset:[self.assets objectAtIndex:index] targetSize:CGSizeMake(200, 200) contentMode:PHImageContentModeAspectFit options:self.requestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
if (result) {
imageBlock = result;
}
}];
return imageBlock;
}
#end
I'm trying to load image date for person entries in the shared ABAddressBook. In particular, I'm calling
-[ABPerson beginLoadingImageDataForClient:]
and passing as the argument an object which adopts ABImageClient and implements
-[<ABPersonClient> consumeImageData:forTag:]
The approach I'm using works fine on Mountain Lion, but fails on Snow Leopard. In particular, -consumeImageData:forTag: never gets called.
The following sample command line program demonstrates my approach:
#import <Foundation/Foundation.h>
#import <AddressBook/AddressBook.h>
#interface ImageConsumer : NSObject <ABImageClient>
#property (nonatomic, strong) NSMutableDictionary *imagesForNumbers;
#end
#implementation ImageConsumer
- (id)init
{
self = [super init];
if (self) {
self.imagesForNumbers = [NSMutableDictionary dictionary];
}
return self;
}
- (void)consumeImageData:(NSData *)data forTag:(NSInteger)tag
{
[self.imagesForNumbers setObject:data forKey:[NSNumber numberWithInteger:tag]];
NSLog(#"%s: loaded data of length %zu for tag %zd", __PRETTY_FUNCTION__, data.length, tag);
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *persons = [[ABAddressBook sharedAddressBook] people];
ImageConsumer *imageConsumer = [[ImageConsumer alloc] init];
for (ABPerson *person in persons) {
NSInteger loadingKey = [person beginLoadingImageDataForClient:imageConsumer];
NSLog(#"requested data person named %# %# and received tag %zd", [person valueForProperty:kABFirstNameProperty], [person valueForProperty:kABLastNameProperty], loadingKey);
}
[[NSRunLoop mainRunLoop] run];
}
return 0;
}
Am I misusing the ABAddressBook calls for loading image data for instances of ABPerson? Is this a bug with ABAddressBook on Snow Leopard? If so, is there a work-around?
According to Apple, the new XPC Services API, introduced in Lion, provides a lightweight mechanism for basic interprocess communication integrated with Grand Central Dispatch (GCD) and launchd.
It seems possible to use this API as a kind of IPC, like the POSIX IPC, however, I cannot find how to do it.
I am trying to communicate two processes using the XPC API so I can pass messages between them but I always get a "XPC connection invalid" error in the server side.
I don't want an XPC Service, I just want to exchange messages using a client-server architecture.
I am using two BSD-like processes, so there is no Info.plist or whatever...
I have been following this discussion http://lists.macosforge.org/pipermail/launchd-dev/2011-November/000982.html but this topic seems a bit obscure and undocumented.
Thanks!
Yes, that is possible, but not the way you'd expect.
You can not have a (non launchd) process vend a service. That is for security reasons, since it would make it easy to do man-in-the-middle attacks.
You can still achieve what you want, though: You have to set up a launchd service that vends an XPC / mach service. Both process A and B then connect to your launchd service. Process A can then create a so called anonymous connection and send that to the launchd service which will forward it to process B. Once that has happened, processes A and B can talk to each other directly through that connection (i.e. the launchd service can exit without the connection breaking).
This may seem round-about, but it's necessary for security reasons.
See the xpc_object(3) man page for details about anonymous connections.
It's a bit counter intuitive, because process A will create a listener object with xpc_connection_create(). A then creates an endpoint object from the listener with xpc_endpoint_create() and sends that endpoint across the wire (over XPC) to process B. B can then turn that object into a connection with xpc_connection_create_from_endpoint(). A's event handler for the listener will then receive a connection object matching the connection that B created with xpc_connection_create_from_endpoint(). This works similar to the way that the event handler of xpc_connection_create_mach_service() will receive connection objects when clients connect.
Here is how I am doing Bi-Directional IPC using XPC.
The Helper (login item) is the server or listener. The main app or any other app are considered clients.
I created the following manager:
Header:
#class CommXPCManager;
typedef NS_ENUM(NSUInteger, CommXPCErrorType) {
CommXPCErrorInvalid = 1,
CommXPCErrorInterrupted = 2,
CommXPCErrorTermination = 3
};
typedef void (^XPCErrorHandler)(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error);
typedef void (^XPCMessageHandler)(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message);
typedef void (^XPCConnectionHandler)(CommXPCManager *peerConnection);
#interface CommXPCManager : NSObject
#property (readwrite, copy, nonatomic) XPCErrorHandler errorHandler;
#property (readwrite, copy, nonatomic) XPCMessageHandler messageHandler;
#property (readwrite, copy, nonatomic) XPCConnectionHandler connectionHandler;
#property (readonly, nonatomic) BOOL clientConnection;
#property (readonly, nonatomic) BOOL serverConnection;
#property (readonly, nonatomic) BOOL peerConnection;
#property (readonly, nonatomic) __attribute__((NSObject)) xpc_connection_t connection;
#property (readonly, strong, nonatomic) NSString *connectionName;
#property (readonly, strong, nonatomic) NSNumber *connectionEUID;
#property (readonly, strong, nonatomic) NSNumber *connectionEGID;
#property (readonly, strong, nonatomic) NSNumber *connectionProcessID;
#property (readonly, strong, nonatomic) NSString *connectionAuditSessionID;
- (id) initWithConnection:(xpc_connection_t)aConnection;
- (id) initAsClientWithBundleID:(NSString *)bundleID;
- (id) initAsServer;
- (void) suspendConnection;
- (void) resumeConnection;
- (void) cancelConnection;
- (void) sendMessage:(NSDictionary *)dict;
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event;
#end
Implementation:
#interface CommXPCManager ()
#property (readwrite, nonatomic) BOOL clientConnection;
#property (readwrite, nonatomic) BOOL serverConnection;
#property (readwrite, nonatomic) BOOL peerConnection;
#property (readwrite, strong, nonatomic) __attribute__((NSObject)) dispatch_queue_t dispatchQueue;
#end
#implementation CommXPCManager
#synthesize clientConnection, serverConnection, peerConnection;
#synthesize errorHandler, messageHandler, connectionHandler;
#synthesize connection = _connection;
#synthesize dispatchQueue = _dispatchQueue;
#pragma mark - Message Methods:
- (void) sendMessage:(NSDictionary *)dict {
dispatch_async( self.dispatchQueue, ^{
xpc_object_t message = dict.xObject;
xpc_connection_send_message( _connection, message );
xpc_release( message );
});
}
- (void) sendMessage:(NSDictionary *)dict reply:(void (^)(NSDictionary *replyDict, NSError *error))reply {
dispatch_async( self.dispatchQueue, ^{
xpc_object_t message = dict.xObject;
xpc_connection_send_message_with_reply( _connection, message, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type( object );
if ( type == XPC_TYPE_ERROR ) {
/*! #discussion Reply: XPC Error */
reply( [NSDictionary dictionary], [NSError errorFromXObject:object] );
} else if ( type == XPC_TYPE_DICTIONARY ) {
/*! #discussion Reply: XPC Dictionary */
reply( [NSDictionary dictionaryFromXObject:object], nil );
}
}); xpc_release( message );
});
}
+ (void) sendReply:(NSDictionary *)dict forEvent:(xpc_object_t)event {
xpc_object_t message = [dict xObjectReply:event];
xpc_connection_t replyConnection = xpc_dictionary_get_remote_connection( message );
xpc_connection_send_message( replyConnection, message );
xpc_release( message );
}
#pragma mark - Connection Methods:
- (void) suspendConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_suspend( _connection ); });
}
- (void) resumeConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_resume(_connection); });
}
- (void) cancelConnection {
dispatch_async(self.dispatchQueue, ^{ xpc_connection_cancel(_connection); });
}
#pragma mark - Accessor Overrides:
- (void) setDispatchQueue:(dispatch_queue_t)queue {
if ( queue ) dispatch_retain( queue );
if ( _dispatchQueue ) dispatch_release( _dispatchQueue );
_dispatchQueue = queue;
xpc_connection_set_target_queue( self.connection, self.dispatchQueue );
}
#pragma mark - Getter Overrides:
- (NSString *) connectionName {
__block char* name = NULL;
dispatch_sync(self.dispatchQueue, ^{ name = (char*)xpc_connection_get_name( _connection ); });
if(!name) return nil;
return [NSString stringWithCString:name encoding:[NSString defaultCStringEncoding]];
}
- (NSNumber *) connectionEUID {
__block uid_t uid = 0;
dispatch_sync(self.dispatchQueue, ^{ uid = xpc_connection_get_euid( _connection ); });
return [NSNumber numberWithUnsignedInt:uid];
}
- (NSNumber *) connectionEGID {
__block gid_t egid = 0;
dispatch_sync(self.dispatchQueue, ^{ egid = xpc_connection_get_egid( _connection ); });
return [NSNumber numberWithUnsignedInt:egid];
}
- (NSNumber *) connectionProcessID {
__block pid_t pid = 0;
dispatch_sync(self.dispatchQueue, ^{ pid = xpc_connection_get_pid( _connection ); });
return [NSNumber numberWithUnsignedInt:pid];
}
- (NSNumber *) connectionAuditSessionID{
__block au_asid_t auasid = 0;
dispatch_sync(self.dispatchQueue, ^{ auasid = xpc_connection_get_asid( _connection ); });
return [NSNumber numberWithUnsignedInt:auasid];
}
#pragma mark - Setup Methods:
- (void) setupConnectionHandler:(xpc_connection_t)conn {
__block CommXPCManager *this = self;
xpc_connection_set_event_handler( conn, ^(xpc_object_t object) {
xpc_type_t type = xpc_get_type( object );
if ( type == XPC_TYPE_ERROR ) {
/*! #discussion Client | Peer: XPC Error */
NSError *xpcError = [NSError errorFromXObject:object];
if ( object == XPC_ERROR_CONNECTION_INVALID ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorInvalid, xpcError );
} else if ( object == XPC_ERROR_CONNECTION_INTERRUPTED ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorInterrupted, xpcError );
} else if ( object == XPC_ERROR_TERMINATION_IMMINENT ) {
if ( this.errorHandler )
this.errorHandler( this, CommXPCErrorTermination, xpcError );
}
xpcError = nil; return;
} else if ( type == XPC_TYPE_CONNECTION ) {
/*! #discussion XPC Server: XPC Connection */
CommXPCManager *xpcPeer = [[CommXPCManager alloc] initWithConnection:object];
if ( this.connectionHandler )
this.connectionHandler( xpcPeer );
xpcPeer = nil; return;
} else if ( type == XPC_TYPE_DICTIONARY ) {
/*! #discussion Client | Peer: XPC Dictionary */
if ( this.messageHandler )
this.messageHandler( this, object, [NSDictionary dictionaryFromXObject:object] );
}
});
}
- (void) setupDispatchQueue {
dispatch_queue_t queue = dispatch_queue_create( xpc_connection_get_name(_connection), 0 );
self.dispatchQueue = queue;
dispatch_release( queue );
}
- (void) setupConnection:(xpc_connection_t)aConnection {
_connection = xpc_retain( aConnection );
[self setupConnectionHandler:aConnection];
[self setupDispatchQueue];
[self resumeConnection];
}
#pragma mark - Initialization:
- (id) initWithConnection:(xpc_connection_t)aConnection {
if ( !aConnection ) return nil;
if ( (self = [super init]) ) {
self.peerConnection = YES;
[self setupConnection:aConnection];
} return self;
}
- (id) initAsClientWithBundleID:(NSString *)bundleID {
xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [bundleID UTF8String], nil, 0 );
if ( (self = [super init]) ) {
self.clientConnection = YES;
[self setupConnection:xpcConnection];
}
xpc_release( xpcConnection );
return self;
}
- (id) initAsServer {
xpc_connection_t xpcConnection = xpc_connection_create_mach_service( [[[NSBundle mainBundle] bundleIdentifier] UTF8String],
dispatch_get_main_queue(),
XPC_CONNECTION_MACH_SERVICE_LISTENER );
if ( (self = [super init]) ) {
self.serverConnection = YES;
[self setupConnection:xpcConnection];
}
xpc_release( xpcConnection );
return self;
}
#end
Obviously, I am using some Category methods which are self explanatory.
For example:
#implementation NSError (CategoryXPCMessage)
+ (NSError *) errorFromXObject:(xpc_object_t)xObject {
char *description = xpc_copy_description( xObject );
NSError *xpcError = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:#{
NSLocalizedDescriptionKey:
[NSString stringWithCString:description encoding:[NSString defaultCStringEncoding]] }];
free( description );
return xpcError;
}
#end
Okay, using this I set myself up an interface for both the client-side and server-side. The header looks like this:
#class CommXPCManager;
#protocol AppXPCErrorHandler <NSObject>
#required
- (void) handleXPCError:(NSError *)error forType:(CommXPCErrorType)errorType;
#end
static NSString* const kAppXPCKeyReturn = #"AppXPCInterfaceReturn"; // id returnObject
static NSString* const kAppXPCKeyReply = #"AppXPCInterfaceReply"; // NSNumber: BOOL
static NSString* const kAppXPCKeySEL = #"AppXPCInterfaceSelector"; // NSString
static NSString* const kAppXPCKeyArgs = #"AppXPCInterfaceArguments"; // NSArray (Must be xObject compliant)
#interface AppXPCInterface : NSObject
#property (readonly, strong, nonatomic) CommXPCManager *managerXPC;
#property (readonly, strong, nonatomic) NSArray *peerConnections;
- (void) sendMessage:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (void) sendMessageToPeers:(SEL)aSelector withArgs:(NSArray *)args reply:(void (^)(NSDictionary *replyDict, NSError *error))reply;
- (id) initWithBundleID:(NSString *)bundleID andDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (id) initListenerWithDelegate:(id<AppXPCErrorHandler>)object forProtocol:(Protocol *)proto;
- (void) observeListenerHello:(CommReceptionistNoteBlock)helloBlock;
- (void) removeListenerObserver;
- (void) startClientConnection;
- (void) startListenerConnection;
- (void) stopConnection;
#end
Here is the implementation to start the listener:
- (void) startListenerConnection {
[self stopConnection];
self.managerXPC = [[CommXPCManager alloc] initAsServer];
__block AppXPCInterface *this = self;
self.managerXPC.connectionHandler = ^(CommXPCManager *peerConnection) {
[(NSMutableArray *)this.peerConnections addObject:peerConnection];
peerConnection.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {
[this processMessage:message forEvent:event];
};
peerConnection.errorHandler = ^(CommXPCManager *peer, CommXPCErrorType errorType, NSError *error) {
[this processError:error forErrorType:errorType];
[(NSMutableArray *)this.peerConnections removeObject:peer];
};
};
[CommReceptionist postGlobalNote:kAppXPCListenerNoteHello];
}
Here is the implementation to start the client:
- (void) startClientConnection {
[self stopConnection];
self.managerXPC = [[CommXPCManager alloc] initAsClientWithBundleID:self.identifierXPC];
__block AppXPCInterface *this = self;
self.managerXPC.messageHandler = ^(CommXPCManager *mgrXPC, xpc_object_t event, NSDictionary *message) {
[this processMessage:message forEvent:event];
};
self.managerXPC.errorHandler = ^(CommXPCManager *mgrXPC, CommXPCErrorType errorType, NSError *error) {
[this processError:error forErrorType:errorType];
};
}
Now here is the order of things.
Your main app starts its helper The helper starts listening using
its bundleID <--- Important!
The main app listens for a global notification and then sends a message
When the client sends a message the connection is established
Now the server can send messages to the client and the client can send messages to the server (with or without a reply).
It's very fast, it works well, and is designed for OS X 10.7.3 or greater.
A few notes:
The name of the helper must be the same name as the bundle ID
The name must begin with your team ID
For sandboxing, both the Main app and Helper app application group setting must be start with prefix of the helper Bundle ID
e.g.
Helper bundle id is:
ABC123XYZ.CompanyName.GroupName.Helper
App Group ID will be:
ABC123XYZ.CompanyName.GroupName
There are additional details I left out so as not to bore anyone. But if it's still unclear just ask and I will answer.
Ok, hope this helps.
Arvin
Alright for anyone that has been struggling with this, I was finally able to 100% get communication working between two application processes, using NSXPCConnection
The key to note is that you can only create an NSXPCConnection to three things.
An XPCService. You can connect to an XPCService strictly through
a name
A Mach Service. You can also connect to a Mach Service
strictly through a name
An NSXPCEndpoint. This is what we're
looking for, to communicate between two application processes.
The problem being that we can't directly transfer an NSXPCListenerEndpoint from one application to another.
It involved creating a machservice Launch Agent (See this example for how to do that) that held an NSXPCListenerEndpoint property. One application can connect to the machservice, and set that property to it's own [NSXPCListener anonymousListener].endpoint
Then the other application can connect to the machservice, and ask for that endpoint.
Then using that endpoint, an NSXPCConnection can be created, which successfully established a bridge between the two applications. I have tested sending objects back and forth, and it all works as expected.
Note that if your application is sandboxed, you will have to create an XPCService, as a middle man between your Application and the Machservice
I'm pretty pumped that I got this working-- I'm fairly active in SO, so if anybody is interested in source code, just add a comment and I can go through the effort to post more details
Some hurdles I came across:
You have to launch your machservice, these are the lines:
OSStatus err;
AuthorizationExternalForm extForm;
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
NSLog(#"SUCCESS AUTHORIZING DAEMON");
}
assert(err == errAuthorizationSuccess);
Boolean success;
CFErrorRef error;
success = SMJobBless(
kSMDomainSystemLaunchd,
CFSTR("DAEMON IDENTIFIER HERE"),
self->_authRef,
&error
);
Also, every time you rebuild your daemon, you have to unload the previous launch agent, with these bash commands:
sudo launchctl unload /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/LaunchDaemons/com.example.apple-samplecode.EBAS.HelperTool.plist
sudo rm /Library/PrivilegedHelperTools/com.example.apple-samplecode.EBAS.HelperTool
(With your corresponding identifiers, of course)