Skip to content

Instantly share code, notes, and snippets.

@Kirow
Created July 19, 2023 11:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kirow/fe42d544e819de635de067018f0fdd03 to your computer and use it in GitHub Desktop.
Save Kirow/fe42d544e819de635de067018f0fdd03 to your computer and use it in GitHub Desktop.
SwiftUI, LazyVStack, ObservedObject, Memory Leak
import Foundation
import SwiftUI
struct ReferenceCounter {
class Box {
weak var value: ViewModel.Element?
}
private var objects: [Box] = []
var count: Int = 0
mutating func append(_ value: ViewModel.Element) {
let box = Box()
box.value = value
objects.append(box)
count = objects.count
}
mutating func clean() {
objects.removeAll { $0.value == nil }
count = objects.count
}
}
class ViewModel: ObservableObject {
class Element: ObservableObject {
let id: UUID
@Published var name = ""
init(id: UUID = UUID(), name: String = "") {
print("Element init \(name)")
self.id = id
self.name = name
}
deinit {
print("Element deinit \(name)")
}
}
let id: UUID
@Published var name = ""
@Published var elements: [Element] = []
@Published var referenceCounter = ReferenceCounter()
init(
id: UUID = UUID(),
name: String = "",
elements: [Element] = []
) {
print("ViewModel init")
self.id = id
self.name = name
self.elements = elements
}
deinit {
print("ViewModel deinit")
}
func createNew() {
let element = Element(name: "Element \(elements.count)")
referenceCounter.append(element)
elements.append(element)
}
func removeElement(id: UUID) -> Int? {
guard let index = elements.firstIndex(where: { $0.id == id }) else { return nil }
elements.remove(at: index)
DispatchQueue.main.async {
self.referenceCounter.clean()
}
return index
}
}
struct ElementView: View {
@ObservedObject var viewModel: ViewModel.Element
@Binding var isSelected: Bool
var body: some View {
Text(viewModel.name)
.frame(width: 120, height: 40)
.background(isSelected ? Color.red : Color.yellow )
.cornerRadius(4)
.onTapGesture {
isSelected.toggle()
}
}
}
struct MemoryLeakView: View {
@ObservedObject var viewModel: ViewModel
@State private var selectedID: UUID?
private func isSelected(id: UUID) -> Binding<Bool>{
Binding(
get: { selectedID == id },
set: { selectedID = $0 ? id : nil }
)
}
var body: some View {
GeometryReader { proxy in
HStack {
ScrollView {
LazyVStack {
ForEach(viewModel.elements, id: \.id) { element in
ElementView(
viewModel: element,
isSelected: isSelected(id: element.id)
)
}
}
}.frame(width: proxy.size.width / 2)
VStack(spacing: 20) {
Spacer()
Button("Add") {
viewModel.createNew()
selectedID = viewModel.elements.last?.id
}
.frame(width: 120, height: 40)
.background(Color.green)
Button("Remove") {
guard let id = selectedID else { return }
if let index = viewModel.removeElement(id: id) {
if index < viewModel.elements.count {
selectedID = viewModel.elements[index].id
} else {
selectedID = viewModel.elements.last?.id
}
} else {
selectedID = nil
}
}
.frame(width: 120, height: 40)
.background(Color.red)
Text("Ref Count: \(viewModel.referenceCounter.count)")
Spacer()
}.frame(width: proxy.size.width / 2)
}
}.background(Color.gray)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment