개인프로젝트 앱 "포항맛집"은 맛집을 보여주는 앱으로 database는 Notion DB를 사용한다. 아래 그림처럼 우리가 일반적으로 사용하는 Notion의 테이블을 사용하는 것이다.
Notion API를 사용해서 Database 정보를 쿼리하면 이미지 파일의 경우 response로 url을 받을 수 있다.
클라이언트측에서는 이 url로 다시 http통신을 하고 이미지 데이터를 받아오게 된다. AsynImage라는 FirstParty 구조체를 지원하고 있지만 캐싱처리가 어렵다는 단점이 있어 KingFisher 3rd Party 라이브러리를 도입했고 아래와 같이 사용하고 있다.
그런데 어떻게 이렇게 간단하게 처리할 수 있는 것일까?
궁금증이 생겨 내부 구조를 분석해보기로 했다.
가장 맨 처음 KFImage를 따라 들어가면 나오는 코드이다.
당장에 신기했던 것은, init할 때 url을 받는 형식으로 사용했는데 바로 보이는 것은 context를 init 파라미터로 사용하고 있다는 것이다.
KFImageProtocol 내부를 들여다보니, url을 파라미터로 init 하는 함수가 정의되어있었다.
주어진 url을 받아서 Source 타입으로 변환시키고, 그 source를 만들어서 context를 만든 후 그 context로 KFImage 구조체를 init하고 있다.
Resource 프로토콜안에 URL을 프로퍼티로 선언하고 extension하여 convertToSource를 만들었다. 처음에는 URL에 원래 convertToSource라는 함수가 있나? 하고 헷갈렸지만 이렇게 보니 이해가 갔다. 그냥 단순히 URL을 extension하여 함수를 정의해도 될 텐데 이렇게 한 이유는 잘 모르겠다.
어쨋든 이렇게 Source를 만드는 과정에서 cacheKey가 들어가는 것을 볼 수 있다. 여기서는 안나왔지만 cacheKey는 단순 String 타입이다.
자 그런데 여기 까지 봤을 때는 비동기 통신 관련 코드나 캐싱처리 로직에 대한 세부사항은 전혀 보이지 않았다. 최종적으로 반환하는 view 타입인 KFImageRenderer 세부 코드를 살펴보자
KFImagerRenderer 안으로 들어가면 결국에는 renderedImage를 반화하고 있는것을 볼 수 있다.
그리고 renderedImage 함수는 configuredImage를 반환하는데 그것을 만드는 과정에서 loadedImage가 사용된다.
loadedImage는 ImageBinder라는 클래스 내에 존재하는 프로퍼티였는데 계속 타고 타고 들어가보니 결국 그 타입이 UIImage였다
ImageBinder내에는 retrieveImage라는 함수가 있는데 여기서 통신관련 로직이 등장했다. 여러 옵션들을 가지고 init하여 DownloadTask를 만드는 함수엿던 것이다. 그리고 completionHandler를 통해서 결과값 처리를 하는데 RetrieveImageResult 타입 내에 UIImage 값이 들어있었다.
아래는 ImageBinder내 통신을 시작하는 start함수이다. 함수 내에서 downloadTask를 만들어서 통신을 시작하고 통신이 성공하는 경우 image값에 UIImage를 넣어주는 것이다!
참고로 start 함수는 KFImageRenderer를 생성하면 메인큐에 비동기 태스크로 배정하게 만들어놓았다.
SwiftUI는 이미지를 생성하는 Image 구조체가 있는데, UIKit에서 사용하던 UIImage 클래스를 파라미터로 받아 생성할 수 있다. 최종적으로 그렇게 이미지를 생성하는 것이다!
결론은 그냥 http 통신해서 data 받아오고 그거로 UIImage를 만든다음, 다시 SwiftUI의 Image로 감싸서 띄우는 것이다.
전체적인 구조를 보고 나니 생각보다 내가 예상했던 큰 틀에서 벗어나지않았다. 하지만 protocol과 associatedType그리고 dataTask 등... 매우 복잡한 구조로 타고 타고 들어가야하다보니 분석하는데 어려움을 겪었다.
이후에는 캐싱 처리 로직에 대해서 좀 더 살펴봐야겠다.