Injecting parameter in Typhoon Swift - ios

I am using the following code to inject a enum parameter in Typhoon in Swift:
public dynamic func introPageViewController() -> AnyObject {
return TyphoonDefinition.withClass(UIPageViewController.self) {
(definition) in
definition.useInitializer("initWithTransitionStyle:navigationOrientation:options:"){
(initializer) in
initializer.injectParameterWith(UIPageViewControllerTransitionStyle.Scroll)
}
}
}
The problem is that injectParameterWith method only accepts parameters of type AnyObject and the parameter I want to inject is of Int type so this code gives a compiler error.
How would I achieve this without causing any compiler error or crash?

As outlined in the Typhoon User Guide here, to inject an enum it is necessary to box it as an NSNumber. I'm actually not sure how to box explicitly in Swift but you could do it with something like:
var num: NSNumber = mode.rawValue

I'm not able to try it now but according to this you should be able to just add import Foundation to the top of the file and it will do an implicit cast to NSNumber from Int

Related

Swift init from unknown class which conforms to protocol

I'm currently working on updating a large project from Objective-C to Swift and I'm stumped on how to mimic some logic. Basically we have a class with a protocol which defines a couple functions to turn any class into a JSON representation of itself.
That protocol looks like this:
#define kJsonSupport #"_jsonCompatible"
#define kJsonClass #"_jsonClass"
#protocol JsonProtocol <NSObject>
- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);
#end
I've adapted that to Swift like this
let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"
protocol JsonProtocol
{
func convertToJSON() -> NSDictionary
init(json: NSDictionary)
}
One of the functions in the ObjC class runs the convertToJSON function for each object in an NSDictionary which conforms to the protocol, and another does the reverse, creating an instance of the object with the init function. The output dictionary also contains two keys, one denoting that the dictionary in question supports this protocol (kJsonSupport: BOOL), and another containing the NSString representation of the class the object was converted from (kJsonClass: NSString). The reverse function then uses both of these to determine what class the object was converted from to init a new instance from the given dictionary.
All of the classes are anonymous to the function itself. All we know is each class conforms to the protocol, so we can call our custom init function on it.
Here's what it looks like in ObjC:
Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:#protocol(JsonProtocol)])
{
Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
[arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];
}
However, I'm not sure how to make this behavior in Swift.
Here's my best attempt. I used Swiftify to try and help me get there, but the compiler isn't happy with it either:
let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol
{
weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
arr.add(jsonClass.init(json: obj))
}
I get several errors on both the weak var line and the arr.add line, such as:
Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type
'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type
Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'
Extraneous argument label 'json:' in call
Is there any way for me to instantiate from an unknown class which conforms to a protocol using a custom protocol init function?
You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.
The most important thing is that all the objects must be #objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.
You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:
#objc(Person) // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}
extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}
This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).
With that, in Swift, this code would be written this way:
if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}
The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.
Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.
I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.

Cannot convert value of type 'Option' to expected argument type '#noescape (Option) throws -> Bool'

I have a class called Option. This is a Realm object so it's a subclass of Realm's own base class Object.
In a view controller I have an array property that holds a bunch of Option objects.
private var options = [Option]()
In a method down the view controller, I need to check if a certain Option object is contained within the aforementioned options array.
Previously (Swift 1.2), I have the checking logic like this.
func itemSelected(selection: Object) {
let option = selection as! Option
if !contains(options, option) {
// act accordingly
}
}
Now I'm converting the project to Swift 2 (I have updated the Realm's version to Swift 2 version as well). I updated the code to this.
func itemSelected(selection: Object) {
let option = selection as! Option
if !options.contains(option) {
// act accordingly
}
}
But now I'm getting the following compile error!
Cannot convert value of type 'Option' to expected argument type '#noescape (Option) throws -> Bool'
I can't figure out why. Any ideas?
This is because the contains function now expects a closure rather than an element on all non-equatable types. All you need to do is change it to the following
if !(options.contains{$0==option}) {
// act accordingly
}
What this does is it passes a closure in to the function, which returns true only if that closure satisfies any of its elements. $0 stands for the current element in the array that the contains function is testing against, and it returns true if that element is equal to the one that you are looking for.
While the first answer, that indicates this issue occurs because the contains method needs to operate on an Equatable type, is true, that's only half the story. The Realm Object class inherits NSObject, which conforms to Equatable (thus this should work without a closure). For more discussion on this, you can refer to this issue on the Realm GitHub page: https://github.com/realm/realm-cocoa/issues/2519. The Realm developers indicate that they believe this is a bug in Swift.
Ultimately, the suggested workaround is to re-declare the conformance to Equatable and Hashable, like so (this is copied verbatim from GitHub user bdash's comment on the previously posted issue):
public class A: Object, Equatable, Hashable {
}
public func ==(lhs: A, rhs: A) -> Bool {
return lhs.isEqual(rhs)
}
You'd replace the all instances of type A with type Option in that sample.
I've tested this solution, and it works for me in XCode 7.2.1, using Swift version 2.1.1.

Cannot have a function with same name as parameter type

I have a class that conforms to an Objective-C protocol and has a function with the same name as one of it's parameter types.
class MessageDataController: NSObject, MCOHTMLRendererDelegate {
#objc func MCOAbstractMessage(msg: MCOAbstractMessage!, canPreviewPart part: MCOAbstractPart!) -> Bool {
return false
}
}
This causes Xcode to give the error
"Use of undeclared type 'MCOAbstractMessage'"
for using MCOAbstractMessage as both the function name and a parameter type. It doesn't give an error if I change the function name to abstractMessage or similar. I think the issue is related to this question and/or this issue but am unsure how to resolve. My project's header file is correctly configured to use MailCore2.
Tried changing the declaration to:
#objc(MCOAbstractMessage:canPreviewPart:) func abstractMessage(msg: MCOAbstractMessage!, canPreviewPart part: MCOAbstractPart!) -> Bool
which gives the error
"~/src/project/MessageDataController.swift:11:52: Objective-C method 'MCOAbstractMessage:canPreviewPart:' provided by method 'abstractMessage(:canPreviewPart:)' conflicts with optional requirement method 'MCOAbstractMessage(:canPreviewPart:)' in protocol 'MCOHTMLRendererDelegate'"
This may be solved by using the fully-qualified type name in the parameter list. I'm not familiar with the library you are using, but the suggestion below assumes that the type MCOAbstractMessage is declared in a module called MCO. Prepend MCO. to the type name.
class MessageDataController: NSObject, MCOHTMLRendererDelegate {
#objc func MCOAbstractMessage(msg: MCO.MCOAbstractMessage!, canPreviewPart part: MCOAbstractPart!) -> Bool {
return false
}
}
I tested this by adding a method called Array to one of my classes. Sure enough it threw compiler errors everywhere else that I had used Array as a type. I prefixed all of those as Swift.Array and all was well.
If you want a shorter version, use a typealias,

Swift Protocol Function Overloading

Is it possible to overload a protocol function and have the correct definition be called when dealing directly with the protocol type?
Here's some code to illustrate the issue
protocol SomeProtocol {
func doSomething<T>(obj: T)
}
class SomeClass : SomeProtocol {
func doSomething<T>(obj: T) {
print("Generic Method")
}
func doSomething(obj: String) {
print(obj)
}
}
let testClass = SomeClass()
testClass.doSomething("I will use the string specific method")
(testClass as SomeProtocol).doSomething("But I will use the generic method")
Edit: To clarify, the code works. I want to know why both calls do not use the string specific method.
Double Edit: Removed the intermediary dispatch class for a simpler example
Is this a bug, current limitation, or intended functionality? If this is intended, can someone please explain why?
Swift 2.0, Xcode 7.0
Answer
You can't overload a protocol function and expect the correct definition to be called. This is because the definition to call is picked at compile time. Since the compiler doesn't know the concrete type, it chooses the only definition known at compile time, doSomething<T>.
I tested your code here http://swiftstub.com/ and it worked fine.
First it prints "I will use the specific method" and then "Generic Method":
I will use the specific methodGeneric Method

Swift dynamic variable can't be of type Printable

I have a Swift project that contains two UITableViewControllers. The second UITableViewController is linked to a MVC model called Model. According to the UITableViewCell I select in the first UITableViewController, I want to initialize some properties of Model with Ints or Strings. Therefore, I've decided to define those properties with Printable protocol type. In the same time, I want to perform Key Value Observing on one of these properties.
Right now, Model looks like this:
class Model: NSObject {
let title: String
let array: [Printable]
dynamic var selectedValue: Printable //error message
init(title: String, array: [Printable], selectedValue: Printable) {
self.title = title
self.array = array
self.selectedValue = selectedValue
}
}
The problem here is that the following error message appears on the selectedValue declaration line:
Property cannot be marked dynamic because its type cannot be
represented in Objective-C
If I go to the Xcode Issue Navigator, I can also read the following line:
Protocol 'Printable' is not '#objc'
Is there any workaround?
There is no way to do what you want. Non-#objc protocols cannot be represented in Objective-C. One reason is that Non-#objc protocols can represent non-class types (and indeed, you said that you wanted to use it for Int and String, both non-class types), and protocols in Objective-C are only for objects.
KVO is a feature designed for Objective-C, so you must think about what you expect it to see from the perspective of Objective-C. If you were doing this in Objective-C, you would not want to have a property that could either be an object like id or a non-object like int -- you can't even declare that. Instead, as you said in your comment, you probably want it to be just objects. And you want to be able to use Foundation's bridging to turn Int into NSNumber * and String into NSString *. These are regular Cocoa classes that inherit from NSObject, which implements Printable.
So it seems to me you should just use NSObject or NSObjectProtocol.
Unfortunately ObjC does not treat protocols as types, they are just a convenient way of grouping members. Under the covers they are of type Any, so regretfully you will have to make the property Any and cast to Printable.
The best I can thing of is:
dynamic var selectedValue: Any
var printableValue : Printable {
get {
return (Printable)selectedValue
}
set {
selectedValue = newValue
}
}

Resources