Initializer is inaccessable due to 'internal' protection level
Asked Answered
B

4

143

I have a protocol

LoginStrategy

public protocol LoginStrategy {
    func login(_ viewController: UIViewController)
    func getUserInfo(withCompletionHandler completionHandler: @escaping (_ userInfo: [String: Any]?) -> ())
    func createLoginButton(_ frame: CGRect, withCompletionHandler completionHandler: @escaping (_ loginButton: UIView) -> ())
    func getUserId() -> String
}

and two classes:

LoginProvider

public class LoginProvider {
    
    public let strategy: LoginStrategy
    
    public func login(_ viewController: UIViewController) {
        return self.strategy.login(viewController)
    }
    
    public func getUserInfo(withCompletionHandler completionHandler: @escaping (_ userInfo: [String: Any]?) -> ()) {
        return self.strategy.getUserInfo(withCompletionHandler: completionHandler)
    }
    
    public func createLoginButton(_ frame: CGRect, withCompletionHandler completionHandler: @escaping (_ loginButton: UIView) -> ()) {
        return self.strategy.createLoginButton(frame, withCompletionHandler: completionHandler)
    }
    
    public func getUserId() -> String {
        return self.strategy.getUserId()
    }
    
    public init(strategy: LoginStrategy) {
        self.strategy = strategy
    }
    
}

FacebookLoginStrategy

import Foundation
import FacebookCore
import FacebookLogin

public class FacebookLoginStrategy: LoginStrategy {
    
    public var grantedPermissions: Set<Permission>? = nil

    public var declinedPermissions: Set<Permission>? = nil
    
    public var userId: String = ""
    
    public func login(_ viewController: UIViewController) {
        let loginManager = LoginManager()
        let permissions: [ReadPermission] = [.publicProfile, .userFriends, .email]
        loginManager.logIn(permissions, viewController: viewController) { loginResult in
            switch loginResult {
            case .failed(let error):
                print(error)
            case .cancelled:
                print("User cancelled login.")
            case .success(let grantedPermissions, let declinedPermissions, let accessToken):
                self.userId = accessToken.userId ?? ""
                self.grantedPermissions = grantedPermissions
                self.declinedPermissions = declinedPermissions
                print("Logged in!")
            }
        }
    }
    
    public func getUserId() -> String {
        return userId
    }
    
    public func getUserInfo(withCompletionHandler completionHandler: @escaping (_ userInfo: [String: Any]?) -> ()) {
        let request = GraphRequest(graphPath: "me", parameters: ["fields":"email, name"], accessToken: AccessToken.current, httpMethod: .GET, apiVersion: FacebookCore.GraphAPIVersion.defaultVersion)
        request.start { (response, result) in
            switch result {
            case .success(let value):
                print(value.dictionaryValue)
                completionHandler(value.dictionaryValue)
            case .failed(let error):
                print(error)
            }
        }
    }

    public func createLoginButton(_ frame: CGRect, withCompletionHandler completionHandler: @escaping (_ loginButton: UIView) -> ()) {
        let permissions: [ReadPermission] = [.publicProfile, .userFriends, .email]
        let loginButton = LoginButton(readPermissions: permissions)
        loginButton.frame = frame
        completionHandler(loginButton)
    }
}

In my ViewController:

When I use:

let facebookLoginProvider = LoginProvider(strategy: FacebookLoginStrategy())

It says:

'FacebookLoginStrategy' is inaccessable due to 'internal' protection level

Basia answered 29/11, 2016 at 6:28 Comment(0)
R
375

Just add to your FacebookLoginStrategy:

public init() {}

As long as you do not implement init() explicitly, it is marked as internal by default. You need to overwrite that permission level to be able to instantiate from outside your framework.

Residentiary answered 29/11, 2016 at 6:42 Comment(9)
Great answer! And watch out for Swift4, it's actually an error if you don't implement it.Jeep
Just into the class.Residentiary
this is so dumb that Swift requires this. If I declare the whole class type public, the free init() should come as public as wellFlashbulb
This should a bug of Swift. It should not an error of compiler, since we had marked public for class/struct.Goulet
Just learned, that it also works for structs. Got the error-message, was confused, googled and found my own answer. It's getting late on a Friday...Residentiary
I've added public before my init and I am still getting that same message. Is there another protection?Spermicide
Hi @Spermicide , can you share some code with us? Maybe just ask your question.Residentiary
@Goulet It's not dumb, nor is it a bug. Sometimes you don't want the initializers for a type to be public. It's easy to forget about the existence of synthesized initializers because they don't actually appear in your code. Therefore, if these initializers were public, it'd be easy to accidentally expose them outside of the module.Sudorific
@Flashbulb This intended and I hope it stays this way, sometime you don't want all your init being public. On the other hand, the documentation, should be more clear about this.Versify
C
23

If you are running in to this in code within an XCTestCase, make sure that you have added @testable import My-Awesome-App to the top of your test file.

Coveney answered 16/12, 2019 at 16:10 Comment(1)
Thank you so much.Vanillin
P
8

Add init method as Public access

public init() {}
Particularly answered 9/12, 2021 at 7:3 Comment(0)
R
4

In case anyone faces this issue when implementing Swift packages for building reusable SwiftUI views, there are a couple of things to keep in mind.

  1. If there are component props that are being set from the parent component, don't forget to add them to the constructor
  2. If the property is wrapped with @State, then assign the state to the underlying object as shown below.
public struct Layout: View {
    @State var layout: NavType = .TOP
    var navItems: [NavItemObject]
    public init(layout: NavType, navItems: [NavItemObject]) {
        _layout = State(initialValue: layout);
        self.navItems = navItems;
    }
}
Reinhold answered 3/6, 2022 at 21:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.