title: SwiftUI Grid Item Memory Leak
body: <p>I'm trying to create a photo app in SwiftUI that display the photos from <code>PHAsset</code> on a device.
  However when I keep scrolling the grid view, the memory would keep growing and eventually crash because of memory issue.</p>
  <p>I'm also switching from RxSwfit to SwiftUI, so if there's a better way to construct grid items and their view models, please do suggest. Thank you.</p>
  <pre><code>struct LibraryView: View {
      @ObservedObject var viewModel = ViewModel(photoLibraryService: PhotoLibraryService.shared)
  
      private var gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 2), count: 4)
  
      var body: some View {
          GeometryReader { gp in
              let width = CGFloat((Int(gp.size.width) - (2 * 3)) / 4)
              ScrollView {
                  LazyVGrid(columns: gridItemLayout, spacing: 2) {
                      ForEach(viewModel.assets) { asset in
                          PhotoGridItem(asset: asset)
                              .frame(width: width, height: width)
                              .clipped()
                      }
                  }
              }
          }
      }
  }
  </code></pre>
  <pre><code>struct PhotoGridItem: View {
      @ObservedObject var viewModel: ViewModel
  
      init(asset: LibraryAsset) {
          viewModel = ViewModel(asset: asset)
      }
  
      var body: some View {
          Image(uiImage: viewModel.image)
              .resizable()
              .scaledToFill()
              .clipped()
              .onAppear {
                  viewModel.fetchImage()
                  print(&quot;onAppear called&quot;)
              }.onDisappear {
                  viewModel.cancelRequest()
                  print(&quot;onDisappear called&quot;)
          }
      }
  }
  
  extension PhotoGridItem {
      class ViewModel: ObservableObject {
          @Published var image: UIImage = UIImage()
          var imageRequestID: PHImageRequestID?
  
          let asset: LibraryAsset
  
          init(asset: LibraryAsset) {
              self.asset = asset
          }
  
          // Note that the completion block below would be called multiple times
          // At first a smaller image would get returned first
          // Then it would return a clear image
          // Like it's progressively loading the image
          func fetchImage() {
              DispatchQueue.global(qos: .userInteractive).async {
                  let requestOptions = PHImageRequestOptions()
                  requestOptions.isNetworkAccessAllowed = true
                  requestOptions.deliveryMode = .opportunistic
  
                  PHImageManager.default().requestImage(
                      for: self.asset.asset,
                      targetSize: CGSize(width: 300, height: 300),
                      contentMode: .aspectFill,
                      options: requestOptions,
                      resultHandler: { [weak self] (image: UIImage?, info: [AnyHashable: Any]?) -&gt; Void in
                          if let imageRequestID = info?[PHImageResultRequestIDKey] as? PHImageRequestID {
                              self?.imageRequestID = imageRequestID
                          }
                          DispatchQueue.main.async {
                              if let image = image {
                                  self?.image = image
                              }
                          }
                      }
                  )
              }
          }
  
          func cancelRequest() {
              image = UIImage()
              if let imageRequestID = imageRequestID {
                  PHImageManager.default().cancelImageRequest(imageRequestID)
              }
          }
      }
  }
  </code></pre>