Fezravien / re-ios-open-market

REST API 연동을 통한 상품 목록 조회, 상세 조회, 등록, 수정, 삭제 기능을 가진 앱
3 stars 0 forks source link

상품 등록/수정 페이지에 수정으로 진입했을때 이미지 오류 #20

Open Fezravien opened 3 years ago

Fezravien commented 3 years ago

상품 수정시 상품 등록/수정 페이지에 이미지가 안뜨는 문제

18 상품을 등록하고 메인 페이지를 통해 상세 페이지로 등록된 상품을 보여주는 작업에서 비슷한 오류가 발생

위와 같이 이미지가 나타나지 않는 문제가 있다. 그래서 비동기 작업에 딜레이를 줬다

    func downloadImage(imageURL: [String]) {
        var images: [UIImage] = []
        if imageURL.isEmpty { return }
        for index in 0..<imageURL.count {
            guard let url = URL(string: imageURL[index]) else { return }

            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2) {
                if let image = try? Data(contentsOf: url) {
                    DispatchQueue.main.async {
                        images.append(UIImage(data: image) ?? UIImage())
                        if images.count == imageURL.count {
                            self.itemImages = images
                        }
                    }
                }
            }
        }

딜레이 1초 후 이미지 정상 동작

하지만 이미지의 갯수에 따라 동작하지 않는 상황이 존재했다. 딜레이를 1초로 주고 이미지가 5개가 존재한다면 똑같이 이미지가 반영되지 않는 모습이 보였다.

다만 2초에 딜레이를 둔다면 5개도 동작이 정상정으로 수행됨을 알 수 있었다,

예상 이유

이미지 URL을 가지고 비동기로 이미지를 다운로드 받는 작업에서 문제가 발생되고 있다고 예상된다.

if let image = try? Data(contentsOf: url) { ... }

이부분에서 Data 작업이 실패하여 try? 를 통해 밖으로 나가지는 것으로 디버깅을 하며 찾을 수 있었다. 만약 2개 이상의 이미지가 있을때 반복문을 통해 비동기 작업을 계속 호출하기 때문에 하나하나 작업에 걸리는 시간이 존재함에도 반복문이 동작하기 때문에 발생했다고 예상한다.

Fezravien commented 3 years ago

상품 등록, 수정 페이지에 상품 수정으로 진입했을 때 이미지가 보이지 않는 문제

트러블

사용자가 등록한 상품을 수정하기 위해 상품 상세 페이지Action Sheet를 통해 상품 수정 페이지로 이동하게 된다. 상품 수정 페이지에는 이미지, 제목, 화폐, 가격, 할인 가격(옵셔널), 수량, 상세 정보가 기존의 정보로 채워져 있어야 하지만, 처음 앱을 구동했을 때 최초의 등록화면에 진입하면 이미지가 보여지는 것을 볼 수 있지만, 그 이후에 동일한 작업을 하게되면 이미지가 없는 현상이 생겼다.

Simulator Screen Recording - iPhone 12 Pro Max - 2021-10-19 at 13.08.41


문제 인식

추측
  1. 서버에 등록된 이미지를 가져오는 것(네트워크)에 딜레이로 인해 이미지가 업데이트 되지 못했다.
  2. 서버에 이미지가 AWS 서버에 등록되는 시점과 이미지를 화면에 띄워주는 시점이 맞지 않았다.
  3. ViewController가 생성되어 메모리에 올라오기 전에 데이터를 업데이트 시켜줬다. (MVVM 이키텍처 - Model의 변화로 ViewModel과 View간의 바인딩을 통해 업데이트를 한다.)


해결

상품 수정 페이지에서 이미지를 다운로드 할 때 DispatchQueue.main.asyncAfter를 통해 딜레이를 시켜서 해결할 수 있었다.

    func downloadImage(imageURL: [String]) {
        var images: [UIImage] = []
        if imageURL.isEmpty { return }
        for index in 0..<imageURL.count {
            guard let url = URL(string: imageURL[index]) else { return }

            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2) {
                if let image = try? Data(contentsOf: url) {
                    DispatchQueue.main.async {
                        images.append(UIImage(data: image) ?? UIImage())
                        if images.count == imageURL.count {
                            self.itemImages = images
                        }
                    }
                }
            }
        }

하지만 이미지의 갯수에 따라 동작하지 않는 상황이 존재했다. 딜레이를 1초로 주고 이미지가 5개가 존재한다면, 똑같이 이미지가 반영되지 않는 모습이 보였다. 다만 2초에 딜레이를 둔다면 5개도 동작이 정상정으로 수행됨을 알 수 있었다,

딜레이를 넉넉하게 줌으로써 기능이 동작하도록 했지만, 왜 딜레이를 줘야 기능이 정상적으로 작동하는 것인지 조금 더 디버깅을 구체적으로 해보면서 생각해보았다.

스크린샷 2021-10-19 오후 10.11.38

위의 그림처럼 서버에 비밀번호를 확인하기 위해 PATCH 요청을 보내게 되면 동일한 이미지를 가지고 있지만 AWS 서버에는 다른 URL로 저장되게 되는데, 이때 딜레이를 주지 않고 이미지를 다운로드하게되면 기존의 이미지 URL로 요청하게 되서 처음에는 이미지가 보여지겠지만, 두 번째 동일한 동작을 하게되면 이미지 URL이 바뀌었기 때문에 서버로부터 이미지를 불러올 수 없게된다.

그래서 딜레이를 주게되면 서버에 비밀번호 요청과 함께 (이미지는 동일하지만) 수정되는 사항을 동기화 시킬 시간을 주게되어 이미지가 동일한 동작에도 보여질 수 있게된다.


개선

비빌번호 검증으로 인한 PATCH 요청은 어쩔 수 없는 부분인 것 같다. 이 부분은 서버와 협업을 통해 비밀번호만 검증할 수 있도록 API를 구현해달라고 요청해야 원활하게 처리할 수 있을 것 같다.

이 부분에서 개선해보고 싶은 부분은 딜레이를 효율적으로 사용하는 것이다.

기존의 해결방식인 동시큐, 비동기 작업이다. 이미지의 순서를 고려하지 않는다.

DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 2) {
    if let image = try? Data(contentsOf: url) {
        DispatchQueue.main.async {
            images.append(UIImage(data: image) ?? UIImage())
            if images.count == imageURL.count {
                self.itemImages = images
            }
        }
    }
}

스크린샷 2021-10-20 오전 12.49.24


기존의 방식에서 이미지의 순서를 고려하고, 불필요할 수도 있는 딜레이를 하지 않기 위해 개선했다. CustomQueue를 사용하여 시리얼 큐를 통한 동기적 작업으로 이미지의 순서를 보장했고, 딜레이를 이미지 전체에 한번만 줘서 기존에 불필요 할 수 있었던 딜레이를 줄였다.

작업을 보장하게 할 수 있는 방법은 여러가지가 존재한다.

  1. Custom Queue

    커스텀 큐는 디폴트는 시리얼 큐이며 동시큐로 만들 수도 있다.

    현재 이슈에서 이미지의 순서를 보장하기 위해 사용했다.

  2. Dispatch Semaphore

    동일한 자원에 접근하는 것을 막기위한 세마포어 현재 이슈에서도 세마포어 1개로 지정하고 wait, signal를 통해 순서를 보장할 수 있다.

    하지만 동일한 자원 접근이 아니므로 Custom Queue의 시리얼이 더 적합하다고 판단했다.

  3. Dispatch Group

    연관된 작업을 그룹으로 묶어서 전체가 끝남을 알리거나 최대 얼마나 기다리게 데드라인을 정할 수 있다. 현재 이슈에서 이미지 순서보장에서 적합하지 않다고 생각했다.

let dispatchSerialQueue = DispatchQueue(label: "이미지 다운로드 시리얼 큐")
var images: [UIImage] = []
if imageURL.isEmpty { return }
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 2) {
    for index in 0..<imageURL.count {
        guard let url = URL(string: imageURL[index]) else { return }
        dispatchSerialQueue.sync {
            if let image = try? Data(contentsOf: url) {
                DispatchQueue.main.async {
                    images.append(UIImage(data: image) ?? UIImage())
                        if images.count == imageURL.count {
                            self.itemImages = images
                        }
                    }
                }
            }
        }
    }
}

스크린샷 2021-10-20 오전 1.01.28


이제 여러번 동일한 동작을 해도 이미지가 올라오는 것을 확인할 수 있다.