Using Typhoon and Swift, I am setting up my project and I have this problem. I have a class TPLAddInteractor this way
class TPLAddInteractor: NSObject, TPLAddInteractorInput {
var output: TPLAddInteractorOutput?
var dataManager: TPLDataManagerInterface?
}
My assembly looks like this
class TPLAddAssembly: TyphoonAssembly {
var applicationAssembly: TPLApplicationAssembly?
dynamic func addInteractor() -> AnyObject {
return TyphoonDefinition.withClass(TPLAddInteractor.self) {
(definition) in
definition.injectProperty("output", with: self.addPresenter())
definition.injectProperty("dataManager", with: self.applicationAssembly?.dataManager())
}
}
dynamic func addPresenter() -> AnyObject {
return TyphoonDefinition.withClass(TPLAddPresenter.self) {
(definition) in
definition.injectProperty("interactor", with: self.addInteractor())
}
}
}
And then I receive this error right after running the app:
reason: 'Can't inject property 'dataManager' for object '<TPL.TPLAddInteractor: 0x7ff5b2d2bcf0>'. Setter selector not found. Make sure that property exists and writable'
I am reading the Swift example of Typhoon and I don't see anything unusual in my code. But I am new on Swift so maybe I'm missing something.
Thanks
To work with Typhoon, it's necessary for Swift protocols to have the '#objc' directive. This is because Typhoon uses the Objective-C runtime as Swift has no native dynamism as yet.
This requirement is documented in the Swift Quick Start.
//Inject as follows it will give a warning but its working for me:
definition?.injectProperty(Selector(("cityInfo")), with: self.coreAssembly.cityInfo())
Related
I am working on iOS app which is having Obj C code as well as Swift. I am migrating my existing Objective C category to swift code. But when I override existing method in swift extension, it is not compiling. Swift extension is working well for new methods but not for overriding existing methods.
Code:
extension UIViewController {
public override func shouldAutorotate() -> Bool {
return false
}
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
}
Error:
Method 'shouldAutorotate()' with Objective-C selector 'shouldAutorotate' conflicts with previous declaration with the same Objective-C selector
Method does not override any method from its superclass
Method 'supportedInterfaceOrientations()' with Objective-C selector 'supportedInterfaceOrientations' conflicts with previous declaration with the same Objective-C selector
Here, Am I missing anything?
I am using Xcode 7.3.1 and Swift 2.x
EDIT:
From below Answers, I got to know that We cant change behaviour of existing method of class at runtime in Swift extension like in Objective C Categories. Here I should make a base class which will override method and I should use all my ViewControllers as child classes from new base class as parent.
But in my case, I want to change behaviour of all "shouldAutorotate" methods including 3rd party frameworks UIViewController. In above case I cant force all third party frameworks UIviewControllers to become subclass of my base class. In Objective C i could achieve this.
Swift extensions aren't to be used to override methods declared in the class they're extending – especially for Objective-C classes, that's very much like providing two definitions of the same method in the same class. Imagine seeing a class that looked like:
class UIViewController : UIResponder {
public func shouldAutorotate() -> Bool {
return true
}
public func shouldAutorotate() -> Bool {
return false
}
}
Which one wins? That's the conflict you're being warned about.
If you need to override methods for a view controller of yours, you'll need to do that in a subclass, not an extension.
Ninja edit: this may have been possible to do in Objective-C, but was a programming error there. If a category duplicates a method from a main class, which definition gets used is undefined. See this SO post and the backing Apple documentation.
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.
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))
}
}
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.
Exploring Swift headers I'm seeing this pattern used by Apple, in particular the init declaration of the following struct HAS NO IMPLEMENTATION.
Obviously the init() implementation is hidden somehow, since it's Apple stuff, but I was trying to understand how.
This is only an example, but it seems a common behavior in the headers
struct AutoreleasingUnsafePointer<T> : Equatable, LogicValue {
let value: Builtin.RawPointer
init(_ value: Builtin.RawPointer) // <---- how is it possible? where is the implementation?
func getLogicValue() -> Bool
/// Access the underlying raw memory, getting and
/// setting values.
var memory: T
}
I know that it is possible to declare a protocol plus a class extension, doing this it's possible to "hide" the implementation from the class declaration and moving it elsewhere
class TestClass :TestClassProtocol
{
// nothing here
}
protocol TestClassProtocol
{
func someMethod() // here is the method declaration
}
extension TestClass
{
func someMethod() // here is the method implementation
{
println("extended method")
}
}
But it's different from what I have seen in the Apple Headers, since the method "declaration" is inside the "class", not inside the "protocol". if I try to put the method declaration inside the class TestClass, however, I have two errors (function without body on the class, and invalid redeclaration on the extension)
In Objective C this was "implicit", since the method declaration was in the .h and the implementation in the .m
How to do the same in Swift?
I think the explanation is very simple. What you see in Xcode is not actually a valid Swift
code.
It's a result from an automatic conversion of an Obj-C header into Swift-like code but it's not compilable Swift.