상세 컨텐츠

본문 제목

[iOS] CoreBluetooth 블루투스 권한 처리에 대한 고찰

iOS

by 홍루피 2025. 7. 10. 21:23

본문

이전글(직접적인 관련은 없지만 읽어보면 좋을 자료)

WWDC 19: What's New in Core Bluetooth

회사에서 블루투스 관련한 기능을 다루면서 권한에 대한 처리 필요성이 생겼음.

WWDC에서 항상 마지막쯤에 "Privacy"를 강조하는데 이런 권한과 관련된 처리들은 항상 강조해도 모자름이 없다고 생각함.

그러면 어떤 처리들을 해주어야 하는지 알아보도록 하겠음. 생각보다 CoreBluetooth 권한을 다루는 글이 많지 않아서 나와 같이 삽질하고 있는 누군가에게 도움이 됬으면 좋겠음.

info.plist 권한 설정

권한 하면 빼놓을 수 없는 plist 파일 설정임. CoreLocation으로 위치를 사용할 때 허용하는 권한과 매우 비슷함.

이전에는 백그라운드에서 블루투스를 사용할 경우에만 info.plist에서 권한을 받으면 되었음.

하지만 이제는 블루투스를 사용하기 위해서는 권한이 무조건적으로 필요함. 

아래와 같이 "Privacy - Bluetooth Always Usage Description"을 설정해주어야 함.

샘플 프로젝트이기 때문에 대충 작성을 해보았지만 "OOO를 사용하기 위해 권한이 필요합니다."와 같이 목적을 명시하는게 좋음.

 

공식문서에 따르면 iOS12 이하 버전은 Peripheral Usage 권한이 필요할 수 있음

 

권한 요청에 대해 의문이 생긴 지점들

Q. 권한을 요청하는 타이밍을 정할 수 있나?

A. 권한 요청은 앱 런치가 되었을 때 1회만 요청하고 이 시점은 변경할 수 없음.

이때 "허용"이나 "허용안함"을 누른다면 "설정-해당앱"의 권한 설정에서 이를 다시 설정해줘야 함.

이때 허용을 하지 않는다면 CentralManager(중앙기기 관리자)의 상태는 .unauthorizated를 가짐.

 

Q. 권한이 없으면 그래서 어떻게 됨?

A. 권한이 없으면 표면적으로 오류가 나거나 앱이 크래시되거나 하지는 않음.

하지만 스캔을 요청한다거나 블루투스 권한이 필요할 때 아래와 같은 로그와 함께 앱이 동작하지 않는 것을 확인할 수 있음.

 

권한이 갱신될 때마다 확인하기

CoreBluetooth의 CBCentralManagerDelegate를 사용하면 상태가 변경될 때마다 콜백을 받을 수 있음

extension CentralViewController: CBCentralManagerDelegate {
    // Central기기의 상태 변경에 대한 처리
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unknown:
            print("일시적인 알 수 없음 상태")
        case .resetting:
            print("재연결을 시도하는 중")
        case .unsupported:
            print("이 기기는 블루투스를 사용할 수 없음")
        case .unauthorized:
            print("권한 요청 필요!")
        case .poweredOff:
            statusLabel.text = "블루투스가 꺼져 있음😅"
        case .poweredOn:
            statusLabel.text = "연결 대기 중😊"
            central.scanForPeripherals(withServices: nil)
        @unknown default:
            return
        }
    }

 

위의 코드를 활용해서 아래 스타벅스 앱과 같이 설정으로 유도하는 얼럿을 띄우는 등의 처리를 할 수 있음.

 

권한 갱신에 대해 생기는 의문들

1. 설정에서 블루투스 권한이 변경된다면 ?

2. 제어센터에서 블루투스 ON/OFF가 변경된다면?

3. 권한은 있는데 블루투스가 꺼져있다면?

4. 권한이 없고 블루투스만 켜져있다면?

5. 권한이 없고 블루투스도 꺼져있다면?

물음표 살인마도 아니고 케이스가 참 다양함.

정답은 아니지만 어떤식으로 핸들링 할 수 있을지 생각해보았음.

 

1. "권한"에 대한 처리가 선행된 후에 "블루투스 자체"의 상태를 체크 (권한이 없다면 블루투스 자체의 상태는 의미가 없기 때문)

2. 블루투스를 사용하는 화면에 머무르는 동안 권한에 변경이 생긴다면, 이는 설정으로 갔다가 돌아왔다는 뜻임( = 앱 생명주기의 변경)

2-1. sceneWillResignActive 상태 진입, 다시 재개 시점에 sceneDidBecomeActive 상태 진입

2-2. 앱이 Background, InActive(제어센터 오버레이) 상태에서 centralManagerDidUpdateState 콜백이 온다면 (= 권한의 변경)

2-3. 뷰 컨트롤러에서 블루투스 변경사항을 저장

3. 앱이 다시 재개될 때 SceneDelegate에서 뷰 컨트롤러에 알림(노티피케이션을 주는 등..)

4. 변경된 권한을 뷰컨트롤러가 확인하고 권한이 없거나, 블루투스가 꺼져있다면 얼럿을 띄우거나 그 외의 처리를 할 수 있음

 

앱의 생명주기를 이용해서 처리했을 때 이점이 있을까?

그냥 뷰 컨트롤러에서도 콜백을 받을 수 있음.

하지만 제어센터가 오버레이되었을 때 실시간으로 콜백을 뷰 컨트롤러에서 받고 있다고 하면 사용자가 무한으로 블루투스를 껐다 켰다했을 때 계속 UI가 변경될 것임. 

하지만 실질적으로 앱이 포어그라운드로 왔을 때 "변경된 마지막 상태"만 받을 수 있다면 계속 변경되는 현상을 방지할 수 있을 것임.

앱이 백그라운드<-> 포어그라운드를 이동할때는 뷰 컨트롤러의 생명주기 메서드(viewWillAppear 등)는 호출이 되지 않기 때문에 앱의 생명주기 메서드를 이용해야 함.

 

시스템 블루투스 해제와 DidDisconnect 콜백의 호출

블루투스가 꺼지면 기기 연결도 함께 끊어짐.

그렇다면 CBCentralManagerDelegate 에서 "didDisconnectPeripheral" 메서드도 블루투스가 꺼지면 호출될까?

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: (any Error)?) {
        print("\(peripheral.name)와 연결 해제되었음")
    }

 

이 메서드는 직접 기기를 끄거나, 배터리가 꺼지거나, 수동으로 연결을 해지하거나 했을 때는 호출됨.

하지만 그냥 블루투스 자체를 끈다면 기기와 연결이 끊어지긴 하지만 해당 콜백이 불리진 않음.

 

만약에 "기기 연결 해제"와 같은 기능이 있다면

1. 정상 종료 또는 명시적 해제 - didDisconnectPeripheral에서 처리

2. 블루투스 OFF - centralManagerDidUpdateState에서 처리

 

위와 같이 두 곳에서 모두 처리를 해주는 것이 필요함.

 

참고자료

'iOS' 카테고리의 다른 글

[iOS] WWDC 20: Explore logging in Swift  (0) 2025.06.01
[스탠포드iOS] Swift 기초문법 정리(Optional, Tuple, Data Structure)  (1) 2024.03.16
RxSwift(2) - 연산  (0) 2020.04.04
RxSwift(1)  (0) 2020.04.03
MVVM패턴  (0) 2020.04.02

관련글 더보기