Fix memory leaks #4329

pull/4330/head
Krypto Pank 3 years ago
parent 50f6354123
commit c50818c60a
  1. 25
      AlphaWallet/Core/TokensAutodetector/AutoDetectTokensOperation.swift
  2. 21
      AlphaWallet/Core/TokensAutodetector/AutoDetectTransactedTokensOperation.swift
  3. 45
      AlphaWallet/Core/TokensAutodetector/TokensAutodetector.swift
  4. 2
      AlphaWallet/InCoordinator.swift
  5. 12
      AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift
  6. 10
      AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift
  7. 7
      AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift
  8. 8
      AlphaWallet/Transactions/EtherscanSingleChainTransactionProvider.swift
  9. 4
      AlphaWallet/Transactions/TransactionsService.swift

@ -11,13 +11,11 @@ import PromiseKit
protocol AutoDetectTokensOperationDelegate: class {
var isAutoDetectingTokens: Bool { get set }
func autoDetectTokensImpl(withContracts contractsToDetect: [(name: String, contract: AlphaWallet.Address)], server: RPCServer) -> Promise<Void>
func autoDetectTokensImpl(withContracts contractsToDetect: [(name: String, contract: AlphaWallet.Address)], server: RPCServer) -> Promise<[SingleChainTokensAutodetector.AddTokenObjectOperation]>
}
class AutoDetectTokensOperation: Operation {
private let wallet: AlphaWallet.Address
final class AutoDetectTokensOperation: Operation {
private let tokens: [(name: String, contract: AlphaWallet.Address)]
private let server: RPCServer
weak private var delegate: AutoDetectTokensOperationDelegate?
override var isExecuting: Bool {
@ -29,20 +27,22 @@ class AutoDetectTokensOperation: Operation {
override var isAsynchronous: Bool {
return true
}
init(forServer server: RPCServer, delegate: AutoDetectTokensOperationDelegate, wallet: AlphaWallet.Address, tokens: [(name: String, contract: AlphaWallet.Address)]) {
private let session: WalletSession
private let tokensDataStore: TokensDataStore
init(session: WalletSession, tokensDataStore: TokensDataStore, delegate: AutoDetectTokensOperationDelegate, tokens: [(name: String, contract: AlphaWallet.Address)]) {
self.delegate = delegate
self.wallet = wallet
self.session = session
self.tokens = tokens
self.server = server
self.tokensDataStore = tokensDataStore
super.init()
self.queuePriority = server.networkRequestsQueuePriority
self.queuePriority = session.server.networkRequestsQueuePriority
}
override func main() {
guard let strongDelegate = delegate else { return }
strongDelegate.autoDetectTokensImpl(withContracts: tokens, server: server).done { [weak self] in
strongDelegate.autoDetectTokensImpl(withContracts: tokens, server: session.server).done { [weak self] values in
guard let strongSelf = self else { return }
strongSelf.willChangeValue(forKey: "isExecuting")
@ -50,6 +50,9 @@ class AutoDetectTokensOperation: Operation {
strongDelegate.isAutoDetectingTokens = false
strongSelf.didChangeValue(forKey: "isExecuting")
strongSelf.didChangeValue(forKey: "isFinished")
guard !strongSelf.isCancelled else { return }
strongSelf.tokensDataStore.addTokenObjects(values: values)
}.cauterize()
}
}
}

@ -11,13 +11,11 @@ import PromiseKit
protocol AutoDetectTransactedTokensOperationDelegate: class {
var isAutoDetectingTransactedTokens: Bool { get set }
func autoDetectTransactedErc20AndNonErc20Tokens(wallet: AlphaWallet.Address) -> Promise<Void>
func autoDetectTransactedErc20AndNonErc20Tokens(wallet: AlphaWallet.Address) -> Promise<[SingleChainTokensAutodetector.AddTokenObjectOperation]>
}
class AutoDetectTransactedTokensOperation: Operation {
final class AutoDetectTransactedTokensOperation: Operation {
private let wallet: AlphaWallet.Address
weak private var delegate: AutoDetectTransactedTokensOperationDelegate?
override var isExecuting: Bool {
return delegate?.isAutoDetectingTransactedTokens ?? false
@ -29,17 +27,21 @@ class AutoDetectTransactedTokensOperation: Operation {
return true
}
init(forServer server: RPCServer, delegate: AutoDetectTransactedTokensOperationDelegate, wallet: AlphaWallet.Address) {
private let session: WalletSession
private let tokensDataStore: TokensDataStore
init(session: WalletSession, tokensDataStore: TokensDataStore, delegate: AutoDetectTransactedTokensOperationDelegate) {
self.delegate = delegate
self.wallet = wallet
self.session = session
self.tokensDataStore = tokensDataStore
super.init()
self.queuePriority = server.networkRequestsQueuePriority
self.queuePriority = session.server.networkRequestsQueuePriority
}
override func main() {
guard let delegate = delegate else { return }
delegate.autoDetectTransactedErc20AndNonErc20Tokens(wallet: wallet).done { [weak self] _ in
delegate.autoDetectTransactedErc20AndNonErc20Tokens(wallet: session.account.address).done { [weak self] values in
guard let strongSelf = self else { return }
strongSelf.willChangeValue(forKey: "isExecuting")
@ -47,6 +49,9 @@ class AutoDetectTransactedTokensOperation: Operation {
delegate.isAutoDetectingTransactedTokens = false
strongSelf.didChangeValue(forKey: "isExecuting")
strongSelf.didChangeValue(forKey: "isFinished")
guard !strongSelf.isCancelled else { return }
strongSelf.tokensDataStore.addTokenObjects(values: values)
}.cauterize()
}
}

@ -43,9 +43,8 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
private let assetDefinitionStore: AssetDefinitionStore
private let autoDetectTransactedTokensQueue: OperationQueue
private let autoDetectTokensQueue: OperationQueue
private let server: RPCServer
private let config: Config
private let wallet: Wallet
private let session: WalletSession
private let queue: DispatchQueue
private let tokenObjectFetcher: TokenObjectFetcher
@ -53,8 +52,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
var isAutoDetectingTokens = false
init(
wallet: Wallet,
server: RPCServer,
session: WalletSession,
config: Config,
tokensDataStore: TokensDataStore,
assetDefinitionStore: AssetDefinitionStore,
@ -65,8 +63,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
) {
self.tokenObjectFetcher = tokenObjectFetcher
self.queue = queue
self.wallet = wallet
self.server = server
self.session = session
self.config = config
self.tokensDataStore = tokensDataStore
self.assetDefinitionStore = assetDefinitionStore
@ -80,7 +77,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
self?.autoDetectTransactedTokens()
self?.autoDetectPartnerTokens()
}
}
}
///Implementation: We refresh once only, after all the auto detected tokens' data have been pulled because each refresh pulls every tokens' (including those that already exist before the this auto detection) price as well as balance, placing heavy and redundant load on the device. After a timeout, we refresh once just in case it took too long, so user at least gets the chance to see some auto detected tokens
private func autoDetectTransactedTokens() {
@ -90,7 +87,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
guard !isAutoDetectingTransactedTokens else { return }
isAutoDetectingTransactedTokens = true
let operation = AutoDetectTransactedTokensOperation(forServer: server, delegate: self, wallet: wallet.address)
let operation = AutoDetectTransactedTokensOperation(session: session, tokensDataStore: tokensDataStore, delegate: self)
autoDetectTransactedTokensQueue.addOperation(operation)
}
@ -128,7 +125,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
}
private func autoDetectTransactedTokensImpl(wallet: AlphaWallet.Address, erc20: Bool) -> Promise<[SingleChainTokensAutodetector.AddTokenObjectOperation]> {
let server = server
let server = session.server
return firstly {
autoDetectTransactedContractsImpl(wallet: wallet, erc20: erc20, server: server)
@ -149,7 +146,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
private func autoDetectPartnerTokens() {
guard !config.development.isAutoFetchingDisabled else { return }
switch server {
switch session.server {
case .main:
autoDetectMainnetPartnerTokens()
case .xDai:
@ -177,7 +174,7 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
guard !isAutoDetectingTokens else { return }
isAutoDetectingTokens = true
let operation = AutoDetectTokensOperation(forServer: server, delegate: self, wallet: wallet.address, tokens: contractsToDetect)
let operation = AutoDetectTokensOperation(session: session, tokensDataStore: tokensDataStore, delegate: self, tokens: contractsToDetect)
autoDetectTokensQueue.addOperation(operation)
}
@ -191,10 +188,10 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
}
private func fetchCreateErc875OrErc20Token(forContract contract: AlphaWallet.Address, forServer server: RPCServer) -> Promise<AddTokenObjectOperation> {
let accountAddress = wallet.address
let accountAddress = session.account.address
let queue = queue
return TokenProvider(account: wallet, server: server)
return TokenProvider(account: session.account, server: server)
.getTokenType(for: contract)
.then(on: queue, { [weak tokenObjectFetcher] tokenType -> Promise<AddTokenObjectOperation> in
guard let tokenObjectFetcher = tokenObjectFetcher else { return .init(error: PMKError.cancelled) }
@ -233,34 +230,28 @@ class SingleChainTokensAutodetector: NSObject, TokensAutodetector {
extension SingleChainTokensAutodetector: AutoDetectTransactedTokensOperationDelegate {
func autoDetectTransactedErc20AndNonErc20Tokens(wallet: AlphaWallet.Address) -> Promise<Void> {
func autoDetectTransactedErc20AndNonErc20Tokens(wallet: AlphaWallet.Address) -> Promise<[SingleChainTokensAutodetector.AddTokenObjectOperation]> {
let fetchErc20Tokens = autoDetectTransactedTokensImpl(wallet: wallet, erc20: true)
let fetchNonErc20Tokens = autoDetectTransactedTokensImpl(wallet: wallet, erc20: false)
return when(resolved: [fetchErc20Tokens, fetchNonErc20Tokens])
.get(on: queue, { [weak tokensDataStore] results in
guard let tokensDataStore = tokensDataStore else { return }
let values = results.compactMap { $0.optionalValue }.flatMap { $0 }
tokensDataStore.addTokenObjects(values: values)
}).asVoid()
.map(on: queue, { results in
return results.compactMap { $0.optionalValue }.flatMap { $0 }
})
}
}
extension SingleChainTokensAutodetector: AutoDetectTokensOperationDelegate {
func autoDetectTokensImpl(withContracts contractsToDetect: [(name: String, contract: AlphaWallet.Address)], server: RPCServer) -> Promise<Void> {
func autoDetectTokensImpl(withContracts contractsToDetect: [(name: String, contract: AlphaWallet.Address)], server: RPCServer) -> Promise<[SingleChainTokensAutodetector.AddTokenObjectOperation]> {
let promises = contractsToAutodetectTokens(withContracts: contractsToDetect, forServer: server)
.map { each -> Promise<AddTokenObjectOperation> in
return fetchCreateErc875OrErc20Token(forContract: each, forServer: server)
}
return when(resolved: promises)
.get(on: queue, { [weak tokensDataStore] results in
guard let tokensDataStore = tokensDataStore else { return }
let values = results.compactMap { $0.optionalValue }
tokensDataStore.addTokenObjects(values: values)
}).asVoid()
.map(on: queue, { results in
return results.compactMap { $0.optionalValue }
})
}
}

@ -35,7 +35,7 @@ class InCoordinator: NSObject, Coordinator, DappRequestHandlerDelegate {
return MultipleChainsTokensDataStore(realm: realm, servers: config.enabledServers)
}()
private lazy var eventSourceCoordinator: EventSourceCoordinatorType = {
private lazy var eventSourceCoordinator: EventSourceCoordinator = {
EventSourceCoordinator(wallet: wallet, tokensDataStore: tokensDataStore, assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, config: config)
}()

@ -17,13 +17,7 @@ extension PromiseKit.Result {
}
}
protocol EventSourceCoordinatorType: AnyObject {
func start()
}
//TODO: Create XMLHandler store and pass it everwhere we use it
//TODO: Rename this generic name to reflect that it's for event instances, not for event activity
class EventSourceCoordinator: NSObject, EventSourceCoordinatorType {
final class EventSourceCoordinator: NSObject {
private var wallet: Wallet
private let tokensDataStore: TokensDataStore
private let assetDefinitionStore: AssetDefinitionStore
@ -49,7 +43,7 @@ class EventSourceCoordinator: NSObject, EventSourceCoordinatorType {
func start() {
setupWatchingTokenChangesToFetchEvents()
setupWatchingTokenScriptFileChangesToFetchEvents()
}
}
private func setupWatchingTokenChangesToFetchEvents() {
tokensDataStore
@ -64,7 +58,7 @@ class EventSourceCoordinator: NSObject, EventSourceCoordinatorType {
//TODO this is firing twice for each contract. We can be more efficient
assetDefinitionStore.bodyChange
.receive(on: RunLoop.main)
.compactMap { self.tokensDataStore.token(forContract: $0) }
.compactMap { [weak self] in self?.tokensDataStore.token(forContract: $0) }
.sink { [weak self] token in
guard let strongSelf = self else { return }

@ -6,11 +6,7 @@ import PromiseKit
import web3swift
import Combine
protocol EventSourceCoordinatorForActivitiesType: AnyObject {
func start()
}
class EventSourceCoordinatorForActivities: EventSourceCoordinatorForActivitiesType {
final class EventSourceCoordinatorForActivities {
private var wallet: Wallet
private let config: Config
private let tokensDataStore: TokensDataStore
@ -34,7 +30,7 @@ class EventSourceCoordinatorForActivities: EventSourceCoordinatorForActivitiesTy
func start() {
setupWatchingTokenChangesToFetchEvents()
setupWatchingTokenScriptFileChangesToFetchEvents()
}
}
private func setupWatchingTokenChangesToFetchEvents() {
tokensDataStore
@ -48,7 +44,7 @@ class EventSourceCoordinatorForActivities: EventSourceCoordinatorForActivitiesTy
private func setupWatchingTokenScriptFileChangesToFetchEvents() {
assetDefinitionStore.bodyChange
.receive(on: RunLoop.main)
.compactMap { self.tokensDataStore.token(forContract: $0) }
.compactMap { [weak self] in self?.tokensDataStore.token(forContract: $0) }
.sink { [weak self] token in
guard let strongSelf = self else { return }

@ -165,6 +165,11 @@ class TokensCoordinator: Coordinator {
showTokens()
alertService.start()
}
deinit {
autoDetectTransactedTokensQueue.cancelAllOperations()
autoDetectTokensQueue.cancelAllOperations()
}
private func setupSingleChainTokenCoordinators() {
for session in sessions.values {
@ -175,7 +180,7 @@ class TokensCoordinator: Coordinator {
}()
let tokensAutodetector: TokensAutodetector = {
SingleChainTokensAutodetector(wallet: session.account, server: server, config: config, tokensDataStore: tokensDataStore, assetDefinitionStore: assetDefinitionStore, withAutoDetectTransactedTokensQueue: autoDetectTransactedTokensQueue, withAutoDetectTokensQueue: autoDetectTokensQueue, queue: tokensAutoDetectionQueue, tokenObjectFetcher: tokenObjectFetcher)
SingleChainTokensAutodetector(session: session, config: config, tokensDataStore: tokensDataStore, assetDefinitionStore: assetDefinitionStore, withAutoDetectTransactedTokensQueue: autoDetectTransactedTokensQueue, withAutoDetectTokensQueue: autoDetectTokensQueue, queue: tokensAutoDetectionQueue, tokenObjectFetcher: tokenObjectFetcher)
}()
let coordinator = SingleChainTokenCoordinator(session: session, keystore: keystore, tokensStorage: tokensDataStore, assetDefinitionStore: assetDefinitionStore, eventsDataStore: eventsDataStore, analyticsCoordinator: analyticsCoordinator, tokenActionsProvider: tokenActionsService, coinTickersFetcher: coinTickersFetcher, activitiesService: activitiesService, alertService: alertService, tokensAutodetector: tokensAutodetector)

@ -46,7 +46,7 @@ class EtherscanSingleChainTransactionProvider: SingleChainTransactionProvider {
autoDetectERC20Transactions()
autoDetectErc721Transactions()
}
}
}
func stopTimers() {
timer?.invalidate()
@ -282,7 +282,9 @@ class EtherscanSingleChainTransactionProvider: SingleChainTransactionProvider {
firstly {
EtherscanSingleChainTransactionProvider.functional.fetchTransactions(startBlock: startBlock, sortOrder: sortOrder, session: coordinator.session, alphaWalletProvider: coordinator.alphaWalletProvider, tokensDataStore: coordinator.tokensDataStore, queue: coordinator.queue)
}.done { transactions in
}.done { [weak self] transactions in
guard let strongSelf = self else { return }
guard !strongSelf.isCancelled else { return }
coordinator.addOrUpdate(transactions: transactions)
}.catch { e in
error(value: e, rpcServer: coordinator.session.server, address: self.session.account.address)
@ -297,7 +299,7 @@ class EtherscanSingleChainTransactionProvider: SingleChainTransactionProvider {
strongSelf.didChangeValue(forKey: "isExecuting")
strongSelf.didChangeValue(forKey: "isFinished")
}
}
}
}
}

@ -55,6 +55,10 @@ class TransactionsService {
NotificationCenter.default.addObserver(self, selector: #selector(restartTimers), name: UIApplication.didBecomeActiveNotification, object: nil)
}
deinit {
fetchLatestTransactionsQueue.cancelAllOperations()
}
private func setupSingleChainTransactionProviders() {
providers = sessions.values.map { each in
let providerType = each.server.transactionProviderType

Loading…
Cancel
Save