본문 바로가기
IOS App Programming/IOS 연습

[SwiftUI/WatchOS] WatchConnectivity - WCSession을 이용하여 WatchOS와 IOS 데이터 주고 받기

by B_Tori 2024. 3. 20.

워치앱을 개발하면서 워치와 iOS앱의 데이터를 연동해야 하는 상황이었다.

위젯의 경우 UserDefaults와 같은 데이터를 AppGroup을 통해 데이터를 공유하여 연동할 수 있었기에 워치도 이와 같은 방법으로 구현할 수 있으리라 생각하고 쉽게 생각하였다.

하지만 워치의 경우 iOS App의 Data Store 부분과 Watch App의 Data Store 부분이 완전히 분리되어 있어 위젯처럼 데이터 공유를 할 수 없다.

따라서 다른 방법을 이용해서 iOS와 워치 간의 데이터를 동기화시킬 수 있도록 해야헀다.

그 방법이 바로 Watch Connectivity이다.

Watch Connectivity
iOS 앱과 페어링 된 watchOS 앱 간의 양방향 통신을 구현합니다.
- 공식문서 Watch Connectivity

 

Watch Connectivity은 WCSession 객체를 통해 통신을 진행하며 WCSessionDelegate 대리자를 위임받아 기능을 작성할 수 있다.

 

❗️공식문서에서 실기기로 테스트하라는 말을 자주 볼 수 있다고 한다. 따라서 테스트는 실기기로 테스트하기를 권장한다. 이 사실을 모르고 시뮬레이터로 연습 코드를 실행하면서 데이터 연동이 안 됐다 됐다 이상해서 오류 인가 하고 시간을 많이 잡아먹었는데 시뮬레이터의 문제였다..

 

플러스 마이너스 버튼을 누르면 카운터가 증감되는 간단한 앱을 워치와 핸드폰을 연동시켜 보면서

WCSession으로 데이터를 주고받아볼 예정이다.

WCSession 사용 방법

  1. 먼저 WCSession.default를 (init) 통해 세션을 얻고
  2. delegate를 설정한 후 activate() 메서드를 호출하여 세션을 활성화
  3. 데이터 송수신을 위한 함수를 작성
  • 세션의 상태는 activationState 프로퍼티를 통해 확인 가능
  • isReachable 프로퍼티를 통해 현재 다른 장치가 도달 가능한 상태인지 확인 가능
  • 데이터를 수신하려면, WCSessionDelegate 프로토콜의 메서드를 구현해야 함.
    예를 들어, session(_:didReceiveMessage:) 메서드를 통해 메시지를 수신하고, session(_:didReceiveUserInfo:) 메서드를 통해 사용자 정보를 수신 가능

 

IPhone - ViewModel

IPhone의 ViewModel 클래스이다.

import Foundation
import WatchConnectivity

final class ViewModelIPhone: NSObject, WCSessionDelegate, ObservableObject {
    @Published var count: Int = 0
    var session: WCSession

    init(session: WCSession = .default) {
        self.session = session
        super.init()
        self.session.delegate = self
        session.activate()
    }
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

    }

    func sessionDidBecomeInactive(_ session: WCSession) {

    }

    func sessionDidDeactivate(_ session: WCSession) {

    }

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        DispatchQueue.main.async {
            self.count = message["count"] as? Int ?? 0
        }
    }

}

init

  • 클래스 안에 WCSession 객체를 선언해 주고 클래스 init 시 세션을 생성해 주었다.
  • WCSessionDelegate의 델리게이트를 위임받아 통신한 데이터를 핸들링할 수 있도록 하였다.
  • session.activate() 를 통해 세션 활성화

WCSessionDelegate 메서드

WCSessionDelegate 프로토콜을 준수하면 ios의 경우 아래 세 가지 함수를 필수적으로 추가해야 한다.

하지만 실제로 사용되지 않을 가능성도 많기 때문에 모두 비어있을 수 있다.

나 또한 사용할 일이 없어 비어 두었다.

  • session(_:activationDidCompleteWith:error:) : 세션 활성화가 완료되었음
  • sessionDidBecomeInactive(_:) : 세션이 현재 Apple Watch와의 통신을 중단할 것임 (ios 만 존재)
  • sessionDidDeactivate(_:) : 세션이 이전 세션의 모든 데이터를 전달했으며 Apple Watch와의 통신이 종료되었음 (ios 만 존재)

가장 중요한 것은 워치에서 보낸 데이터를 수신하는 함수이다.

  • session(WCSession, didReceiveMessage: [String : Any]) : 메시지가 도착했음을 대리인에게 알리는 함수이다.
  • 메시지를 보내는 Send함수에 따라 호출되는 Receive함수가 다르다.

IPhone/ Watch 공통 - 메시지 보내기 - Send

메시지를 보내는 함수는 간단하게 그냥 뷰에서 버튼 액션에 작성해 주었다.

// - 버튼 
Button {
    vm.count -= 1
    vm.session.sendMessage(["count": vm.count], replyHandler: nil) { error in
        text = error.localizedDescription
    }
} label: {
    Text("-")
}
  • func sendMessage(\_ message: \[String : Any\], replyHandler: ((\[String : Any\]) -> Void)?, errorHandler: ((Error) -> Void)? = nil ) 를 통해 즉시 메시지를 전송할 수 있도록 하였다.
  • [String : Any] 타입의 메시지를 보냈기 때문에 받는 부분의 델리게이트에서도 didReceiveMessage: [String : Any]로 받아주면 된다.

Watch - ViewModel

watch의 뷰모델의 경우 거의 비슷한 형태지만 모습이 조금 다르다.

import Foundation
import WatchConnectivity

final class ViewModelWatch: NSObject, WCSessionDelegate, ObservableObject {
    @Published var count: Int = 0
    var session: WCSession

    init(session: WCSession = .default) {
        self.session = session
        super.init()
        self.session.delegate = self
        session.activate()
    }
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

    }
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        DispatchQueue.main.async {
            self.count = message["count"] as? Int ?? 0
        }
    }
}

 

필수로 구현해야 하는 델리게이트 메서드가 session(_:activationDidCompleteWith:error:) 뿐이기 때문에 아이폰보다는 짧은 코드이다.

나머지 내용은 아이폰에서 구현한 방식과 동일하다.

Send와 Receive 종류

위에서는 didReceiveMessage - sendMessage를 사용하여 데이터를 주고받았다.

이 외에도 다양한 전송 방식이 있고 이에 따라 받는 함수도 다르게 호출이 된다.

Send

  1. 메시지 전송
    • sendMessage(_:replyHandler:errorHandler:)
      • sendMessage → [String : Any] 타입 메시지 전송
      • sendMessageData → Data 타입 메시지 전송
    • 연결된 장치가 활성 상태일 때 즉시 데이터를 전송할 수 있음
    • 백그라운드 전송 불가 (세션이 활성화된 동안에만 메서드 호출 가능
  2. 사용자 정보 전송
    • transferUserInfo(_:)
    • 메서드를 사용하면, 앱이 백그라운드에 있을 때도 데이터를 전송 가능
    • 이 메서드는 주로 작은 크기의 변경 가능한 데이터(Dictionary 타입의 데이터)를 전송하는 데 사용
    • 전송된 딕셔너리는 다른 기기의 큐에 들어가 대기하게 되고, 순차적으로 전달됨
    • 시뮬레이터에서는 안됨, 실기기 사용해야 함
  3. 파일 전송
    • transferFile(_:metadata:)
    • 파일 또는 데이터를 전송해 주는 메서드
    • 앱이 백그라운드에 있을 때도 파일을 전송 가능 (transferUserInfo(_:)와 거의 동일하지만 파일 전송도 된다는 차이를 가짐)

Receive

  1. 메시지 받기
    • session(_:didReceiveMessage:replyHandler:)sendMessage
    • session(_:didReceiveMessageData:)sendMessageData
  2. 사용자 정보 받기
    • session(_:didReceiveUserInfo:)
  3. 파일 받기
  • session(_:didReceive:) : didReceive file: WCSessionFile

댓글