Swift 2.0, closure = function() - ios

Hey I have a question that I cant seem to get it work
I have closure type
public typealias VoidCompletionHandler = ()->Void
Then I create a optional variable
var favouritePropertyStateHandler:VoidCompletionHandler?
Then I call it
self.favouriteCellStateHandler?()
All works good
self.propertyModel?.favouritePropertyStateHandler = { self.favouriteStateChanged() }
Woks perfectly with fucntion type
func favouriteStateChanged()->Void
But why cant I just
self.propertyModel?.favouritePropertyStateHandler = self.favouriteStateChanged()
Types match - both are ?
()->Void
The error I get is
Cannot assign a value of type 'Void' ('aka '()') to a value of type 'VoidCopletionHanlder?'
Solved
self.propertyModel?.favouritePropertyStateHandler = self.favouriteStateChanged
However that creates another problem, how do I not cause eternal retain cycle? if I want self to be weak?

You're assigning the result from self.favouriteStateChanged(), not the function itself. Try
self.propertyModel?.favouritePropertyStateHandler = self.favouriteStateChanged
instead.

Related

Using init as a Closure

Recently I saw the following code line in a book (about CoreData)
return modelURLs(in: modelName).compactMap(NSManagedObjectModel.init)
I know what the code does but the question is: Why and how does it work?
There should be a closure as the argument of the compactMap function but there's only a "NSManagedObjectModel.init" in NORMAL parenthesis. What's the secret about it? What is it doing there? I would understand it if there's a static/class property called init which returns a closure but I don't think there is.
Unfortunately the book doesn't say more about this line of code. I would like to have further readings from the apple docs but I can't find anything. When I make a google search about "init in closures" then I don't get helpful results.
So you guys are my last hope :)
By the way: the function modelURLs(in: modelName) returns an Array of URLs but that's not really important here.
When using closures different syntax can be used as in the below example that converts an int array to a string array
let array = [1, 2, 3]
The following calls to compactMap will all correctly convert the array and generate the same result
let out1 = array.compactMap({return String($0)})
let out2 = array.compactMap({String($0)})
let out3 = array.compactMap {String($0)}
let out4 = array.compactMap(String.init)
When there are two init methods that takes the same number and types of argument then you must add the full signature for the init method to use. Consider this simple example struct
struct TwoTimesInt: CustomStringConvertible {
let value: Int
let twiceTheValue: Int
var description: String {
return "\(value) - \(twiceTheValue)"
}
init(value: Int) {
self.value = value
self.twiceTheValue = 2 * value
}
}
With only 1 init method we can do
let out5 = array.compactMap(TwoTimesInt.init)
But if we add a second init method
init(twiceTheValue: Int) {
self.value = twiceTheValue / 2
self.twiceTheValue = twiceTheValue
}
Then we need to give the full signature of the init method to use
let out6 = array.compactMap( TwoTimesInt.init(value:) )
Another thing worth mentioning when it comes to which method is selected is to look at the full signature of the init method including if it returns an optional value or not. So for example if we change the signature of the second init method to return an optional value
init?(twiceTheValue: Int) {
self.value = twiceTheValue / 2
self.twiceTheValue = twiceTheValue
}
then compactMap will favour this init since it expects a closure that returns an optional value, so if we remove the argument name in the call
let out7 = array.compactMap(TwoTimesInt.init)
will use the second init while the map function on the other hand will use the first init method if called the same way.
let out8 = array.map(TwoTimesInt.init)

Error "nil requires a contextual type" using Swift

I'd like to achieve the following in code:
class MyService {
let mySubject = BehaviorSubject<MyData>(value: nil)
//....
}
Unfortunately, I get the "nil requires a contextual type" error. I want the subject to be "empty" till I actually put something in there. How can I pass nil as the argument then? Can I cast it to my own type to make it work?
Based on the reference for RxSwift BehaviorSubject, the init(value:) initializer is declared as
public init(value: Element)
Where the value parameter is described as:
value
Initial value sent to observers when no other value has been received
by the subject yet.
And where Element is the placeholder type of BehaviorSubject:
public final class BehaviorSubject<Element> ...
This means you need to specify the placeholder type Element as an Optional type if you are to be able to set the initial value (used when no other value has been received) to nil. E.g.:
class MyService {
let mySubject = BehaviorSubject<MyData?>(value: nil)
//....
}
Or, letting the compiler infer the placeholder as MyData? by using the non-sugared .none form for the nil argument:
class MyService {
let mySubject = BehaviorSubject(value: Optional<MyData>.none)
//....
}
As for understanding the actual error message better, consider the following self-contained example:
struct Foo<T> {
init(value: T) {}
}
struct Bar {}
let bar = Bar()
_ = Foo<Bar>(value: bar) // OK
_ = Foo(value: bar) // OK, T inferred as Bar
_ = Foo<Bar>(value: nil) // Error: error: 'nil' requires a contextual type
_ = Foo<Bar?>(value: nil) // OK
_ = Foo(value: Optional<Bar>.none) // OK, T inferred as Bar?
While dfri's answer is technically correct, you might want to consider a different type when working with RxSwift. Since you want your subject to be empty only at the beginning, I'd suggest to use ReplaySubject or PublishSubject.
A similar question has also been asked on RxSwift's GitHub issue page. Allow BehaviorSubject without initial value. There, kzaher suggests the ReplaySubject.
Your subject would then look like this, without any initial value and without MyData being Optional.
let subject = ReplaySubject<MyData>().create(bufferSize: 1)

Lazy var giving 'Instance member can not be used on type' error

I had this error several times now and I resorted to different workarounds, but I'm really curious why it happens. Basic scenario is following:
class SomeClass {
var coreDataStuff = CoreDataStuff!
lazy var somethingElse = SomethingElse(coreDataStuff: coreDataStuff)
}
So I understand I can not use self before class is fully initialised, but in this case I'm using self property coreDataStuff to initialise a lazy var which will not happen until my instance is ready.
Anybody could explain me why I'm getting
Instance member can not be used on type error?
Try that :
class SomeClass {
var coreDataStuff = CoreDataStuff!
lazy var somethingElse: SomethingElse = SomethingElse(coreDataStuff: self.coreDataStuff)
}
It is important to precise the type of your lazy var and to add self. to the argument you pass
There are two requirements that are easily overlooked with lazy variables in Swift, and, unfortunately, the warnings are cryptic and don't explain how to fix it.
Lazy Variable Requirements
Use self.: When referring to instance members, you must use self.. (E.g. self.radius.)
If you forget to use self., you'll get this error:
Instance member 'myVariable' cannot be used on type 'MyType'
Specify the type: The type cannot be inferred, it must be explicitly written. (E.g. : Float.)
If you forget to specify the type, you'll get this error:
Use of unresolved identifier 'self'
Example
struct Circle {
let radius: Float
lazy var diameter: Float = self.radius * 2 // Good
//lazy var diameter = radius * 2 // Bad (Compile error)
}

Assigning Closure to variable in Swift causes 'variable used before being initialized'

I have a problem with a closure that is meant to be created and then being executed within another function over the range of the 2D pixel raster of an image where it shall basically called like this:
filter(i,j) and return a value based on its arguments.
I thought this code should work but it complains that the closure variable I have created is not initialized. I guess that means that I did not gave it arguments, but I wont within this function as the data is known to the closure at the time when it interacts with the image. How can I setup a closure which does not care about initialization?
Thank you in advance :)
func processFilter(type:FilterType){
var x = 0
var y = 0
//create cloure
var closure:(i:Int, j:Int)->Int
if(type == FilterType.MyFilter) {
x = 1024
y = 2048
func filter(i:Int, j:Int)->Int {
return i*j*x*y*4096
}
//compiler does not complain here...
closure = filter
}
//other if statements with different closure definitions follow...
//This call throws error: variable used before being initialized
let image = filterImage(closure)
}
You use the variable closure before the compiler is certain that it is initialized. You can solve this in 2 ways, depending on what you need:
Add an else-clause to your if and set closure to a default closure.
Make closure optional by defining it as var closure: ((i: Int, j: Int) -> Int)? and then you can check if it is optional before using it by using closure?(i, j) or if let filter = closure { filter(i, j)}.
Also, try to use better variable names such as filterClosure. closure on its own doesn't really say much.
The problem is that you define your closure as:
var closure:(i:Int, j:Int)->Int
Then you initialize it only if you enter the if
If not, that var is not initialized, hence the compiler warning
Possible solution:
func processFilter(type:FilterType){
var x = 0
var y = 0
//create cloure
var filterClosure:((i:Int, j:Int)->Int)?
if(type == FilterType.MyFilter) {
x = 1024
y = 2048
func filter(i:Int, j:Int)->Int {
return i*j*x*y*4096
}
//compiler does not complain here...
filterClosure = filter
}
//other if statements with different closure definitions follow...
if let closure = filterClosure {
let image = filterImage(closure)
}
}
Your closure is only initialized if the code enters your if block (i.e. if type == FilterType.MyFilter). In the other case it is left uninitialized.

Cannot assign a value of type '[Thing]' to a value of type '[Any]'

I'm struggling with what seems like a simple Swift problem.
I've declared a struct with a static function that returns some instances:
struct Thing {
static func allTheThings() -> [Thing] {
...
}
}
I've got a CustomViewController with an property declared:
var objects = [Any]()
In the subclass of that controller in viewDidLoad(), I'm trying to assign the objects property.
objects = Thing.allTheThings()
But I'm getting a compiler error
Cannot assign a value of type '[Thing]' to a value of type '[Any]'
Isn't that the whole point of Any?
This works:
objects = Thing.allTheThings().map { $0 }
But this doesn't
let things = Thing.allTheThings().map { $0 }
objects = things
Any ideas what's going on here?
It seems Swift can convert Thing to Any but not [Thing] to [Any].
The reason this works
objects = Thing.allTheThings().map { $0 }
is that the compiler can infer that the type of $0 is Any, but in the second example
let things = Thing.allTheThings().map { $0 }
it infers $0 to be of type Thing. You end up with the variable things being of type [Thing], which means that the assignment
objects = things
will mean a conversion from [Thing] to [Any] which does not work.

Resources