Typhoon with view controller property - ios

I have class:
class InformationTableViewController: UITableViewController {
private var cos: Int!
}
And I'm trying to inject property:
public dynamic func informationTableViewController() -> AnyObject {
return TyphoonDefinition.withClass(InformationTableViewController.self) {
(definition) in
definition.injectProperty("cos", with: 3)
}
}
When it's a simple class it works normal. But when I use InformationTableViewController on Storyboard (as some view class) I'm getting error:
'Can't inject property 'cos' for object 'Blah.InformationTableViewController: 0x7fca3300afe0'. Setter selector not found. Make sure that property exists and writable'
What's the problem?

Private access modifier restricts the use of an entity to its own defining source file.
So one problem is that you are trying to set your property from outside of it private scope. Remove private keyword from property declaration.
Another problem here is that you are trying to inject primitive type.
In Obj-C Typhoon has support of injecting primitive types but not in Swift yet.
Every class you want to inject has to be a subclass of NSObject in some way (either by subclassing or adding #objc modifier).
As a workaround you may use NSNumber instead of an Int type for your property.
class InformationTableViewController: UITableViewController {
var cos: NSNumber!
}
Assembly:
public dynamic func informationTableViewController() -> AnyObject {
return TyphoonDefinition.withClass(InformationTableViewController.self) {
(definition) in
definition.injectProperty("cos", with: NSNumber.init(int: 3))
}
}

Related

Typhoon: Inject subclass property into definition from withFactory:selector: injection style

I am using Typhoon to inject dependencies into a subclass of UIViewController. I've found a potential solution to a different question i had which involves doing instantiation of the view controller using the "factory" injection method:
return TyphoonDefinition.withFactory(self.storyboard(), selector:"instantiateViewControllerWithIdentifier:", parameters: { (method) in
method.injectParameterWith("camera-mode-controller")
}, configuration: { (definition) in
definition.injectProperty("cameraProvider", with: self.systemComponents.systemCameraProvider())
})
However, the UIStoryboard signature func instantiateViewControllerWithIdentifier(identifier: String) -> UIViewController indicates that this factory method initializer will create a UIViewController, which does not have the property of my subclass (dynamic var cameraProvider). Therefore, at runtime, the property injection fails with setter not found.
Is there a way to say something like, "create definition with class: and factory: with method: and properties", so that the definition knows the class it is producing is not a UIViewController but in fact, in my case, a CameraModeViewController: UIViewController? Digging in the API docs I see, TyphoonDefinition.withParent:class:configuration: might chainable with the withFactory:selector:parameters: method to produce this effect? However, my attempt:
public dynamic func viewControllerFromID(id: String) -> AnyObject {
return TyphoonDefinition.withFactory(self.storyboard(), selector: "instantiateViewControllerWithIdentifier:") {
(method) in
method.injectParameterWith(id)
}
}
public dynamic func cameraModeViewController() -> AnyObject {
return TyphoonDefinition.withParent(self.viewControllerFromID("camera-mode-controller"), `class`: CameraModeViewController.self) {
(definition) in
definition.injectProperty("cameraProvider", with: self.systemComponents.systemCameraProvider())
}
}
produces the error: 'You can't call a method on the runtime argument being passed in. It has to be passed in as-is' in -[TyphoonInjectionByRuntimeArgument forwardingTargetForSelector:] with selector argument "length". Any thoughts?
I solved this problem for myself: due to a version control problem, the storyboard member with that identifier had forgotten its UIViewController subclass assignment, and as such was NOT in fact castable, and did not have that property. As it turns out, Typhoon works just fine with injecting properties declared in view controller subclasses.

How to inject dependency via protocol in Typhoon using Swift?

I have a problem with Typhoon dependency injection framework.
My viewcontroller MainViewController depends on dataProvider property that I want to declare as AnyObject corresponding to protocol DataProviderProtocol
class MainViewController: UIViewController {
// Compiler error here
var dataProvider : DataProviderProtocol!
// something more
}
protocol DataProviderProtocol {
func fetchAllBanks(closure : ([BankObject]) -> Void)
}
class TestDataProvider: NSObject, CEDataProviderProtocol {
func fetchAllBanks(closure : ([CEBankObject]) -> Void) {
var resultBanks = ///////code that creates test data
closure(resultBanks);
}
I want this dataProvider property to be injected by the Typhoon and initialized to the corresponding instance of class TestDataProvider, that implements this protocol. But I also have RealDataProvider that also corresponds to the DataProviderProtocol and might be used sometimes
But this code crashes with the message
Can't inject property 'dataProvider' for object
''. Setter selector not
found. Make sure that property exists and writable'
I can inject this property without crashes if I use the property class of TestDataProvider, but this disables the ability to inject different DataProviderProtocol implementations.
I understand this this crash happens because DataProviderProtocol property type is not NSObject successor. But I just can't find a way to declare property as NSObject<DataProviderProtocol> in Swift
I would appreciate any help
P.S. My Assembly class
public class CEAppAssembly:TyphoonAssembly {
//uiviewcontrollers' components assembly
var mainComponentsAssembly : CEMainComponentsAssembly!
/**
UI Dependencies
**/
public dynamic func mainViewController() -> AnyObject {
return TyphoonDefinition.withClass(MainViewController.self) {
(definition) in
definition.injectProperty("dataProvider", with: self.mainComponentsAssembly.dataProvider())
}
}
Typhoon uses the Objective-C run-time introspection and dynamic features. Therefore:
Protocols must be marked with the '#objc' directive.
Types incompatible with Objective-C (ie 'pure swift') can't be injected.
There's more information about this in the Quick Start guide. If you're still having trouble after reviewing and making those changes, let us know.
We plan to release a Pure Swift version of Typhoon in the near future.

App doesn't enter in the initial ViewController using Typhoon

I have created a project to test the Typhoon framework , I have created two classes ApplicationAssembly and CoreAssembly where I inject some properties and constructors and a default Configuration.plist to load data from it.
ApplicationAssembly
public class ApplicationAssembly: TyphoonAssembly {
public dynamic func config() -> AnyObject {
return TyphoonDefinition.configDefinitionWithName("Config.plist")
}
}
CoreAssembly
public class CoreAssembly: TyphoonAssembly {
public dynamic func apiHandler() -> AnyObject {
return TyphoonDefinition.withClass(ApiHandler.self) {
(definition) in
definition.useInitializer("initWithDebugging:debugProcess:mainURL:") {
(initializer) in
initializer.injectParameterWith(TyphoonConfig("debug_mode"))
initializer.injectParameterWith(TyphoonConfig("debug_path"))
initializer.injectParameterWith(TyphoonConfig("api_url"))
}
definition.scope = TyphoonScope.Singleton
}
}
public dynamic func viewController() -> AnyObject {
return TyphoonDefinition.withClass(ViewController.self) {
(definition) in
definition.injectProperty("apiHandler", with:self.apiHandler())
}
}
}
I set in my Info.plist the TyphoonInitialAssemblies first the ApplicationAssembly and then the CoreAssembly.
Everything works fine without exceptions or anything except that the app never enters in AppDelegate neither in the ViewController class. I don't know maybe I missed something in the doc or anything.
What I'm missing here?
Why in debug not enter in the ViewController class that is the initial view controller in Storyboard?
The problem was that the ApiHandler class does not extend NSObject, which is a requirement. This is because Typhoon is an introspective Dependency Injection container. As Swift has no native introspection it uses the Objective-C run-time.
The App should not however have crashed in such an obfuscated way. I have opened an issue to look at how to fail with a meaningful error, rather than infinitely recurse.
After solving the initial problem, I also noted that the init method for ApiHandler passing in a Swift Bool object. This needs to be an NSNumber.
init(debugging : NSNumber, debugProcess : String, mainURL : String) {
self.debugging = debugging.boolValue
self.debugProcess = debugProcess
self.mainURL = mainURL
}
Given that Typhoon uses the Objective-C runtime, there are a few quirks to using it with Swift - the same kinds of rules outlined for using Swift with KVO apply.

How to use multiple protocols in Swift with same protocol variables?

In swift I'm implementing two protocols, GADCustomEventInterstitial and GADCustomEventBanner.
Both of these protocols require a property called delegate. delegate is a different type in each protocol, and thus a conflict arises.
class ChartBoostAdapter : NSObject, GADCustomEventInterstitial, GADCustomEventBanner, ChartboostDelegate{
var delegate:GADCustomEventInterstitialDelegate?; // Name conflict
var delegate:GADCustomEventBannerDelegate?; // Name conflict
override init(){
}
...
}
They are libraries/frameworks it's not my definition
Then obviously you cannot make the same class adopt both protocols. But you don't really need to. Just separate this functionality into two different classes, as is evidently intended by the designer of these protocols. You are supposed to have one class that adopts GADCustomEventInterstitial and has its delegate, and another class that adopts GADCustomEventBanner and has its delegate. What reason do you have for trying to force these to be one and the same class? As in all things where you are using a framework, don't fight the framework, obey it.
It is actually possible, I just encountered same situation. I had two different but kind of related protocols. In some cases I needed both to be implemented by delegate and in other cases only one and I didn't want to have two properties eg... delegate1, delegate2.
What you need to do is create another combined protocol that inherits from both protocols:
protocol ChartBoostAdapterDelegate: GADCustomEventInterstitialDelegate, GADCustomEventBannerDelegate { }
class ChartBoostAdapter : NSObject, GADCustomEventInterstitial, GADCustomEventBanner, ChartboostDelegate {
weak var delegate: ChartBoostAdapterDelegate?
override init(){
}
...
}
The simple answer is that you can't.
Maybe one protocol depends on another, in which case you would use the dependent protocol for the type of your delegate.
Note that this can be solved using Mixins (possible since Swift 2.0) if you are in a Swift-only environment. It just cannot be solved as long as you need to have the code bridged to Obj-C, as this problem is unsolvable in Obj-C. Yet that can usually be solved by a wrapper class, which I will show later on.
Let's break this down to a minimalist example:
import Foundation
#objc
protocol ProtoA {
var identifier: String { get }
}
#objc
protocol ProtoB {
var identifier: UUID { get }
}
#objc
class ClassA: NSObject, ProtoA, ProtoB {
let identifier = "ID1"
let identifier = UUID()
}
The code above will fail as no two properties can have the same name. If I only declare identifier once and make it a String, compiler will complain that ClassA does not conform to ProtoB and vice verse.
But here is Swift-only code that actually does work:
import Foundation
protocol ProtoA {
var identifier: String { get }
}
protocol ProtoB {
var identifier: UUID { get }
}
class ClassA {
let stringIdentifier = "ID1"
let uuidIdentifier = UUID()
}
extension ProtoA where Self: ClassA {
var identifier: String {
return self.stringIdentifier
}
}
extension ProtoB where Self: ClassA {
var identifier: UUID {
return self.uuidIdentifier
}
}
extension ClassA: ProtoA, ProtoB { }
Of course, you cannot do that:
let test = ClassA()
print(test.identifier)
The compiler will say ambigous use of 'identifier', as it has no idea which identifier you want to access but you can do this:
let test = ClassA()
print((test as ProtoA).identifier)
print((test as ProtoB).identifier)
and the output will be
ID1
C3F7A09B-15C2-4FEE-9AFF-0425DF66B12A
as expected.
Now to expose a ClassA instance to Obj-C, you need to wrap it:
class ClassB: NSObject {
var stringIdentifier: String { return self.wrapped.stringIdentifier }
var uuidIdentifier: UUID { return self.wrapped.uuidIdentifier }
private let wrapped: ClassA
init ( _ wrapped: ClassA )
{
self.wrapped = wrapped
}
}
extension ClassA {
var asObjCObject: ClassB { return ClassB(self) }
}
If you put it directly into the class declaration of ClassA, you could even make it a stored property, that way you don't have to recreate it ever again but that complicates everything as then ClassB may only hold a weak reference to the wrapped object, otherwise you create a retain cycle and neither of both objects will ever be freed. It's better to cache it somewhere in your Obj-C code.
And to solve your issue, one would use a similar wrapper approach by building a master class and this master class hands out two wrapper class, one conforming to GADCustomEventInterstitial and one conforming to GADCustomEventBanner but these would not have any internal state or logic, they both use the master class as storage backend and pass on all requests to this class that implements all required logic.

Override var conforming to a protocol with a var conforming to a child of the overridden var protocol

This is my inheritance structure
Protocols
protocol BaseProtocol {
}
protocol ChildProtocol: BaseProtocol {
}
Classes
class BaseClass: NSObject {
var myVar: BaseProtocol!
}
class ChildClass: BaseClass {
override var myVar: ChildProtocol!
}
I'm receiving a compiler error:
Property 'myVar' with type 'ChildProtocol!' cannot override a property with type 'BaseProtocol!'
What is the best approach to achieve this?
UPDATE
I updated the question trying to implement the solution with generics but it does not work :( This is my code (now the real one, without examples)
Protocols
protocol TPLPileInteractorOutput {
}
protocol TPLAddInteractorOutput: TPLPileInteractorOutput {
func errorReceived(error: String)
}
Classes
class TPLPileInteractor<T: TPLPileInteractorOutput>: NSObject, TPLPileInteractorInput {
var output: T!
}
And my children
class TPLAddInteractor<T: TPLAddInteractorOutput>: TPLPileInteractor<TPLPileInteractorOutput>, TPLAddInteractorInput {
}
Well, inside my TPLAddInteractor I can't access self.output, it throws a compiler error, for example
'TPLPileInteractorOutput' does not have a member named 'errorReceived'
Besides that, when I create the instance of TPLAddInteractor
let addInteractor: TPLAddInteractor<TPLAddInteractorOutput> = TPLAddInteractor()
I receive this other error
Generic parameter 'T' cannot be bound to non-#objc protocol type 'TPLAddInteractorOutput'
Any thoughts?
#tskulbru is correct: it can't be done, and this has nothing to do with your protocols. Consider the example below, which also fails…this time with Cannot override with a stored property 'myVar':
class Foo {
}
class Goo: Foo {
}
class BaseClass: NSObject {
var myVar: Foo!
}
class ChildClass: BaseClass {
override var myVar: Foo!
}
To understand why, let's reexamine the docs:
Overriding Properties
You can override an inherited instance or class property to provide
your own custom getter and setter for that property, or to add
property observers to enable the overriding property to observe when
the underlying property value changes.
The implication is that if you are going to override a property, you must write your own getter/setter, or else you must add property observers. Simply replacing one variable type with another is not allowed.
Now for some rampant speculation: why is this the case? Well, consider on the one hand that Swift is intended to be optimized for speed. Having to do runtime type checks in order to determine whether your var is in fact a Foo or a Bar slows things down. Then consider that the language designers likely have a preference for composition over inheritance. If both of these are true, it's not surprising that you cannot override a property's type.
All that said, if you needed to get an equivalent behavior, #tskulbru's solution looks quite elegant, assuming you can get it to compile. :)
I don't think you can do that with protocols
The way i would solve the problem you are having is with the use of generics. This means that you essentially have the classes like this (Updated to a working example).
Protocols
protocol BaseProtocol {
func didSomething()
}
protocol ChildProtocol: BaseProtocol {
func didSomethingElse()
}
Classes
class BaseClass<T: BaseProtocol> {
var myProtocol: T?
func doCallBack() {
myProtocol?.didSomething()
}
}
class ChildClass<T: ChildProtocol> : BaseClass<T> {
override func doCallBack() {
super.doCallBack()
myProtocol?.didSomethingElse()
}
}
Implementation/Example use
class DoesSomethingClass : ChildProtocol {
func doSomething() {
var s = ChildClass<DoesSomethingClass>()
s.myProtocol = self
s.doCallBack()
}
func didSomething() {
println("doSomething()")
}
func didSomethingElse() {
println("doSomethingElse()")
}
}
let foo = DoesSomethingClass()
foo.doSomething()
Remember, you need a class which actually implements the protocol, and its THAT class you actually define as the generic type to the BaseClass/ChildClass. Since the code expects the type to be a type which conforms to the protocol.
There are two ways you can go with your code, depending what you want to achieve with your code (you didn't tell us).
The simple case: you just want to be able to assign an object that confirms to ChildProtocol to myVar.
Solution: don't override myVar. Just use it in ChildClass. You can do this by design of the language Swift. It is one of the basics of object oriented languages.
Second case: you not only want to enable assigning instances of ChildProtocol, you also want to disable to be able to assign instances of BaseProtocol.
If you want to do this, use the Generics solution, provided here in the answers section.
If you are unsure, the simple case is correct for you.
Gerd

Resources