Skip to content

Commit 7842184

Browse files
authored
Refactor FXIOS-12796 [Swift 6 Migration] Annotate misc. completion handlers with @sendable (#30461)
* Annotate misc. warning completion handlers with @sendable and main actor / threading warnings that come from adding @sendable to closures. * Fix unit tests.
1 parent 400b789 commit 7842184

File tree

12 files changed

+57
-46
lines changed

12 files changed

+57
-46
lines changed

firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,8 @@ extension BrowserViewController: WKNavigationDelegate {
744744
let request = request {
745745
// Certain files are too large to download before the preview presents, so block until we have something to show
746746
let group = DispatchGroup()
747-
var url: URL?
747+
// FIXME: FXIOS-14054 Should not mutate local properties in concurrent code
748+
nonisolated(unsafe) var url: URL?
748749
group.enter()
749750
let temporaryDocument = DefaultTemporaryDocument(preflightResponse: response, request: request)
750751
temporaryDocument.download { docURL in

firefox-ios/Client/Frontend/Browser/SearchLoader.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ final class SearchLoader: Loader<Cursor<Site>, SearchViewModel>, FeatureFlaggabl
3333
fileprivate func getBookmarksAsSites(
3434
matchingSearchQuery query: String,
3535
limit: UInt,
36-
completionHandler: @escaping (([Site]) -> Void)
36+
completionHandler: @escaping @Sendable (([Site]) -> Void)
3737
) {
3838
profile.places.searchBookmarks(query: query, limit: limit).upon { result in
3939
guard let bookmarkItems = result.successValue else {
@@ -49,7 +49,7 @@ final class SearchLoader: Loader<Cursor<Site>, SearchViewModel>, FeatureFlaggabl
4949
private func getHistoryAsSites(
5050
matchingSearchQuery query: String,
5151
limit: Int,
52-
completionHandler: @escaping (([Site]) -> Void)
52+
completionHandler: @escaping @Sendable (([Site]) -> Void)
5353
) {
5454
profile.places.interruptReader()
5555
profile.places.queryAutocomplete(matchingSearchQuery: query, limit: limit).upon { result in

firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksViewController.swift

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ final class BookmarksViewController: SiteTableViewController,
181181
// MARK: - Data
182182

183183
override func reloadData() {
184-
viewModel.reloadData { [weak self] in
185-
ensureMainThread {
184+
viewModel.reloadData {
185+
ensureMainThread { [weak self] in
186186
self?.tableView.reloadData()
187187
if self?.viewModel.shouldFlashRow ?? false {
188188
self?.flashRow()
@@ -668,16 +668,19 @@ final class BookmarksViewController: SiteTableViewController,
668668

669669
extension BookmarksViewController: LibraryPanelContextMenu {
670670
func presentContextMenu(for indexPath: IndexPath) {
671-
viewModel.getSiteDetails(for: indexPath) { [weak self] site in
672-
guard let self else { return }
673-
if let site {
674-
presentContextMenu(for: site, with: indexPath, completionHandler: {
675-
return self.contextMenu(for: site, with: indexPath)
676-
})
677-
} else if let bookmarkNode = viewModel.bookmarkNodes[safe: indexPath.row],
678-
bookmarkNode.type == .folder,
679-
isCurrentFolderEditable(at: indexPath) {
680-
presentContextMenu(for: bookmarkNode, indexPath: indexPath)
671+
viewModel.getSiteDetails(for: indexPath) { site in
672+
ensureMainThread { [weak self] in
673+
guard let self else { return }
674+
675+
if let site {
676+
self.presentContextMenu(for: site, with: indexPath, completionHandler: {
677+
return self.contextMenu(for: site, with: indexPath)
678+
})
679+
} else if let bookmarkNode = self.viewModel.bookmarkNodes[safe: indexPath.row],
680+
bookmarkNode.type == .folder,
681+
self.isCurrentFolderEditable(at: indexPath) {
682+
self.presentContextMenu(for: bookmarkNode, indexPath: indexPath)
683+
}
681684
}
682685
}
683686
}

firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ final class BookmarksPanelViewModel {
5858
return true
5959
}
6060

61-
func reloadData(completion: @escaping () -> Void) {
61+
func reloadData(completion: @escaping @Sendable () -> Void) {
6262
// Can be called while app backgrounded and the db closed, don't try to reload the data source in this case
6363
if profile.isShutdown {
6464
completion()
@@ -97,7 +97,7 @@ final class BookmarksPanelViewModel {
9797
bookmarkNodes.insert(bookmarkNode, at: destinationIndexPath.row)
9898
}
9999

100-
func getSiteDetails(for indexPath: IndexPath, completion: @escaping (Site?) -> Void) {
100+
func getSiteDetails(for indexPath: IndexPath, completion: @escaping @Sendable (Site?) -> Void) {
101101
guard let bookmarkNode = bookmarkNodes[safe: indexPath.row],
102102
let bookmarkItem = bookmarkNode as? BookmarkItemData
103103
else {
@@ -117,7 +117,7 @@ final class BookmarksPanelViewModel {
117117
func createPinUnpinAction(
118118
for site: Site,
119119
isPinned: Bool,
120-
successHandler: @escaping (String) -> Void
120+
successHandler: @MainActor @escaping @Sendable (String) -> Void
121121
) -> PhotonRowActions {
122122
return SingleActionViewModel(
123123
title: isPinned ? .Bookmarks.Menu.RemoveFromShortcutsTitle : .AddToShortcutsActionTitle,
@@ -129,14 +129,16 @@ final class BookmarksPanelViewModel {
129129
: profile.pinnedSites.addPinnedTopSite(site)
130130

131131
action.uponQueue(.main) { result in
132-
if result.isSuccess {
133-
let message: String = isPinned
134-
? .LegacyAppMenu.RemovePinFromShortcutsConfirmMessage
135-
: .LegacyAppMenu.AddPinToShortcutsConfirmMessage
136-
successHandler(message)
137-
} else {
138-
let logMessage = isPinned ? "Could not remove pinned site" : "Could not add pinne site"
139-
logger.log(logMessage, level: .debug, category: .library)
132+
MainActor.assumeIsolated {
133+
if result.isSuccess {
134+
let message: String = isPinned
135+
? .LegacyAppMenu.RemovePinFromShortcutsConfirmMessage
136+
: .LegacyAppMenu.AddPinToShortcutsConfirmMessage
137+
successHandler(message)
138+
} else {
139+
let logMessage = isPinned ? "Could not remove pinned site" : "Could not add pinne site"
140+
logger.log(logMessage, level: .debug, category: .library)
141+
}
140142
}
141143
}
142144
}
@@ -157,7 +159,7 @@ final class BookmarksPanelViewModel {
157159
return max(index - LocalDesktopFolder.numberOfRowsTaken, 0)
158160
}
159161

160-
private func setupMobileFolderData(completion: @escaping () -> Void) {
162+
private func setupMobileFolderData(completion: @escaping @Sendable () -> Void) {
161163
bookmarksHandler
162164
.getBookmarksTree(rootGUID: BookmarkRoots.MobileFolderGUID, recursive: false)
163165
.uponQueue(.main) { result in
@@ -190,7 +192,7 @@ final class BookmarksPanelViewModel {
190192
}
191193

192194
/// Subfolder data case happens when we select a folder created by a user
193-
private func setupSubfolderData(completion: @escaping () -> Void) {
195+
private func setupSubfolderData(completion: @escaping @Sendable () -> Void) {
194196
bookmarksHandler.getBookmarksTree(rootGUID: bookmarkFolderGUID,
195197
recursive: false).uponQueue(.main) { result in
196198
guard let folder = result.successValue as? BookmarkFolderData else {
@@ -217,7 +219,7 @@ final class BookmarksPanelViewModel {
217219

218220
// Create a local "Desktop bookmarks" folder only if there exists a bookmark in one of it's nested
219221
// subfolders
220-
private func createDesktopBookmarksFolder(completion: @escaping () -> Void) {
222+
private func createDesktopBookmarksFolder(completion: @escaping @Sendable () -> Void) {
221223
bookmarksHandler.countBookmarksInTrees(folderGuids: BookmarkRoots.DesktopRoots.map { $0 }) { result in
222224
switch result {
223225
case .success(let bookmarkCount):
@@ -237,7 +239,11 @@ final class BookmarksPanelViewModel {
237239
}
238240
}
239241

240-
private func checkIfPinnedURL(_ url: String, queue: DispatchQueue = .main, completion: @escaping (Bool) -> Void ) {
242+
private func checkIfPinnedURL(
243+
_ url: String,
244+
queue: DispatchQueue = .main,
245+
completion: @escaping @Sendable (Bool) -> Void
246+
) {
241247
profile.pinnedSites.isPinnedTopSite(url)
242248
.uponQueue(queue) { result in
243249
completion(result.successValue ?? false)

firefox-ios/Client/Frontend/Onboarding/Models/UpdateViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class UpdateViewModel: OnboardingViewModelProtocol,
7373

7474
// Function added to wait for AccountManager initialization to get
7575
// if the user is Sign in with Sync Account to decide which cards to show
76-
func hasSyncableAccount(completion: @escaping () -> Void) {
76+
func hasSyncableAccount(completion: @MainActor @escaping @Sendable () -> Void) {
7777
hasSyncableAccount = profile.hasAccount()
7878
ensureMainThread {
7979
completion()

firefox-ios/Client/Frontend/PasswordManagement/BreachAlertsClient.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ protocol BreachAlertsClientProtocol {
1414
func fetchEtag(
1515
endpoint: BreachAlertsClient.Endpoint,
1616
profile: Profile,
17-
completion: @escaping (_ etag: String?) -> Void
17+
completion: @escaping @Sendable (_ etag: String?) -> Void
1818
)
1919
func fetchData(
2020
endpoint: BreachAlertsClient.Endpoint,
2121
profile: Profile,
22-
completion: @escaping (_ result: Maybe<Data>) -> Void
22+
completion: @escaping @Sendable (_ result: Maybe<Data>) -> Void
2323
)
2424
}
2525

@@ -33,7 +33,7 @@ class BreachAlertsClient: BreachAlertsClientProtocol {
3333
static let etagDateKey = "BreachAlertsDataDate"
3434

3535
/// Makes a header-only request to an endpoint and hands off the endpoint's etag to a completion handler.
36-
func fetchEtag(endpoint: Endpoint, profile: Profile, completion: @escaping (_ etag: String?) -> Void) {
36+
func fetchEtag(endpoint: Endpoint, profile: Profile, completion: @escaping @Sendable (_ etag: String?) -> Void) {
3737
guard let url = URL(string: endpoint.rawValue) else { return }
3838
var request = URLRequest(url: url)
3939
request.httpMethod = "HEAD"
@@ -58,7 +58,7 @@ class BreachAlertsClient: BreachAlertsClientProtocol {
5858
}
5959

6060
/// Makes a network request to an endpoint and hands off the result to a completion handler.
61-
func fetchData(endpoint: Endpoint, profile: Profile, completion: @escaping (_ result: Maybe<Data>) -> Void) {
61+
func fetchData(endpoint: Endpoint, profile: Profile, completion: @escaping @Sendable (_ result: Maybe<Data>) -> Void) {
6262
guard let url = URL(string: endpoint.rawValue) else { return }
6363

6464
dataTask?.cancel()

firefox-ios/Client/Frontend/PasswordManagement/PasswordManagerViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ final class PasswordManagerViewModel {
7878

7979
/// Searches SQLite database for logins that match query.
8080
/// Wraps the SQLiteLogins method to allow us to cancel it from our end.
81-
func queryLogins(_ query: String, completion: @escaping ([Login]) -> Void) {
81+
func queryLogins(_ query: String, completion: @escaping @Sendable ([Login]) -> Void) {
8282
loginProvider.searchLoginsWithQuery(query) { result in
8383
ensureMainThread {
8484
switch result {
@@ -155,7 +155,7 @@ final class PasswordManagerViewModel {
155155
}
156156
}
157157

158-
public func save(loginRecord: LoginEntry, completion: @escaping ((String?) -> Void)) {
158+
public func save(loginRecord: LoginEntry, completion: @escaping @Sendable ((String?) -> Void)) {
159159
loginProvider.addLogin(login: loginRecord, completionHandler: { result in
160160
switch result {
161161
case .success(let encryptedLogin):

firefox-ios/Client/Frontend/Settings/PasswordDetailViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ extension PasswordDetailViewController {
375375
}
376376
}
377377

378-
func onProfileDidFinishSyncing(completion: @escaping () -> Void) {
378+
func onProfileDidFinishSyncing(completion: @escaping @Sendable () -> Void) {
379379
// Reload details after syncing.
380380
viewModel.profile.logins.getLogin(id: viewModel.login.id) { [weak self] result in
381381
DispatchQueue.main.async {

firefox-ios/Client/TabManagement/Document/TemporaryDocument.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protocol TemporaryDocument: Sendable {
1717

1818
func download() async -> URL?
1919

20-
func download(_ completion: @escaping (URL?) -> Void)
20+
func download(_ completion: @escaping @Sendable (URL?) -> Void)
2121

2222
func cancelDownload()
2323

@@ -152,7 +152,7 @@ final class DefaultTemporaryDocument: NSObject,
152152
return tempFileURL
153153
}
154154

155-
func download(_ completion: @escaping (URL?) -> Void) {
155+
func download(_ completion: @escaping @Sendable (URL?) -> Void) {
156156
if let tempFile = queryTempFile() {
157157
ensureMainThread {
158158
completion(tempFile)

firefox-ios/Client/TabManagement/Tab.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,8 +956,8 @@ class Tab: NSObject, ThemeApplicable, FeatureFlaggable, ShareTab {
956956
func enqueueDocument(_ document: TemporaryDocument) {
957957
temporaryDocument = document
958958

959-
temporaryDocument?.download { [weak self] url in
960-
ensureMainThread {
959+
temporaryDocument?.download { url in
960+
ensureMainThread { [weak self] in
961961
guard let url else { return }
962962

963963
// Prevent the WebView to load a new item so it doesn't add a new entry to the back and forward list.

0 commit comments

Comments
 (0)