Why can't I reassign an instance method in Swift? - ios

So this question has an obvious answer: "because the compiler won't let you" but I'm hoping someone can explain to me why this part of Swift works the way it does.
I ran into this question because I was building a view controller that needed to satisfy a protocol (UIImagePickerControllerDelegate). The protocol requires a callback function to call after a user selected an image. I wanted to be able to change the callback behavior at runtime.
Coming from a Python background, I figured that should be easy: just define the callback method on my class to satisfy the protocol and then redefine it later by just reassigning to it. That works perfectly fine in Python:
class Foo(object):
def bar(self):
print "bar"
foo = Foo()
foo.bar() # output "bar"
def baz():
print "baz"
foo.bar = baz
foo.bar() # output "baz"
But it doesn't work in Swift (even though I can do very nearly the same thing by declaring a variable to hold a closure):
import UIKit
class Foo {
func bar() -> String {
return "bar"
}
var baz: ()-> String = {
return "baz"
}
}
let foo = Foo()
foo.bar() // output: bar
foo.baz() // output: baz
let gee = {
return "gee"
}
foo.baz = gee
foo.baz() // output: gee
foo.bar = gee // error: cannot assign to bar in foo
So the question...why does Swift work this way? It's clearly not because it's impossible to alter function routing at runtime (otherwise the closure assignment wouldn't work). My best guess is that it's analogous to the let/var distinction for variables and that using "func" is implicitly telling the compiler that a function should be immutable. And I grant that it may be better to make instance methods immutable by default. But it is annoying when we need to comply with rigid protocols from UIKit. At least it would be nice if I could use a variable to satisfy a function requirement in a protocol.
(For the curious: I worked around this issue by declaring an instance variable to hold a closure that can be reassigned. I then declared the required protocol function and made it do nothing but call the closure. Which might(?) cause a retain cycle, but works.)

The code the compiler is allowed to emit is fundamentally different.
Making a simple test, compiling with -Onone for readability, and disassembling with Hopper, you can see what's going on (comments added manually):
In the case of an "instance method"/function, they can be called after being looked up in the vtable — in this example, *(*rax + 0x48) and *(*rax + 0x70) are pointers to the functions, and they're passed rax (the object itself) as a parameter (this becomes self).
However in the case of a closure variable, *(*rax + 0x50) is a pointer to the getter for bar. The getter is called first, and returns the closure which is then called — (rax)(rdx).
So these are simply different things. If you have a modifiable property that stores a closure, then certainly you need to call the getter before you can call the closure (since the value could have changed by being set elsewhere). But simple function dispatch doesn't require the extra level of indirection.

Im not certain how functions and closures work behind the scenes in swift, but i would think a function in swift is basically like a function in c, its defined at runtime and thats it. its compiled and lives at a certain address in memory and anything referencing that function has to look at that memory address, and that cant change at runtime.
A closure i would see as like a function pointer + normal function combination in c. so its probably a limitation of the way they implemented the function in swift. in python maybe behind the scenes everything is implemented like a function pointer + normal function.
as to why swift didnt implement it like python, i think only someone who works at Apple could tell you that, but maybe there is some overhead with using everything like a closure instead of just plain functions, so they make you only use closures when needed and the rest should be functions.
also having functions immutable could be the reason why protocols work behind the scenes, maybe allowing you to change the function at run time would break the protocol system.
Im not sure if anyone here is really fit to answer this (besides an apple employee lurking here maybe), but this is my best guess

Related

Swift instance members & functions calls, could someone please clarify?

I'm practicing functions in an Xcode project (not playground, if that matters) and when I set the constant's function name to the above function's name, Xcode won't let me call it.
In other functions I've done the same thing I haven't had this error, I don't know why it's being triggered. I've tried putting the "let fun" constant into it's own struct but that just throws up a different error.
Your let fun = playstation(... is code. It is an executable command. Code consisting of an executable command cannot just live anywhere. It cannot exist just free-floating the way you have it. It must live in a method. For example you could put it inside your viewDidLoad method.
I am sure you know this, but I would like to say that learning the basics/fundamentals of the Swift language is really good if you use playgrounds for that or any other online IDEs which support Swift language.
Swift Playgrounds Experience is very, very different than what an iOS dev has to do later, i.e. when it actually comes to building an app.
Of course in app making, the Swift language is used, and one uses the knowledge he practiced using playgrounds. BUT!
When it comes to storyboards, ViewControllers, etc., one has to understand that this time, it is all about OOP (classes and structs).
It is about managing views, loading them, displaying UIView objects, implementing logic, saving/loading data...
So as people mentioned above, here you are creating an instance method and trying to use it as a regular function inside that Class.
To be able to use this function as you expect, you have to create an object/instance of this class, then you can call this function.
In this picture, you may see that using a static keyword also might be a solution. static func(){} means that this function belongs to the class itself. You access that func by using the full name of the type/class.
You cannot call an instance method to initialize this same exact instance property, so you will need to convert to a lazy var like so:
lazy var fun = playStation(game: "Monsters")
This way, the playStation(game: "Monsters") call will be triggered lazily, in the exact moment when the fun property will be called for the first time. This can be very useful, especially when performing more intensive tasks. The quirk with using lazy keyword is that it can only be used with var, which means the property can be mutated or set again - we lose immutability of let constants.
But maybe you do not want to store the fun value as an instance property and only want to get it once? You could then move it into the viewDidLoad implementation, so the fun is no longer an instance property but a local one, available only within the viewDidLoad method scope.
override func viewDidLoad() {
super.viewDidLoad()
let fun = playStation(game: "Monsters")
// do what you want with the `fun` value.
// maybe set it as text to some label?
myLabel.text = fun
}
You can read more about Lazy Initialization in this blogpost, also make sure to read the official documentation.

Can I trigger a compiler warning when there is an implicitly captured "self" in Swift

If I have some code that takes a closure, and self is implicitly captured, can I make it cause a compiler warning? Is there some flag I can add to the compiler command for this?
Not exactly. But there is something that addresses what's presumably the reason you'd want this warning — to prevent situation where a closure capturing self can cause a retain cycle...
The potential retain cycle from capturing self in a closure comes up when the closure is passed as a parameter to a function but is used after the function returns — that is, the lifetime of the closure (and anything captured in it) "escapes" the context in which it's written. When the closure escapes, it has to be sure it still has access to the things it uses (like self and any properties or methods thereof), so we get into memory management and possible retain cycles, [weak self]/strongSelf, etc.
With #noescape, you can declare that closures passed as parameters to a function will not escape the calling context, and as such don't require the extra memory management overhead for closures that do. And the compiler can apply some other optimizations that make running your function and its closure faster.
func someFunctionWithNoescapeClosure(#noescape closure: () -> Void) {
closure()
}
Within a #noescape closure, you can then access methods/properties on self without explicitly writing self., because you're not capturing self anymore. And, once you label a parameter #noescape, if you try to do anything that would permit it to escape (say, take the closure from the parameter and assign it to a stored property), you'd get a compiler error (not just a warning).
For the full writeup from Apple, see Nonescaping Closures in The Swift Programming Language. And this post provides an alternate perspective that might explain things further.
No, the only thing I can think of which can do anything similar is linting.
SwiftLint might be able to do something like that, and if not you can probably write your own rule for just such a thing.

Casting method parameters in Objective-C

Update
Okay, first of all, thank you all for the huge amount of activity. It seems that I did not phrase my question too well, since many of the answers got (rightfully) stuck on the id input parameter, and following poor design patterns, but it was merely an example. I'll add some context to my question:
Suppose that there are multiple different implementations for doSomethingWithParameter:, requiring a specific instance as input parameter
My class in the example will only ever get called with an instance of SpecificClass as input parameter
With these assertions, here is my assumption: Given, that you know the type of the parameter, there is no benefit in type checking and casting, just for the sake of extra safety.
Original post
Suppose I have a general method in my protocol declaration, which takes an id input parameter:
#protocol MyProtocol <NSObject>
- (void)doSomethingWithParameter:(id)inputParameter;
#end
In a class, which conforms to MyProtocol, I usually prefer making the type of inputParameter explicit like so:
- (void)doSomethingWithParameter:(SpecificClass *)inputParameter
{
/... do something with param
}
Occasionally I received critique for choosing this solution, as opposed to the following:
- (void)doSomethingWithParameter:(id)inputParameter
{
if ([inputParameter isKindOfClass:[SpecificClass class]]) {
SpecificClass *myInstance = (SpecificClass *)inputParameter;
/... do something with param
}
}
I really prefer the first version, since it clearly states the parameter my instance is expecting. It is more concise, and clear. I generally don't think I can gain much from type checking/casting.
My question: from a coding standard standpoint, which one is the better solution? Does the first one have any disadvantages?
Update
From the update to your question, it seems that you are trying to achieve some variation of a functionality provided by the generics in modern languages.
Since Objective-C does not support this pattern, you can either sacrifice type safety, or rethink your design decisions.
If you go the first way, you should make it really clear by other means (naming, documentation) what types are you expecting. Then it might be reasonable to assume that your method will only be called with proper params.
But I would still add NSParameterAssert to simplify future debugging.
Original Answer
If you are using the first approach, you have a mismatch between declaration and definition of the method. Due to dynamic nature of obj-c (method signature does not include types of parameters), compiler does not complain about it.
However, when calling the method, only declaration is visible, so any information about the type of parameters is derived from that - all the type checking (yes, here compiler does it) is performed based on declaration.
In conclusion, to avoid confusing bugs and misuse of API, you should definitely use the second approach. Or change declaration together with definition.
Edit
Also, I can think of third solution, that somewhat merges convenience of the first approach with type safety of the second one:
- (void)doSomethingWithParameter:(SpecificClass *)inputParameter
{
NSParameterAssert([inputParameter isKindOfClass:[SpecificClass class]]);
// do something
}
First of all, when you use id for a parameter type that means either that type may vary or you may invoke method with ambiguous parameter. For both cases, second one is preferred as it checks type and prevents unwanted crash.
If you prefer the type of inputParameter explicit then simply define it in the protocol, like
#protocol MyProtocol <NSObject>
- (void)doSomethingWithParameter:(SpecificClass *)inputParameter;
#end
and for this forward declaration you may have to import module/class, like
#import "SpecificClass.h" // import class
OR
#class SpecificClass; // import module
What you do is perfectly fine. If your method is called with a parameter that is an instance of the wrong class, that is a bug in the caller. In Objective-C, you don't work around bugs, you make them crash your code, and then you fix the bug (that is why nobody handles exceptions, exceptions are bugs in your code and when they crash your code, the cause of the exception needs to be fixed).
This is much more common when you pass blocks, for example a block testing array elements, where you know exactly what type of array to expect.

Are class-based Swift Constants Forced to be Optional

I'm very new to Swift development, so apologise if this is a really silly question - having read Apple's guide, I am surprised by the behaviour I have come across (and a little miffed).
I am creating a really simple class which defines a number of uninitialised constants at the beginning of the class. When the class is initialised, the values are set. It seems as though I cannot do this unless I declare the constants to be optional, even though they are not optional and are all set in the constructor. For example:
class TestClass {
private let urlAddress:String
init(urlAddress: String) {
self.urlAddress = getUrlAddressWithProto("http", urlAddress:urlAddress)
}
func getUrlAddressWithProto(proto: String, urlAddress:String) -> String {
return "\(proto)://\(urlAddress)/"
}
}
The function getUrlAddressWithProto returns a non-optional String but Xcode throws the error:
error: variable 'self.urlAddress' used before being initialized
Does anyone have any thoughts firstly on why this is the case and if there is a suitable alternative to declaring all my constants as optional?
The compiler probably wants to tell you that you are not allowed to call that function before all non-optional variables are initialized. Sometimes the error messages are not very obvious.
Swift prevents the use of instances that are in an inconsistent state. Swift does not know that the function you call doesn't rely on internal state, so you can't call instance functions before init is done at all.
You have a couple of options, besides marking the variable as optional.
Turn that function into a class function. Since class functions are not bound to an instance you can call them before the init of the instance is done
move the code from the function to the initializer
Turn the variable into an implicitly unwrapped optional. (i.e. let urlAddress: String!). You can use that variable as if it were a non-optional variable. Just make sure that you set it during init. If you do something with it while it is nil the program will abort.
Depending on your real code the last one is probably the only viable option.

What is the swift equivalent to _cmd?

I want to get current method name to use in a format message similar to this one
[NSExeception raise:NSInternalInconsistencyException format:#"You must override %# in a subclass", NSStringFromSelector(_cmd)]
Also, I want to use _cmd to set associated object. I appreciate any idea.
NSStringFromSelector(_cmd); // Objective-C
print(#function) // Swift 4, 5
There is no Swift equivalent of _cmd. There is little reason to use it in Swift.
Consider _cmd in Objective-C. When is it useful? Most of the time, the value of _cmd would be the same as the name of the method that the code is in, so it is already known at compile time, and there is no need for a runtime value. Here are some possible cases when _cmd is useful in Objective-C:
In a macro. The macro is expanded in the code, so if _cmd appears in the macro, then it is inserted into the source where it is used, and so the method name can be used inside the macro. However, such macros do not exist in Swift. Plus macros are compile-time, so a compile-time mechanism like __FUNCTION__ would work similarly.
You can define a C function that takes self and _cmd, and use it (the same function) as the implementation of multiple methods, by adding it using class_addMethod and class_replaceMethod, and the _cmd inside the function will help distinguish between the different method calls. However, class_addMethod and class_replaceMethod are not available in Swift.
Method swizzling is also a process that messes with the implementation of a method. Since in swizzling you swap the implementations of two methods, _cmd will help reveal the actual method name used in the call, which may not match the method that the code is in in the source code, since implementations are swapped. I guess method swizzling may still be possible in Swift, since method_exchangeImplementations is still available in Swift. But in method swizzling, the method you swap in is tailored for the method it is swapping with, so if it is called, there is no ambiguity which method name is being called.
In the case where you manually get the IMP (implementing function) of a method, and manually call it with a different selector. In this case, the inside of the function can see the different selector in _cmd. However, you don't have to worry about this in Swift because the methods that get the IMP are unavailable.

Resources