Object conforming to a protocol should hide the protocol methods - swift

I have an app designed with Viper architecture. To avoid exeptions, each module is created by a factory class which comply to BaseFactory protocol.
Two of one hundred (2%) modules in my app should be created with a custom factory method which is not enforced via protocol, a factory method which accept an argument.
Is it possible to "hide/disable" a function createViperModule() in the MemberProfileFactory class?
protocol BaseFactory {
static func createViperModule () -> UIViewController
}
class HelloFactory: BaseFactory {
static func creatViperModule() -> UIViewController {
let p = HelloPresenter()
let storyboard = UIStoryboard.init(name: "Hello", bundle: nil)
let vc = (storyboard.instantiateInitialViewController() as? HelloVC)!
p.vc = vc
vc.p = p
return vc
}
}
class MemberProfileFactory: BaseFactory {
static func createViperModule() -> UIViewController {
return PublicProfileVC()
}
static func createViperModule(withMember member: MemberModel) -> UIViewController {
let p = MemberProfilePresenter()
let storyboard = UIStoryboard.init(name: "MemberProfile", bundle: nil)
let vc = (storyboard.instantiateInitialViewController() as? MemberProfileVC)!
p.vc = vc
p.user = user
vc.p = p
return vc
}
}

You can't make 'createViperModule' private because of 'createViperModule' declared as internal in 'BaseFactory'. but you can declare it optional so it is not mandatory to implement.
#objc protocol BaseFactory {
#objc optional static func createViperModule () -> UIViewController
}

Related

Swift, iOS: How to get Polymorphic and Decouple Coordinator from concrete type

I have these protocols:
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func mainViewController() -> UIViewController
}
And I created a MainCoordinator that conforms to this protocol and I pass a factory that allows me to decouple the coordinator from creating and capturing a concrete type so it can be polymorphic and can be used with more implementations of UIViewController either as rootViewControllers and mainMenuViewController as shown below:
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
}
start() {
guard let mainVC = factory.mainViewController() as? MainViewController, let rootViewController = rootViewController as? UINavigationViewController else { return }
mainVC.delegate = self
rootViewController.push(mainVC, animated: true)
}
As you can see, although I've created the coordinator to accept any subclass of UIViewController it has been coupled in the start function to the concrete implementation of UIViewController: MainViewController.
So my question is how to decouple it from MainViewController and have it more polymorphic?
You can pass coordinator as a parameter type in factory function and set delegate directly in factory function while creating controller instance. That way you wouldn’t have to expose controller type explicitly out of factory classes.
I came up with below approach.
protocol Coordinator {
var rootViewController: UIViewController { get set }
func start()
}
protocol UIViewControllerFactory {
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?
}
class MainCoordinator: Coordinator {
var rootViewController: UIViewController
let factory: UIViewControllerFactory
init(rootViewController: UIViewController, factory: UIViewControllerFactory) {
self.rootViewController = rootViewController
self.factory = factory
}
func start() {
guard let controller = factory.getViewController(delegateType: .MainCoordinator, delegateObject: self),let rootViewController = rootViewController as? UINavigationViewController else {
return
}
rootViewController.push(mainVC, animated: true)
}
}
extension MainCoordinator:DelegateCaller{
func printHello() {
print("helloo")
}
}
enum CoordinatoreTypes{
case MainCoordinator
case none
}
class Factory:UIViewControllerFactory{
func getViewController(delegateType:CoordinatoreTypes,delegateObject:Coordinator) -> UIViewController?{
switch delegateType{
case .MainCoordinator:
let controller = MainViewController()
controller.delegate = delegateObject as? MainCoordinator
return controller
case .none:
break
}
return nil
}
}
class MainViewController:UIViewController{
weak var delegate:DelegateCaller?
}
protocol DelegateCaller:AnyObject{
func printHello()
}

Using self in function in a protocol

I have this protocols:
One to instantiate a ViewController from Storyboard:
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
// this pulls out "MyApp.MyViewController"
let fullName = NSStringFromClass(self)
// this splits by the dot and uses everything after, giving "MyViewController"
let className = fullName.components(separatedBy: ".")[1]
// load our storyboard
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
// instantiate a view controller with that identifier, and force cast as the type that was requested
return storyboard.instantiateViewController(withIdentifier: className) as! Self
}
}
One to inject Dependencies in to Viewcontrollers:
protocol DependencyInjection where Self: UIViewController {
associatedtype myType: DependencyVC
func injectDependencys(dependency: myType)
}
Now I want to add another one, so I can create the ViewController from the Dependency itself:
protocol DependencyVC {
associatedtype myType: DependencyInjectionVC & Storyboarded
func createVC() -> myType
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self {
let viewController = T.instantiate()
viewController.injectDependencys(dependency: self)
return viewController
}
}
But I get this error for self:
Cannot invoke 'injectDependencys' with an argument list of type
'(dependency: Self)'
This is a DependencyClass I have:
class TopFlopDependency: DependencyVC {
typealias myType = TopFlopVC
var topFlopState: TopFlopState
lazy var topFlopConfig: TopFlopConfig = {
let SIBM = StatIntervalBaseModel(stat: "ppc", interval: "24h", base: "usd")
return TopFlopConfig(group: Groups.large, base: "usd", valueOne: SIBM)
}()
init(state: TopFlopState) {
self.topFlopState = state
}
func createVC() -> TopFlopVC {
let topflopVC = TopFlopVC.instantiate()
topflopVC.injectDependencys(dependency: self)
let viewController: TopFlopVC = makeVC()
return topflopVC
}
}
I get this error when using makeVC:
'TopFlopDependency' requires the types 'TopFlopDependency.myType' and
'TopFlopDependency.myType' (aka 'TopFlopVC') be equivalent to use
'makeVC'
other Solution:
protocol DependencyVC {
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self {
let viewController = T.instantiate()
viewController.injectDependencys(dependency: self)
return viewController
}
}
When trying to use:
let viewController: TopFlopVC = makeVC()
I get the error that T could not be inferred.
Why can I not do this? Do you have a solution how I would get it to work?
Thank you!
You need to add another constraint. Your DependencyInjection protocol requires a very specific type of DependencyVC (myType). But your DependencyVC extension works with any DependencyVC. So you need to constrain T’s myType to be the same type with a where clause: func createVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self
So a complete example would look like this:
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
...
}
}
protocol DependencyVC {
}
protocol DependencyInjection where Self: UIViewController {
associatedtype myType: DependencyVC
func injectDependencys(dependency: myType)
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>(type _: T.Type? = nil) -> T where T.myType == Self {
let viewController = T.instantiate()
viewController.injectDependencys(dependency: self)
return viewController
}
}
struct MyDependency: DependencyVC {}
class MyVC: UIViewController, Storyboarded, DependencyInjection {
func injectDependencys(dependency: MyDependency) {
print(dependency)
}
}
When you call viewController.injectDependencys(dependency: self), self is known to be of some subtype of DependencyVC. However, DependencyInjection's associatedtype myType: DependencyVC just says that a type conforming to DependencyInjection will use some type for myType (that conforms to DependencyVC). So there's no guarantee that its actual type will be a subtype of myType.
associatedtypes don't quite work the same way as generic type parameters in that associatedtypes are given when "defining" a type, while generic type parameters are given when "using" a type.
It all boils down to the fact that you probably don't want to have an associatedtype myType, instead taking a DependencyVC directly.
Update
In light of the additional information you've provided, I believe this would be the best solution:
protocol DependencyInjection where Self: UIViewController {
func injectDependency(_ dependency: DependencyVC)
}
protocol DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T
}
extension DependencyVC {
func makeVC<T: Storyboarded & DependencyInjection>() -> T {
let viewController = T.instantiate()
viewController.injectDependency(self)
return viewController
}
}
As you may notice, I took the liberty of renaming injectDependencys(dependency: DependencyVC) to injectDependency(_ dependency: DependencyVC), because you're only injecting one dependency and the dependency: label doesn't really add anything at the call site.
Anyway, this allows you to create instances of view controllers using your dependency. Say you have the dependency stored in a variable named dependency, then you can create a view controller from it by going let topFlopVC: TopFlopVC = dependency.makeVC()

Static function that returns the dynamic type of class [duplicate]

I'm trying to make this extension:
extension UIViewController
{
class func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! Self
return controller
}
}
But I get compile error:
error: cannot convert return expression of type 'UIViewController' to
return type 'Self'
Is it possible? Also I want to make it as init(storyboardName: String, storyboardId: String)
Similar as in Using 'self' in class extension functions in Swift, you can define a generic helper method which infers the type of self from the calling context:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
return instantiateFromStoryboardHelper(storyboardName, storyboardId: storyboardId)
}
private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! T
return controller
}
}
Then
let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
compiles, and the type is inferred as MyViewController.
Update for Swift 3:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
return instantiateFromStoryboardHelper(storyboardName: storyboardName, storyboardId: storyboardId)
}
private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) as! T
return controller
}
}
Another possible solution, using unsafeDowncast:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: storyboardId)
return unsafeDowncast(controller, to: self)
}
}
Self is determined at compile-time, not runtime. In your code, Self is exactly equivalent to UIViewController, not "the subclass that happens to be calling this." This is going to return UIViewController and the caller will have to as it into the right subclass. I assume that's what you were trying to avoid (though it is the "normal Cocoa" way to do it, so just returning UIViewController is probably the best solution).
Note: You should not name the function initialize in any case. That's an existing class function of NSObject and would cause confusion at best, bugs at worst.
But if you want to avoid the caller's as, subclassing is not usually the tool to add functionality in Swift. Instead, you usually want generics and protocols. In this case, generics are all you need.
func instantiateViewController<VC: UIViewController>(storyboardName: String, storyboardId: String) -> VC {
let storyboad = UIStoryboard(name name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! VC
return controller
}
This isn't a class method. It's just a function. There's no need for a class here.
let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)
A cleaner solution (at least visually tidier):
Swift 5.1
class func initialize(storyboardName: String, storyboardId: String) -> Self {
return UIStoryboard(name: storyboardName, bundle: nil)
.instantiateViewController(withIdentifier: storyboardId).view as! Self
}
Another way is to use a protocol, which also allows you to return Self.
protocol StoryboardGeneratable {
}
extension UIViewController: StoryboardGeneratable {
}
extension StoryboardGeneratable where Self: UIViewController
{
static func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewController(withIdentifier: storyboardId) as! Self
return controller
}
}

swift - Initialize view controller from storyboard by overriding init

I have a ViewController instance defined in a storyboard. I can initialize it by the following
var myViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("myViewControllerIdentifier") as! ViewController
Is there a way to override the init method of ViewController so that I can initialize it using
var myViewController = ViewController()
I tried overriding init
convenience init() {
self = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SearchTableViewController") as! SearchTableViewController
}
but the compiler doesn't like that. Any ideas?
A convenience initializer must always delegate to a designated initializer for the same class, and a designated initializer must call a superclass initializer.
Since the superclass doesn't have an appropriate initializer, you would probably be better served by a class factory method:
static func instantiate() -> SearchTableViewController
{
return UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SearchTableViewController") as! SearchTableViewController
}
then use:
var myViewController = SearchTableViewController.instantiate()
A class factory method is the way to go for now. Here's a protocol that you can use to quickly add makeFromStoryboard support to all UIViewControllers.
protocol StoryboardInstantiable {
static var storyboardName: String { get }
static var storyboardBundle: NSBundle? { get }
static var storyboardIdentifier: String? { get }
}
​
extension StoryboardInstantiable {
static var storyboardBundle: NSBundle? { return nil }
static var storyboardIdentifier: String? { return nil }
static func makeFromStoryboard() -> Self {
let storyboard = UIStoryboard(name: storyboardName, bundle: storyboardBundle)
if let storyboardIdentifier = storyboardIdentifier {
return storyboard.instantiateViewControllerWithIdentifier(storyboardIdentifier) as! Self
} else {
return storyboard.instantiateInitialViewController() as! Self
}
}
}
Example:
extension MasterViewController: StoryboardInstantiable {
static var storyboardName: String { return "Main" }
static var storyboardIdentifier: String? { return "Master" }
}
In case the view controller is the initial view controller in the storyboard, you can simply ignore storyboardIdentifier.
In case all the view controllers are in the same storyboard, you can also override storyboardName under the StoryboardInstantiable extension and return the name.
You can try Generics. Like this:
func Tvc<T>(_ vcType: T.Type) -> T {
let vc = (vcType as! UIViewController.Type).vc//限制或明确为 UIViewController.Type
return vc as! T
}
extension UIViewController {
fileprivate static var vc: UIViewController {
let M = UIStoryboard.init(name: "V", bundle: Bundle.main)
return M.instantiateViewController(withIdentifier: "\(self)")
}
}
Example
let vc = Tvc(HLCases.self)
_vc.navigationController?.pushViewController(vc, animated: true)

howto determine the class when i get a NSObject in a function with swift

I have a function like this, which I can provide 2 different classes (MyClass1 or MyClass2) :
func getClassName(anyobject: NSObject) -> String {
return anyobject.getClassName(); // <---- Howto do that ?
}
class MyClass1: NSObject {
}
class MyClass2: NSObject {
}
var myclass:MyClass1;
var sClassName:String=getClassName(myclass); <---- Howto get that ?
I would like to get the Classname of an provided object. Howto do that in swift ? I would like to provide my Class myclass to the func getClassName(anyobject: NSObject) and get the result "MyClass1".
Add this extension
extension NSObject {
var theClassName: String {
return NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
}
}
class MyClass1: NSObject{
}
class MyClass2: NSObject {
}
func getClassName(obj: NSObject) -> String {
return obj.theClassName
}
var myClass: MyClass1 = MyClass1()
var sClassName: String = getClassName(myClass)
Let me know if this is what you asked for.
If use static var ,you don't need to make instance.
extension NSObject {
static var className: String { String(describing: self) }
}
For example in StroyBoard
func changeWindow() {
let vc: UIViewController!
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser?.uid != nil {
//here
vc = storyboard.instantiateViewController(identifier: HomeViewController.className)
}
else {
vc = storyboard.instantiateViewController(identifier: LoginViewController.className)
}