Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix onPreferenceChange modifier on Xcode 16.2 #115

Merged
merged 3 commits into from
Jan 1, 2025

Conversation

Calculable
Copy link
Contributor

@Calculable Calculable commented Dec 13, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced popup functionality with improved thread handling for UI updates.
    • Added support for safe concurrency with the Sendable protocol in popup styles.
  • Bug Fixes

    • Ensured proper handling of optional parameters in popup preferences.
  • Documentation

    • Updated struct declarations to reflect new concurrency attributes.

Copy link

coderabbitai bot commented Dec 13, 2024

Walkthrough

The changes introduce concurrency improvements to the UBPopupPreference, UBPopupStyle, and UBPopupWrapper structures. The UBPopupPreference struct is now marked with the @MainActor attribute, ensuring its methods run on the main thread, while its equality operator is modified to allow calls from outside the actor context. The UBPopupStyle struct is updated to conform to the Sendable protocol, enabling safe concurrency. Lastly, the UBPopupWrapper struct's body property now executes UI updates asynchronously on the main thread.

Changes

File Path Change Summary
Sources/UBUserInterface/SwiftUI/Popup/UBPopupPreferenceKey.swift Added @MainActor to UBPopupPreference, modified == operator to nonisolated, and updated init method for optional binding on date.
Sources/UBUserInterface/SwiftUI/Popup/UBPopupStyle.swift Updated UBPopupStyle to conform to Sendable protocol.
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift Wrapped onPreferenceChange logic to ensure UI updates occur on the main thread using DispatchQueue.main.async.

Possibly related PRs

  • Swift 6 Fixes #104: The addition of the @MainActor attribute to the QRScannerViewDelegate protocol is related to the main PR's introduction of the @MainActor attribute to the UBPopupPreference struct, both ensuring that their respective methods are executed on the main thread.

🐰 "In the code where popups play,
The main thread leads the way.
With Sendable and async cheer,
Our UI is bright, never drear!
A hop, a jump, all smooth and neat,
In the world of code, we dance to the beat!" 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupPreferenceKey.swift (1)

Line range hint 29-35: Consider marking the content closure as @mainactor

The content closure might capture references that should only be accessed on the main thread. Consider explicitly marking it as @MainActor:

-    @ViewBuilder let content: () -> AnyView
+    @ViewBuilder let content: @MainActor () -> AnyView
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7edaf15 and f96a75c.

📒 Files selected for processing (3)
  • Sources/UBUserInterface/SwiftUI/Popup/UBPopupPreferenceKey.swift (2 hunks)
  • Sources/UBUserInterface/SwiftUI/Popup/UBPopupStyle.swift (1 hunks)
  • Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (1 hunks)
🔇 Additional comments (4)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupStyle.swift (1)

12-12: LGTM! Appropriate use of Sendable protocol

The addition of Sendable conformance is correct as all properties are implicitly Sendable. This change ensures thread-safe passing of style information across concurrency domains.

Sources/UBUserInterface/SwiftUI/Popup/UBPopupPreferenceKey.swift (2)

23-23: LGTM! Appropriate use of @mainactor

The @MainActor attribute ensures UI-related operations are performed on the main thread, which is crucial for SwiftUI view updates.


Line range hint 40-45: LGTM! Correct implementation of nonisolated operator

The nonisolated keyword is necessary here to allow equality checks from any context, which is required for PreferenceKey.reduce to work properly.

Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (1)

28-34: Verify UBPopupWindowManager thread safety

Ensure that UBPopupWindowManager is properly marked with @MainActor since it handles UI operations.

✅ Verification successful

Thread safety is properly implemented

The UBPopupWindowManager class is already correctly marked with @MainActor attribute, ensuring all UI operations are performed on the main thread. The implementation is thread-safe as required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for UBPopupWindowManager definition to verify @MainActor usage
ast-grep --pattern 'class UBPopupWindowManager {
  $$$
}'

Length of output: 59


Script:

#!/bin/bash
# Let's try a broader search pattern to find UBPopupWindowManager class definition
rg -l "class.*UBPopupWindowManager"

# Also search for the implementation details
rg "UBPopupWindowManager" -A 5

Length of output: 2754


Script:

#!/bin/bash
# Let's get the full content of UBPopupWindowManager.swift to verify thread safety implementation
cat Sources/UBUserInterface/SwiftUI/Popup/UBPopupWindowManager.swift

Length of output: 1792

Comment on lines 28 to 34
DispatchQueue.main.async {
if let popupPreference, popupPreference.isPresented.wrappedValue {
UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
} else {
UBPopupWindowManager.shared.hideWindow()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove redundant DispatchQueue.main.async

The onPreferenceChange modifier already executes on the main thread, and UBPopupPreference is marked with @MainActor. Therefore, wrapping the code in DispatchQueue.main.async is redundant and could cause unnecessary animation delays.

Remove the redundant dispatch:

 .onPreferenceChange(UBPopupPreferenceKey.self) { popupPreference in
-    DispatchQueue.main.async {
-        if let popupPreference, popupPreference.isPresented.wrappedValue {
-            UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
-        } else {
-            UBPopupWindowManager.shared.hideWindow()
-        }
-    }
+    if let popupPreference, popupPreference.isPresented.wrappedValue {
+        UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
+    } else {
+        UBPopupWindowManager.shared.hideWindow()
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DispatchQueue.main.async {
if let popupPreference, popupPreference.isPresented.wrappedValue {
UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
} else {
UBPopupWindowManager.shared.hideWindow()
}
}
if let popupPreference, popupPreference.isPresented.wrappedValue {
UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
} else {
UBPopupWindowManager.shared.hideWindow()
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (1)

29-39: 🛠️ Refactor suggestion

Remove unnecessary thread checking and dispatching

This issue is similar to a previous comment regarding redundant use of DispatchQueue.main.async. The onPreferenceChange modifier is already executed on the main thread, so checking Thread.isMainThread and using DispatchQueue.main.sync is redundant and can introduce unnecessary complexity or potential deadlocks.

Simplify the code by directly calling popupPreferenceChanged within MainActor.assumeIsolated:

 .onPreferenceChange(UBPopupPreferenceKey.self) { popupPreference in
-    if Thread.isMainThread {
-        MainActor.assumeIsolated {
-            popupPreferenceChanged(popupPreference: popupPreference)
-        }
-    } else {
-        Log.reportError("onPreferenceChange called on non-main thread")
-        DispatchQueue.main.sync {
-            MainActor.assumeIsolated {
-                popupPreferenceChanged(popupPreference: popupPreference)
-            }
-        }
-    }
+    MainActor.assumeIsolated {
+        popupPreferenceChanged(popupPreference: popupPreference)
+    }
 }
🧹 Nitpick comments (2)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (2)

45-50: Mark popupPreferenceChanged with @MainActor for clarity

Since popupPreferenceChanged performs UI updates, it's best practice to mark it with @MainActor. This ensures the method is always executed on the main thread and eliminates the need to manually assume isolation.

Apply this diff to annotate the method:

+@MainActor
 private func popupPreferenceChanged(popupPreference: UBPopupPreference?) {
     if let popupPreference, popupPreference.isPresented.wrappedValue {
         UBPopupWindowManager.shared.showPopup(
             isPresented: popupPreference.isPresented,
             style: popupPreference.customStyle ?? style,
             content: popupPreference.content
         )
     } else {
         UBPopupWindowManager.shared.hideWindow()
     }
 }
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 46-46: Line should be 120 characters or less; currently it has 170 characters

(line_length)


46-46: Line exceeds the maximum recommended length

Line 46 is 170 characters long, exceeding the recommended maximum of 120 characters. This can affect code readability.

Consider formatting the line to improve readability:

     if let popupPreference, popupPreference.isPresented.wrappedValue {
-        UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
+        UBPopupWindowManager.shared.showPopup(
+            isPresented: popupPreference.isPresented,
+            style: popupPreference.customStyle ?? style,
+            content: popupPreference.content
+        )
     } else {
         UBPopupWindowManager.shared.hideWindow()
     }
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 46-46: Line should be 120 characters or less; currently it has 170 characters

(line_length)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f96a75c and 33d1cd7.

📒 Files selected for processing (1)
  • Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (2 hunks)
🧰 Additional context used
🪛 SwiftLint (0.57.0)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift

[Warning] 46-46: Line should be 120 characters or less; currently it has 170 characters

(line_length)

} else {
UBPopupWindowManager.shared.hideWindow()
Log.reportError("onPreferenceChange called on non-main thread")
DispatchQueue.main.sync {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ev. wäre async hier besser, sonst haben wir möglicherweise einen Deadlock

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--> Async it should be oder so

Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (2)

29-41: LGTM: Robust thread safety implementation

The thread safety implementation is well-structured and addresses potential concurrency issues. The use of MainActor.assumeIsolated is appropriate for UI updates.

Consider making the error message more descriptive:

-    Log.reportError("onPreferenceChange called on non-main thread")
+    Log.reportError("UBPopupWrapper.onPreferenceChange was called on a background thread, which may cause UI inconsistencies")

46-46: Consider improving readability by splitting the long line

The line exceeds the recommended length of 120 characters.

Consider reformatting for better readability:

-            UBPopupWindowManager.shared.showPopup(isPresented: popupPreference.isPresented, style: popupPreference.customStyle ?? style, content: popupPreference.content)
+            UBPopupWindowManager.shared.showPopup(
+                isPresented: popupPreference.isPresented,
+                style: popupPreference.customStyle ?? style,
+                content: popupPreference.content
+            )
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 46-46: Line should be 120 characters or less; currently it has 170 characters

(line_length)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33d1cd7 and de420bc.

📒 Files selected for processing (1)
  • Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (2 hunks)
🧰 Additional context used
🪛 SwiftLint (0.57.0)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift

[Warning] 46-46: Line should be 120 characters or less; currently it has 170 characters

(line_length)

🔇 Additional comments (2)
Sources/UBUserInterface/SwiftUI/Popup/UBPopupWrapper.swift (2)

11-11: LGTM: Required import for logging functionality

The addition of UBFoundation import is necessary for the error logging capability.


44-50: LGTM: Well-structured method extraction

The extraction of popup preference handling logic into a separate private method improves code organization and reusability while maintaining the original functionality.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 46-46: Line should be 120 characters or less; currently it has 170 characters

(line_length)

@maerki maerki merged commit 9767a49 into main Jan 1, 2025
3 of 5 checks passed
@maerki maerki deleted the fix-on-preference-change-for-xcode-16-2 branch January 1, 2025 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants