일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- ios
- 뷰 생명주기
- SwiftUI
- panorama view
- Android
- 360도 이미지 뷰어
- 파노라마 뷰
- React-Native
- requirenativecomponent
- 리액트 네이티브
- 네이티브
- 앱 성능 개선
- 뷰 정체성
- react-native-fast-image
- data driven construct
- native
- 구조적 정체성
- completion handler
- 리액트
- 명시적 정체성
- @sendable
- 스켈레톤 통합
- 라이브러리 없이
- launchscreen
- react
- 360도 뷰어
- launch screen
- React Native
- 360도 이미지
- ssot
- Today
- Total
Neoself의 기술 블로그
UIViewRepresentable에서 발생하는 콜백 중복 호출 문제 해결 본문
동일한 이미지를 담는 NeoImage의 onSuccess 콜백이 반복호출되는 이슈가 있었습니다.
@State private var imageTask: ImageTask?
func updateUIView(_ uiView: UIImageView, context: Context) {
let url: URL? = {
switch source {
case .url(let url):
return url
case .urlString(let string):
if let string = string {
return URL(string: string)
}
return nil
}
}()
Task {
do {
let result = try await uiView.neo.setImage(with: url, options: options)
if let onSuccess = onSuccess {
await MainActor.run {
print("onSuccess called")
onSuccess(result)
}
}
} catch {
if let onFailure = onFailure {
await MainActor.run {
onFailure(error)
}
}
}
}
}
SwiftUI의 UIViewRepreesentable 프로토콜에서 필수로 작성해야하는 updateUIView 메서드는 뷰의 상태가 변경될 때마다 SwiftUI의 뷰 업데이트 사이클에 맞춰 자동으로 호출됩니다. 때문에, 초기 뷰를 띄우는 makeUIView 메서드 호출 이후, 바로 이미지 다운로드 혹은 로딩 로직은 Task 블록으로 감싸 updateUIView 메서드에 구현하였습니다. 하지만 이로 인해 뷰 업데이트를 야기하는 외부 트리거가 발생할때마다, 이미지를 새로 로드하는 Task를 불필요하게 호출하게 되었습니다.
때문에, 이 뷰 업데이트 트리거의 위치를 찾는 것이 필요했습니다.
struct NeoImageTestView: View {
let url: URL?
@State private var loadingTime: TimeInterval = 0 // 뷰 업데이트에 대한 트리거
@State private var startTime: Date = Date()
var body: some View {
Text(String(loadingTime))
.padding(.bottom,12)
NeoImage(url: url)
.onSuccess { _ in
// onSuccess 콜백함수 호출 시 loadingTime 변수값을 변경시킴
loadingTime = Date().timeIntervalSince(startTime)
}
}
}
트리거의 위치는 패키지 외부에서 이미지 로딩 시간을 저장하고 렌더하는 데에 사용된 @State var loadingTime 변수였는데요.
이미지 로드 Task 완료 -> onSuccess 콜백함수 호출 -> 패키지 외부 loadingTime 변수 변경 및 렌더 -> 뷰 업데이트 -> updateUIView 메서드 호출
위 사이클이 순환되면서 불필요하게 onSuccess가 호출되는 것이였습니다.
가설 검증을 위해 아래와 같이 loadingTime 변수를 표시하는 Text 컴포넌트를 주석처리하여, onSuccess 콜백함수가 재호출되지 않음을 확인해볼 수 있었습니다.
struct NeoImageTestView: View {
let url: URL?
@State private var loadingTime: TimeInterval = 0
@State private var startTime: Date = Date()
var body: some View {
// Text(String(loadingTime))
// .padding(.bottom,12)
NeoImage(url: url)
.onSuccess { _ in
loadingTime = Date().timeIntervalSince(startTime)
}
}
}
이를 해결하기 위해, updateUIView 메서드에서 이미지 URL이 변경되었을 때만 새로운 Task를 시작하게끔 예외처리를 추가하였습니다.
func updateUIView(_ uiView: UIImageView, context: Context) {
// URL 추출
let url: URL? = {
switch source {
case .url(let url):
return url
case .urlString(let string):
if let string = string {
return URL(string: string)
}
return nil
}
}()
// URL이 변경되었거나 아직 로드되지 않은 경우에만 이미지 로딩
if url != loadedURL {
// 이전 작업이 있다면 취소
imageTask?.cancel()
// 새 이미지 로딩 작업 시작
imageTask = Task {
do {
let result = try await uiView.neo.setImage(with: url, options: options)
// 작업이 취소되지 않았고 성공했을 경우에만 콜백 호출
if !Task.isCancelled, let onSuccess = onSuccess {
await MainActor.run {
loadedURL = url // URL 상태 업데이트
onSuccess(result)
}
}
} catch {
if !Task.isCancelled, let onFailure = onFailure {
await MainActor.run {
onFailure(error)
}
}
}
}
}
}
'개발지식 정리 > Swift' 카테고리의 다른 글
Kingfisher 라이브러리 분석하기 (Networking 레이어) (1) | 2025.03.06 |
---|---|
기초 CS (0) | 2025.03.06 |
Kingfisher 라이브러리 분석하기 (Cache 레이어) (0) | 2025.02.25 |
BookMatchKit 리펙토링 과정-3/3 (Factory Method 패턴) (0) | 2025.02.17 |
BookMatchKit 리펙토링 과정-2/3 (Template Method 패턴) (2) | 2025.02.17 |