├── xpc ├── xpc.go ├── utils.go ├── xpc_wrapper.c └── service.go ├── LICENSE ├── cocoa ├── XPCConnection.h └── XPCConnection.m └── README.md /xpc/xpc.go: -------------------------------------------------------------------------------- 1 | package xpc 2 | 3 | // DefaultServer is the default instance of *Server. 4 | var DefaultService = NewService() 5 | 6 | // Register publishes the receiver's methods in the DefaultServer. 7 | func Register(rcvr interface{}) error { return DefaultService.Register(rcvr) } 8 | 9 | func Start() { DefaultService.Start() } 10 | 11 | func CallHost(name string, args []interface{}) { DefaultService.CallHost(name, args) } 12 | 13 | // NewService returns a new XPCService. 14 | func NewService() *XPCService { 15 | return &XPCService{serviceMap: make(map[string]*service)} 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 BLITZ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cocoa/XPCConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // XPCConnection.h 3 | // OfficeBeats 4 | // 5 | // Created by Adam Venturella on 7/2/14. 6 | // Copyright (c) 2014 BLITZ. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "MessagePack.h" 12 | 13 | @interface XPCConnection : NSObject 14 | 15 | @property (copy) NSString *name; 16 | @property (strong) xpc_connection_t serviceConnection; 17 | @property (strong) xpc_connection_t hostConnection; 18 | @property (strong) dispatch_queue_t hostQueue; 19 | @property (strong) dispatch_queue_t serviceQueue; 20 | @property (strong) NSMutableDictionary *serviceMap; 21 | @property (strong) NSMutableDictionary *keyMap; 22 | 23 | 24 | - (instancetype) initWithName:(NSString *)name; 25 | - (void)resume; 26 | - (void)host:(xpc_connection_t)connection event:(xpc_object_t)event; 27 | - (void)service:(xpc_connection_t)connection event:(xpc_object_t)event; 28 | - (void)serviceTerminationImminent:(xpc_connection_t) connection; 29 | - (void)serviceConnectionInvalid:(xpc_connection_t) connection; 30 | - (void)serviceConnectionInterrupted:(xpc_connection_t) connection; 31 | - (void)call:(NSString *)method withArgs:(NSArray *)args reply:(void (^)(id))reply; 32 | - (void)register:(id)obj; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-xpc 2 | 3 | This borrows VERY VERY VERY heavily from the rpc package in 4 | http://golang.org/src/pkg/net/rpc/server.go 5 | 6 | It relies on msgpack (github.com/ugorji/go/codec) for it's serialization. 7 | 8 | Right now, I just threw this together from some of the other work I have 9 | been doing lately. It works, but it needs some love to be sure; bear with me. 10 | 11 | There is a companion XPCConnection (Obj-C) (Swift doesn't have it's XPC 12 | stuff all there at the time of this writing). 13 | 14 | 15 | 16 | Sample usage: 17 | 18 | ### From Go: 19 | 20 | ``` go 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "github.com/aventurella/go-xpc/xpc" 26 | ) 27 | 28 | type Sample struct{} 29 | 30 | func (s *Sample) Test(args *[]interface{}, reply *interface{}) error { 31 | fmt.Println("CALLED SAMPLE.TEST!!!") 32 | fmt.Println(args) 33 | return nil 34 | } 35 | 36 | func main() { 37 | sample := new(Sample) 38 | xpc.Register(sample) 39 | xpc.Start() 40 | } 41 | ``` 42 | 43 | 44 | 45 | ### From Cocoa App (Swift): 46 | 47 | ```swift 48 | import Cocoa 49 | 50 | // @objc is required here. 51 | @objc class Foo { 52 | 53 | func Bar(args: Array){ 54 | println("Bazzle Got Args: \(args)") 55 | } 56 | } 57 | 58 | // ... later in some func ... 59 | 60 | func initializeXPCService(){ 61 | connection = XPCConnection(name:"com.blitzagency.officebeats-api") 62 | connection.register(Foo()) 63 | connection.resume() 64 | 65 | connection.call("Sample.Test", withArgs: [1,2,3]) { 66 | (value) -> () in 67 | println(value) 68 | } 69 | } 70 | 71 | ``` 72 | 73 | 74 | 75 | ### you can even call back from your Go XPC Service into your app: 76 | 77 | Building on the *Go* example above: 78 | 79 | ```go 80 | package main 81 | 82 | import ( 83 | "fmt" 84 | "github.com/aventurella/go-xpc/xpc" 85 | ) 86 | 87 | type Sample struct{} 88 | 89 | func (s *Sample) Test(args *[]interface{}, reply *interface{}) error { 90 | fmt.Println("CALLED SAMPLE.TEST!!!") 91 | fmt.Println(args) 92 | 93 | // Note that we MUST use selector style here, 94 | // aka: passingAnArgEndsWithAColon: 95 | // 96 | //------------------------------------- 97 | xpc.CallHost("Foo.Bar:", args) 98 | //------------------------------------- 99 | // 100 | 101 | return nil 102 | } 103 | 104 | func main() { 105 | sample := new(Sample) 106 | xpc.Register(sample) 107 | xpc.Start() 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /xpc/utils.go: -------------------------------------------------------------------------------- 1 | package xpc 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | "unicode" 7 | "unicode/utf8" 8 | ) 9 | 10 | // Precompute the reflect type for error. Can't use error directly 11 | // because Typeof takes an empty interface value. This is annoying. 12 | var typeOfError = reflect.TypeOf((*error)(nil)).Elem() 13 | 14 | // suitableMethods returns suitable xpc methods of typ, it will report 15 | // error using log if reportErr is true. 16 | func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType { 17 | methods := make(map[string]*methodType) 18 | for m := 0; m < typ.NumMethod(); m++ { 19 | method := typ.Method(m) 20 | mtype := method.Type 21 | mname := method.Name 22 | // Method must be exported. 23 | 24 | if method.PkgPath != "" { 25 | continue 26 | } 27 | 28 | // Method needs three ins: receiver, *args, *reply. 29 | if mtype.NumIn() != 3 { 30 | if reportErr { 31 | log.Println("method", mname, "has wrong number of ins:", mtype.NumIn()) 32 | } 33 | continue 34 | } 35 | // First arg need not be a pointer. 36 | argType := mtype.In(1) 37 | if !isExportedOrBuiltinType(argType) { 38 | if reportErr { 39 | log.Println(mname, "argument type not exported:", argType) 40 | } 41 | continue 42 | } 43 | // Second arg must be a pointer. 44 | replyType := mtype.In(2) 45 | if replyType.Kind() != reflect.Ptr { 46 | if reportErr { 47 | log.Println("method", mname, "reply type not a pointer:", replyType) 48 | } 49 | continue 50 | } 51 | // Reply type must be exported. 52 | if !isExportedOrBuiltinType(replyType) { 53 | if reportErr { 54 | log.Println("method", mname, "reply type not exported:", replyType) 55 | } 56 | continue 57 | } 58 | // Method needs one out. 59 | if mtype.NumOut() != 1 { 60 | if reportErr { 61 | log.Println("method", mname, "has wrong number of outs:", mtype.NumOut()) 62 | } 63 | continue 64 | } 65 | // The return type of the method must be error. 66 | if returnType := mtype.Out(0); returnType != typeOfError { 67 | if reportErr { 68 | log.Println("method", mname, "returns", returnType.String(), "not error") 69 | } 70 | continue 71 | } 72 | methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType} 73 | } 74 | 75 | return methods 76 | } 77 | 78 | // Is this an exported - upper case - name? 79 | func isExported(name string) bool { 80 | rune, _ := utf8.DecodeRuneInString(name) 81 | return unicode.IsUpper(rune) 82 | } 83 | 84 | // Is this type exported or a builtin? 85 | func isExportedOrBuiltinType(t reflect.Type) bool { 86 | for t.Kind() == reflect.Ptr { 87 | t = t.Elem() 88 | } 89 | // PkgPath will be non-empty even for an exported type, 90 | // so we need to check the type name as well. 91 | return isExported(t.Name()) || t.PkgPath() == "" 92 | } 93 | -------------------------------------------------------------------------------- /xpc/xpc_wrapper.c: -------------------------------------------------------------------------------- 1 | // 2 | // xpc_wrapper.c 3 | // 4 | // Created by Adam Venturella on 7/3/14. 5 | // 6 | 7 | #ifndef xpc_impl 8 | #define xpc_impl 9 | #include 10 | #include 11 | 12 | struct PayloadResult{ 13 | int length; 14 | void *bytes; 15 | }; 16 | 17 | 18 | extern void ReceivedErrorEvent(char* err); 19 | extern struct PayloadResult ReceivedPayload(void *payload, int length); 20 | 21 | static xpc_connection_t host_connection; 22 | static dispatch_queue_t payloadQueue; 23 | 24 | 25 | static xpc_connection_t initialize_host_connection(xpc_object_t event) 26 | { 27 | xpc_connection_t connection = xpc_dictionary_create_connection(event, "endpoint"); 28 | 29 | xpc_connection_set_event_handler(connection, ^(xpc_object_t event) { 30 | xpc_type_t type = xpc_get_type(event); 31 | 32 | if (XPC_TYPE_ERROR == type && 33 | XPC_ERROR_CONNECTION_INTERRUPTED == event) { 34 | // the app has gone away here. 35 | } 36 | }); 37 | 38 | return connection; 39 | } 40 | 41 | static void cleanup() 42 | { 43 | if(host_connection){ 44 | xpc_release(host_connection); 45 | } 46 | } 47 | 48 | static void call_host(void *bytes, int len) 49 | { 50 | xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 51 | 52 | xpc_object_t payload = xpc_data_create(bytes, len); 53 | xpc_object_t length = xpc_int64_create(len); 54 | 55 | xpc_dictionary_set_value(message, "payload", payload); 56 | xpc_dictionary_set_value(message, "length", length); 57 | 58 | xpc_connection_send_message(host_connection, message); 59 | } 60 | 61 | static void peer_event_handler(xpc_connection_t peer, xpc_object_t event) 62 | { 63 | xpc_type_t type = xpc_get_type(event); 64 | 65 | if (type == XPC_TYPE_ERROR) { 66 | if (event == XPC_ERROR_CONNECTION_INVALID) { 67 | // The client process on the other end of the connection has either 68 | // crashed or cancelled the connection. After receiving this error, 69 | // the connection is in an invalid state, and you do not need to 70 | // call xpc_connection_cancel(). Just tear down any associated state 71 | // here. 72 | ReceivedErrorEvent("XPC_ERROR_CONNECTION_INVALID"); 73 | cleanup(); 74 | 75 | } else if (event == XPC_ERROR_TERMINATION_IMMINENT) { 76 | // Handle per-connection termination cleanup. 77 | ReceivedErrorEvent("XPC_ERROR_TERMINATION_IMMINENT"); 78 | cleanup(); 79 | } 80 | } else { 81 | 82 | 83 | xpc_object_t endpoint = xpc_dictionary_get_value(event, "endpoint"); 84 | 85 | if(endpoint){ 86 | host_connection = initialize_host_connection(event); 87 | xpc_connection_resume(host_connection); 88 | return; 89 | } 90 | 91 | int64_t length; 92 | const void *bytes = xpc_dictionary_get_data(event, "payload", (size_t *)&length); 93 | bool shouldReply = xpc_dictionary_get_bool(event, "reply"); 94 | 95 | xpc_object_t reply; 96 | 97 | if(shouldReply){ 98 | reply = xpc_dictionary_create_reply(event); 99 | } 100 | 101 | dispatch_async(payloadQueue, ^{ 102 | 103 | struct PayloadResult result = ReceivedPayload((void*)bytes, length); 104 | 105 | dispatch_async(payloadQueue, ^{ 106 | 107 | if(shouldReply){ 108 | xpc_object_t payload = xpc_data_create(result.bytes, (uint) result.length); 109 | xpc_dictionary_set_value(reply, "payload", payload); 110 | xpc_connection_send_message(peer, reply); 111 | } 112 | }); 113 | }); 114 | } 115 | } 116 | 117 | static void event_handler(xpc_connection_t peer) 118 | { 119 | // By defaults, new connections will target the default dispatch 120 | // concurrent queue. 121 | xpc_connection_set_event_handler(peer, ^(xpc_object_t event) { 122 | peer_event_handler(peer, event); 123 | }); 124 | 125 | // This will tell the connection to begin listening for events. If you 126 | // have some other initialization that must be done asynchronously, then 127 | // you can defer this call until after that initialization is done. 128 | xpc_connection_resume(peer); 129 | } 130 | 131 | 132 | static void start_xpc() 133 | { 134 | payloadQueue = dispatch_queue_create("com.go-xpc.payloads", NULL); 135 | xpc_main(event_handler); 136 | } 137 | #endif 138 | -------------------------------------------------------------------------------- /xpc/service.go: -------------------------------------------------------------------------------- 1 | package xpc 2 | 3 | import ( 4 | "errors" 5 | _ "fmt" 6 | "github.com/ugorji/go/codec" 7 | "log" 8 | "reflect" 9 | "strings" 10 | "sync" 11 | "unsafe" 12 | ) 13 | 14 | /* 15 | #include "xpc_wrapper.c" 16 | */ 17 | import "C" 18 | 19 | //export ReceivedErrorEvent 20 | func ReceivedErrorEvent(err *C.char) { 21 | str := C.GoString(err) 22 | log.Printf("Received Error Event '%s'", str) 23 | } 24 | 25 | //export ReceivedPayload 26 | func ReceivedPayload(payload unsafe.Pointer, length C.int) { 27 | 28 | bytes := C.GoBytes(payload, length) 29 | 30 | data := &Payload{} 31 | 32 | decoder := codec.NewDecoderBytes(bytes, &codec.MsgpackHandle{}) 33 | decoder.Decode(data) 34 | 35 | service, mtype, err := locateService(data) 36 | 37 | if err != nil { 38 | log.Println(err) 39 | return 40 | } 41 | 42 | var argv, replyv reflect.Value 43 | 44 | argv = reflect.ValueOf(&data.Args) 45 | replyv = reflect.New(mtype.ReplyType.Elem()) 46 | 47 | go service.call(DefaultService, mtype, argv, replyv) 48 | 49 | } 50 | 51 | func locateService(data *Payload) (service *service, mtype *methodType, err error) { 52 | dot := strings.LastIndex(data.Method, ".") 53 | 54 | if dot < 0 { 55 | err = errors.New("xpc: service/method request ill-formed: " + data.Method) 56 | return 57 | } 58 | 59 | serviceName := data.Method[:dot] 60 | methodName := data.Method[dot+1:] 61 | 62 | // Look up the request. 63 | DefaultService.mu.RLock() 64 | service = DefaultService.serviceMap[serviceName] 65 | DefaultService.mu.RUnlock() 66 | 67 | if service == nil { 68 | err = errors.New("xpc: can't find service " + data.Method) 69 | return 70 | } 71 | 72 | mtype = service.method[methodName] 73 | 74 | if mtype == nil { 75 | err = errors.New("xpc: can't find method " + data.Method) 76 | } 77 | 78 | return 79 | } 80 | 81 | // service and methodType structs are striaght from 82 | // http://golang.org/src/pkg/net/rpc/server.go 83 | type service struct { 84 | name string // name of service 85 | rcvr reflect.Value // receiver of methods for the service 86 | typ reflect.Type // type of the receiver 87 | method map[string]*methodType // registered methods 88 | } 89 | 90 | type methodType struct { 91 | sync.Mutex // protects counters 92 | method reflect.Method 93 | ArgType reflect.Type 94 | ReplyType reflect.Type 95 | numCalls uint 96 | } 97 | 98 | type XPCService struct { 99 | mu sync.RWMutex 100 | serviceMap map[string]*service 101 | } 102 | 103 | type Payload struct { 104 | Method string `codec:"method"` 105 | Args []interface{} `codec:"args"` 106 | } 107 | 108 | func (xpc *XPCService) Start() { 109 | C.start_xpc() 110 | } 111 | 112 | func (xpc *XPCService) CallHost(name string, args []interface{}) { 113 | 114 | p := Payload{Method: name, Args: args} 115 | 116 | bytes := make([]byte, 0) 117 | 118 | encoder := codec.NewEncoderBytes(&bytes, &codec.MsgpackHandle{}) 119 | encoder.Encode(p) 120 | 121 | // why we pass the 1st index: 122 | // https://coderwall.com/p/m_ma7q 123 | C.call_host(unsafe.Pointer(&bytes[0]), C.int(len(bytes))) 124 | } 125 | 126 | func (s *service) call(xpc *XPCService, mtype *methodType, argv, replyv reflect.Value) { 127 | mtype.Lock() 128 | mtype.numCalls++ 129 | mtype.Unlock() 130 | 131 | function := mtype.method.Func 132 | 133 | // Invoke the method, providing a new value for the reply. 134 | returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv}) 135 | 136 | // The return value for the method is an error. 137 | errInter := returnValues[0].Interface() 138 | errmsg := "" 139 | 140 | if errInter != nil { 141 | errmsg = errInter.(error).Error() 142 | log.Println(errmsg) 143 | } 144 | 145 | // TODO Here is where we send our response back, if we have one. 146 | // need to figure what that looks like. 147 | // server.sendResponse(sending, req, replyv.Interface(), codec, errmsg) 148 | // server.freeRequest(req) 149 | } 150 | 151 | // Register publishes in the server the set of methods of the 152 | // receiver value that satisfy the following conditions: 153 | // - exported method 154 | // - two arguments, both of exported type 155 | // - the second argument is a pointer 156 | // - one return value, of type error 157 | // It returns an error if the receiver is not an exported type or has 158 | // no suitable methods. It also logs the error using package log. 159 | // The client accesses each method using a string of the form "Type.Method", 160 | // where Type is the receiver's concrete type. 161 | func (xpc *XPCService) Register(rcvr interface{}) error { 162 | return xpc.register(rcvr, "", false) 163 | } 164 | 165 | func (xpc *XPCService) register(rcvr interface{}, name string, useName bool) error { 166 | xpc.mu.Lock() 167 | defer xpc.mu.Unlock() 168 | if xpc.serviceMap == nil { 169 | xpc.serviceMap = make(map[string]*service) 170 | } 171 | 172 | s := new(service) 173 | s.typ = reflect.TypeOf(rcvr) 174 | s.rcvr = reflect.ValueOf(rcvr) 175 | sname := reflect.Indirect(s.rcvr).Type().Name() 176 | if useName { 177 | sname = name 178 | } 179 | if sname == "" { 180 | s := "xpc.Register: no service name for type " + s.typ.String() 181 | log.Print(s) 182 | return errors.New(s) 183 | } 184 | if !isExported(sname) && !useName { 185 | s := "xpc.Register: type " + sname + " is not exported" 186 | log.Print(s) 187 | return errors.New(s) 188 | } 189 | if _, present := xpc.serviceMap[sname]; present { 190 | return errors.New("xpc: service already defined: " + sname) 191 | } 192 | s.name = sname 193 | 194 | // Install the methods 195 | s.method = suitableMethods(s.typ, true) 196 | 197 | if len(s.method) == 0 { 198 | str := "" 199 | 200 | // To help the user, see if a pointer receiver would work. 201 | method := suitableMethods(reflect.PtrTo(s.typ), false) 202 | if len(method) != 0 { 203 | str = "xpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)" 204 | } else { 205 | str = "xpc.Register: type " + sname + " has no exported methods of suitable type" 206 | } 207 | log.Print(str) 208 | return errors.New(str) 209 | } 210 | xpc.serviceMap[s.name] = s 211 | return nil 212 | } 213 | -------------------------------------------------------------------------------- /cocoa/XPCConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // XPCConnection.m 3 | // OfficeBeats 4 | // 5 | // Created by Adam Venturella on 7/2/14. 6 | // Copyright (c) 2014 BLITZ. All rights reserved. 7 | // 8 | 9 | #import "XPCConnection.h" 10 | #import 11 | #import 12 | 13 | typedef id dynamic_action(id, Method, NSArray *); 14 | 15 | #pragma mark - XPCMethod 16 | @interface XPCMethod: NSObject 17 | @property(assign) Method method; 18 | @property(assign) NSUInteger numArgs; 19 | @property(assign) SEL selector; 20 | @end 21 | 22 | @implementation XPCMethod 23 | @end 24 | 25 | #pragma mark - XPCService 26 | @interface XPCService: NSObject 27 | @property(copy) NSString *name; 28 | @property(strong) id rcvr; 29 | @property(strong) NSMutableDictionary* method ; 30 | @end 31 | 32 | @implementation XPCService 33 | - (instancetype) init{ 34 | self = [super init]; 35 | 36 | if(self){ 37 | self.method = [[NSMutableDictionary alloc] init]; 38 | } 39 | 40 | return self; 41 | } 42 | @end 43 | 44 | 45 | #pragma mark - XPCPayload 46 | @interface XPCPayload: NSObject 47 | @property(copy) NSString *method; 48 | @property(copy) NSArray *args; 49 | @end 50 | 51 | @implementation XPCPayload 52 | @end 53 | 54 | 55 | #pragma mark - XPCConnection (Private) 56 | @interface XPCConnection (Private) 57 | 58 | - (xpc_connection_t)createConnection:(NSString *)name queue:(dispatch_queue_t)queue; 59 | - (void)prepareConnection:(xpc_connection_t)connection; 60 | - (void)initializeServiceConnection; 61 | - (void)initializeHostConnection; 62 | - (void)host:(XPCPayload *)payload; 63 | 64 | @end 65 | 66 | #pragma mark - XPCConnection 67 | @implementation XPCConnection 68 | 69 | - (instancetype)initWithName:(NSString *)name 70 | { 71 | self = [super init]; 72 | 73 | if(self){ 74 | self.name = name; 75 | self.serviceMap = [[NSMutableDictionary alloc] init]; 76 | self.keyMap = [[NSMutableDictionary alloc] init]; 77 | } 78 | 79 | return self; 80 | } 81 | 82 | - (void)register:(id)obj 83 | { 84 | Class cls = [obj class]; 85 | NSString *className = [NSString stringWithUTF8String:class_getName(cls)]; 86 | NSString * key; 87 | 88 | // Swift will mangle the names of classes made available to it: 89 | // http://stackoverflow.com/a/24329118/1060314 90 | // _TtC$$AppName%%ClassName 91 | 92 | NSString *prefix = @"_TtC"; 93 | 94 | if([className hasPrefix:prefix]){ 95 | NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; 96 | 97 | NSUInteger index = 0; 98 | NSString *length = [NSString stringWithFormat:@"%lu", (unsigned long)[appName length]]; 99 | 100 | index = index + [prefix length] + [length length] + [appName length]; 101 | 102 | NSString *stage1 = [className substringFromIndex:index]; 103 | NSString *stage2; 104 | NSScanner *scanner = [NSScanner scannerWithString:stage1]; 105 | [scanner scanInteger:NULL]; 106 | [scanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] intoString:&stage2]; 107 | 108 | key = stage2; 109 | } else { 110 | key = className; 111 | } 112 | 113 | self.keyMap[key] = className; 114 | 115 | if(self.serviceMap[className] != nil){ 116 | return; 117 | } 118 | 119 | XPCService *service = [[XPCService alloc] init]; 120 | service.name = key; 121 | service.rcvr = obj; 122 | 123 | uint methodCount; 124 | Method *methods = class_copyMethodList(cls, &methodCount); 125 | 126 | for (int i=0; i < methodCount; i++){ 127 | 128 | Method method = methods[i]; 129 | NSString *methodName = NSStringFromSelector(method_getName(method)); 130 | 131 | // TODO: right now we just blindly assume the 1st arg will be an array 132 | // 133 | // char buffer[256]; 134 | // method_getArgumentType(method, 0, buffer, 256); 135 | // could help alleviate that. 136 | // 137 | // all our methods that qualify should conform to the following: 138 | // arg 0: Array 139 | // arg 1: Pointer to the return value. 140 | // return error 141 | // ^ matches go side of things. 142 | // This is not implemented yet. 143 | 144 | XPCMethod *m = [[XPCMethod alloc] init]; 145 | m.method = method; 146 | m.numArgs = method_getNumberOfArguments(method); 147 | m.selector = NSSelectorFromString(methodName); 148 | 149 | service.method[methodName] = m; 150 | } 151 | 152 | self.serviceMap[className] = service; 153 | } 154 | 155 | 156 | - (void)call:(NSString *)method withArgs:(NSArray *)args reply:(void (^)(id))reply 157 | { 158 | xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 159 | NSDictionary *obj = [NSDictionary dictionaryWithObjects:@[method, args] 160 | forKeys:@[@"method", @"args"]]; 161 | 162 | NSData *data = [obj messagePack]; 163 | xpc_object_t payload = xpc_data_create([data bytes], [data length]); 164 | 165 | xpc_dictionary_set_value(message, "payload", payload); 166 | 167 | if(reply){ 168 | 169 | xpc_dictionary_set_bool(message, "reply", true); 170 | 171 | void (^response)(xpc_object_t) = ^(xpc_object_t value){ 172 | 173 | NSUInteger length; 174 | const void * responseBytes = xpc_dictionary_get_data(value, "payload", &length); 175 | 176 | NSData *responseData = [NSData dataWithBytes:responseBytes length:length]; 177 | reply([responseData messagePackParse]); 178 | }; 179 | 180 | xpc_connection_send_message_with_reply(self.serviceConnection, 181 | message, self.serviceQueue, 182 | response); 183 | } 184 | } 185 | 186 | - (void)resume 187 | { 188 | [self initializeServiceConnection]; 189 | [self initializeHostConnection]; 190 | 191 | // send the service a reference back to this host 192 | // so it can connect to us. 193 | xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); 194 | xpc_dictionary_set_value(message, "endpoint", xpc_endpoint_create(self.hostConnection)); 195 | xpc_connection_send_message(self.serviceConnection, message); 196 | } 197 | 198 | 199 | - (void)initializeHostConnection 200 | { 201 | NSString *queueName = [self.name stringByAppendingString:@"-host"]; 202 | self.hostQueue = dispatch_queue_create([queueName UTF8String], NULL); 203 | 204 | // we want an anonymous connection, so we pass nil 205 | // for the service name. 206 | self.hostConnection = [self createConnection:nil queue:self.hostQueue]; 207 | 208 | xpc_connection_set_event_handler(self.hostConnection, ^(xpc_object_t event) { 209 | [self host:self.hostConnection event:event]; 210 | }); 211 | 212 | xpc_connection_resume(self.hostConnection); 213 | } 214 | 215 | - (void)initializeServiceConnection 216 | { 217 | // represents our connection TO the XPC service. 218 | // allows us to send information to the XPC Service. 219 | // this must be initialized first prior to sending 220 | // a host connection to the service to enable the XPC 221 | // service to talk back to us. 222 | NSString *queueName = [self.name stringByAppendingString:@"-service"]; 223 | self.serviceQueue = dispatch_queue_create([queueName UTF8String], NULL); 224 | 225 | self.serviceConnection = [self createConnection:self.name queue:self.serviceQueue]; 226 | 227 | xpc_connection_set_event_handler(self.serviceConnection, ^(xpc_object_t event) { 228 | [self service:self.serviceConnection event:event]; 229 | }); 230 | 231 | xpc_connection_resume(self.serviceConnection); 232 | } 233 | 234 | - (xpc_connection_t)createConnection:(NSString *)name queue:(dispatch_queue_t)queue 235 | { 236 | xpc_connection_t result; 237 | 238 | if (!name){ 239 | result = xpc_connection_create(NULL, queue); 240 | } else { 241 | const char * serviceName = [name UTF8String]; 242 | result = xpc_connection_create(serviceName, queue); 243 | } 244 | 245 | return result; 246 | } 247 | 248 | 249 | - (void)host:(xpc_connection_t)connection event:(xpc_object_t)event 250 | { 251 | xpc_type_t type = xpc_get_type(event); 252 | 253 | if (type == XPC_TYPE_CONNECTION) { 254 | // handle messages sent FROM the service back to us: 255 | xpc_connection_t peer = (xpc_connection_t) event; 256 | 257 | char *queue_name = NULL; 258 | 259 | // make a unique queue for the event handler 260 | // so we are left with a 1:1 relationship between 261 | // these connections 262 | asprintf(&queue_name, 263 | "%s-peer-%d", 264 | [self.name UTF8String], 265 | xpc_connection_get_pid(peer)); 266 | 267 | dispatch_queue_t peer_event_queue = dispatch_queue_create(queue_name, NULL); 268 | free(queue_name); 269 | 270 | xpc_connection_set_target_queue(peer, peer_event_queue); 271 | xpc_connection_set_event_handler(peer, ^(xpc_object_t event) { 272 | xpc_type_t type = xpc_get_type(event); 273 | 274 | if(type == XPC_TYPE_DICTIONARY){ 275 | 276 | NSUInteger length; 277 | const void *bytes = xpc_dictionary_get_data(event, "payload", &length); 278 | 279 | NSData *data = [NSData dataWithBytes:bytes length:length]; 280 | NSDictionary *obj = (NSDictionary *)[data messagePackParse]; 281 | 282 | XPCPayload *payload = [[XPCPayload alloc] init]; 283 | payload.method = obj[@"method"]; 284 | payload.args = obj[@"args"]; 285 | 286 | [self host:payload]; 287 | } 288 | }); 289 | 290 | xpc_connection_resume(peer); 291 | } 292 | } 293 | 294 | - (void)host:(XPCPayload *)payload 295 | { 296 | NSArray *parts = [payload.method componentsSeparatedByString:@"."]; 297 | NSString *key = self.keyMap[parts[0]]; 298 | 299 | if(!key) return; 300 | 301 | XPCService *service = self.serviceMap[key]; 302 | 303 | if(!service) return; 304 | 305 | XPCMethod *method = service.method[parts[1]]; 306 | 307 | if(!method) return; 308 | 309 | // method_invoke must be cast to an appropriate function pointer type 310 | // before being called. 311 | ((dynamic_action*) method_invoke) (service.rcvr, method.method, payload.args); 312 | } 313 | 314 | - (void)service:(xpc_connection_t)connection event:(xpc_object_t)event 315 | { 316 | xpc_type_t type = xpc_get_type(event); 317 | 318 | if (type == XPC_TYPE_ERROR) { 319 | if (event == XPC_ERROR_TERMINATION_IMMINENT) { 320 | [self serviceTerminationImminent: connection]; 321 | } else if (event == XPC_ERROR_CONNECTION_INVALID) { 322 | [self serviceConnectionInvalid: connection]; 323 | } else if (event == XPC_ERROR_CONNECTION_INTERRUPTED){ 324 | [self serviceConnectionInterrupted: connection]; 325 | } 326 | } 327 | } 328 | 329 | - (void)serviceConnectionInterrupted:(xpc_connection_t) connection 330 | { 331 | // The service has either cancaled itself, crashed, or been 332 | // terminated. The XPC connection is still valid and sending a 333 | // message to it will re-launch the service. If the service is 334 | // state-full, this is the time to initialize the new service. 335 | NSLog(@"received XPC_ERROR_CONNECTION_INTERRUPTED"); 336 | } 337 | 338 | - (void)serviceTerminationImminent:(xpc_connection_t) connection 339 | { 340 | NSLog(@"received XPC_ERROR_TERMINATION_IMMINENT"); 341 | } 342 | 343 | - (void)serviceConnectionInvalid:(xpc_connection_t) connection 344 | { 345 | // The service is invalid. Either the service name supplied to 346 | // xpc_connection_create() is incorrect or we (this process) have 347 | // canceled the service; we can do any cleanup of appliation 348 | // state at this point. 349 | 350 | NSLog(@"received XPC_ERROR_CONNECTION_INVALID"); 351 | self.serviceConnection = nil; 352 | } 353 | @end 354 | --------------------------------------------------------------------------------