class MyClass {
func firstFunc() {
callSomeTask(arg: "", completion: {
// must use `self` here, causes capture
self.anotherFunc()
})
callSomeTask(arg: "", completion: { [weak self] in
// another way, with `weak self`, too verbose
self?.anotherFunc()
})
callSomeTask(arg: "", completion: { [weak self] in
// another way, using a `static` func, verbose `Self`
Self.anotherStaticFunc()
})
}
func anotherFunc() {
// doesn't reference `self`
}
static func anotherStaticFunc() {
// doesn't reference `self`
}
}
I'm wondering whether a modifier like free for a function could be useful for these cases to reduce self capture, like so:
class MyClass {
func firstFunc() {
callSomeTask(arg: "", completion: {
// no `self`
anotherFunc()
})
}
free func anotherFunc() {
// doesn't reference `self`, checked by compiler
}
}
Notes: I'm aware that for structs, self capture is implicit now. But even in that case, it causes unnecessary memory pressure by copying/retaining for longer.
That is another solution, however, this causes the function locality to be somewhere else - it's considered good code style to locate local functions together if they're used together.
There is no reason for all your methods to need to be inside the type declaration itself. The standard library's practice is to declare a type with the minimum (stored properties and basic initializers) and then to close the declaration, and do the rest via extensions.
Once you do this, your local free functions can just be above your method declarations.
There are use cases where this doesn't work, for example if you're already in an extension:
extension SomeViewController {
struct SomeModel {
// ....
}
static func processSomething(_ input: String) -> String {
// how do I call this from `SomeModel` now?
}
}
There's definitely a way to solve this with extensions, but the side effect is fragmenting a source file into many tiny islands with their own declaration boilerplate, thus actually reducing co-locality on average.
Not to mention that extensions are a semantic construct, and not just a syntax feature.
I'm gonna try to redefine the problem:
There's currently a way to define an instance method within a type
There's currently a way to define a type method within a type
There's no way to declare a free function within a type
You can call it from anywhere in the module using SomeViewController.processSomething
Why can't you just use a static method? By "free" are you referring to a global function? Why would you define a global function within a type? That just doesn't make any sense.
This is precisely the over-verbosity I was referring to - since the call site is within SomeViewController - if it was a free function, the prefixing would have been unnecessary.
A free function is a function that isn't a method. It doesn't have any self. A global function would be a free function in the module scope, which isn't what I want.
I'm still unclear about one thing. In the following example, do I need an instance of MyType to call myFunc on? Or can I just call myFunc as if it was a global function?
struct MyType {
free func myFunc() { }
}
myFunc() // is this valid?
At first, I was skeptical of the idea, but I find the rationale to be reasonable. With such functions, we get the following:
no capture of self
no pollution of the type's enclosing namespace
no need for awkward or verbose identifiers to describe scope (MyType.myFunc(...))
It would allow the type definition to act as a namespace for otherwise free (read: global) functions. I don't believe there's a way to accomplish all 3 points today.
I think a new declaration syntax is overkill. I would prefer to be able to use static functions in instance scope without explicit namespacing.
(For compatibility, if this leads to ambiguity between instance and static methods, the instance method would be used with a warning and a fixit to add self. to disambiguate.)
Always found strange to use Self.staticMethod when there is no conflict. Most notably when passing method references like .map(Self.transform) being able to remove the Self when there is no conflict would be nice.
If you have a free function defined in MyType, then code located outside of MyTypestill has to prefix the call with either the type name or instance variable name, right? How is this any less verbose or awkward?
For code located within MyType, the only difference between a free and static method is the Self prefix. I think it would be a huge stretch to call this verbose or awkward. It's only five extra characters.
For these reasons, I think that adding a free function is completely unnecessary.
If that is the last brick in the wall, I would suggest using this strategy and then, outside of the extension, writing a free function that just calls through to this method. Then, You have the association and the free function, both.
As an aside, is what you want a pure annotation that can be used on methods? I admit that I am… fond of the idea so maybe I'm just seeing what I want to see but I think if that is what you want, you might have a different, if not less, frustrating time arguing for it with that phrasing.
I too have often come across this problem of wanting to limit visibility of a function to within a type, but have the function not be instance bound.
I think we should keep the static func but extend symbol lookup rules to accept the “naked” function name when calling a static function from a non-static context.
If a static and non-static function share names, it should cause an ambiguity error, and the proposed fixit should be to prepend with either self. or Self.
That would be a breaking change. Instead, the instance method should take priority over a static method. Furthermore, in the current version of Swift, instance methods already take priority over global methods.