Skip to content

Commit

Permalink
Fix up concurrency warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Jul 9, 2024
1 parent aa956a8 commit f3e746c
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
name: Test
runs-on: macOS-14
env:
DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
steps:
- uses: actions/checkout@v4
with:
Expand Down
34 changes: 23 additions & 11 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
// swift-tools-version:5.0
// swift-tools-version: 5.8

import PackageDescription

let package = Package(
name: "WindowTreatment",
platforms: [.macOS("10.11")],
products: [
.library(name: "WindowTreatment", targets: ["WindowTreatment"]),
],
dependencies: [],
targets: [
.target(name: "WindowTreatment", dependencies: []),
.testTarget(name: "WindowTreatmentTests", dependencies: ["WindowTreatment"]),
]
name: "WindowTreatment",
platforms: [
.macOS(.v10_13),
],
products: [
.library(name: "WindowTreatment", targets: ["WindowTreatment"]),
],
dependencies: [],
targets: [
.target(name: "WindowTreatment", dependencies: []),
.testTarget(name: "WindowTreatmentTests", dependencies: ["WindowTreatment"]),
]
)

let swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("StrictConcurrency"),
]

for target in package.targets {
var settings = target.swiftSettings ?? []
settings.append(contentsOf: swiftSettings)
target.swiftSettings = settings
}
3 changes: 2 additions & 1 deletion Sources/WindowTreatment/ApplicationWindowState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Cocoa

@MainActor
public class ApplicationWindowState {
private var lastWindowListHash: Int
private var lastMainWindowHash: Int
Expand Down Expand Up @@ -39,7 +40,7 @@ public class ApplicationWindowState {
@objc private func windowWillCloseNotification(_ notification: NSNotification) {
// The reason for the async thing here is because this notification tells
// you which window *will* close, but it is currently still in the current list.
OperationQueue.main.addOperation {
DispatchQueue.main.async {
self.deliverChangeNotificationIfNeeded()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import SwiftUI

@available(macOS 11.0, *)
@MainActor
final class WindowObserverModel: ObservableObject {
private let observer = WindowStateObserver()
@Published var windowState = WindowStateObserver.State(window: nil)
Expand Down
4 changes: 2 additions & 2 deletions Sources/WindowTreatment/NSWindow+Animation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
import Cocoa

extension NSWindow {
public func withAnimationDisabled(block: () -> Void) {
public func withAnimationDisabled(block: @MainActor () -> Void) {
let currentBehavior = animationBehavior

animationBehavior = .none

block()

OperationQueue.main.addOperation {
DispatchQueue.main.async {
self.animationBehavior = currentBehavior
}
}
Expand Down
81 changes: 42 additions & 39 deletions Sources/WindowTreatment/WindowStateObserver.swift
Original file line number Diff line number Diff line change
@@ -1,43 +1,48 @@
import Cocoa

extension NSWindow {
static var tabStateDidChangeNotification = Notification.Name("windowTabStateDidChangeNotification")
static let tabStateDidChangeNotification = Notification.Name("windowTabStateDidChangeNotification")
}

@MainActor
public final class WindowStateObserver {
public struct State: Hashable {
public var isMain: Bool
public var isKey: Bool
public var tabBarVisible: Bool
public var tabCount: Int

public init(window: NSWindow?) {
self.isMain = window?.isMainWindow ?? false
self.isKey = window?.isKeyWindow ?? false

// use of this property is essential
self.tabBarVisible = window?.isTabBarVisible ?? false

if #available(macOS 10.12, *) {
self.tabCount = window?.tabbedWindows?.count ?? 0
} else {
self.tabCount = 0
}
}

public var isKeyOrMain: Bool {
return isMain || isKey
}

public var tabbed: Bool {
return tabBarVisible && tabCount > 1
}

public func tabStateEqual(to other: State) -> Bool {
return tabBarVisible == other.tabBarVisible && tabCount == other.tabCount
}
}

public struct State: Hashable, Sendable {
public var isMain: Bool
public var isKey: Bool
public var tabBarVisible: Bool
public var tabCount: Int

@MainActor
public init(window: NSWindow?) {
self.isMain = window?.isMainWindow ?? false
self.isKey = window?.isKeyWindow ?? false

// use of this property is essential
self.tabBarVisible = window?.isTabBarVisible ?? false

self.tabCount = window?.tabbedWindows?.count ?? 0
}

public init() {
self.isMain = false
self.isKey = false
self.tabBarVisible = false
self.tabCount = 0
}

public nonisolated var isKeyOrMain: Bool {
return isMain || isKey
}

public nonisolated var tabbed: Bool {
return tabBarVisible && tabCount > 1
}

public nonisolated func tabStateEqual(to other: State) -> Bool {
return tabBarVisible == other.tabBarVisible && tabCount == other.tabCount
}
}

public typealias ObserverBlock = (State, State) -> Void

public var block: ObserverBlock?
Expand All @@ -56,10 +61,6 @@ public final class WindowStateObserver {
self.block = block
}

deinit {
deregisterForWindowNotifications()
}

private func registerForWindowNotifications(_ window: NSWindow) {
if (window.canBecomeKey || window.canBecomeMain) == false {
print("Warning: WindowStateObserver is monitoring a window that cannot become key or main")
Expand Down Expand Up @@ -97,7 +98,9 @@ public final class WindowStateObserver {
// While this API is actually available in 10.12, observing this will reliably cause crashes in 10.14...
if #available(macOS 10.15, *) {
self.tabbedWindowsObservation = window.observe(\.tabbedWindows, options: [], changeHandler: { [unowned self] (obj, _) in
self.handlePossibleStateChange(for: obj, forward: true)
MainActor.assumeIsolated {
self.handlePossibleStateChange(for: obj, forward: true)
}
})
}
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/WindowTreatment/WindowStateObservingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import SwiftUI

@available(macOS 10.15, *)
public struct WindowStateKey: EnvironmentKey {
public typealias Value = WindowStateObserver.State
public static var defaultValue: Value = .init(window: nil)
public static let defaultValue: WindowStateObserver.State = .init()
}

@available(macOS 10.15, *)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import XCTest
@testable import WindowTreatment

class WindowTitlebarAwareViewTests: XCTestCase {
final class WindowTitlebarAwareViewTests: XCTestCase {
@MainActor
func testCreateViewInWindowWithVisibleTitlebar() {
let view = WindowTitlebarAwareView()
let contentView = NSView()
Expand All @@ -27,6 +28,7 @@ class WindowTitlebarAwareViewTests: XCTestCase {
XCTAssertEqual(contentView.frame, window.contentLayoutRect)
}

@MainActor
func testCreateViewInWindowWithInvisibleTitlebar() {
let view = WindowTitlebarAwareView()
let contentView = NSView()
Expand Down

0 comments on commit f3e746c

Please sign in to comment.