Photos 프레임워크

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Photos 프레임워크

Photos 프레임워크는 iOS 및 macOS에서 사진 애플리케이션, 사진 확장 기능을 지원하는 클래스를 제공한다. Photos 프레임워크를 통해 iOS 및 tvOS에서 iCloud 사진 라이브러리를 포함하여 사진 및 비디오에 직접 접근할 수 있다. 이 프레임워크를 사용하여 화면에 표시 및 재생할 에셋을 검색하고 이미지 또는 비디오를 편집하거나 앨범, 특별한 순간 및 iCloud 공유 앨범과 같은 에셋을 사용하여 작업할 수 있다.

특징 및 개념

Photos 프레임워크에는 iOS 및 tvOS에서 사용자의 사진 라이브러리와 직접 작업하기 위한 여러 가지 기능이 있다.

에셋

에셋 컬렉션(컬렉션)

컬렉션 리스트

  • 객체 가져오기 및 변경요청
    • Photos 프레임워크 모델 클래스 (PHAsset, PHAssetCollection, PHCollectionList)의 인스턴스는 사진 애플리케이션에서 에셋(이미지, 비디오, 라이브 포토), 에셋 컬렉션(앨범, 특별한 순간) 및 사용자가 작업하는 항목을 나타낸다. 그리고 컬렉션 리스트(앨범 폴더, 특별한 순간)다. 이 객체는 읽기 전용이며 변경할 수 없고 메타 데이터만 포함한다.
    • 해당 객체를 사용하여 작업해야 하는 데이터를 가져와서 에셋 및 컬렉션 작업을 할 수 있습니다. 변경 요청을 하려면 변경 요청 객체를 만들고 이를 공유 PHPhotoLibrary 객체에 명시적으로 알려줍니다. 이 아키텍처를 사용하면 다수의 스레드 혹은 다수의 애플리케이션과 동일한 에셋을 사용하여 쉽고 안전하며 효율적으로 작업할 수 있습니다.
  • 변경을 관찰
    • 가져온 에셋 및 컬렉션에 대한 변경 핸들러를 등록하려면 공유 PHPhotoLibrary 객체를 사용한다.
    • 다른 애플리케이션이나 기기가 에셋의 콘텐츠나 메타 데이터 또는 컬렉션의 리스트를 변경할 때마다 애플리케이션에 알려준다.
    • PHChange 객체는 변경 전후의 객체 상태에 대한 정보를 제공하여 쉽게 컬렉션뷰 또는 유사한 인터페이스를 업데이트할 수 있도록 한다.
  • Photos 애플리케이션의 기능들을 지원
    • PHCollectionList 클래스를 사용해 사진 애플리케이션의 특별한 순간 계층에 해당하는 에셋을 찾는다. 버스트, 파노라마 사진 및 고프레임 비디오를 식별하려면 PHAsset 클래스를 사용
    • iCloud 사진 라이브러리가 활성화되면 Photos 프레임워크의 에셋과 컬렉션에는 동일한 iCloud 계정의 사용할 수 있는 내용이 반영된다.
  • 에셋과 미리보기 로딩 및 캐싱
    • PHImageManager 클래스를 사용해 지정된 크기로 에셋의 이미지를 요청하거나 비디오 에셋에 사용할 AVFoundation 객체를 요청
  • 에셋 콘텐츠 편집
    • PHAsset 및 PHAssetChangeRequest 클래스는 편집을 위해 사진 또는 비디오를 요청하여 사진 라이브러리에 편집한 내용을 반영하는 메서드를 정의

Photos 라이브러리 상호작용

PHPhotoLibrary 객체를 사용하여 사진 콘텐츠에 접근하고, 에셋 및 컬렉션을 변경할 수 있도록 애플리케이션의 사용자 권한을 얻는다.
권한을 얻으면 사진 라이브러리가 변경될 때 변경사항을 전달받을 수도 있다.

PHPhotoLibrary: 사용자의 사진 라이브러리에 대한 접근 및 변경을 관리하는 공유 객체

에셋 검색과 조사

이 모델 클래스는 사진 라이브러리의 콘텐츠(에셋, 컬렉션)을 나타낸다. 읽기 전용이며 변경 불가능하며 메타 데이터만 포함되고, 에셋과 컬렉션을 사용하려면 이 클래스를 사용하여 지정한 쿼리와 일치하는 객체를 가져오게 된다.

  • PHAsset: 사진 라이브러리의 이미지, 비디오, 라이브 포토를 나타냄
  • PHAssetCollection: 특별한 순간, 사용자정의 앨범 또는 스마트 앨범과 같은 사진, 에셋 그룹을 나타냄
  • PHCollectionList: 특별한 순간, 사용자정의 앨범, 특별한 순간들 연도와 같은 에셋 컬렉션이 포함된 그룹을 나타냄
  • PHCollection: 에셋 컬렉션 및 컬렉션 리스트의 추상 수퍼 클래스
  • PHObject: 모델 객체(에셋 및 컬렉션)의 추상 수퍼 클래스
  • PHFetchResult: 가져오기 메서드에서 반환된 에셋 또는 컬렉션의 정렬된 목록
  • PHFetchOptions: 에셋 또는 컬렉션 객체를 가져올 때 Photos에서 반환하는 결과에 필터링, 정렬 등 영향을 주는 옵션

에셋 콘텐츠 로딩

이 클래스를 사용하여 이미지, 비디오, 라이브 포토 콘텐츠를 요청할 수 있다

  • PHImageManager: 미리보기 썸네일 및 에셋과 전체 크기의 이미지 또는 비디오 데이터를 검색하거나 생성하는 방법을 제공
  • PHCachingImageManager: 많은 에셋을 일괄적으로 미리 로딩하기 위해 최적화된 에셋과 관련된 섬네일 및 전체 크기의 이미지 또는 비디오 데이터를 검색하거나 생성하는 방법을 제공
  • PHImageRequestOptions: 이미지 매니저로부터 요청한 에셋 이미지의 영향을 주는 옵션들
  • PHVideoRequestOptions: 이미지 매니저로부터 요청한 비디오 에셋 데이터의 영향을 주는 옵션들
  • PHLivePhotoRequestOptions: 이미지 매니저로부터 요청한 라이브 포토 에셋의 영향을 주는 옵션들
  • PHLivePhoto: 캡처 직전과 직후 순간의 움직임 및 소리가 포함된 라이브 사진을 표현

변경 요청

에셋이나 컬렉션을 변경하려면 변경 요청 객체를 만들고 명시적으로 사진 라이브러리에 반영한다.
이 방법을 사용하면 여러 스레드 또는 여러 애플리케이션 및 애플리케이션 확장에서 같은 에셋을 가지며 쉽고, 안전하며 효율적으로 작업할 수 있다.

  • PHAssetChangeRequest: 사진 라이브러리 변경 블록(클로저)에서 사용하기 위해 에셋의 생성, 삭제, 메타 데이터 수정할 변경 요청 객체
  • PHAssetCollectionChangeRequest: 사진 라이브러리 변경 블록(클로저)에서 사용하기 위해 에셋 컬렉션을 생성, 삭제, 수정할 변경 요청 객체
  • PHCollectionListChangeRequest: 사진 라이브러리 변경 블록(클로저)에서 사용하기 위해 컬렉션 리스트 생성, 삭제, 수정할 변경 요청 객체

에셋 콘텐츠 수정

애플리케이션 또는 확장 프로그램에서 이 클래스들을 사용하여 사진 라이브러리의 편집 및 반영을 위해 에셋 데이터에 접근한다. 사진들은 각 수정 사항을 버전별로 에셋 및 보정 데이터를 관리 하므로 애플리케이션 또는 확장 프로그램을 사용하여 다른 기기에서도 이전에 수정한 내용을 되돌리거나 계속 사용할 수 있다. 사진 편집 확장기능을 만들려면 이 클래스들과 PhotosUI 프레임워크와 같이 사용!!

  • PHContentEditingInput: 편집할 에셋의 이미지, 비디오, 라이브 포토의 콘텐츠에 대한 정보와 접근 권한을 제공하는 컨테이너
  • PHContentEditingOutput: 에셋의 사진, 비디오, 라이브 포토의 콘텐츠를 편집한 결과를 제공하는 컨테이너
  • PHAdjustmentData: 편집 효과를 재구성하거나 되돌릴 수 있는 에셋의 사진, 비디오, 라이브 포토 콘텐츠의 수정사항에 대한 설명

Adjustment Data

  • PHContentEditingInputRequestOptions: 에셋의 콘텐츠를 수정하도록 요청할 때 이미지, 비디오 데이터전송에 영향을 주는 옵션
  • PHLivePhotoEditingContext: 라이브 포토의 사진, 비디오, 오디오 콘텐츠를 수정하기 위한 편집 세션
  • PHLivePhotoFrame: 편집 컨텍스트에서 라이브 포토의 단일 프레임에 대한 이미지 콘텐츠를 제공하는 컨테이너

변경사항 관찰

Photos 프레임워크는 다른 애플리케이션이나 다른 기기에서 사진의 정보를 변경할 때마다 애플리케이션에 알려준다. 이러한 객체는 변경 전후의 객체 상태에 대한 정보를 제공하므로 사용자 인터페이스를 쉽게 업데이트하여 일치시킬 수 있다.

  • PHPhotoLibraryChangeObserver: 사진 라이브러리에서 발생한 변경사항을 알리기 위해 구현할 수 있는 프로토콜
  • PHChange: 사진 라이브러리에서 발생한 변경사항에 대한 설명
  • PHObjectChangeDetails: 에셋 또는 컬렉션 객체에서 발생한 변경사항에 대한 설명
  • PHFetchResultChangeDetails: 가져오기 결과에 나열된 에셋 또는 컬렉션 객체에서 발생한 변경사항에 대한 설명

에셋 리소스로 작업하기

에셋 리소스 객체는 각 에셋의 데이터 저장소를 나타냅니다. 이러한 객체를 사용해 에셋을 직접 백업하고 복원할 수 있다.

  • PHAssetResource: 사진 라이브러리의 사진, 비디오, 라이브 포토 에셋과 관련된 기본 데이터 리소스
  • PHAssetCreationRequest: 사진 라이브러리 변경 블록(클로저)에서 사용하기 위해 기본 데이터 리소스에서 새로운 에셋을 생성하라는 요청
  • PHAssetResourceCreationOptions: 기본 리소스에서 새로운 에셋을 만드는데 영향을 주는 옵션들
  • PHAssetResourceManager: 애샛과 관련된 리소스에 대한 기본 데이터 저장소에 접근하는 방법을 제공
  • PHAssetResourceRequestOptions: 에셋 리소스 관리자가 요청한 기본 에셋 데이터 전달에 영향을 주는 옵션

오토 레이아웃을 잘 다루려면 어떻게 해야할까?

|

swift - 꼼꼼하게 다시 정리해본 옵셔널 개념(Optional)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


Optional

우리가 일반적으로 아는 영어뜻처럼 Optional이라는 것은 선택적인 이라는 뜻을 가진다. 선택적인 이라는 단어뜻처럼 이는 스위프트에서도 있어도 되고 없어도 된다는 의미를 가진다. 즉, 코딩을 하면서 어떤 변수에 값이 있을수도 있고 없을 수도 있는 경우를 위해서 존재한다. swift 개발을 할때 ? 혹은 ! 기호를 자주 봤을텐데, swift에서는 이 기호들을 타입 어노테이션 옆에 붙여줘야 한다.

var test: Int
test = nil

위 간단한 코드를 봐보도록 하자.
반드시 에러가 뜰 것이다 > nil은 Int형 태입에 할당될 수 없다.

swift에서는 기본적으로 변수를 선언할 때 non-optional인 값을 할당해주어야 한다. 즉 어떠한 값을 변수에 반드시 할당해주어야 하는데 위 코드에서는 우리가 변수에 Int형으로 변수를 선언하였음에도 불구하고 test라는 변수에 nil을 할당해주었기 때문에 에러가 뜨게 된것이다.(nil은 어떠한 값이 아니기 때문에 발생하는 에러) 따라서 애초에 Int 타입에 Int 타입이 아닌것을 넣었기 때문에 Int라는 메모리 공간이 초기화되지 못한 것을 의미한다.

그러나 우리가 코딩을 하다보면, 반드시 값이 그때그때 할당되지 않는 경우도 있을 것이다.

즉, 변수안에 값이 확실히 있다는 것을 보장할 수 없을때 Optional을 사용한다.

var test: Int?
test = nil

이제 위에처럼 코드를 변경해보자. 에러가 사라졌을 것이다. 이때 ?가 아닌 !를 사용해도 에러는 사라질 것이다(optional기호를 사용했기 때문)
이 변수 안에 값이 있을수도 없을수도 있다는 것을 명시해줌으로써 optional변수는 초기화하지 않으면 nil로 자동 초기화가 되기 떄문에 위 같은 코드도 정상적으로 동작하는 것을 볼 수 있다.

optional값을 만들때 우리는 아래와같은 상자를 생각해보면 이해하기 쉬워진다.

?

옵셔널로 변수를 선언했고 그때 ?를 사용했다면 우리는 ‘노크’를 했다고 생각하면 된다.
이때 만약 값을 가지고 있다면 해당하는 값을 반환해주고 값이 없다면 nil을 반환하게 된다. 이때 nil 또한 메모리를 차지하고 있다.

var someValue: Int? = 30
var Value = someValue

이때 위 코드에서 Value의 타입은 무엇일까? 바로 옵셔널이다.
즉, Value는 옵셔널 타입이며 Int데이터형을 가질 수 있는 변수를 의미한다. 이는 Int일수도 있고 Int가 아닐수도 있다.

var someValue: Int? = 30
var Value: Int = someValue

그런데 만약 위처럼 Value에 Int 타입을 지정해줬다고 해보자. 오류가 뜨게 될 것이다.

기본적으로 Int와 Int?는 다른 타입이다.

someValue는 옵셔널타입으로 값을 가질수도 있고 nil을 가질수도 있다. 그런데 Value에는 무조건 Int 타입만을 가질것이라고 선언을 해줬다. 그러니 당연히 Value는 someValue를 받아들이지 못할 것이다. 따라서 Int와 Int? 즉 옵셔널인 것과 옵셔널이 아닌것은 완전히 다른 타입을 의미하는 것을 알아야 한다.

!

!는 ?와 다르게 강제로 값을 빼내오는 것을 의미한다. 유명한 짤로 망치로 상자를 부수는 것이 떠오를 것이다.
이는 강제 언래핑(Unwrapping)이라고 하고 이 단어가 의미하는 것은 상자안에 값이 있든 없든 무조건 값을 가져오겠다는 것을 의미한다.

깨부순 상자안에는 값이 있을수도 있고 없을 수도 있을 것이다.

var someValue: Int? = 30
var Value: Int = someValue

위에서 본 예제를 다시 한번 봐보도록 하자. 오류가 났던 위 예제를 아래처럼 고쳐보도록 하자

var someValue: Int? = 30
var Value: Int = someValue!

자. 강제로 상자를 부쉈기때문에 에러는 발생하지 않았을 것이다. 그리고 다행히 상자 안에는 30이라는 값이 있었기 때문에 30이라는 숫자또한 값을 가지게 될 것이다. 그렇기 떄문에 옵셔널이 아닌 Int형의 Value라는 변수안에 데이터(30)이 들어갈 수 있게 될 것이다. 상자를 부수고, 값을 꺼내어 그 값을 Value안에 넣어준 코드이니 Value입장에서는 문제가 없을 것이다. (Value는 Int형 데이터만을 받고 있는데 운이좋게 someValue에는 30이라는 Int형의 데이터가 있었기 때문이다)

그런데 만약 상자안에 값이 없다면? 생각해보자

var someValue: Int? = nil  // someValue는 옵셔널타입으로 nil이 들어갈 수 있다
var Value: Int = someValue!

컴파일 오류도 일어나지 않을 것이며 build success도 되겠지만 금방 런타임에러가 발생하게 될 것이다.

!를 사용해 값이 존재하지 않는 옵셔널 값에 접근하려 시도하면 런타임 에러가 발생한다.
느낌표를 사용해 강제 언랩핑을 하기 전에는 항상 옵셔널값이 nil이 아님을 확실히 해야한다.

즉, nil이 아니라는 것이 확실하지도 않은 상태에서 !를 남용하면 에러가 날 확률도 높아질 것이다. 그리고 !도 옵셔널이기 때문에 초기화할 때 갑을 요구하지 ㅇ낳는다. 초기화해주징낳으면 ?와 마찬가지로 nil값이 들어가게 될 것이다. 잊지말자

그러면 이제 옵셔널 타입의 변수값을 가져오는 방법에 대해 생각해 보자

옵셔널 바인딩(Optional Bounding)

옵셔널 바인딩은 주로 if let(if var)구문과 같이 사용된다. 즉 먼저 체크해준다 를 생각하면 이해하기 쉽다.
이 값이 nil인지, 값이 있는지 경우에 따라 결과를 달리하고 싶을때 옵셔널 바인딩을 사용해 겁사해주면 된다.

func printName(_name:String) {
  print(_name)
}

var myName: String? = nil
if let name = myName {
  printName(_name: name)
}

실행해보면 콘솔창에는 아무것도 나오지 않을 것이고(myName은 nil값으로 초기화되어있기 때문) 이는 if let구문이 myName이라는 상자에 노크해보고 값이 있으면 name에 넣어주고 조건문을 실행해 라는 것을 의미한다는 것을 알 수 있을 것이다. 이때 값이 있는 경우에만 값이 바인딩 되기 때문에 콘솔창에는 아무것도 실행되지 않아 비어져있을 것이다.

옵셔널 체이닝(Optional Channing)

옵셔널 체이닝은 하위 프로퍼티에 옵셔널값이 있는지 없는지를 연속적으로 확인하면서 중간에 하나라도 nil이 존재함다면 nil이 반환되는 형식을 의미한다.

let roomCount = zehye.residence?.numberOfRoomes

위처럼 진행하는 방식이 옵셔널 체이닝이다. 이때 zehye의 residence가 nil이 아니라면 다음으로 넘어가 residence의 numberOfRoomes를 확인하게 될 것이다. 만약 zehye의 residence가 nil이라면 뒤의 else문을 수행하게 될 것이다. 그런데 이때 residence 뒤에 왜 ?가 붙을까?

그 이유는 residence가 nil을 반환할 수도 있고 아닐수도 있기 때문이다.

즉 하위 프로퍼티에 옵셔널 값이 있는지 연속적으로 확인하면서 중간에 하나라도 nil이 발견된다면 nil을 반환하는 것을 옵셔널체이닝 방식이라고 한다.

그러면 이때 roomCount의 타입은 무엇일까? 당연히 옵셔널 타입일 것이다. 즉 우리가 특별히 지정해주지 않았음에도 불구하고 roomCount의 타입은 옵셔널임을 알 수 있다. 그 이유는 zehye.residence?.numberOfRoomes가 nil을 반환할 수도 있고 아닐수도 있기 때문이다.


그러면 도대체 !를 왜 쓰는 것일까? 일단 우선적으로 !는 최대한 적게 사용하는 것이 좋다. 그러나 유용하게 쓸 수 있는 구석은 있다.

!는 언제 사용할까?

때로는 프로그램의 구조상 옵셔널 값을 먼저 설정한 후 그 값이 항상 있는 것이 명백한 경우가 있다.

즉 변수에 값이 있다는 것을 확신할 수 있는 경우를 의미한다. 그런때에 우리가 옵셔널 바인딩과 체이닝을 사용해 조건문을 사용할 이유가 있을까? 없다고 생각한다! 이런 종류의 옵셔널을 implicitly unwrapped optionals이라고 정의한다. 직역을 하자면 암시적으로 언랩핑된 옵셔널? 이라고 할 수 있을 것이다.

이는 주로 클래스 초기화 과정에서 사용된다고 한다.

class ViewContrller: UIViewController {
  @IBOutlet weak var leftButton: UIButton!
}

이렇게 IBOutlet같은 변수는 연결했다는 것을 확실히 할수 있기 때문에 !를 붙힐 수 있는 것이다. 근데 이렇게 IBOutlet을 선언했음에도 이 버튼을 사용할수도 안사용할수도 있을 텐데 그럴때는 ?를 사용하면 된다. 만약 ? 혹은 ! 둘다 붙이지 않는다면 에러가 뜨게 될 것이다 > UIButton타입은 non-optional일 수 없는 프로퍼티라는 의미의 에러일 것이다.

지금까지의 실습에서 나는 무조건 IBOutlet 뒤에는 무조건 !를 썼지만 사실 이런 사용법도 좋은 방법이 아니라고 한다. 무조건적으로 !의 사용은 줄이는 것이 좋다고 한다. 지금에야 확실히 이 버튼을 사용한다는 확신이 있지만 연결이 되지 않은 경우도 생길 수 있기 때문이다. 즉 만약을 대비하여 UIButton?을 사용하는 것이 훨씬 좋은 방법이라고 한다! 앞으로의 코드는 ?를 사용하도록 해야지

즉 정리하면 !는 옵셔널 바인딩, 체이닝으로 매번 값을 추출하기 귀찮거나 로직상으로 nil이 할당되지 않을 것 같다는 확신이 들때 사용하면 될 것같다.


옵셔널은 왜 사용하는 것일까?

swift는 일반적으로 아래와 같은 언어이다

  1. Safe
  2. Fast
  3. Expressive

옵셔널을 통해 swift의 안정성을 제공한다.
그리고 더 나아가 프로그래머들간의 원활한 커뮤니케이션을 위해 사용한다고 생각한다.

코드만으로도 변수들이 어떤 타입을 가질지 명확하게 보여주며 함수의 파라미터 부분만 봐도 여기에 어떠한 값들이 들어올 수 있는지 추측이 가능하기 때문이다.

JSONEncoder와 JSONDecoder, 코드 실습

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


JSONEncoder / JSONDecoder

스위프트 4 버전 이전에는 JSONSerialization을 사용해 JSON 타입의 데이터를 생성했다. 그러나 스위프트 4 버전부터 JSONEncoder / JSONDecoder가 Codable 프로토콜을 지원하기 때문에 JSONEncoder / JSONDecoder와 Codable 프로토콜을 이용 해 손쉽게 JSON 형식으로 인코딩 및 디코딩할 수 있다.

즉, JSONEncoder 및 JSONDecoder를 활용하여 스위프트 타입의 인스턴스를 JSON 데이터로 인코딩, JSON 데이터에서 스위프트 타입의 인스턴스로 디코딩할 수 있다.

JSONEncoder

Codable 프로토콜을 준수하는 GroceryProduct 구조체의 인스턴스를 JSON 데이터로 인코딩하는 방법

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

do {
	let data = try encoder.encode(pear)
	print(String(data: data, encoding: .utf8)!)
} catch {
	print(error)
}

// ----- 출력
 {
   "name" : "Pear",
   "points" : 250,
   "description" : "A ripe pear."
 }

// Tip : encoder.outputFormatting = .prettyPrinted 설정하면 들여쓰기를 통해 가독성이 좋게 출력해줍니다.

JSONDecoder

JSON 데이터를 Codable 프로토콜을 준수하는 GroceryProduct 구조체의 인스턴스로 디코딩하는 방법

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

/// 스위프트 4 버전부터 """를 통해 여러 줄 문자열을 사용할 수 있습니다.
let json = """
{
    "name": "Durian",
    "points": 600,
    "description": "A fruit with a distinctive scent."
}
""".data(using: .utf8)!

let decoder = JSONDecoder()

do {
	let product = try decoder.decode(GroceryProduct.self, from: json)
	print(product.name)
} catch {
	print(error)
}

// ----- 출력
"Durian"

실제 실습해보기

새 프로젝트를 하나 만들고 기존에 공유받은 json파일을 assets폴더에 import 해준다.

json 예시

{
    "name":"하나",
    "age":22,
    "address_info": {
        "country":"대한민국",
        "city":"울산"
    }
}

해당 코드를 처리해줄 swift File하나를 만들어준다.

그리고 해당 폴더에 아래와 같이 작성해준다.

struct Friend: Codable {

    struct Address: Codable {
        let country: String
        let city: String
    }

    let name: String
    let age: Int
    let addressInfo: Address
}

그리고 Main.storyboard에 tableView하나와 tableViewCell 하나를 만들어준다. 해당 테이블뷰에 연결될 outlet을 viewController에 작성해준 뒤 드래그해서 연결해주고, tableViewCell의 style은 Subtitle Identifier: cell로 지정해준다.

그리고 viewController로 넘어가 간단하게 코드를 작성해준다.

import UIKit

class ViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    let cellIdentifier: String = "cell"
    var friends: [Friend] = []

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.friends.count  // friend의 수만큼 보여달라
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.cellIdentifier, for: indexPath)
        let friend: Friend = self.friends[indexPath.row]
//        cell.textLabel?.text = friend.name + "(\(friend.age))"
//        cell.detailTextLabel?.text = friend.address_info.city + ", " + friend.address_info.country
        cell.textLabel?.text = friend.nameAndAge  // 해당 코드를 사용해주기 위해서 Friend.swift 파일을 수정해주어야 한다
        cell.detailTextLabel?.text = friend.fullAddress

        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()  // 친구를 가져옴 > 뷰가 로딩된 이후에 해야할일을 적어줌
        // Do any additional setup after loading the view.
        // assets을 통해 Json 데이터를 가져옴
        let jsonDecorder: JSONDecoder = JSONDecoder()
        guard let dataAssets: NSDataAsset = NSDataAsset(name: "friends") else {
            return  //
        }
        // jsonDecorder를 가지고 데이터 에셋의 데이터를 통해 json 데이터를 통해 friends를 불러옴
        do {
            self.friends = try jsonDecorder.decode([Friend].self, from: dataAssets.data)
        } catch {
            print(error.localizedDescription)
        }
        self.tableView.reloadData()
    }
}

viewController의 delegate를 연결해주고 위 코드를 사용하기 위한 Friend.swift 파일 수정

import Foundation

struct Friend: Codable {

    struct Address: Codable {
        let country: String
        let city: String
    }

    let name: String
    let age: Int
    let addressInfo: Address

    var nameAndAge: String {
        return self.name + "(\(self.age))"
    }

    var fullAddress: String {
        return self.addressInfo.city + ", " + self.addressInfo.country
    }

    enum CodingKeys: String, CodingKey {
        case name, age
        case addressInfo = "addess_info"
    }
}

인코딩과 디코딩, Codable 프로토콜

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


인코딩과 디코딩

  • 인코딩(Encoding):정보의 형태나 형식을 표준화, 보안, 처리 속도 향상, 저장 공간 절약 등을 위해서 다른 형태나 형식으로 변환하는 처리 혹은 그 처리 방식(출처 : 위키백과 - 부호화)
  • 디코딩(Decoding): 인코딩의 반대 작업을 수행하는 것

또, 인코더(Encoder)는 부호화를 수행하는 장치나 회로, 컴퓨터 소프트웨어, 알고리즘을 뜻함!

Codable

스위프트4 버전에서는 스위프트의 인스턴스를 다른 데이터 형태로 변환하고 그 반대의 역할을 수행하는 방법을 제공한다. 스위프트의 인스턴스를 다른 데이터 형태로 변환할 수 있는 기능을 Encodable 프로토콜로 표현 하였고, 그 반대의 역할을 할 수 있는 기능을 Decodable로 표현 해 두었으며, 둘을 합한 타입을 Codable로 정의 해두었다.

typealias Codable = Decodable & Encodable

Codable은 스위프트 4 버전에서 처음 소개한 프로토콜로 Codable은 다양한 상황에서 사용할 수 있다. 예를 들어 JSON 형식으로 서버와 애플리케이션이 통신한다면 Codable 프로토콜을 이용해 편리하게 인코딩 및 디코딩할 수 있다.

  • Decodable: 스위프트 타입의 인스턴스로 디코딩할 수 있는 프로토콜
  • Encodable: 스위프트 타입의 인스턴스를 인코딩할 수 있는 프로토콜

선언 예제

Codable

Coordinate 타입과 Landmark 타입의 인스턴스를 다른 데이터 형식으로 변환하고 싶은 경우에 Codable 프로토콜을 준수하도록 하면된다. Codable 타입의 프로퍼티는 모두 Codable 프로토콜을 준수하는 타입이어야 하며, 스위프트의 기본 타입은 대부분 Codable 프로토콜을 준수한다.

struct Coordinate: Codable {
	var latitude: Double
	var longitude: Double
}

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var vantagePoints: [Coordinate]
    var metadata: [String: String]
    var website: URL?
}

CodingKey

자주 사용하게 될 JSON 형태의 데이터로 상호 변환하고자 할 때는 기본적으로 인코딩/디코딩할 JSON 타입의 키와 애플리케이션의 사용자정의 프로퍼티가 일치해야 한다.

만약 JSON의 키 이름을 구조체 프로퍼티의 이름과 다르게 표현하려면 타입 내부에 String 타입의 원시값을 갖는 CodingKeys라는 이름의 열거형을 선언하고 CodingKey 프로토콜을 준수하도록 하면 된다. CodingKeys 열거형 케이스의 이름은 해당 프로퍼티의 이름과 일치해야 하며 프로퍼티의 열거형 케이스의 값으로 매칭할 JSON 타입의 키를 할당하면 된다. 만약, JSON 타입의 키와 프로퍼티 이름이 일치한다면 값을 할당하지 않아도 무방하다!

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    var vantagePoints: [Coordinate]

    enum CodingKeys: String, CodingKey {
        case name = "title"
        case foundingYear = "founding_date"

        case location
        case vantagePoints
    }
}