Skip to content

Commit

Permalink
feat: add AuthStateChangeListenerRegistration type (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev authored Mar 8, 2024
1 parent 5a3e4c2 commit 27a173e
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 21 deletions.
22 changes: 2 additions & 20 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,6 @@ import Foundation
import FoundationNetworking
#endif

public final class AuthStateChangeListenerHandle {
var onCancel: (@Sendable () -> Void)?

public func cancel() {
onCancel?()
onCancel = nil
}

deinit {
cancel()
}
}

public typealias AuthStateChangeListener = @Sendable (
_ event: AuthChangeEvent,
_ session: Session?
) -> Void

public actor AuthClient {
/// FetchHandler is a type alias for asynchronous network request handling.
public typealias FetchHandler = @Sendable (
Expand Down Expand Up @@ -216,7 +198,7 @@ public actor AuthClient {
@discardableResult
public func onAuthStateChange(
_ listener: @escaping AuthStateChangeListener
) async -> AuthStateChangeListenerHandle {
) async -> AuthStateChangeListenerRegistration {
let handle = eventEmitter.attachListener(listener)
await emitInitialSession(forHandle: handle)
return handle
Expand All @@ -240,7 +222,7 @@ public actor AuthClient {
}

continuation.onTermination = { _ in
handle.cancel()
handle.remove()
}
}

Expand Down
41 changes: 41 additions & 0 deletions Sources/Auth/AuthStateChangeListener.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// AuthStateChangeListener.swift
//
//
// Created by Guilherme Souza on 17/02/24.
//

import ConcurrencyExtras
import Foundation

/// A listener that can be removed by calling ``AuthStateChangeListenerRegistration/remove()``.
///
/// - Note: Listener is automatically removed on deinit.
public protocol AuthStateChangeListenerRegistration: Sendable, AnyObject {
/// Removes the listener. After the initial call, subsequent calls have no effect.
func remove()
}

final class AuthStateChangeListenerHandle: AuthStateChangeListenerRegistration {
let _onRemove = LockIsolated((@Sendable () -> Void)?.none)

public func remove() {
_onRemove.withValue {
if $0 == nil {
return
}

$0?()
$0 = nil
}
}

deinit {
remove()
}
}

public typealias AuthStateChangeListener = @Sendable (
_ event: AuthChangeEvent,
_ session: Session?
) -> Void
2 changes: 1 addition & 1 deletion Sources/Auth/Internal/EventEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class DefaultEventEmitter: EventEmitter {
let handle = AuthStateChangeListenerHandle()
let key = ObjectIdentifier(handle)

handle.onCancel = { [weak self] in
handle._onRemove.setValue { [weak self] in
self?.listeners.withValue {
$0[key] = nil
}
Expand Down
44 changes: 44 additions & 0 deletions Tests/AuthTests/AuthStateChangeListenerHandleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// AuthStateChangeListenerHandleTests.swift
//
//
// Created by Guilherme Souza on 17/02/24.
//

@testable import Auth
import ConcurrencyExtras
import Foundation
import XCTest

final class AuthStateChangeListenerHandleTests: XCTestCase {
func testRemove() {
let handle = AuthStateChangeListenerHandle()

let onRemoveCallCount = LockIsolated(0)
handle._onRemove.setValue {
onRemoveCallCount.withValue {
$0 += 1
}
}

handle.remove()
handle.remove()

XCTAssertEqual(onRemoveCallCount.value, 1)
}

func testDeinit() {
var handle: AuthStateChangeListenerHandle? = AuthStateChangeListenerHandle()

let onRemoveCallCount = LockIsolated(0)
handle?._onRemove.setValue {
onRemoveCallCount.withValue {
$0 += 1
}
}

handle = nil

XCTAssertEqual(onRemoveCallCount.value, 1)
}
}

0 comments on commit 27a173e

Please sign in to comment.