diff --git a/AlphaWallet/Core/TokensAutodetector/AutoDetectTokensOperation.swift b/AlphaWallet/Core/TokensAutodetector/AutoDetectTokensOperation.swift index 361e1efac..34096f022 100644 --- a/AlphaWallet/Core/TokensAutodetector/AutoDetectTokensOperation.swift +++ b/AlphaWallet/Core/TokensAutodetector/AutoDetectTokensOperation.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 + 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() - } + } } diff --git a/AlphaWallet/Core/TokensAutodetector/AutoDetectTransactedTokensOperation.swift b/AlphaWallet/Core/TokensAutodetector/AutoDetectTransactedTokensOperation.swift index 623112781..4f1dbe35e 100644 --- a/AlphaWallet/Core/TokensAutodetector/AutoDetectTransactedTokensOperation.swift +++ b/AlphaWallet/Core/TokensAutodetector/AutoDetectTransactedTokensOperation.swift @@ -11,13 +11,11 @@ import PromiseKit protocol AutoDetectTransactedTokensOperationDelegate: class { var isAutoDetectingTransactedTokens: Bool { get set } - func autoDetectTransactedErc20AndNonErc20Tokens(wallet: AlphaWallet.Address) -> Promise + 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() } } diff --git a/AlphaWallet/Core/TokensAutodetector/TokensAutodetector.swift b/AlphaWallet/Core/TokensAutodetector/TokensAutodetector.swift index a6f06c94f..f94cbf857 100644 --- a/AlphaWallet/Core/TokensAutodetector/TokensAutodetector.swift +++ b/AlphaWallet/Core/TokensAutodetector/TokensAutodetector.swift @@ -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 { - 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 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 { + 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 { + func autoDetectTokensImpl(withContracts contractsToDetect: [(name: String, contract: AlphaWallet.Address)], server: RPCServer) -> Promise<[SingleChainTokensAutodetector.AddTokenObjectOperation]> { let promises = contractsToAutodetectTokens(withContracts: contractsToDetect, forServer: server) .map { each -> Promise 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 } + }) } } diff --git a/AlphaWallet/InCoordinator.swift b/AlphaWallet/InCoordinator.swift index fbf44b263..a9cb8593e 100644 --- a/AlphaWallet/InCoordinator.swift +++ b/AlphaWallet/InCoordinator.swift @@ -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) }() diff --git a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift index 1a870f26d..0fbc4b1c2 100644 --- a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift +++ b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinator.swift @@ -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 } diff --git a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift index f1a417f57..7046ce4a7 100644 --- a/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift +++ b/AlphaWallet/TokenScriptClient/Coordinators/EventSourceCoordinatorForActivities.swift @@ -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 } diff --git a/AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift b/AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift index 98eac6cb3..8ded8ca94 100644 --- a/AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift +++ b/AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift @@ -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) diff --git a/AlphaWallet/Transactions/EtherscanSingleChainTransactionProvider.swift b/AlphaWallet/Transactions/EtherscanSingleChainTransactionProvider.swift index 4468d6352..4cd894da1 100644 --- a/AlphaWallet/Transactions/EtherscanSingleChainTransactionProvider.swift +++ b/AlphaWallet/Transactions/EtherscanSingleChainTransactionProvider.swift @@ -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") } - } + } } } diff --git a/AlphaWallet/Transactions/TransactionsService.swift b/AlphaWallet/Transactions/TransactionsService.swift index 99274bbea..9fabba565 100644 --- a/AlphaWallet/Transactions/TransactionsService.swift +++ b/AlphaWallet/Transactions/TransactionsService.swift @@ -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