Make MainThreadOperationQueue MainActor.
This commit is contained in:
parent
e867487031
commit
ae4dcc0b27
@ -42,7 +42,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
|||||||
private let accountZone: CloudKitAccountZone
|
private let accountZone: CloudKitAccountZone
|
||||||
private let articlesZone: CloudKitArticlesZone
|
private let articlesZone: CloudKitArticlesZone
|
||||||
|
|
||||||
private let mainThreadOperationQueue = MainThreadOperationQueue()
|
@MainActor private let mainThreadOperationQueue = MainThreadOperationQueue()
|
||||||
|
|
||||||
private lazy var refresher: LocalAccountRefresher = {
|
private lazy var refresher: LocalAccountRefresher = {
|
||||||
let refresher = LocalAccountRefresher()
|
let refresher = LocalAccountRefresher()
|
||||||
|
@ -27,7 +27,7 @@ class CloudKitReceiveStatusOperation: MainThreadOperation {
|
|||||||
self.articlesZone = articlesZone
|
self.articlesZone = articlesZone
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
@MainActor func run() {
|
||||||
guard let articlesZone = articlesZone else {
|
guard let articlesZone = articlesZone else {
|
||||||
self.operationDelegate?.operationDidComplete(self)
|
self.operationDelegate?.operationDidComplete(self)
|
||||||
return
|
return
|
||||||
|
@ -31,7 +31,7 @@ class CloudKitRemoteNotificationOperation: MainThreadOperation {
|
|||||||
self.userInfo = userInfo
|
self.userInfo = userInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
@MainActor func run() {
|
||||||
guard let accountZone = accountZone, let articlesZone = articlesZone else {
|
guard let accountZone = accountZone, let articlesZone = articlesZone else {
|
||||||
self.operationDelegate?.operationDidComplete(self)
|
self.operationDelegate?.operationDidComplete(self)
|
||||||
return
|
return
|
||||||
|
@ -41,12 +41,13 @@ class CloudKitSendStatusOperation: MainThreadOperation {
|
|||||||
self.database = database
|
self.database = database
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
@MainActor func run() {
|
||||||
os_log(.debug, log: log, "Sending article statuses...")
|
os_log(.debug, log: log, "Sending article statuses...")
|
||||||
|
|
||||||
if showProgress {
|
if showProgress {
|
||||||
|
|
||||||
database.selectPendingCount() { result in
|
database.selectPendingCount() { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let count):
|
case .success(let count):
|
||||||
let ticks = count / self.blockSize
|
let ticks = count / self.blockSize
|
||||||
@ -57,6 +58,7 @@ class CloudKitSendStatusOperation: MainThreadOperation {
|
|||||||
self.operationDelegate?.cancelOperation(self)
|
self.operationDelegate?.cancelOperation(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -72,10 +74,12 @@ private extension CloudKitSendStatusOperation {
|
|||||||
|
|
||||||
func selectForProcessing() {
|
func selectForProcessing() {
|
||||||
database.selectForProcessing(limit: blockSize) { result in
|
database.selectForProcessing(limit: blockSize) { result in
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let syncStatuses):
|
case .success(let syncStatuses):
|
||||||
|
|
||||||
func stopProcessing() {
|
@MainActor func stopProcessing() {
|
||||||
if self.showProgress {
|
if self.showProgress {
|
||||||
self.refreshProgress?.completeTask()
|
self.refreshProgress?.completeTask()
|
||||||
}
|
}
|
||||||
@ -102,6 +106,7 @@ private extension CloudKitSendStatusOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func processStatuses(_ syncStatuses: [SyncStatus], completion: @escaping (Bool) -> Void) {
|
func processStatuses(_ syncStatuses: [SyncStatus], completion: @escaping (Bool) -> Void) {
|
||||||
guard let account = account, let articlesZone = articlesZone else {
|
guard let account = account, let articlesZone = articlesZone else {
|
||||||
|
@ -65,7 +65,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
private let database: SyncDatabase
|
private let database: SyncDatabase
|
||||||
|
|
||||||
private weak var currentSyncAllOperation: MainThreadOperation?
|
private weak var currentSyncAllOperation: MainThreadOperation?
|
||||||
private let operationQueue = MainThreadOperationQueue()
|
@MainActor private let operationQueue = MainThreadOperationQueue()
|
||||||
|
|
||||||
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API, secretsProvider: SecretsProvider) {
|
init(dataFolder: String, transport: Transport?, api: FeedlyAPICaller.API, secretsProvider: SecretsProvider) {
|
||||||
// Many operations have their own operation queues, such as the sync all operation.
|
// Many operations have their own operation queues, such as the sync all operation.
|
||||||
@ -550,9 +550,11 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
|
|
||||||
/// Suspend all network activity
|
/// Suspend all network activity
|
||||||
func suspendNetwork() {
|
func suspendNetwork() {
|
||||||
|
MainActor.assumeIsolated {
|
||||||
caller.suspend()
|
caller.suspend()
|
||||||
operationQueue.cancelAllOperations()
|
operationQueue.cancelAllOperations()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Suspend the SQLLite databases
|
/// Suspend the SQLLite databases
|
||||||
func suspendDatabase() {
|
func suspendDatabase() {
|
||||||
@ -572,7 +574,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
|||||||
|
|
||||||
extension FeedlyAccountDelegate: FeedlyAPICallerDelegate {
|
extension FeedlyAccountDelegate: FeedlyAPICallerDelegate {
|
||||||
|
|
||||||
func reauthorizeFeedlyAPICaller(_ caller: FeedlyAPICaller, completionHandler: @escaping (Bool) -> ()) {
|
@MainActor func reauthorizeFeedlyAPICaller(_ caller: FeedlyAPICaller, completionHandler: @escaping (Bool) -> ()) {
|
||||||
guard let account = initializedAccount else {
|
guard let account = initializedAccount else {
|
||||||
completionHandler(false)
|
completionHandler(false)
|
||||||
return
|
return
|
||||||
@ -600,8 +602,6 @@ extension FeedlyAccountDelegate: FeedlyAPICallerDelegate {
|
|||||||
completionHandler(refreshAccessTokenDelegate.didReauthorize && !operation.isCanceled)
|
completionHandler(refreshAccessTokenDelegate.didReauthorize && !operation.isCanceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
MainThreadOperationQueue.shared.add(refreshAccessToken)
|
MainThreadOperationQueue.shared.add(refreshAccessToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -51,7 +51,7 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
|||||||
self.oauthClient = Account.oauthAuthorizationClient(for: accountType, secretsProvider: secretsProvider)
|
self.oauthClient = Account.oauthAuthorizationClient(for: accountType, secretsProvider: secretsProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func run() {
|
@MainActor public func run() {
|
||||||
assert(presentationAnchor != nil, "\(self) outlived presentation anchor.")
|
assert(presentationAnchor != nil, "\(self) outlived presentation anchor.")
|
||||||
|
|
||||||
let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType, secretsProvider: secretsProvider)
|
let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType, secretsProvider: secretsProvider)
|
||||||
@ -101,6 +101,7 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func didEndAuthentication(url: URL?, error: Error?) {
|
private func didEndAuthentication(url: URL?, error: Error?) {
|
||||||
|
MainActor.assumeIsolated {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
didFinish()
|
didFinish()
|
||||||
return
|
return
|
||||||
@ -125,6 +126,7 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
|||||||
didFinish(error)
|
didFinish(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
||||||
guard let anchor = presentationAnchor else {
|
guard let anchor = presentationAnchor else {
|
||||||
@ -174,12 +176,12 @@ public enum OAuthAccountAuthorizationOperationError: LocalizedError {
|
|||||||
|
|
||||||
// MARK: Managing Operation State
|
// MARK: Managing Operation State
|
||||||
|
|
||||||
private func didFinish() {
|
@MainActor private func didFinish() {
|
||||||
assert(Thread.isMainThread)
|
assert(Thread.isMainThread)
|
||||||
operationDelegate?.operationDidComplete(self)
|
operationDelegate?.operationDidComplete(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func didFinish(_ error: Error) {
|
@MainActor private func didFinish(_ error: Error) {
|
||||||
assert(Thread.isMainThread)
|
assert(Thread.isMainThread)
|
||||||
delegate?.oauthAccountAuthorizationOperation(self, didFailWith: error)
|
delegate?.oauthAccountAuthorizationOperation(self, didFailWith: error)
|
||||||
didFinish()
|
didFinish()
|
||||||
|
@ -12,7 +12,7 @@ import RSWeb
|
|||||||
import Secrets
|
import Secrets
|
||||||
import Core
|
import Core
|
||||||
|
|
||||||
class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate {
|
@MainActor final class FeedlyAddExistingFeedOperation: FeedlyOperation, FeedlyOperationDelegate, FeedlyCheckpointOperationDelegate {
|
||||||
|
|
||||||
private let operationQueue = MainThreadOperationQueue()
|
private let operationQueue = MainThreadOperationQueue()
|
||||||
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
var addCompletionHandler: ((Result<Void, Error>) -> ())?
|
||||||
|
@ -23,6 +23,7 @@ final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEn
|
|||||||
|
|
||||||
override func run() {
|
override func run() {
|
||||||
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
|
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let articleIds):
|
case .success(let articleIds):
|
||||||
self.entryIds.formUnion(articleIds)
|
self.entryIds.formUnion(articleIds)
|
||||||
@ -34,3 +35,4 @@ final class FeedlyFetchIdsForMissingArticlesOperation: FeedlyOperation, FeedlyEn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -78,6 +78,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
database.selectPendingStarredStatusArticleIDs { result in
|
database.selectPendingStarredStatusArticleIDs { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let pendingArticleIds):
|
case .success(let pendingArticleIds):
|
||||||
self.remoteEntryIds.subtract(pendingArticleIds)
|
self.remoteEntryIds.subtract(pendingArticleIds)
|
||||||
@ -89,6 +90,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateStarredStatuses() {
|
private func updateStarredStatuses() {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
@ -97,6 +99,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
account.fetchStarredArticleIDs { result in
|
account.fetchStarredArticleIDs { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let localStarredArticleIDs):
|
case .success(let localStarredArticleIDs):
|
||||||
self.processStarredArticleIDs(localStarredArticleIDs)
|
self.processStarredArticleIDs(localStarredArticleIDs)
|
||||||
@ -106,6 +109,7 @@ final class FeedlyIngestStarredArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) {
|
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
|
@ -53,6 +53,7 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
|
|||||||
case .success(let streamIds):
|
case .success(let streamIds):
|
||||||
account.createStatusesIfNeeded(articleIDs: Set(streamIds.ids)) { databaseError in
|
account.createStatusesIfNeeded(articleIDs: Set(streamIds.ids)) { databaseError in
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
if let error = databaseError {
|
if let error = databaseError {
|
||||||
self.didFinish(with: error)
|
self.didFinish(with: error)
|
||||||
return
|
return
|
||||||
@ -66,6 +67,8 @@ class FeedlyIngestStreamArticleIdsOperation: FeedlyOperation {
|
|||||||
|
|
||||||
self.getStreamIds(continuation)
|
self.getStreamIds(continuation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
didFinish(with: error)
|
didFinish(with: error)
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
database.selectPendingReadStatusArticleIDs { result in
|
database.selectPendingReadStatusArticleIDs { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let pendingArticleIds):
|
case .success(let pendingArticleIds):
|
||||||
self.remoteEntryIds.subtract(pendingArticleIds)
|
self.remoteEntryIds.subtract(pendingArticleIds)
|
||||||
@ -90,6 +91,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateUnreadStatuses() {
|
private func updateUnreadStatuses() {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
@ -98,6 +100,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
account.fetchUnreadArticleIDs { result in
|
account.fetchUnreadArticleIDs { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let localUnreadArticleIDs):
|
case .success(let localUnreadArticleIDs):
|
||||||
self.processUnreadArticleIDs(localUnreadArticleIDs)
|
self.processUnreadArticleIDs(localUnreadArticleIDs)
|
||||||
@ -107,6 +110,7 @@ final class FeedlyIngestUnreadArticleIdsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) {
|
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
|
@ -18,7 +18,7 @@ protocol FeedlyOperationDelegate: AnyObject {
|
|||||||
///
|
///
|
||||||
/// Normally we don’t do inheritance — but in this case
|
/// Normally we don’t do inheritance — but in this case
|
||||||
/// it’s the best option.
|
/// it’s the best option.
|
||||||
class FeedlyOperation: MainThreadOperation {
|
@MainActor class FeedlyOperation: MainThreadOperation {
|
||||||
|
|
||||||
weak var delegate: FeedlyOperationDelegate?
|
weak var delegate: FeedlyOperationDelegate?
|
||||||
var downloadProgress: DownloadProgress? {
|
var downloadProgress: DownloadProgress? {
|
||||||
|
@ -29,6 +29,7 @@ final class FeedlySendArticleStatusesOperation: FeedlyOperation {
|
|||||||
os_log(.debug, log: log, "Sending article statuses...")
|
os_log(.debug, log: log, "Sending article statuses...")
|
||||||
|
|
||||||
database.selectForProcessing { result in
|
database.selectForProcessing { result in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
if self.isCanceled {
|
if self.isCanceled {
|
||||||
self.didFinish()
|
self.didFinish()
|
||||||
return
|
return
|
||||||
@ -43,6 +44,7 @@ final class FeedlySendArticleStatusesOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private extension FeedlySendArticleStatusesOperation {
|
private extension FeedlySendArticleStatusesOperation {
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
|||||||
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||||
|
|
||||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { databaseError in
|
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { databaseError in
|
||||||
|
MainActor.assumeIsolated {
|
||||||
if let error = databaseError {
|
if let error = databaseError {
|
||||||
self.didFinish(with: error)
|
self.didFinish(with: error)
|
||||||
return
|
return
|
||||||
@ -38,3 +39,4 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -75,15 +75,15 @@ public protocol MainThreadOperation: AnyObject {
|
|||||||
|
|
||||||
public extension MainThreadOperation {
|
public extension MainThreadOperation {
|
||||||
|
|
||||||
func cancel() {
|
@MainActor func cancel() {
|
||||||
operationDelegate?.cancelOperation(self)
|
operationDelegate?.cancelOperation(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDependency(_ parentOperation: MainThreadOperation) {
|
@MainActor func addDependency(_ parentOperation: MainThreadOperation) {
|
||||||
operationDelegate?.make(self, dependOn: parentOperation)
|
operationDelegate?.make(self, dependOn: parentOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func informOperationDelegateOfCompletion() {
|
@MainActor func informOperationDelegateOfCompletion() {
|
||||||
guard !isCanceled else {
|
guard !isCanceled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,10 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public protocol MainThreadOperationDelegate: AnyObject {
|
public protocol MainThreadOperationDelegate: AnyObject {
|
||||||
func operationDidComplete(_ operation: MainThreadOperation)
|
|
||||||
func cancelOperation(_ operation: MainThreadOperation)
|
@MainActor func operationDidComplete(_ operation: MainThreadOperation)
|
||||||
func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation)
|
@MainActor func cancelOperation(_ operation: MainThreadOperation)
|
||||||
|
@MainActor func make(_ childOperation: MainThreadOperation, dependOn parentOperation: MainThreadOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manage a queue of MainThreadOperation tasks.
|
/// Manage a queue of MainThreadOperation tasks.
|
||||||
@ -23,7 +24,7 @@ public protocol MainThreadOperationDelegate: AnyObject {
|
|||||||
/// Use this only on the main thread.
|
/// Use this only on the main thread.
|
||||||
/// The operation can be suspended and resumed.
|
/// The operation can be suspended and resumed.
|
||||||
/// It is *not* suspended on creation.
|
/// It is *not* suspended on creation.
|
||||||
public final class MainThreadOperationQueue {
|
@MainActor public final class MainThreadOperationQueue {
|
||||||
|
|
||||||
/// Use the shared queue when you don’t need to create a separate queue.
|
/// Use the shared queue when you don’t need to create a separate queue.
|
||||||
@MainActor public static let shared: MainThreadOperationQueue = {
|
@MainActor public static let shared: MainThreadOperationQueue = {
|
||||||
@ -46,10 +47,6 @@ public final class MainThreadOperationQueue {
|
|||||||
// Silence compiler complaint about init not being public.
|
// Silence compiler complaint about init not being public.
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
|
||||||
cancelAllOperations()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an operation to the queue.
|
/// Add an operation to the queue.
|
||||||
@MainActor public func add(_ operation: MainThreadOperation) {
|
@MainActor public func add(_ operation: MainThreadOperation) {
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
@ -132,7 +129,7 @@ public final class MainThreadOperationQueue {
|
|||||||
|
|
||||||
extension MainThreadOperationQueue: MainThreadOperationDelegate {
|
extension MainThreadOperationQueue: MainThreadOperationDelegate {
|
||||||
|
|
||||||
public func operationDidComplete(_ operation: MainThreadOperation) {
|
@MainActor public func operationDidComplete(_ operation: MainThreadOperation) {
|
||||||
precondition(Thread.isMainThread)
|
precondition(Thread.isMainThread)
|
||||||
operationDidFinish(operation)
|
operationDidFinish(operation)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user