Jon Vogel
Jon Vogel

Reputation: 5644

Access modification Apple Combine

I'm trying to think in terms of API Design because ultimately I want to ship code to myself or others.

Let's make some assumptions in order to get a succinct scenario. We will assume I have some Code that authenticates with my server and returns a user object. Defined simply like this:

public struct User: Codable {
    public let id: UUID
    public let email: String
    public let name: String
}

I'm writing this code as an SDK I would ship to myself or a third party where all the guts of AUTH are handled. Using Apples new Combine framework I might expose a publisher for the consumer of my SDK like this.

public enum CurrentUserError: Error {
    case loggedOut
    case sessionExpired
}

public struct MyAuthFrameworkPublishers {
    static let currentUser: CurrentValueSubject<User?, CurrentUserError> = CurrentValueSubject<User?, CurrentUserError>(nil)
}

Now, my private auth could accomplish it's task and retrieve a user then publish that to anything outside the SDK that it listening like so:

class AuthController {
    func authenticate() {
        ///Get authenticated user.
        let user = User.init(id: UUID(), email: "[email protected]", name: "some")
        MyAuthFrameworkPublishers.currentUser.send(user)
    }
}
let authController = AuthController.init()
authController.authenticate()

Is there a way to keep or stop the user of this SDK from sending it's own User object to the publisher? Like a private or access controller .send() function in combine?

Upvotes: 0

Views: 52

Answers (1)

rob mayoff
rob mayoff

Reputation: 385970

Do you really want to use CurrentUserError as your Failure type? Sending a failure ends any subscriptions to the subject and fails new ones immediately. If the session expires, don't you want to let the user log in again? That would require publishing another User? output, which you cannot do if you have published a failure.

Instead, use Result<User?, CurrentUserError> as your Output type, and Never as your Failure type.

Now, on to your question. In general, the way to prevent the user from calling send is to vend an AnyPublisher instead of a Subject:

public class AuthController {

    public let currentUser: AnyPublisher<Result<User?, AuthenticationError>, Never>

    public func authenticate() {
        let user: User = ...
        subject.send(user)
    }

    public init() {
        currentUser = subject.eraseToAnyPublisher()
    }

    private let subject = CurrentValueSubject<Result<User?, AuthenticationError>, Never>(nil)
}

Upvotes: 2

Related Questions