2

On the simulator I was noticing some lag, but it runs fine. I finished the feature and went to run it on my phone and it crashes saying:

Message from debugger: Terminated due to memory issue

I do have zombie objects turned off in the Schema (as I was reading it can give problems)

Doing some debugging I found the issue happens after I assigned the JSON downloaded from the api, so maybe the object is not being released? This is the first time I face something like this. Here's how the api request looks:

let fetchWalletBalances = Task(priority: .high) {
    try await fetchTokenBalances(user: user, userWallets: userWallets, verifiedTokensDictionary: verifiedTokensDictionary)
}
let tokensPlusCoinGeckoIds = try await fetchWalletBalances.value

At this point the memory shows on XCode like 1Gb already

enter image description here

then I assign it to do further calculations (all in my view model):

let walletsTokenData: OrderedDictionary<String, [TokenBalanceClassAModel]> = tokensPlusCoinGeckoIds.walletsTokenData

and here it crashes.

How can I release some memory or go about this issue?

EDIT:

After lots of debugging and trying some of the tools suggested by @Alexander, I found that the memory leak happens in the block below, it jumps from 33 Mb to 1.76 GB (I'm using Alamofire to do the api fetch):

func afRequest(url: URL) async throws -> Data {
    try await withUnsafeThrowingContinuation { continuation in
        // If another error try lowering the request error: (statusCode: 200..<300)
        // timeoutInterval is in seconds
        AF.request(url, method: .get, requestModifier: { $0.timeoutInterval = .infinity })
            .validate(statusCode: 200..<600)
            .responseData { response in
            
                if let data = response.data {
                    continuation.resume(returning: data)
                    return
                }
            
                if case let .failure(error) = response.result {
                    print(" Error on afRequest(): \(error)")
                    continuation.resume(throwing: error)
                    return
                }
            }
    }
}

The memory jumps at line: AF.request(url, method: .get, requestModifier: { $0.timeoutInterval = .infinity }).validate(statusCode: 200..<600).responseData { response in

The problem is that on every loop (see below) it keeps adding the memory from the previous iteration, so it's never clear.

for chain in chains {
    if(chain.chain_id != nil) {
        // at some point here I get 1.67 GB of memory, shouldn't at the end of the loop clear that memory?
        let decodedData = await tokenBalancesClassAViewModel.fetchTokenBalancesData(walletAddress: wallet.address, chain: chain.chain_id!)
        let data: TokenBalanceClassAModel = decodedData
        let chainId = data.data.chain_id

        if(chainId == nil) {
            shouldCacheBalances = false
            deleteCachedData()
            print(" Reloaded Chain: Nil. Balances won't be cached")
        } else {
            // Save into Single Address
            singleWalletData.append(data)
            print(" Reloaded Chain: \(chainId!)")
        }
    }
}

What's happening there and how can I fix it?

Arturo
  • 3,254
  • 2
  • 22
  • 61
  • Have you tried poking into the memory graph debugger to inspect what objects are at play? Also, turn on address sanitizer (ASan), malloc guard pages, malloc scribble, and all the other memory-related debugging instrumentation. That should help you narrow it down a bit – Alexander Jan 09 '22 at 21:02
  • I'll try all that @Alexander – Arturo Jan 09 '22 at 21:04
  • One of the graph debugger stack trace points me to this: `private let allChainsClassAViewModel: AllChainsClassAViewModel = AllChainsClassAViewModel()` where I declared it inside `final class TokenBalancesClassAViewModel: ObservableObject {` what harm could that do? I'm using SwiftUI btw, if that matters @Alexander – Arturo Jan 09 '22 at 21:16
  • Another one is pointing me to `@main` – Arturo Jan 09 '22 at 21:46
  • It's super weird, on the simulator it takes 200Mbs, and on my phone when I attach it takes 1Gb+ ... – Arturo Jan 09 '22 at 22:49
  • For huge data sets consider moving to CoreData. Do importing and processing in batches. Implementation will be more complicated and performance will be slower but it will keep your memory in check. – de. Jan 09 '22 at 23:09
  • Found the memory leak! Please check my edited post above, what's wrong in that block? @Alexander – Arturo Jan 09 '22 at 23:23
  • I'll take that into consideration but the leak seems to happen at api data retrieval, has nothing to do with manipulating data. I updated the code above, could you take a look at the EDIT section? @de. – Arturo Jan 09 '22 at 23:24
  • @Arturo I don't much about Alamofire, so I don't think I'll be able to help much. But the more details you can add, the more traction your question will probably get – Alexander Jan 09 '22 at 23:29
  • I assume that `chains` is very large. See https://stackoverflow.com/questions/9086913/why-is-autoreleasepool-still-needed-with-arc for some examples of `@autoreleasepool`. You need to create an autorelease pool context inside your loop so that autoreleased objects get released. (unless `singleWalletData.append(data)` is actually accumulating a huge amount of data) From your comment, I think you're expecting memory to be cleared at the end of the loop, but that's not a thing. You need to tell it to drain the pool. – Rob Napier Jan 10 '22 at 02:22

1 Answers1

0

I found the issue. The problem was in the view with this block during the ScrollView:

else if(updateAPIDataViewModel.allWalletsData.walletsData.isEmpty && updateAPIDataViewModel.isTotalPortfolioBalanceBeingCalculated && !walletDataStoreHandlerViewModel.userWallets.isEmpty) {
    ScrollView(.horizontal, showsIndicators: false) {
        ForEach(walletDataStoreHandlerViewModel.userWallets) { wallet in
            HStack {
                ForEach(0..<(wallet.chainsWithBalance?.count ?? 0)) { _ in
                    DummyWallet()
                            .redacted(reason: .placeholder)
                }
            }
        }
    }
}
Arturo
  • 3,254
  • 2
  • 22
  • 61