Swift Variable Constraints - ios

Is it possible in the current iteration of Swift (2.1) to add constraints on a variable type?
If I have a class Element
class Element: NSObject {
var type: ElementType
init(type: ElementType) {
self.type = type
}
}
with an enum ElementType
enum ElementType {
case Cell
case Header
case Footer
case Decoration(kind: String)
}
in some other class if I have a variable of type Element, is it at all possible to put constraints like:
var element: Element? where Self == .Header
or instead would I have to override didSet
var element: Element? {
didSet {
if let value = element where value.type == Decoration {
element = Optional.None
}
}
}
I'm sure it's not possible, the generic system isn't as powerful as I would like (being able to only constrain extensions by protocol and class inheritance for example and no variadic generic parameters).

Type constraints and types checks are a compilation feature but you want to check the runtime value of an object.
I am pretty sure that's impossible. You will have to use a runtime check (e.g. willSet or didSet).
Or, instead of enforcing the type by the value of a type atrribute, enforce it with a real type, e.g.
subclassing
class Header: Element
or make Element a protocol implemented by four separate classes:
protocol Element {
}
class Header: Element
(you can use protocol extension for shared functions).

Related

Calling class properties from within protocol extension

In using protocol extension for default implementation I encounter a problem. I define a protocol with different optional properties and functions to use. In the extension I implement a default function and nil properties. In the implementation I use properties and functions from within the protocol.
On the device it works how I expect. Also on the debugger it gave me the property from the class when I run into breakpoint within the extension implementation.
Can someone help me out why I didn't get the property in the example from the class but the nil property from extension.
Example from playground
import UIKit
protocol Collectable {
var item: String? { get }
func collect()
}
extension Collectable {
var item: String? { return nil }
func collect() {
if let some = item {
print("collect \(some)")
} else {
print("nothing")
}
}
}
class Car: Collectable {
var item = "letter"
}
let car = Car()
car.collect()
In the protocol your item is optional string, while in your class you declared another variable named also item. Your collect function is looking for the optional item, and in the extension you specified that it should return nil.
As #SavcaMarin stated in their answer, you have a type conflict between the item property declared in your extension (String?) and the one in your Car (String).
You can see this if you comment the extension. The compiler will tell you that Car does not conform to Collectable. If you ask it to create the required stubs it will create func collect and var item: String?. It then gives an error that item is re-defined.
As Rob and Matt pointed out in comments on the other answer, this seems to be a bug in the Swift compiler. It doesn't indicate the re-definition of item or the non-conformance of your item to the protocol if there is a default implementation that satisfies the protocol conformance (your extension).
The simple fix is to tell Swift that the item in Car is the same as item in Collectable by declaring the type explicitly (String?) rather than relying on type inference (Which gives String):
class Car: Collectable {
var item:String? = "letter"
}

Dependency injection with associated types causing arguments without type names (undescores) in Swift

The situation is following: I'm using a protocol to inject dependencies and the best way I found to implement this in Swift is to use the associatedtype keyword. I am also using protocol composition since some implementations of TestProtocol need more than one dependency.
protocol TestProtocol: class {
associatedtype Dependencies
func inject(_ dependency: Dependencies)
}
protocol HasSomething {
var something: Something { get set }
}
protocol HasSomethingElse {
var somethingElse: SomethingElse { get set }
}
To use this I found that I'll need to use generics like this:
class TestService<T> where T: TestProtocol, T.Dependencies == TestService {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
self.testProtocol?.inject(self)
}
}
Now when I want to use this service somewhere else and I'm trying to initiate it I get following problem:
The parameter is displayed as _ and not as the protocol name TestProtocol.
Let's say I would use this code in a library. How would a user know (without reading the documentation of course) what type could be used in this context when he is not even knowing what protocol he has to implement?
Is there a better way on how to use dependency injection with the type actually being displayed to the user, or am I doing something wrong in the where clause of the TestService class, or is this simply not possible in the current versions of Swift?
There is nothing wrong with your code, this is simply not possible.
class TestService<T> where T: TestProtocol
The where clause means T could be anything, with the constraint that the given object must conform to TestProtocol.
The Xcode autocomplete feature only displays the resolved type when available, but it doesn't show the constraints on a generic, and unfortunately there is nothing you can do about that.
You have the exact same issue in the swift standard library, with Dictionary for example
public struct Dictionary<Key, Value> where Key : Hashable {
public init(dictionaryLiteral elements: (Key, Value)...) {
// ..
}
}
The generic Key as a constraint to Hashable, but Xcode still shows _ in the autocomplete list.
I guess Swift developers are use to this behaviour, so it won't be a big issue, even if your code is embedded in a library.
How would a user know (without reading the documentation of course) what type could be used in this context when he is not even knowing what protocol he has to implement?
Because Xcode is pretty clear about the protocol requirement.
If I try to initialize the TestService with a String I'll get the error:
Referencing initializer 'init(with:)' on 'TestService' requires that 'String' conform to 'TestProtocol'
Which is pretty self explanatory.
Actually at the time of init(with testProtocol: T) Compiler doesn't know about T of course because it is generic
if you provide directly class it will show you in suggestion
For example
class TestService<T:Something> {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
}
}
Now you will see compiler know that it need SomeThing at T
For your case For TestProtocol You can replace with with something user readable world. for next time compiler will give you provided type as suggestion
For Example
class TestService<T:TestProtocol> {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
}
func add(t:T) {
}
}
class Test {
init() {
let t = Something()
let ts = TestService(with: t)
}
}
In Test class you can type ts.add now it knows

Casting subclass from base class where subclass has generic in Swift

I have a base class type and a subclass type, where the subclass includes a generic type. If I have the subclass stored in the form of the base class type but I would like to type cast it back to the subclass type, Swift won't seem to let me.
Below is an example of what I mean:
class Base {
}
class Next<T> : Base where T : UIView {
var view: T
init(view: T) {
self.view = view
}
}
let a: [Base] = [Next(view: UIImageView()), Next(view: UILabel())]
for item in a {
if let _ = item as? Next {
print("Hey!")
}
}
Why is "Hey!" never printed?
EDIT:
"Hey!" is printed if the cast reads:
if let _ item as? Next<UIImageView> ...
but only for the case of the UIImageView class.
and
"Hey!" is printed if one of the items in the array a is:
Next(view: UIView())
Ideally, I would like to not know what type the generic is when casting, but I realise this may not be possible.
The generic Next<T> is a template of sorts that creates unique separate classes. Next<UIView> and Next<UIImageView> are two completely unrelated types.
You can unite them with a protocol:
class Base {
}
class Next<T> : Base where T : UIView {
var view: T
init(view: T) {
self.view = view
}
}
protocol NextProtocol { }
extension Next: NextProtocol { }
let a: [Base] = [Next(view: UIImageView()), Next(view: UILabel()), Base()]
for item in a {
if item is NextProtocol {
print("Hey!")
} else {
print("Only a Base")
}
}
Output:
Hey!
Hey!
Only a Base
By defining a protocol such as NextProcotol that all classes derived from Next<T> conform to, you can refer to them as a group and distinguish them from other classes that derive from Base.
Note: To check if an item is of a type, use is instead of checking if the conditional cast as? works.
This is where protocols comes to help.
you need a dummy protocol to confirm Next to it
And use that dummy protocol to check your items types.
Therefore we create a dummy protocol confirm Next to it and use that protocol to compare items.
code would be something like this.
class Base {
}
class Next<T> : Base where T : UIView {
var view: T
init(view: T) {
self.view = view
}
}
protocol MyType {
}
extension Next: MyType {}
let a: [Base] = [Next(view: UILabel()), Next(view: UILabel())]
for item in a {
if let _ = item as? MyType {
print("Hey!")
}
}
UIView is super class for all the UIElements.
saying this will result into true,
if let _ = UILabel() as? UIView {print("yes") }
Next does not confirm to UIView but rather it requires something that confirm to UIView therefore you can't use the subs of UIView to check if they are Next, next have no exact type,
here we use the dummy protocol above !
When I saw your example I was expecting it to fail during compilation.
It seems that as long as you specify a concrete base class for your T (in this case UIView), then the compiler can infer that class in as or is statements. So when you type item as? Next the compiler understands item as?Next` because UIView is a concrete type.
This would not work if instead of UIView you would use a protocol. Also, simply using Next instead of Next<UIView> (or Next<concrete subclass of UIView>) will result in a compiler error outside an is or as statement.

Typhoon with view controller property

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))
}
}

Swift: 'var' declaration without getter/setter method not allowed here

I've tried to declare IBOutlet property on extension of class. But it give error as
'var' declaration without getter/setter method not allowed here
class ExampleView : UIView
{
}
extension ExampleView
{
#IBOutlet var btn1, btn2 : UIButton // here I got error.
}
Please any one suggest me correct way to do it?
From Extensions -> Computed Properties in The Swift Programming Language
NOTE
Extensions can add new computed properties, but they cannot add stored
properties, or add property observers to existing properties.
Addition in response to twlkyao's comment: Here is my implementation of the absoluteValue property of a Double
extension Double {
var absoluteValue: Double {
if self >= 0 {
return self
} else {
return -self
}
}
}
// Simple test -> BOTH println() should get called.
var a = -10.0
if (a < 0) {
println("Smaller than Zero")
}
if (a.absoluteValue > 5) {
println("Absolute is > 5")
}
From The Swift Programming Language:
Extensions in Swift can:
Add computed properties and computed static properties
Define instance methods and type methods
Provide new initializers
Define subscripts
Define and use new nested types
Which means you can't add IBOutlets and other stored properties.
If you really want to cheat, you can create global vars or a bookkeeping object which would allow you to query these vars or the object in order to add those properties (and have them be computed properties).
But it seems like it would go against the best practices. I would only do it if there's absolutely no other way.

Resources