앱에서 전화와 관련된 기능을 구현하고 싶어서 찾아본 결과 CallKit이라는 프레임워크를 사용하는 것 같았습니다.
그럼, CallKit프레임워크가 무엇인지 알아보도록 공식문서를 보겠습니다.
공식문서의 설명을 보면 CallKit
이란?
→ 앱의 VoIP서비스를 시스템호출UI를 표시하고 다른 앱 및 시스템과 전화서비스를 할 수 있도록 지원하는 프레임워크라고 쓰여있습니다.
CallKit은 2016년 iOS10에 발표되었습니다. 다른 앱과 통화서비스를 통합하여 사용합니다.
기본적인 통화화면을 제공할 뿐만아니라 백엔드에서 VoIP서비스와 통신하여 처리할 수 있습니다.
수신 및 발신전화에서 기본인터페이스를 제공할 수 있습니다. 발신자 및 차단된번호 목록등을 제공가능합니다.
설명에 있는 VoIP가 정확히 무엇인지 알아보도록 하겠습니다.
VoIP는 (Voice over Internet Protocol)의 약자입니다. 인터넷과 같은 인터넷 프로토콜(IP) 네트워크를 통해 음성 통신과 멀티미디어(텍스트, 오디오, 비디오, 이미지 등등)세션의 전달을 위한 기술들의 모임을 가리키는 용어입니다.
💡 IP : 인터넷 공간에서 상대방의 PC 혹은 단말기의 위치정보를 나타낼 때 사용된다.(인터넷 공간에서 통신을 하기위한 가장 기초적인 주소의 역할을 담당)
mVoIP(Mobile Voice over Internet Protocol) : 무선 인터넷망을 활용해 무료로 음성통화를 이용하는 서비스(Voip를 Mobile영역으로 확장)
메신저 앱에서 음성통화를 지원하는 경우 mVoIP를 사용합니다.
이동통신망의 음성을 사용하지 않고 데이터만 사용해서 지원하는 중입니다.
기존에는 VoIP앱들은 수신전화를 받기위해 서버와 지속적으로 네트워크 연결을 유지했어 했습니다. 하지만 계속해서 앱을 사용하지 않을 때도 연결을 유지한다면 데이터를 낭비하므로 비효율적입니다.
이를 해결하는 방법은 PushKit프레임워크(앱이 원격 서버에서 푸시(데이터를 사용할 수 있을 때 알림)를 수신할 수 있도록 하는 API)를 사용합니다.
프로젝트에서 VoIP 백그라운드 모드 활성화
Apple Developer Member Center에서 VoIP 인증서 생성
VoIP Push Notification 정의
//AppDelegate.swift
import PushKit
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.voipRegistration()
return true
}
// VoIP 알림 등록
func voipRegistration() {
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: .main)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [PKPushType.voIP]
}
// push 알림과 VoIP알림을 수신하고 분리 - 델리게이트 메서드
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
// 두개의 push token을 분리해야합니다.![](https://velog.velcdn.com/images%2Fsookim-1%2Fpost%2Fdf49d01b-6282-44a2-b431-0c7a4c03e8bb%2F1.png)
}
// push 처리
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
// 수신받은 push 처리 - push가 수신될 때 앱이 실행되고 있지않다면 앱을 실행
}
수신전화를 받기위해서는 첫번째로 CXProvider객체를 생성하고 전역에 접근하기위해 저장해야 합니다.
VoIP 관련 푸시 알림 수신
// MARK: PKPushRegistryDelegate
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {
// report new incoming call
}
if let uuidString = payload.dictionaryPayload["UUID"] as? String,
let identifier = payload.dictionaryPayload["identifier"] as? String,
let uuid = UUID(uuidString: uuidString)
{
let update = CXCallUpdate()
update.callerIdentifier = identifier
provider.reportNewIncomingCall(with: uuid, update: update) { error in
// …
}
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// configure audio session
action.fulfill()
}
SiriKit의 INStartAudioCallIntentHandling프로토콜 사용 - Deprecate
전화 발신을 하기위해서는 앱에서 CXCallController객체로부터 CXStartCallAction객체를 요청해야합니다.
let uuid = UUID()
let handle = CXHandle(type: .emailAddress, value: "scstnghks@naver.com")
let startCallAction = CXStartCallAction(call: uuid)
startCallAction.destination = handle
let transaction = CXTransaction(action: startCallAction)
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
} else {
print("Requested transaction successfully")
}
} ****
통화 오디오 시작
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// configure audio session
action.fulfill()
}
앱은 전화번호에 차단되어 있는 번호를 식별하여 전화 목록을 만들 수 있습니다.
class CustomCallDirectoryProvider: CXCallDirectoryProvider {
override func beginRequest(with context: CXCallDirectoryExtensionContext) {
let labelsKeyedByPhoneNumber: [CXCallDirectoryPhoneNumber: String] = [ … ]
for (phoneNumber, label) in labelsKeyedByPhoneNumber.sorted(by: <) {
context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
}
context.completeRequest()
}
}
🚨 이 메서드는 시스템이 앱확장을 시작할 때만 호출되고 각각의 전화마다 호출되지 않으므로 전화식별 정보를 한 번에 모두 지정해야 합니다. 예를 들어 수신 전화에 대한 정보를 찾기 위해 웹 서비스에 요청할 수 없습니다.class CustomCallDirectoryProvider: CXCallDirectoryProvider {
override func beginRequest(with context: CXCallDirectoryExtensionContext) {
let blockedPhoneNumbers: [CXCallDirectoryPhoneNumber] = [ … ]
for phoneNumber in blockedPhoneNumbers.sorted(by: <) {
context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
}
context.completeRequest()
}
}
다른 오디오 앱처럼 VoIP앱은 오디오 세션중단을 처리해야 합니다. 사용자가 다른 전화를 받거나 동영상을 재생하는 등 여러가지 이유로 중단이 발생할 수 있는데 이러한 상황에서 중단알림에는 중단 이유가 포함되며 필요한 경우 앱에서 전화를 올바르게 종료할 수 있습니다.
VoIP
CallKit
PushKit