Memory Leak
지난 글인 ViewBuilder 와 guard let에서 보면 메모리 누수가 발생한 것을 알 수 있다. 이를 고치려고 AnyView
를 대체했지만 소용이없었다. 그러던 중 이미지를 네트워크 api 통신을 통해 불러오는 ImageLoader
객체가 비이상적으로 생성된 것을 발견하였다.
현재 네이버 바이브
클론 프로젝트를 진행중인데 음악 스트리밍 서비스인 만큼 이미지가 많이 필요한 상황이라 이미지 객체를 포기할 수 없어서 고쳐보기로 했다. 사실 뷰는 중요하지 않고 다른 것이 중요한 상황이라 이미지는 정적 이미지로 고정할 수도 있었지만 여태 만든 DB 더미 데이터와 네트워크 객체를 포기하기 아까워 고치기로 했다.
홈 화면(Today Scene)과 더보기 화면(Magazine Scene)을 왔다갔다 이동하며 확인해본 결과 이미지 객체가 누적되어 생성되지는 않았다. 그냥 하나 생길 것이 여러개로 많이 생성될 뿐이었다.
NavigationLink와 View
다음과 같이 NavigationLink
를 사용하면 destination
에 지정된 뷰가 미리 생성된다.
사실 back
버튼을 눌러서 뒤로 가면 기존 뷰는 사라지고 다시 생성되는 것이다. 기존 뷰가 사라지고 새로운 뷰가 생성되는 것이므로 해당 뷰의 수는 1개로 동일하다.
이렇게 미리 생성되는 것 때문에 많이 생성된 걸까 싶어서 미리 생성되는 것을 막기로 했다. 사실 아닌것 같았는데 미리 생성되는 것 자체가 싫어서 그냥 막기로 했다.
다음의 스트럭트 객체를 생성하면 된다. View
를 parameter
로 받기 위해서는 Generic
형태로 만들어야한다.
LazyView 만들기
struct LazyView<Content: View>: View {
let build: () -> Content
init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}
NavigationLink(
destination: LazyView(ErrorView()),
label: {
Text("hi")
}
).buttonStyle(PlainButtonStyle())
사용할 때에는 LazyView(SampleView())
이런 방식으로 기존 뷰를 LazyView로 감싸서 사용하면 된다.
이렇게 했더니 destinationView
가 미리 생성되는 것은 막을 수 있었다.
그래도 여전히 하나만 생성되야할 ImageLoader
객체가 여러개 생성되었다.
이상하게 다른 네트워크 통신하는 객체는 여러개 생성이 안되는데 이미지만 중복해서 생성되었다.
NavigationLink
를 연결시킬 label: {}
부분에 들어갈contents를 변경하면서 여러 상황에서 비교해보았다.
URLImage
URLImage: 1개, ImageLoader: 5개, 네트워킹: 5번
struct ContentView: View {var body: some View {
NavigationView {
NavigationLink(
destination: LazyView(ErrorView()),
label: {
URLImage(urlString: "https://music-phinf.pstatic.net/20200323_53/1584950278400d35jP_PNG/VIBE__%B0%F8%C5%EB_%B9%E6%BE%C8%BF%A1%BC%AD.png")
}
).buttonStyle(PlainButtonStyle())
}.navigationViewStyle(StackNavigationViewStyle())
}
}
ImageLoader
URLImage: 1개, ImageLoader: 1개, 네트워킹: 5번
struct ContentView: View {
@StateObject private var imageLoader = URLImageLoader()
var body: some View {
NavigationView {
NavigationLink(
destination: LazyView(ErrorView()),
label: {
if let image = imageLoader.image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.onAppear {
imageLoader.fetch(urlString: "https://music-phinf.pstatic.net/20200323_53/1584950278400d35jP_PNG/VIBE__%B0%F8%C5%EB_%B9%E6%BE%C8%BF%A1%BC%AD.png")
}
}
}
).buttonStyle(PlainButtonStyle())
}.navigationViewStyle(StackNavigationViewStyle())
}
}
LazyView
URLImage: 5개, ImageLoader: 5개, 네트워킹: 5번
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: LazyView(ErrorView()),
label: {
LazyView(URLImage(urlString: "https://music-phinf.pstatic.net/20200323_53/1584950278400d35jP_PNG/VIBE__%B0%F8%C5%EB_%B9%E6%BE%C8%BF%A1%BC%AD.png"))
}
).buttonStyle(PlainButtonStyle())
}.navigationViewStyle(StackNavigationViewStyle())
}
}
아 진짜 별짓을 다해도 최종적으로 네트워크 객체 호출하는 수는 5번이었다. 하나의 이미지를 생성하려고 하면 저렇게 한 번에 다섯번이 호출되었다.
그러다가 NavigationLink
에서 뺐는데 이미지 API 호출이 원하는 대로 하나만 생겼다.👀
하... NavigationLink
가 문제였구나🤦♀️
검색하니깐 바로 나왔다.
Navigation Link Issue with SwiftUI
여태 이미지나 네트워크 객체가 문제인줄 알고 그것만 뜯어보고 있었는데...
여튼 NavigationLink
를 ZStack
으로 분리하니 문제가 해결되었다. (사실 아님)
ZStack
URLImage: 1개, ImageLoader: 1개, 네트워킹: 1번
단! NavigationLink
depth가 한 번일 경우에만!NavigationLink
depth 더 깊어지면 두 배 정도 생성된다.ㅎ
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
URLImage(urlString: "https://music-phinf.pstatic.net/20200323_53/1584950278400d35jP_PNG/VIBE__%B0%F8%C5%EB_%B9%E6%BE%C8%BF%A1%BC%AD.png")
NavigationLink(
destination: LazyView(ErrorView()),
label: {
Rectangle().hidden()
}
).buttonStyle(PlainButtonStyle())
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
MemorySafeNavigationLink
NavigationLink
가 여기저기 얽혀있는 상태라 모든 NavigationLink
변경이 필요했다. 따라서 다음과 같이 객체로 분리하였다.
struct MemorySafeNavigationLink<Content: View>: View {
let contentView: Content
let destination: AnyView
var body: some View {
ZStack {
contentView
NavigationLink(
destination: LazyView(destination),
label: {
Rectangle().hidden()
}
)
}
}
}
사용은 이런식으로 하면 된다.
let favoritesCategory = Category(playlists: viewModel.favorites,
type: .favorites,
mode: .half)
MemorySafeNavigationLink(
contentView: CategoryView(category: favoritesCategory),
destination: router.getDestination(to: .favorites)
)
해결된 줄 알았으나... 그냥 수가 준거였다. 지난 번엔 4~5배였으면 지금은 2배정도 더 생성된다.
그리고 LazyGrid에서도 문제가 있어서 하...
참조
'iOS > SwiftUI' 카테고리의 다른 글
SwiftUI) 사용자 이벤트 수집 및 Alert로 확인 (0) | 2020.12.16 |
---|---|
SwiftUI) URL로 비동기 이미지 생성하기 - Combine과 Network (0) | 2020.12.05 |
SwiftUI) ViewBuilder 와 guard let (1) | 2020.12.03 |
SwiftUI) ObservableObject와 상속 (1) | 2020.12.02 |
SwiftUI) MVVM과 Combine (0) | 2020.11.24 |