Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] #260 - 무한 스크롤 과정에서 발생한 메모리 사용량 개선 및 레이아웃 계산 최적화 #261

Merged
merged 6 commits into from
May 23, 2024

Conversation

jeongdung-eo
Copy link
Contributor

@jeongdung-eo jeongdung-eo commented May 22, 2024

🌁 Background

  • 빵집 리스트 무한 스크롤 과정에서 메모리가 급격하게 증가하는 것을 확인하여 downsampling을 통해 문제를 해결하고자함

📱 Screenshot

아래 첨부

👩‍💻 Contents

[문제]

Bakery 리스트를 CollectionView에 무한 스크롤하여 이미지를 로딩하는 과정에서 이미지 로딩 속도가 느리고, 메모리 사용량이 급격하게 늘어나는 문제가 발견됨.

스크린샷 2024-05-22 12 38 25

이미지 로딩 과정에서 Kingfisher를 사용하여 얻은 원본 이미지의 크기를 확인

메모리 사용량 분석

Bakery 리스트의 메모리 사용량 증가율을 확인하기 위해, 처음부터 리스트의 끝까지 스크롤하여 메모리 사용량을 확인함.

초기 스크롤 전의 메모리 사용량은 76.4MB로 측정

스크린샷 2024-05-22 12 44 06

스크롤 후의 메모리 사용량은 2.5GB까지 급증함
스크린샷 2024-05-22 12 45 19

이를 통해 Bakery 리스트의 스크롤 과정에서 메모리 사용량이 비정상적으로 증가하는 것을 확인할 수 있었음.

[해결 과정]
이미지 로딩 속도와 메모리 사용량 문제를 해결하기 위해 단순히 이미지 resize 하는 대신 downsampling 을 적용해야 함.

why? UIImage의 이미지 resize 는 원본 이미지를 압축하는 방식으로, 처리 과정에서 많은 비용이 발생함.

WWDC18에서 소개된 Image and Graphics Best Practices 세션을 참고하여 최적화 방법을 적용.

🍎 Discussion 정리 : #262 (comment)

[결과]

Kingfisher를 통한 Downsampling

프로젝트에서는 Kingfisher를 통해 이미지 downsampling을 구현

DownsamplingImageProcessor

DownsamplingImageProcessor 는 고해상도 이미지를 메모리에 로드하기 전에 특정 크기로 downsampling함. 이는 scaleFactorcacheOriginalImage 와 함께 사용.

  • scaleFactor: 이미지를 축소할 비율을 지정
  • cacheOriginalImage: 원본 이미지를 캐시에 저장할지 여부를 지정

Kingfisher의 동작 방식

Kingfisher에서 ImageProcessor 를 사용할 경우, 원본 이미지와 downsampling된 이미지를 각각 다른 위치에 저장함.

  1. 원본 이미지: 디스크에 저장
  2. downsampling된 이미지: 메모리에 저장

이로 인해 원본 이미지가 메모리에 저장되지 않아서 메모리 사용량을 줄일 수 있음.

imageView.kf.setImage(
    with: resource,
    placeholder: placeholderImage,
    options: [
        .processor(DownsamplingImageProcessor(size: imageView.size)),
        .scaleFactor(UIScreen.main.scale),
        .cacheOriginalImage
    ])

Downsampling을 적용한 결과, 메모리 사용량이 2.5GB에서 231MB로 감소하였고, 이는 전체 메모리 사용량의 90.76% 감소

스크린샷 2024-05-22 16 01 18

+리팩토링을 진행하면서 기존의 collection view로 구현했던 chip 로직을 변경할 예정이지만, 현재 상태에서 성능을 향상시킬 수 있는 방법을 고려해봄.

레이아웃 계산 캐싱

[문제]

sizeForItemAt 메서드는 컬렉션 뷰의 아이템마다 호출됨. 텍스트 크기 계산은 비교적 비용이 많이 드는 작업이므로 이를 매번 수행하면 앱의 성능이 떨어질 수 있음.

[해결과정]

temSizeCache 딕셔너리를 도입하여 계산된 크기를 저장하고 재사용함으로써 불필요한 연산을 줄임

변경 전 코드

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let tagTitle = self.breadTypeTag[indexPath.item]
    let itemSize = tagTitle.size(withAttributes: [NSAttributedString.Key.font: UIFont.captionM2])
    return CGSize(width: itemSize.width + 12, height: itemSize.height)
}

변경 후 코드

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let tagTitle = breadTypeTag[indexPath.item]
        
        // 캐시에서 크기를 가져오거나 없으면 새로 계산하여 캐시에 저장
        guard let cachedSize = itemSizeCache[tagTitle.count] else {
            let itemSize = tagTitle.size(withAttributes: [NSAttributedString.Key.font: UIFont.captionM2])
            let finalSize = CGSize(width: itemSize.width + 12, height: itemSize.height)
            itemSizeCache[tagTitle.count] = finalSize
            return finalSize
        }
        
        return cachedSize
    }

[결과]

로그
금양식방
실행 화면

주어진 로그를 통해 금양식방은 성분 chip을 4개 가지고 있으며, sizeForItemAt 메서드가 호출될 때마다 해당 문자열에 대한 크기가 계산되고 캐시되는 것을 확인할 수 있음.

로그를 분석해보면, 처음에는 글루텐프리 문자열을 가진 첫 번째 셀의 크기를 계산할 때 캐시가 없어 새로 계산되고 캐시에 저장됨. 비슷하게 비건빵 문자열 역시 처음 계산될 때 캐시에 저장됨. 그러나 이후에는 넛프리와 대체당 문자열을 가진 셀에 대해서는 이미 캐시된 크기가 있기 때문에 다시 계산되지 않고 이전에 캐시된 값을 사용하여 중복 계산을 피하게됨.

각 태그 타이틀의 크기가 다르더라도 초기 계산 이후 캐시를 통해 신속하게 크기를 제공할 수 있어 중복된 계산을 최소화하고 성능을 향상시킬 수 있음을 확인할 수 있었음.

✅ Testing

📝 Review Note

-[베이커리 리스트 -> 상세 flow]
객체가 메모리에서 해제되지 않는 문제가 존재하지만, 성민쿤과 리팩토링하면서 개선할 예정

스크린샷 2024-05-22 16 56 59 - 로직은,, mvvm+클린아키텍쳐 적용하면서 개선하겠슴다,,

📣 Related Issue

📬 Reference

with: url,
placeholder: UIImage.loading_small,
options: [
.processor(DownsamplingImageProcessor(size: CGSize(width: 86, height: 86))),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아주 필요한 기능을 알잘딱깔센으로다가 잘 넣고 있군요 !!!!

코드 보다가 문득 든 생각이 ... 요 DownsamplingImageProcessor 녀석을 셀마다 인스턴스로 만들지 않고
collectionview마다 만들거나 더 좋은 방법으로 재사용할 수는 없을까에 대한 생각이 들었습니다 !!

셀마다 주입하는건 어떨지 ... 아니면 kingfisher extension으로 어떻게 할 방법은 없을지 고민해보죠 !!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 좋습니당 :)

markStackView.getMarkStatus(isHACCP, isVegan, isNonGMO)

breadTypeTag = breadTypeList.map { $0.toString() }
itemSizeCache.removeAll()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 itemSizeCache도 Cell마다 인스턴스로 갖고 있기 보다는
어차피 모든 BakeryCommonCollectionViewCell 에서 쓰일테니까 한 곳에서 관리하는것도 괜찮지 않을까 싶네요 !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 한 곳에서 관리하는 것도 좋은 방법인 것 같슴돠 !!! 이 부분도 같이 고민하는걸로 ,

Copy link
Contributor

@seongmin221 seongmin221 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 ! 코멘트 남긴 부분은 같이 생각해보죠 ~~

@jeongdung-eo jeongdung-eo merged commit bda0b99 into develop May 23, 2024
2 of 3 checks passed
@jeongdung-eo jeongdung-eo deleted the fix/#260-downsampling branch May 23, 2024 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Fix] 무한 스크롤 과정에서 발생한 메모리 사용량 개선 및 레이아웃 계산 최적화
2 participants