왜 PHPickerController가 세상에? 나오게 되었을까?🤔 🤔
iOS에서 사진을 가져오는 전통적인 방식은 UIImagePickerController였습니다.
하지만 몇 가지 심각한 단점이 있었습니다:
1. 프라이버시 문제 👿
UIImagePickerController를 사용하려면 반드시 info.plist에 이런 설정을 추가해야 했어요:
<key>NSPhotoLibraryUsageDescription</key>
<string>사진에 접근하려고 합니다. 위 사진은 오로지 본인을 위해 사용됩니다</string>
- UIImagePickerController로 앨범을 열면, 사실상 사용자의 전체 사진 라이브러리에 접근 권한을 요구해야 했습니다.
- 앱 입장에서는 단순히 “사용자가 선택한 사진”만 필요하지만, 시스템은 모든 사진 접근 권한을 앱에 넘겨줘야 했죠.
- 이건 사용자 입장에서 부담스럽고, 보안·프라이버시 측면에서도 취약했습니다.
2. 멀티 선택 불가능 😢
- 사용자가 한 번에 여러 장의 사진을 고르고 싶어도 UIImagePickerController는 단일 선택만 지원했습니다.
- 인스타그램이나 카톡처럼 “여러 장 업로드”를 구현하려면 별도의 복잡한 커스텀 앨범 UI를 만들어야 했죠.
🐣🐣
그래서 Apple이 WWDC20에서 새로운 걸 발표했습니다
https://developer.apple.com/videos/play/wwdc2020/10652/
Meet the new Photos picker - WWDC20 - Videos - Apple Developer
Let people select photos and videos to use in your app without requiring full Photo Library access. Discover how the PHPicker API for iOS...
developer.apple.com
오늘도 영상을 베이스로 시작합니다~
iOS에서 사진 접근 권한의 동작원리
우선, 새로운 API를 공부하기 전에 가장 기본적인 어떻게 사진을 접근하고 가져오는 지에 대해 정리해볼께요
기존 UIImagePickerController
[앱] → [시스템 콜] → [커널 권한 체크] → [전체 Photos DB 접근] → [사용자 선택] → [앱으로 전달]
기존 방식에서는 앱이 Photos 데이터베이스 전체에 직접 접근해야 했습니다. iOS의 샌드박스 보안 모델에서 이는 꽤 큰 권한이죠.
- 앱이 Photos framework API 호출
- 커널이 앱의 권한 상태 확인 (샌드박스 규칙, 엔타이틀먼트 등)
- 권한이 없으면 시스템이 권한 요청 다이얼로그 표시
- 사용자가 허용하면 앱이 전체 라이브러리 접근 가능
PHPickerController는?

[앱] → [시스템 프로세스의 PHPickerController] → [사용자 선택] → [선택된 사진만 앱으로 전달]
PHPickerController는 별도의 시스템 프로세스에서 실행됩니다!
- 앱이 PHPickerViewController를 present
- 시스템이 별도 프로세스에서 picker 실행 (앱과 완전히 분리!)
- 사용자가 시스템 프로세스 내에서 사진 선택
- 시스템이 선택된 사진만 앱에 안전하게 전달
이게 핵심이에요! 앱은 전체 라이브러리에 절대 접근하지 않습니다.
PHPickerController의 주요 특징

1. 가장 큰 장점은 앱이 사진 라이브러리 접근 권한을 요구하지 않아도 된다는 점입니다.
2. 멀티 선택 지원
이제 한 번에 여러 장을 선택할 수 있습니다! 더 나아가 zoom in / zoom out 제스처 까지!
3. 라이브 포토와 비디오 지원
Live Photos, 비디오까지 모두 처리할 수 있습니다. 기존 UIImagePickerController보다 훨씬 유연해졌죠.
4. 검색 기능
iOS Photos 앱과 동일한 ML 기반 검색을 제공합니다. "강아지", "바다", "음식" 같은 키워드로 사진을 찾을 수 있어요!
기본 구성 흐름

1. 설정(Configuration) 단계
먼저 PHPickerConfiguration으로 "뭘 선택할 수 있게 할지" 정합니다.
var config = PHPickerConfiguration()
config.selectionLimit = 5 // 최대 몇 장?
config.filter = .images // 뭘 보여줄지?
여기서 PHPickerFilter가 핵심이에요. 사진만 보여줄지, 비디오만 보여줄지, 라이브 포토까지 포함할지 결정할 수 있거든요.
2. 피커 생성 및 present 단계
설정이 끝나면 PHPickerViewController에 연결하고 delegate를 설정해줍니다.
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)
delegate 패턴을 통해 사용자가 사진을 선택했을 때의 응답을 처리할 수 있어요.
3. 결과 처리 단계
사용자가 사진을 고르면 피커는 [PHPickerResult] 배열을 delegate에게 전달합니다
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// results 배열에 선택된 사진들의 정보가 담겨있음
}
PHPickerResult는 실제 이미지가 아니라 "이미지에 대한 정보"를 담고 있어요.
실제 이미지 데이터는 itemProvider를 통해 비동기적으로 가져와야 합니다.
이 3단계 흐름이 PHPickerController의 전부예요!
UIImagePickerController보다 설정할 게 많아 보이지만, 그만큼 더 세밀하게 제어할 수 있다는 장점이 있습니다 👍 👍
데이터 로딩 방법들
자 이제 이미지에 대한 정보를 어떻게 로딩할 수 있을지 공부해볼게요
1. UIImage로 직접 로딩
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] object, error in
DispatchQueue.main.async {
if let image = object as? UIImage {
// 이미지 사용
}
}
}
아마 대부분이 뷰에 바로 띄워줘야 하기에 UIImage로 변환을 하겟죠? 그때 loadObject로 UIImage로 로드하면됩니다.
2. Data로 로딩 (서버 업로드나 저장용)
result.itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { [weak self] data, error in
DispatchQueue.main.async {
if let imageData = data {
// 이미지 데이터 사용
}
}
}
서버나 CoreData에 저장할때 주로 data type으로 보내기 때문에 변환하는거죠.
3. 파일 URL로 로딩
result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.image") { [weak self] url, error in
DispatchQueue.main.async {
if let fileURL = url {
// 임시 파일 URL 사용 (메모리 효율적)
}
}
}
대용량 이미지나 메모리 효율성이 중요할 때 사용합니다.
음....근데 저는 이걸 보고 파일 URL이 뭐지? 라는 생각이 제일 먼저 들었던것 같아요
우리가 일반적으로 생각하는 URL? 도메인
여기서 말하는 파일 URL은 갤러리에 있는 원본 파일 경로가 아니에요!
바로바로바로바로 시스템이 임시로 생성하는 복사본 파일의 위치를 가리키는 URL입니다.
ex) file:///var/mobile/Containers/Data/PluginKitPlugin/...../tmp/IMG_1234.jpeg
왜 이런 방식을 쓸까?
메모리 효율성 때문입니다!
UIImage로 바로 로딩하면 메모리에 바로 올라가는데, file URL 방식은 경로만 저장하고 필요할 때 로딩하는 방식입니다.
// 파일 경로만 저장하고, 필요할 때만 로딩
for result in results {
result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.image") { url, error in
// URL만 저장 (메모리 사용량 거의 0)
self.imageURLs.append(url)
}
}
// 나중에 실제로 보여줄 때만 로딩
func showImage(at index: Int) {
let image = UIImage(contentsOfFile: imageURLs[index].path)
imageView.image = image
}
자 그러면 이제 또다른 궁금증이 생겼죠?? public.image가 뭐야????
파일 형식은 어떻게 식별될까? UTI??
일상속 궁금증
iPad로 셀카를 찍고 그 사진을 MacBook으로 전송한다고 생각해보세요. 해당 파일을 클릭하면 프리뷰에서 사진이 열립니다.
어떻게 Mac은 이게 이미지라는 걸 알고 처리했을까요?
컴퓨터 입장에서 모든 파일은 그냥 바이트의 연속적인 시퀀스일 뿐입니다.
사진이든 음악이든 문서든 결국엔 0과 1로 이루어진 데이터 덩어리죠.
시스템이 매번 파일을 열어서 내용을 분석한다면? 너무 비효율적일 거예요. 실제로도 운영체제는 거의 그런 방식을 사용하지 않습니다.
컴퓨터 운영체제는 파일의 유형을 알아내기 위해 주로 파일 경로의 확장자를 사용합니다.
Mac에서는 안 보이지만 .jpeg가 숨어있는 거예요.
전통적인 파일 식별 방법들
파일 확장자 방식
가장 일반적인 방법은 파일 경로의 확장자를 보는 것입니다. .jpeg, .mp3, .pdf 같은 것들 말이에요.
macOS에서는 기본적으로 확장자가 숨겨져 있지만, 실제로는 존재하고 있습니다.
파인더에서 파일을 선택하고 Cmd+I로 정보를 보면 확장자를 확인할 수 있어요.
하지만 여기서 문제가 생깁니다. JPEG 이미지 하나만 해도 .jpeg, .jpg, .jpe 등 여러 확장자가 존재하거든요.
같은 형식인데 여러 개의 확장자... 뭔가 일관성이 없어 보이죠? 🤷♂️
웹에서의 MIME 방식
웹 환경에서는 파일 경로 확장자만으로 파일을 식별하지 않고 MIME로 표준화된 방식을 사용한다고 합니다:
JPEG의 경우 image/jpeg로 표현하죠.
그런데 또 문제가...🤯 일부 서버에서는 표준이 아닌 image/jpg를 사용하기도 해요.
결국 하나의 파일 형식을 나타내는데 여러 개의 다른 식별자가 존재하는 혼란이 발생합니다.
Apple의 UTI 방식
하지만 Apple에서는 UTI(Uniform Type Identifier)를 이용해 파일 형식을 식별합니다!
UTI는 하나의 파일 형식을 단 하나의 문자열로 표현합니다.
JPEG의 경우 public.jpeg 하나로 끝이에요.
확장자가 .jpeg든 .jpg든, 웹에서 image/jpeg로 오든 image/jpg로 오든 상관없이 모두 public.jpeg라는 UTI로 통일되는 거죠.
UTI의 계층 구조
앱에서 "모든 이미지를 처리할 수 있다"고 선언하면, public.image에 conform하는 모든 하위 타입(JPEG, PNG, TIFF 등)을 자동으로 처리할 수 있게 됩니다.
새로운 이미지 형식이 나와도 public.image를 상속받기만 하면 자동으로 지원되는 거예요!
하나의 시뮬레이션으로 설명을 해보자면
- 사용자가 some_photo.heic 파일을 더블클릭해볼게요
- 시스템: "이 파일의 UTI는 public.heic네"
- 시스템: "public.heic가 public.image에 conform하나? → Yes!"
- 시스템: "그럼 public.image를 지원한다고 한 앱들 중에서 하나 골라서 실행시키자"
UTI는 다중 상속도 지원합니다. 예를 들어:
- public.image는 public.data와 public.content 둘 다에 conform할 수 있어요
- public.content는 "사용자가 직접 다루는 콘텐츠"라는 추상적 개념을 나타냅니다
이렇게 되면 이미지 파일은 "데이터이면서 동시에 사용자 콘텐츠"라는 두 가지 성격을 모두 가지게 되죠.
정리 및 복습
맨 위 public.item: 파일, 폴더, 심볼릭 링크... 파일 시스템에 존재하는 모든 것들의 최상위 추상 타입입니다
public.data: 바이트 시퀀스로 표현되는 일반적인 파일들이에요. 우리가 흔히 아는 "파일"의 개념이죠.
모든 파일은 결국 바이트의 연속이지만, 그 바이트들이 무엇을 표현하는지(특정 의미로 해석되는 데이터)에 따라 처리 방법이 완전히 달라져요:
- public.audio: 바이트 시퀀스를 소리로 해석할 수 있는 데이터
- public.image : 바이트 시퀀스를 시각적 이미지로 해석할 수 있는 데이터
- public.text : 바이트 시퀀스를 문자열로 해석할 수 있는 데이터
예를 들어, FF D8 FF E0... 이런 바이트 시퀀스가 있다면:
- 텍스트 에디터는 "그만 먹어 돼지야"로 표시하고
- 이미지 뷰어는 "🐷"으로 해석해서 보여주죠
그래서 시스템이 "이 데이터를 어떻게 해석해야 할지" 알려주는 것이 UTI의 핵심 역할이에요!
'iOS' 카테고리의 다른 글
데이터 보관은 어떻게이루어질까(직렬화: NSCoding부터 Codable까지) (0) | 2025.09.09 |
---|---|
객체 간 통신(delegate, Closure, NotificationCenter, KVO) (0) | 2025.09.04 |
포켓몬빵으로 이해하는 ObserverPattern & NotificationCenter (5) | 2025.08.27 |
내 아이폰에 터치를 해보았다(Reverse-preorder DFS) (3) | 2025.08.26 |
SceneDelegate가 UIResponder를 상속하는 이유 (2) | 2025.08.25 |