프로세스 통신을 위한 XPC
XPC 매커니즘은 IPC용 소켓 (또는 MIG를 사용하는 Mach Services)에 대한 대안을 제공한다.
예를들어 특정 프로세스의 API에 접근하거나, 서비스를 제공하기 위한 클라이언트를 기다리는 서버역할의 프로세스를가 있을 수 있다.
애플리케이션의 XPC 서비스
보통 XPC 서비스에 대해 얘기할때 XPC Service라고 불리는 번들(Bundle)에 대해 이야기 하는것을 의미한다.
애플 생태계에서 번들은 특정 디렉토리 구조로 표현되는 엔티티를 나타낸다. 가장 잘 알려진 번들은 애플리케이션 번들이다. 애플리케이션(예: Chess.app)을 마우스 오른쪽 버튼으로 클릭하고 콘텐츠 표시를 선택하면 디렉토리 구조가 표시된다.
XPC로 돌아와서, 애플리케이션은 XPC Service 번들을 가질 수 있다. 애플리케이션 번들 내 Contents/XPCServices/ 경로의 디렉터리에서 확인할 수 있다.
또한 Frameworks 내에서 XPC 서비스를 가질 수 있다.
XPC 서비스의 추가 이점
xpc 서비스를 이용한다면 별도의 모듈에서 몇가지 기능을 중단할 수있다.
비용은 많이 들지만 드물게 발생하는 작업을 실행하는 XPC 서비스를 만들 수 있다. 예를 들어 랜덤 수를 생성하는 암호화 작업이 있을 수 있다.
또하나의 이점은 XPC 서비스는 자신의 프로세스에서 작동한다는것이다. 만약 해당 프로세스가 죽어도 우리가 개발하고 있는 메인 앱에 영향을 끼치지 않을것이다.
만약 개발하고 있는 앱이 사용자 정의 플러그인을 지원하고 그 플러그인들은 XPC 서비스를 사용해 개발된 상황을 가정해보자,
플러그인들을 실행시키는 코드가 크래시가 나도 그것은 현재 메인 앱의 무결성에 영향을 주지 않는다.
또한 XPC는 자체 entitlements(서비스 또는 기술을 사용할 수 있는 실행 권한을 부여하는 키-값 쌍, 파일형태이다.)를 가진다.
애플리케이션은 XPC 서비스에서 제공하는 서비스를 사용할때만 entitlement를 필요로한다.
위치를 사용하지만, 특정 기능에서만 위치서비스를 사용하는 앱이 있다 상상해보자, 이러한 기능을 XPC 서비스로 이동하고 해당 XPC 서비스에만 위치 권한을 추가할 수 있다. 사용자가 위치를 사용하는 기능이 필요하지 않은경우, 권한을 묻는 메시지가 표시되지 않으므로 앱 사용을 더욱 신뢰할 수 있다.
XPC와 우리의 친구 launchd
launchd는 시스템에서 실행되는 첫번째 프로세스이다.
launchd는 다른 프로세스, 서비스, 데몬들을 실행시키고 관리할 책임이 있다. 또한 작업들을 스케쥴링을 담당한다. 따라서 launchd는 XPC 서비스의 관리를 담당한다는 뜻도 된다.
+Keynote애플리케이션에서 사용된 XPC 서비스

터미널에서 find [앱 경로] -name ".*xpc" 명령어로 앱에 사용된 xpc 서비스를 확인할 수 있다.
XPC 서비스 구조 & 원리
XPC 서비스를 사용한다는 것은 C API (XPC Services)를 사용하는지, Objective - C API( NSXPCConnection)를 사용하는지에 대해 영향을 받는다.
Objective-C의 NSXPCConnection API는 다른 프로세스에서 한 프로세스의 개채에 대한 메서드를 호출할 수 있는 상위 수준 원격 프로시저 호출 인터페이스를 제공한다.
NSXPCConnection API는 전송을 위해 데이터 구조와 개체를 자동으로 직렬화하고, 다른 끝 쪽에서 역직렬화를 한다.
결과적으로, 원격 개체에서 메서드를 호출하는것은 로컬 개체에서 메서드를 호출하는 것과 매우 유사하게 작동하는것이다.
NSXPCConnection 을 사용하기 위해서 아래와 같이 구성을 해야한다.
- 인터페이스 : 대부분 프로토콜로 이루어져있고, 이 프로토콜은 원격 프로세스에서 호출할 수있는 메서드를 가지고 있다.
호출 어플리케이션과 서비스간의 프로그래밍적 인터페이스를 정의한다. 연결의 반대쪽에서는 호출하려는 모든 인스턴스 메서드는 프로토콜로 명시적으로 정의해ㅐ야한다.
- 양쪽에서의 connection 객체 : 서비스(XPC 서비스) 쪽에서의 connection과 클라이언트 쪽에서의 connection이 필요하다.
- listener : 리스너는 XPC 서비스 커넥션안에 있다.
전체적인 구조

XPC 서비스 구현하기
XPC 서비스는 기본 애플리케이션 번들의 Contents/XPCServices 디렉토리에 있는 번들이다. XPC 서비스 번들은 실행파일, info.plst, 서비스에 필요한 리소스들을 포함한다.
XPC 서비스는 메인함수에서 xpc_main(3) Mac OS X 개발자 도구 매뉴얼 페이지를 호출하여 서비스가 메세지를 수신할 때 호출할 함수를 나타낸다. (이부분은 무슨뜻인지 정확히 모르겠음)
Xcode에서 XPC 서비스의 간단한 예시 (swift)
(해당 예시는 유튜브 AppleProgramming 채널의 Cocoa 프로그래밍 L74-XPC 서비스 영상을 참고하였습니다)
이 예시에서 다룰 XPC 서비스는 영어 문장을 입력으로 소문자를 모두 대문자로 바꿔주는 XPC 서비스를 구현한다.
1. XPC Service 탬플릿을 사용하여 프로젝트에 새 대상을 추가한다.
2. 입력과 출력을 담당할 label, textfield, button을 추가한다.
3. XPC 서비스를 swift로 작성하기 때문에, 기존 TextServices 파일을 swift로 변환해준다.
XPC 서비스를 추가하고 나면 구성되어있는 파일은 위와 같다.
- TextServiceProtocol : 외부에서 해당 XPC 서비스를 사용하기위한 인터페이스 역할.
- TextService.m, TextService.h : Service 자체 코드
- main.m : 서비스의 메인 진입점, delegate를 설정한 다음 xpc 리스너를 설정하고, delegate를 할당한 다음 listener를 시작하여 연결을 한다. 연결을 한 이후엔 애플리케이션이 최초로 호출을 보낸 다음 실제로 서비스를 실행할 수 있게 한다.
위 파일들은 오브젝트 씨 기준으로 생성된 파일이기 때문에 swift로 변환해주는 작업을 진행한다.
TextServiceProtocol.h -> TextServiceProtocol.swift
TextService.h -> TextServiceDelegate.swift
TextService.m -> TextService.swift
main.m -> main.swift
이때 .h는 오브젝트 C의 헤더파일인데, 타겟 설정이 안되어있을 수 있으니 타겟설정을 해주고 넘어가야한다.
헤더파일이였던 파일을 눌러 왼쪽 바에서 보면 TargetMembership 박스 안에 TextService체크박스가 체크 안되어있는것을 확인할 수 있다.
체크가 되어있지 않다면 체크해줘서 타겟 설정을 해준다.
4. Swift를 쓰기 때문에 Object C일때 쓰였던 헤더 설정을 해제해준다.
Target을 TextService로한 후,
- Swift Compiler - Language : Swift 5(최신버전)
- Install Objective-C Compatiblity Header : No
- Objective-C Generated Interface HeaderName : 빈칸
의 설정으로 바꿔준다.
5. 코드 구현
- TextServiceProtocol.swift
import Foundation
@objc func public protocol TextServiceProtocol {
func uppercase(_ string: String, withReply reply: @escaping (String) -> Void)
}
- TextService.swift
import Foundation
class TextService: NSObject, TextServiceProtocol {
func uppercase(_ string: String, withReply reply: @escaping (String) -> Void) {
reply(string.uppercased())
}
}
- TextServiceDelegate.swift
import Foundation
class TextServiceDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: TextServiceProtocol.self)
newConnection.exportedObject = TextService()
newConnection.resume()
return true
}
}
- main.swift
import Foundation
let delegate = TextServiceDelegate()
let listener = NSXPCListener.service()
listener.delegate = delegate
listener.resume()
6. XPC 서비스를 캡슐화 할 Utility 코드 작성
다시 기존에 개발하던 앱 코드로 돌아가 앞서 구현해주었던 XPC 서비스를 캡슐화하고, 유틸리티 역할을 해주는 코드를 작성한다.
UppercaseUtility.swift 라는 클래스를 생성한 후 아래와같은 코드를 작성한다.
- UppercaseUtility.swift
import Foundation
import TextService
class UppercaseUtility {
func uppercase(_ string: String, withReply reply: @escaping (String) -> Void) {
let connection = NSXPCConnection(serviceName: "com.boostcamp.S046.TextService")
connection.remoteObjectInterface = NSXPCInterface(with: TextServiceProtocol.self)
connection.resume()
let service = connection.remoteObjectProxyWithErrorHandler { error in
print("Error: \(error)")
} as? TextServiceProtocol
service?.uppercase(string, withReply: reply)
}
}
Connection을 생성해주고 현재 개발하고있는 서비스와 연결해준다.
연결을 사용하려는 XPC의 번들아이디로 생성(NSXPCConnection)한 후 service 객체 생성한다.
생성한 service 객체를 통해 TextService XPC 서비스의 uppercase 메서드를 사용한다.
7. 원하는 곳에서 해당 기능 사용
텍스트필드로 텍스트를 입력받은 후 버튼을 누르면 대문자로 변환하는 앱이기 때문에 button Action에 해당 기능을 사용하는 코드를 작성한다.
- ViewController.swift
import Cocoa
class ViewController: NSViewController {
@IBOutlet weak var outputLabel: NSTextField!
@IBOutlet weak var textField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func changeUppercase(_ sender: Any) {
UppercaseUtility.uppercase(textField.stringValue) { text in
DispatchQueue.main.async {
self.outputLabel.stringValue = text
}
}
}
}
8. 결과
텍스트를 입력후 버튼을 누르면 최초 첫번째 연결은 시간이 걸리지만, 그 이후의 버튼 액션은 시간이 더 짧아지는것을 확인할 수 있다.
또한 활성상태 앱에서도 TextService 라는 XPC 서비스가 단독으로 실행되고 있음을 확인할 수 있다.
해당 글은
https://medium.com/dwarves-foundation/xpc-services-on-macos-app-using-swift-657922d425cd
XPC services on macOS app using Swift
Before XPC we used to pick up Sockets and Mach Messages (Mach Ports).
medium.com
과
https://www.youtube.com/watch?v=kEUEGOI2WO0
를 번역하여 정리한 글입니다.