본문 바로가기
iOS_Swift 앱개발👍

[iOS_Swift] WatchOS_Watch Connectivity _ 35

by 개발하는윤기사 2023. 8. 11.
728x90
반응형

 

오늘 포스팅은 WatchOS에 대해 다뤄볼까 합니다!

 

요즘 WatchOS를 이용한 개발을 진행하고 있는데, 익숙하지 않은 개념이다 보니 "정리글을 쓰면 좋겠다" 생각해서 글을 씁니다!

 

이번 글에서 다룰 내용은 WatchKit을 이용한 UI를 단순히 그리는 내용이 아닌,

 

Watch Connectivity 프레임워크를 이용한 앱과 워치 간의 Interaction에 대해 설명드릴 겁니다!

 

바로 시작해 보시죠!

 

 

 

🔥 Watch Connectivity Framework

 

공식 문서에 따르면 Watch는 Watch Connectivity라는 framework를 사용해서 App과 watchOS App 사이의 two-way communication을 구현해줘야 합니다. 즉 " iOS 앱과 페어링 된 watchOS 앱 간의 양방향 통신을 구현합니다. " 

 

 

 WatchOS 2부터는 WatchKit Extension을 Watch로 옮겼고 각각의 Data Store를 가지게 되었습니다. 

 

기존에는 App의 Data Store를 같이 사용하는 방식이었다면, WatchOS 2부터는 각자의 Data Store을 가지게 된 것입니다.

 

 

조금 더 자세히 알아보겠습니다!!

 

이 Framework를 이용해서 iOS app과 paired WatchOS app의 WatchKit extension에서 데이터 교환을 할 수 있습니다!

 

초기화한 이후부터는, 시스템이 데이터 전송에 대한 모든 책임을 지게 됩니다.

 

대부분의 교환은 App이 inactive인 상태일 때 background에서 일어납니다.

 

App이 Background -> ForeGround로 올라오면, inactive 상태였을 때 도착한 데이터에 대한 노티를 받게 됩니다. 

 

두 App이 모두 active 상태일 때, 실시간 통신이 가능합니다.

 

이제 하나하나 자세히 살펴보도록 할게요!

 

 

 

💎 1. 초기화하기

 

앱과 워치 간에 통신하기 전에 먼저 초기화 과정이 필요합니다. 

 

iOS app과 watchOS app 둘 다 Session에 대한 설정이 필요하고, 만들어야 합니다.

 

Session Object를 설정하고 active 시킨 후,  메시지를 보내거나 연결 상태에 대한 정보를 얻으려고 시도할 수 있습니다.

 

또한 session을 활성화하기 전에 활성화 상태를 확인하는 메서드를 통해 현재 디바이스가 Watch Connectivity framework를 사용할 수 있는지 알 수 있습니다. 이 내용은 아래에서 다루겠습니다!

 

 

세션 초기화를 하기 위해 initWCSession()이라는 공통 메서드를 만들어서, 적절한 위치에서 호출하겠습니다.

 

session을 만들고 delegate를 self 해줍니다. 그리고 activate() 메서드를 이용해 session을 활성화시켜 줍니다. 

class WCSessionManager: NSObject {

    private var session: WCSession?/** 세션 */

     /** 세션 init */
    public func initWCSession() {

        session = WCSession.default
        session?.delegate = self
        session?.activate()
    }

}

 

WatchKit extension과 iOS App에 모두 각각의 세션을 설정해줘야 합니다.

 

iOS App은  AppDelegate > didFinishLaunchingWithOptions에서 초기화를 해주었고,

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        
        /** 워치 통신 설정 */
        WCSessionManager.shared().initWCSession()

        return true
 }

 

WatchOS App은 ExtensionDelegate에 applicationDidFinishLaunching에서 초기화시켜 주었습니다.

import WatchKit

@available(watchOSApplicationExtension 5.0, *)
class ExtensionDelegate: NSObject, WKExtensionDelegate {

    func applicationDidFinishLaunching() {
        WCSessionManager.shared().initWCSession()/** WCSession init */
    }
    
    /** BackGround  -> ForeGround */
    func applicationDidBecomeActive() {
   
        /** OP_100 알림 */
        WCSessionManager.shared().sendToAppCameForeground()
    }
    
    /** ForeGround -> BackGround */
    func applicationWillResignActive() {
    }

 

 

그리고 info.plist에 WKExtensionDelegateClassName에 아래 내용을 추가해 주었습니다. 

$(PRODUCT_MODULE_NAME).ExtensionDelegate

 

이렇게 추가를 해주어야 Watch에서 ExtensionDelegate를 사용할 수 있습니다.

두 세션이 active 상태가 되면, 두 앱은 즉시 커뮤니케이션을 할 수 있습니다. 

 

 

 

💎 2. 커뮤니케이션하기

 

이제 초기화과정이 끝났으니 커뮤니케이션을 어떻게 하는지 알아보겠습니다!

 

그전에 세션 활성화 상태인 sessionActivationState를 살펴보겠습니다.

 

WCSessionActivationState는 session의 현재 activation state를 말하며

 

notActivated / inactive / activate 세 가지가 있습니다.

 

- notActivated : 세션이 비활성화인 상태. 

- inactive: 세션 활성화되었지만, 비활성화상태로 전환 중인 상태. 데이터 받기는 가능하지만, 앱으로 data를 보낼 수는 없음

- activate: 세션이 활성화된 상태. 

세션 상태 Send Receive
notActivated 불가능 불가능
inactive 불가능 가능
activate 가능 가능

 

실제 사용하기 위해 활성화 여부를 판단하는 Boolean타입의 변수를 하나 설정하고,

 

WCSessionDelegate의 메서드를 이용해서 isActivated 값을 리턴 시켜주도록 구현했습니다! 

 

아래와 같이 말이죠!

private var isActivated = false /** 활성화 여부 */

/** 활성화 여부 */
public func checkActivated() -> Bool {

   return isActivated
}

//WCSessionDelegate
/** 세션 활성화 시 상태 리턴 */
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {

    switch activationState {
    case .notActivated:
        isActivated = false
    case .inactive:
        isActivated = false
    case .activated:
        isActivated = true
    @unknown default:
        isActivated = false
    }
}

 

또 추가로, WCSessionDefine 임의의 클래스를 하나 정의해 놓겠습니다.

 

WCSessionDefine 클래스는 워치와 앱 사이에 메시지를 송수신하기 위한 일종의 정해놓은 규칙이라고 생각하시면 됩니다.

 

앞으로 메시지 송수신은 Key와 Value 값이 들어있는 딕셔너리 타입으로 전달할 겁니다!

class WCSessionDefine: NSObject {
    
    /** OPERATION 키 */
    public static let operation = "OPERATION"/** OPERATION */
    
    /** OPERATION 값 */
    public static let OP_001 = "OP_001"/** 워치앱 설치 유무 */

    public static let OP_107 = "OP_100"/** 앱이 포그라운드로 올라옴 알림 */
    
    public enum SendType {
        case toApp
        case toWatch
    }
}

 

이제 준비가 끝난 것 같네요! 

 

앱과 워치 간의 데이터를 송수신하러 가보시죠!

 

 

💎 3. Send : 메시지 보내기

 

데이터를 보내려면 활성화 상태가 activated 상태여야지만 상대방에게 데이터를 전송할 수 있습니다!

 

iOS App은 데이터를 전송하기 전 isWatchAppInstalledisPaired를 이용하여 상태를 체크하고, 

 

WatchOS App은 isReachable을 이용해 상태를 체크합니다.

/** 워치 앱 접근 가능 여부 */
public func checkWatchAppReachable() -> Bool {

    if let session = session {
        return session.isReachable
    } else {
        debugPrint("WCSesion doesn't exist")
        return false
    }
}

#if os(iOS)
/** 워치 앱 설치 여부 */
public func checkWatchAppInstalled() -> Bool {

    if let session = session {
        return session.isWatchAppInstalled
    } else {
        debugPrint("WCSesion doesn't exist")
        return false
    }
}

/** 워치 연결 여부 */
public func checkWatchPaired() -> Bool {

    if let session = session {
        return session.isPaired
    } else {
        debugPrint("WCSesion doesn't exist")
        return false
    }
}
#endif

 

+ 추가 : iOS 9.3 이후부터는 하나 이상의 애플워치(watchOS 2.2 이후)와 페어링가능하다는 점!!

 

 

 

# 1. 최근 상태 정보 전달하기

 

상대방에게 최근 상태 정보를 전달하려면 updateApplicationContext(_:) 메서드를 사용합니다!

 

연결된 active 상태의 디바이스가 상태를 동기화하는데 사용할 수 있는 딕셔너리의 Value값을 보냅니다.

상대방이 깨어나면 이 정보를 사용하여 자신의 상태를 업데이트할 수도 있습니다. 

 

 

 

# 2. 실시간 통신하기

 iOS app과 WatchKit extension 사이의 실시간 통신을 가능하게 해주는 메서드 두 가지를 알려드리겠습니다!

 

저 같은 경우, 메시지 발신에 관한 공통 메서드를 만들었습니다!

 

- sendMessage(_:replyHandler:errorHandler:) 

/** 메시지 발신 */
public func send(_ info: [String:Any], type: WCSessionDefine.SendType) {

    let operation = info[WCSessionDefine.operation] as? String ?? ""

    print("\(#function) \(operation) \(info) \(type)")

    session?.sendMessage(info, replyHandler: nil) { [weak self] error in

        self?.log("ERROR \(error.localizedDescription) \(error)", type: type)
      
    }
}

미리 선언해 놓은 WCSessionDefine의 operation 코드와 함께 info를 [String:Any] 타입으로 전달해 주면,

 

그에 해당하는 동작처리를 App 쪽에서 실행할 수 있습니다!

 

 

- sendMessageData(_:replyHandler:errorHandler:) 

/** 데이터 메시지 발신 */
public func sendData(_ data: Data, type: WCSessionDefine.SendType) {

    print("\(#function) \(data) \(type)")

    session?.sendMessageData(data, replyHandler: nil) { [weak self] error in
    
        debugPrint(error.localizedDescription)

    }
}

 

🌟 상대방에게 메시지를 보낼 때, background message는 queue에 배치되고 순서대로 전달됩니다.

 

 

 

# 3. 백그라운드에서 전달하기

background에서 딕셔너리 타입의 데이터를 전달하고 싶으면 transferUserInfo(_:)를 쓰면 되고

 

background에서 file을 전달하고 싶으면 transferFile(_:metadata:)를 쓰면 됩니다. 

 

공식 문서에 따르면 연결된 디바이스에서만 테스트해야 한다고 경고문이 나와있으니! 시뮬레이터로는 테스트가 불가능하다는 점!

Warning
Always test Watch Connectivity file transfers on paired devices. The Simulator app doesn’t support the transferFile(_:metadata:) method.

 

# 4. iOS -> Watch로 데이터 전달하기

iOS 앱이 Wath app의 complicaton에게 데이터를 보내고 싶으면 transferCurrentComplicationUserInfo(_:)를 쓰면 됩니다. 

 

 

💎 4. Receive : 메시지 수신하기

WCSessionDelegate의 메서드들을 통해 message를 Receive 할 수 있습니다. 

class WCSessionManager: NSObject {

public var receiveMessageHandler: ([String:Any])->() = { _ in }/** 메시지 수신 */
public var receiveMessageDataHandler: (Data)->() = { _ in }/** 데이터 메시지 수신 */

...

}

extension WCSessionManager : WCSessionDelegate {

    /** 메시지 수신 */
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        receiveExerciseMessageHandler(message)
        receiveExerciseMenuMessageHandler(message)
        receiveMessageHandler(message)
    }
    
    /** 데이터 메시지 수신 */
    func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
        receiveMessageDataHandler(messageData)
    }
   
}

 

 

🍎 실제 프로젝트에서 워치와 앱 인터랙션하기

- receiveMessageHandler를 이용해 수신한 메시지의 대한 info를 탈출 클로저를 이용해 전달할 수 있습니다.

- 딕셔너리 타입의 info를 이용해 앱 단에서 분기처리를 이용한 수신 처리가 가능합니다.

//ViewController
override func viewDidLoad() {
        super.viewDidLoad()
        WCSessionManager.shared().receiveMessageHandler = { info in

                let operation = info[WCSessionDefine.operation] as? String ?? ""
                print("\(operation) \(info) receiveMessageHandler")

                switch operation {
                
                /** 워치앱 설치 유무 */
                case WCSessionDefine.OP_001:
                    print("애플워치 앱 설치됨")

                /** Background -> Foreground */
                case WCSessionDefine.OP_100:
                    WCSessionManager.shared().appCameForeground()

                default:
                    break
                }
                
            }
            
}

//WCSessionManager.swift
public func appCameForeground() {
        
    let message: [String:Any] = [WCSessionDefine.operation: WCSessionDefine.OP_100]

    self.send(message, type: .toWatch)

}

//ExtensionDelegate
/** BackGround  -> ForeGround */
func applicationDidBecomeActive() {

    /** OP_100 알림 */
    WCSessionManager.shared().sendToAppCameForeground()
}

public func sendToAppCameForeground() {

    let message: [String:Any] = [WCSessionDefine.operation: WCSessionDefine.OP_100]

    self.send(message, type: .toApp)
}

 

728x90
반응형