연락처 접근 권한은 기기의 주소록을 읽기 전에 사용자의 명시적 동의를 요구하는 모바일 OS의 메커니즘입니다. iOS에서는 CNContactStore를 통해 연락처에 접근하고, Android에서는 Contacts API와 런타임 권한 시스템을 사용합니다. Apple Developer Documentation, 2025에 따르면, iOS 18부터 모든 앱은 통합된 Contacts Access API를 사용해야 합니다. 권한 요청의 올바른 구현은 앱 스토어 심사에서 승인 가능성을 높입니다.
주요 내용
연락처 접근 권한은 타사 앱의 무단 읽기로부터 사용자의 주소록을 보호하는 운영 체제 메커니즘입니다. 모바일 OS에서 연락처는 사용자 주변 사람들의 이름, 전화번호, 이메일 주소 및 사진을 포함하므로 민감한 데이터로 간주됩니다.
iOS에서 권한은 Contacts 프레임워크와 CNContactStore 클래스에 의해 관리됩니다. 사용자는 첫 번째 접근 요청 시 시스템 대화상자를 보고 접근을 허용하거나 거부할 수 있습니다. Android에서는 런타임 권한 시스템을 기반으로 보호됩니다. 앱은 매니페스트에서 READ_CONTACTS를 선언하고 ActivityResultLauncher 또는 결과를 처리하는 프래그먼트를 통해 런타임에 요청합니다.
Statista(2025년)에 따르면 iOS 사용자의 68% 이상과 Android 사용자의 54%가 첫 번째 요청에서 연락처 접근을 거부합니다. 이는 개발자가 요청을 올바르게 구현할 뿐만 아니라 사용자에게 접근이 필요한 이유를 설명해야 함을 의미합니다.
업계 표준 — 첫 실행 시가 아니라 기능이 실제로 필요할 때만 접근을 요청합니다. 이 접근 방식은 거부율을 낮추고 사용자 경험을 개선합니다.
Apple 생태계에서 연락처 접근은 iOS 9에서 도입된 Contacts 프레임워크에 의해 규제됩니다. CNContactStore 클래스는 권한 요청 및 읽기/쓰기 작업 수행을 위한 메서드를 제공합니다. requestAccess(for:)를 처음 호출하면 시스템이 접근 이유를 설명하는 네이티브 대화상자를 표시합니다.
iOS 17부터 Apple은 단일 연락처 접근 모드를 도입했습니다. 사용자는 주소록에서 하나의 연락처를 선택하고 전체 데이터베이스를 공개하지 않고 앱과 공유할 수 있습니다. 이 모드는 CNContactPickerViewController를 통해 구현되며 requestAccess(for:)를 호출할 필요가 없습니다.
개발자가 이해해야 할 중요 사항: 앱이 전체 접근을 요청하지만 기능적으로 하나의 연락처만 필요한 경우 App Store 검토자가 빌드를 거부할 수 있습니다. Apple App Review Guidelines(2025년) 섹션 5.1.1은 최소 필요 데이터를 명시적으로 요구합니다.
iOS 18 출시와 함께 Apple은 Privacy Manifest(privacy.xcprivacy 파일)에 대한 요구 사항을 강화했습니다. 이 파일에서 개발자는 보호된 데이터에 접근하는 이유를 선언합니다. 연락처의 경우 시스템 대화상자에 표시되는 지역화된 텍스트와 함께 NSContactsUsageDescription 키가 사용됩니다.
올바른 privacy manifest가 없으면 앱이 App Store Connect 검토를 통과할 수 없습니다. 설명 텍스트는 구체적이어야 합니다. “성능 개선을 위해”가 아니라 “전화번호로 친구를 찾기 위해”라고 작성해야 합니다.
Android에서 연락처 접근은 READ_CONTACTS 권한으로 보호되며, 이는 위험 범주에 속합니다 — 설치 시가 아니라 런타임에 요청해야 합니다. 런타임 권한 메커니즘은 Android 6.0(API 23)에서 도입되었으며 민감한 데이터를 보호하는 주요 방법으로 남아 있습니다.
READ_CONTACTS 권한은 매니페스트에서 uses-permission 태그를 통해 선언되고, 코드에서는 ActivityResultLauncher 또는 onRequestPermissionsResult가 있는 프래그먼트를 통해 요청됩니다. 사용자는 요청을 거부하거나 “다시 묻지 않음”을 선택할 수 있으며, 이후 앱은 거부를 올바르게 처리해야 합니다.
Android 14(API 34)부터 런타임 권한의 동작이 변경되었습니다. 두 번 연속 거부하면 OS가 자동으로 neverAskAgain 플래그를 설정합니다. Google Developer Documentation(2024년)에 따르면 개발자는 재요청 전에 shouldShowRequestPermissionRationale를 통해 상태를 확인해야 합니다.
연락처를 읽기 위해 Android는 ContentProvider인 ContactsContract를 사용합니다. 이는 ContentResolver를 통해 접근할 수 있는 구조화된 데이터베이스입니다. 데이터는 여러 테이블로 구성됩니다: Contacts(연락처), RawContacts(다른 계정의 원시 레코드), Data(전화번호, 이메일, 주소 등의 상세 정보).
ContactsContract 쿼리는 URI ContactsContract.Contacts.CONTENT_URI를 통해 수행됩니다. 개발자는 최소 열을 요청하고 필드를 필터링하기 위해 프로젝션을 사용해야 합니다 — 이렇게 하면 쿼리 실행 속도가 빨라지고 메모리 소비가 줄어듭니다.
연락처 접근 요청의 실제 구현은 iOS와 Android에서 다릅니다. 아래는 Swift와 Kotlin의 구체적인 예제로, 가능한 모든 권한 상태 처리를 포함합니다.
iOS에서 요청은 CNContactStore 클래스의 requestAccess 메서드를 통해 수행됩니다. 결과는 부울 값과 선택적 오류와 함께 클로저에서 반환됩니다. 아래 예제는 상태 처리를 포함한 전체 요청 사이클을 보여줍니다.
import Contacts
let store = CNContactStore()
store.requestAccess(for: .contacts) { granted, error in
if granted {
print("연락처 접근이 허용되었습니다")
// 연락처 작업 실행
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
try? store.enumerateContacts(with: request) { contact, stop in
print("\(contact.givenName) \(contact.familyName)")
}
} else {
print("접근이 거부되었습니다: \(error?.localizedDescription ?? "알 수 없는 오류")")
}
}
Android에서 요청은 RequestPermission 계약과 함께 ActivityResultLauncher를 통해 수행됩니다. 아래 예제는 권한 획득 후 ContactsContract.ContentProvider 작업을 보여줍니다.
val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(uri, null, null, null, null)
cursor?.use {
val nameIndex = it.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
while (it.moveToNext()) {
val name = it.getString(nameIndex)
Log.d("Contacts", "연락처: $name")
}
}
} else {
// 사용자에게 접근 필요 이유 설명
showRationaleDialog()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestPermissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)
}
숙련된 모바일 앱 개발자는 연락처 접근 권한을 다룰 때 검증된 모범 사례를 따릅니다. 이러한 규칙은 앱 스토어 검토를 통과하고 사용자의 신뢰를 유지하는 데 도움이 됩니다. 모범 사례를 따르면 게시 및 유지 관리 프로세스가 크게 간소화됩니다.
앱 첫 실행 시 연락처 접근을 요청하지 마십시오. 첫 번째 요청은 특정 기능의 맥락에서 이루어져야 합니다: 친구 찾기, 참가자 초대, 연락처 가져오기 등. Apptentive(2024년) 연구에 따르면 요청 이유를 이해하는 사용자는 2~3배 더 자주 동의합니다.
기능에 하나의 연락처 접근만 필요한 경우 iOS에서는 CNContactPickerViewController를, Android에서는 암시적 인텐트 ACTION_PICK을 사용하세요. 이러한 방법은 사전 권한이 필요하지 않으며 사용자가 앱에 전체 주소록을 공개하지 않고 직접 레코드를 선택할 수 있습니다.
앱은 사용자가 접근을 거부하는 상황을 올바르게 처리해야 합니다. iOS에서는 CNContactStore.authorizationStatus(for:)를 통해 상태를 확인하고 필요한 경우 사용자를 설정으로 안내합니다. Android에서는 재요청 전에 shouldShowRequestPermissionRationale를 사용하여 추가 설명을 표시합니다.
거부 직후 두 번째 대화상자를 표시하지 마십시오 — 이는 공격적으로 인식되어 앱 평점을 낮춥니다. 모범 사례: 잠시 후 설명과 함께 “설정으로 이동” 버튼이 있는 화면을 표시하여 인텐트를 통해 시스템 권한 화면을 엽니다. 실제 기기에서 거부 시나리오를 테스트하세요 — 시뮬레이터는 시스템 권한 대화상자의 동작을 항상 올바르게 재현하지는 않습니다.
자주 묻는 질문
앱은 친구 찾기, 참가자 초대, 양식 자동 입력 및 서버와의 동기화와 같은 기능을 위해 연락처 접근을 요청합니다. 예: 메신저는 전화번호로 연락처를 검색하고, CRM 앱은 클라이언트를 가져옵니다.
단일 연락처 접근(iOS 17+)은 CNContactPickerViewController를 통해 사용자가 전체 주소록을 공개하지 않고 하나의 연락처를 선택할 수 있습니다. 전체 접근은 앱이 CNContactStore를 통해 모든 기기 연락처를 읽을 수 있습니다. 단일 연락처 접근은 더 안전하며 privacy manifest에 NSContactsUsageDescription을 지정할 필요가 없습니다.
iOS에서는 설정 — 개인정보 보호 및 보안 — 연락처로 이동하여 특정 앱의 접근을 끕니다. Android에서는 설정 — 앱 — 앱 선택 — 권한 — 연락처를 열고 “거부”를 선택합니다.
Privacy Manifest(privacy.xcprivacy 파일)는 iOS 18+의 필수 문서로, 개발자가 연락처를 포함한 보호 데이터에 접근하는 이유를 선언합니다. NSContactsUsageDescription 키에는 시스템 권한 요청 대화상자에 표시되는 지역화된 설명이 포함됩니다.
Android는 READ_CONTACTS를 위험 권한으로 분류하는데, 이는 사용자의 개인 데이터에 대한 접근을 제공하기 때문입니다. Android 6.0에서 도입된 런타임 권한 메커니즘은 앱 설치 시가 아니라 런타임에 명시적 동의를 요구합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.