Objc — Photos Framework (번역)

관련 기사: https://www.objc.io/issues/21-camera-and-photos/the-photos-framework/를 번역하였고, 문제 있을 시 삭제하겠습니다.

Introduction

2014년 여름까지 AssetsLibrary Framework를 사용하였다.

수년간 Camera, Photos 앱은 Moment 별로 사진을 구성하는 방법을 포함해 크게 변경되었다. 그 사이 AssetLibrary Framework는 뒤떨어져있었다.

iOS 8에서 Apple은 AssetLibrary보다 성능이 좋은 PhotoKit을 제공하여 사진 라이브러리의 원활하게 작동할 수 있는 기능을 제공.

PhotoKit Object Model

PhotoKit은 Photos 앱에서 유저에게 보여지게 되는 객체를 모델링하는 entity graph를 정의합니다. 이 entity들은 가볍고, immutable하다.

모든 PhotoKit의 entity 객체들은 PHObject 클래스를 상속받습니다.

PHObject는 오직 localIdentifier 프로퍼티를 제공하는 public interface이다.

PHAsset은 유저의 사진 라이브러리에서 하나의 asset을 의미하며, 그 asset에 대한 metadata를 제공합니다.

asset들의 묶음을 asset collection이라 불르며, PHAssetCollection 클래스로 표현된다. 하나의 asset collection은 사진 라이브러리에서 album 혹은 moment가 될 수 있다. 뿐만 아니라 특정 ‘smart album들’ 중 하나가 될 수 있습니다. 이 smart album에는 모든 video collection들, 최근 추가된 목록, 유저가 좋아하는 사진, 고속 연사 모드 등등.. PHAssetCollectionPHCollection의 서브클래스 입니다.

PHCollectionListPHCollection들의 묶음을 의미합니다. PHCollection이므로 하나의 collection list는 다른 collection list들도 포함될 수 있으므로 collection들의 복잡한 계층구조를 형성할 수 있습니다. 실제로 이것은 Photos앱에서 Moment, Moment Cluster — Moment Year으로 나타난다.

Fetching Photos Entities

Fetch Request

fetch는 entity들의 클래스 메소드를 사용하여 작성되어집니다.

모든 fetch 메소드의 이름은 fetchXXX(…, options: PHFetchOptions) -> PHFetchResult이다. options 파라미터는 반환된 결과를 필터링하고 정렬하는 방법을 제공합니다.

Fetch Result

fetch메소드는 비동기적이지 않다. 대신 NSArray와 비슷한 인터페이스를 사용하여 결과들의 collection에 접근할 수 있는 PHFetchResult 객체를 반환합니다. 이 PHFetchResult는 필요에 따라 내용을 동적으로 로드하고 가장 최근에 요청된 값을 중심으로 내용을 캐시합니다.

참고: 사진 라이브러리 내용이 request change과 일치하면, fetch메소드에의해 반환된 PHFetchResult는 자동적으로 업데이트 되지 않는다.

Transient Collections

Asset collection에서 작동하는 구성 요소를 설계했지만, 임의의 asset 집합과 함께 사용하기 원할 때 사용.

Transient asset collection들은 PHAsset 객체의 배열 또는 asset을 포함하는 PHFetchResult에서 명시적으로 생성된다.

PHAssetCollectiontransientAssetCollectionWithAssets(…)transientAssetCollectionWithFetchResult(…) 팩토리 메소드를 사용하여 수행됩니다. PHAssetCollection과 마찬가지로 사용할 수 있지만, 사용자의 사진 라이브러리에 저장되지 않으므로 Photos 앱에 표시되지 않습니다.

Asset collection와 마찬가지로 PHCollectionList에서 transientCollectionListWithXXX(…) 팩토리 메소드를 사용하여 transient collection list을 작성할 수 있습니다.

Photo Metadata

HDR and Panorama Photos

photo asset의 mediaSubtypes 프로퍼티를 사용하여 기본 이미지가 HDR을 사용하도록 설정되었는지, camera 앱의 Panorama 모드로 촬영되었는지 여부를 확인할 수 있습니다.

Favorite and Hidden Assets

유저가 즐겨찾기를 표시하거나 숨긴 처리한 asset인지 확인하려면 PHAssetfavoritehidden 프로퍼티들을 확인하면 된다.

Burst Mode Photos

PHAsset의 representsBurst 프로퍼티가 true이면 연속된 사진 스퀀스를 나타내는 asset을 의미합니다. fetchAssetsWithBurstIdentifier(…)를 통해 해당 burst sequence의 나머지 asset을 가져 오는데 사용할 수 있는 burstIdentifier 값을 갖습니다.

Photo Loading

과거 유저 사진 라이브러리로에서 효율적으로 사진을 로드하고 표시할 수 있도록 수백 개의 파이프 라인을 만들었었다. 이 파이프 라인은 요청을 보내기 및 취소, 이미지 크기 조정, 이미지 자르기, 캐싱 등을 처리합니다. PhotoKit은 PHImageManager를 사용하여 이 모든 작업을 수행하는 클래스를 제공합니다.

Requesting Images

이미지 요청은 requestImageForAsset(…) 메소드를 사용하여 전달합니다. 이 메소드는 PHAsset을 원하는 크기의 이미지 및 기타옵션(PHImageRequestOptions) 및 result handler를 사용합니다. 요청된 데이터가 더 이상 필요하지 않으면 리턴된 값을 사용하여 요청을 취소할 수 있습니다.

Image Sizing and Cropping

requestImageForAsset(…) 메소드에 의한 결과 이미지의 크기 조정(size)과 자르기(crop)와 관련된 매개변수를 두 곳으로 퍼져있습니다.

targetSizecontentMode 매개변수는 requestImageForAsset(…) 메소드에 직접 전달됩니다. contentMode는 UIView의 contentMode와 비슷하게 이미지를 가로할지 세로로 맞출지 또는 세로로 채워야 하는지를 설명합니다.

참고: 사진의 크기를 변경하거나 자르지 않으려면 PHImageManagerMaximumSizePHImageContentMode.Default로 전달하십시오.

또한 PHImageRequestOptions는 image manager가 size를 조정하는 방법을 제공합니다. resizeMode 프로퍼티의 .exact (결과 이미지가 대상 크기와 일치해야 하는 경우), .fast (.exact보다 효율적이지만 결과 이미지가 대상 크기와 다를 수 있음) 또는 .none으로 설정할 수 있습니다. 또한 normalizedCroppingMode 프로퍼티를 사용하면 image manager가 이미지 자르기 방법을 지정할 수 있습니다.

참고: normalizedcroppingMode가 제공되면 resizeMode를 .exact로 설정하십시오.

Request Delivery and Progress

이 매개변수들을 설정할 때, iCloud Photo Library를 사용하도록 설정하는 것에 있어 항상 고려해야 합니다. PhotoKit API는 디바이스에서 사용 가능한 사진과 클라우드에서 사용 가능한 사진을 구분하지 않습니다. 모두 동일한 requestImage 메소드를 사용하여 load됩니다. 즉, 이미지 요청 마다 셀룰러 네트워크을 통한 느린 네트워크 요청일 수 있습니다. .highQualityFormat을 사용하거나 요청을 동기식으로 할때 고려해야 합니다.

참고: request 메소드에서 네트워크를 사용하지 않도록하려면 networkAccessAllowedfalse로 설정하십시오.

다른 iCloud 관련 속성은 progressHandler입니다. iCloud에서 사진을 다운로드 할 때 image manager가 호출하는 PHAssetImageProgressHandler 블록으로 설정할 수 있습니다.

Asset Versions

PhotoKit을 사용하면 앱의 사진을 비파괴적으로 편집할 수 있습니다. 수정 된 사진의 경우 원본 이미지와 앱 별 편집 데이터의 사본이 별도로 유지됩니다. image manager를 사용하여 asset을 가져올 때 result handler를 통해 전달할 이미지 asset의 version을 지정할 수 있습니다. 이 작업은 version 프로퍼티를 설정하여 수행됩니다. .current는 모든 편집된 사항이 적용된 이미지를 전달합니다. .unadjusted는 편집되기 전에 전달됩니다. .original는 원래의 최고 품질 형식 (예 : RAW 데이터, 편집되지 않은 이미지는 JPEG를 제공)으로 이미지를 전송합니다.

Result Handler

UIImageinfo dictionary을 가져오는 block입니다. 이 block은 Image manager가 매개변수와 request option에 따라 request가 지속되는 동안 여러번 호출될 수 있습니다.

Info dictionary는 다음과 같이 request의 현재 상태(current state)에 관한 정보를 제공합니다:

  • 이미지를 iColud에서 요청해야 하는지 여부(networkAccessAllowed를 false로 설정한 경우 이미지를 다시요청 해야 한다) — PHImageResultIsInCloudKey
  • 현재 전달된 UIImage가 최종 결과 이미지의 저하된 형태인지 여부를 나타낸다. 고품질 이미지를 다운로드하는 동안 이미지 미리보기를 사용자에게 표시할 수 있다. — PHImageResultsDegradedKey
  • request ID (요청 취소 편의성) 및 요청이 이미 취소되었는지 여부 — PHImageResultRequestIDKeyPHImageCancelledKey
  • 이미지가 result handler에 제공되지 않은 경우 오류가 발생합니다. — PHImageErrorKey.

이 값을 통해 UI를 업데이트하여 유저에게 알리기 위해서는 위에서 설명한 progressHandler와 함께 이미지의 로딩 상태(loading state)을 알 수 있습니다.

Caching

때로는 scrolling collection에 많은 asset 미리보기 이미지가 있는 화면을 표시할 때 화면이 표시 될 순간에 일부 이미지를 메모리에 로드하는 것은 유용합니다. PhotoKit은 특정 사용 사례인 PHImageCachingManager를 다루는PHImageManager 하위 클래스를 제공합니다.

startCachingImagesForAssets(…)와 같은 단일 키 메소드를 제공합니다. 이 메소드에 PHAssets의 배열을 전달하며, 요청 매개변수와 옵션은 나중에 개별 이미지를 요청할 때 사용할 매개변수와 일치해야 합니다. 또한 caching manager에게 특정 asset을 중지하도록 알리는 데 사용할 수 있는 메소드가 있습니다.

allowsCachingHighQualityImages 프로퍼티를 사용하면 image manager가 고품질로 이미지를 준비해야 하는지 여부를 지정할 수 있습니다. 짧고 변경되지 않는 asset 리스트를 캐시할 때 보통 true값은 정상적으로 작동합니다. 하지만, collection view에서 빠르게 스크롤하면사 caching할 때는 false로 설정하는 것이 좋습니다.

참고: 내 경험상, caching manager를 사용하면 유저가 대규모 asset collection을 매우 빠르게 스크롤 할 때 스크롤 성능에 좋지 않을 수 있습니다. 특정 사용 사례에 대한 캐싱동작을 조정하는 것이 매우 중요합니다. 캐싱 윈도우 크기, 캐싱 윈도우 이동 빈도, allowCachingHighQualityImages 프로퍼티 값-이 매개변수는 신중하게 설정되어야 하며 실제 사진 라이브러리 및 대상 하드웨어에서 테스트해야 합니다. 또한 유저 작업에 따라 이러한 매개변수를 동적으로 설정하는 것이 좋습니다.

Requesting Image Data

마지막, old UIImage들을 요청하는 것외에도 PHImageManager는 asset data를 NSData객체, 범용 유형 식별자(universal type identifier) 및 이미지 표시 방향(orientation)으로 반환하는 또 다른 메소드를 제공합니다.

The Times They Are A-Changin`

PhotoKit을 사용하면 캐시된 상태를 올바르게 업데이트하는 데 필요한 모든 정보와 함께 사진 라이브러리의 변경 사항에 대한 알림을 받을 수 있습니다.

Change Observing

먼저 registerChangeObserver(…) 메소드를 사용하여 (PHPhotoLibraryChangeObserver 프로토콜을 준수하는) change observer를 공유되는 PHPhotoLibrary객체에 등록해야 합니다. photoLibraryDidChange(…) 메소드는 다른 앱이나 유저가 변경하기 전에 가져온 asset이나 collection에 영향을 주는 사진 라이브러리에서 변경을 수행 할 때마다 호출됩니다. 이 메소드에는 PHChange 유형의 단일 매개변수가 있습니다. 이 매개 변수를 사용하면 변경 내용에 관심있는 가져온 객체와 관련되어 있는지 확인할 수 있습니다.

Updating Fetch Results

PHChange는 변경에 관심이 있는 모든 PHObject 또는 PHFetchResults을 가지고 changeDetailsForObject(…)changeDetailsForFetchResult(…) 호출 할 수 있는 메소드를 제공합니다. 변경 사항이 없으면 메소드가 nil을 리턴하고, 그렇지 않으면 PHObjectChangeDetails 또는 PHFetchResultChangeDetails 객체를 리턴합니다.

PHObjectChangeDetails는 업데이트된 사진 entity객체에 대한 참조뿐만 아니라 객체의 이미지 데이터가 변경되었는지 여부와 객체가 삭제되었는지 여부를 알려주는 boolean flag를 제공합니다.

PHFetchResultChangeDetails는 fetch후에 이전에 받은 PHFetchResult의 변경 사항에 대한 정보를 캡슐화합니다. PHFetchResultChangeDetails는 collection view 또는 table view를 가능한 간단하게 업데이트하도록 설계되었습니다. 이것의 프로퍼티들은 일반적인 collection view 업데이트 처리기에서 제공해야하는 정보와 정확히 일치합니다. UITableView / UICollectionView를 올바르게 업데이트하려면 RICE — removedIndexes, insertedIndexes, changedIndexes, enumerateMovesWithBlock (hasMoves가. true 인 경우) 올바른 순서로 변경 내용을 처리해야합니다.

또한 변경 내용의 hasIncrementalChanges 프로퍼티를 false로 설정할 수 있습니다. 즉, 이전 페치 결과가 전체적으로 새 값으로 대체되어야합니다. 이런 경우에는 UITableView / UICollectionView에서 reloadData를 호출해야합니다.

참고: 변경 처리를 중앙 집중식으로 수행할 필요는 없습니다. 사진 entity를 처리하는 앱의 여러 구성요소가 있는 경우 각 구성 요소는 자체 PHPhotoLibraryChangeObserver를 가질 수 있습니다. 그런 다음 구성 요소는 PHChange객체를 자체 쿼리하여 자신의 상태를 업데이트해야하는지 여부를 확인할 수 있습니다.

Wind of Change

이제 사용자 및 다른 앱들에 의해 변경된 변경 사항을 관찰하는 방법을 알았으니 이제는 스스로 변경해야합니다!

Changing Existing Objects

PhotoKit을 사용하여 사진 라이브러리에서 변경작업을 수행하면 asset 또는 asset collection 중 하나에 연결된 change request object(변경 요청 객체)를 만들고 request object(요청 객체)에 관련 프로퍼티들을 설정하거나 커밋할 변경사항을 설명하는 적절한 메소드를 호출할 수 있습니다. 이것은 performChanges(…) 메소드를 통해 공유된 PHPhotoLibrary에 제출된 블록내에서 발생해야 합니다.

참고: performChanges 메소드에 전달된 완료블록의 오류를 처리할 수 있도록 준비 해야합니다. 이 접근법은 앱, 유저, 다른 앱 및 사진확장과 같은 여러 액터(actor)에 의해 변경될 수 있는 상태로 작업하는 동안 안전성과 상대적으로 사용하기 쉬운 기능을 제공합니다.

asset을 수정하려면, PHAssetChangeRequest를 만듭니다. 그런 다음 생성날짜, asset의 위치 및 숨기고 사용자의 즐겨찾기로 간주해야 하는지 여부를 수정할 수 있습니다. 또한 유저 라이브러리에서 asset을 삭제할 수 있습니다.

마찬가지로, asset collection들 혹은 collection list들을 수정하기 위해서는 PHAssetCollectionChangeRequest 혹은 PHCollectionListChangeRequest를 생성한 다음 collection 제목을 수정할 수 있으며, collection의 구성원을 추가 또는 제거하거나 collection을 모두 삭제할 수도 있습니다.

참고: 변경사항들이 유저의 라이브러리에 커밋되기 전에 유저가 명시적으로 권한을 부여하라는 경고가 표시 될 수도 있습니다.

Creating New Objects

새로운 asset들을 만드는 것은 기존 asset들을 변경하는 것과 비슷합니다. change request(변경 요청)을 생성할 때 적절한 createRequestForAssetFromXXX(…) 팩토리 메소드를 사용하고 asset image data(또는 URL)를 전달합니다. 새로 생성된 asset과 관련된 추가 변경이 필요한 경우 create change request(생성 변경 요청)의 placeholderForCreatedAsset 프로퍼티를 사용할 수 있습니다. 이것은 ‘진짜’ PHAsset에 대한 참조 대신 사용될 수 있는 자리 표시자를 반환합니다.

Conclusion

PhotoKit의 기본사항을 논의했다. 샘플 코드를 훑고, WWDC 세션 비디오를 보고 직접 다이빙하여 자신의 코드를 작성하여 더 많은 것을 배워야 합니다.