Static vs class functions/variables in Swift classes?
static
and class
both associate a method with a class, rather than an instance of a class. The difference is that subclasses can override class
methods; they cannot override static
methods.
class
properties will theoretically function in the same way (subclasses can override them), but they're not possible in Swift yet.
Struct , class or enum for service object with static methods?
There are two key difference between a class and a struct in Swift. A class allows for inheritance and a class is a reference type while a struct is a value type.
Make your decision based on those few differences.
Since everything in Linker
is static, the difference between reference and value becomes irrelevant since you won't have any instances.
So that leaves inheritance. Will you ever subclass Linker
? If not, use a struct. If yes, use a class.
And now that you are asking about enum, you can probably rule that out over struct since Linker
doesn't appear to have any constants, just methods.
Do you usually use class or struct to define a list of utility functions?
I think a conversation about this has to start with an understanding of dependancy injection, what it is and what problem it solves.
Dependancy Injection
Programming is all about the assembly of small components into every-more-abstract assemblies that do cool things. That's fine, but large assemblies are hard to test, because they're very complex. Ideally, we want to test the small components, and the way they fit together, rather than testing entire assemblies.
To that end, unit and integration tests are incredibly useful. However, every global function call (including direct calls to static functions, which are really just global functions in a nice little namespace) is a liability. It's a fixed junction with no seam that can be cut apart by a unit test. For example, if you have a view controller that directly calls a sort method, you have no way to test your view controller in isolation of the sort method. There's a few consequences of this:
- Your unit tests take longer, because they test dependancies multiple times (e.g. the
sort
method is tested by every piece of code that uses it). This disincentivizes running them regularly, which is a big deal. - Your unit tests become worse at isolating issues. Broke the sort method? Now half your tests are failing (everything that transitively depends on the sort method). Hunting down the issue is harder than if only a single test-case failed.
Dynamic dispatch introduces seams. Seams are points of configurability in the code. Where one implementation can be changed taken out, and another one put in. For example, you might want to have a an MockDataStore
, a BetaDataStore
, and a ProdDataStore
, which is picked depending on the environment. If all 3 of these types conform to a common protocol, than dependant code could be written to depend on the protocol that allows these different implementations to be swapped around as necessary.
To this end, for code that you want to be able to isolate out, you never want to use global functions (like foo()
), or direct calls to static functinos (which are effectively just global functions in a namespace), like FooUtils.foo()
. If you want to replace foo()
with foo2()
or FooUtils.foo()
with BarUtils.foo()
, you can't.
Dependancy injection is the practice of "injecting" dependancies (depending on a configuration, rather than hard coding them. Rather than hard-coding a dependancy on FooUtils.foo()
, you make a Fooable
interface, which requires a function foo
. In the dependant code (the type that will call foo
), you will store an instance member of type Fooable
. When you need to call foo
, call self.myFoo.foo()
. This way, you will be calling whatever implementation of Fooable
has been provided ("injected") to the self
instance at the time of its construction. It could be a MockFoo
, a NoOpFoo
, a ProdFoo
, it doesn't care. All it knows is that its myFoo
member has a foo
function, and that it can be called to take care of all of it's foo needs.
The same thing above could also be achieve a base-class/sub-class relationship, which for these intents and purposes acts just like a protocol/conforming-type relationship.
The tools of the trade
As you noticed, Swift gives much more flexibility in Java. When writing a function, you have the option to use:
- A global function
- An instance function (of a struct, class, or enum)
- A static function (of a struct, class, or enum)
- A class function (of a class)
There is a time and a place where each is appropriate. Java shoves options 2 and 3 down your throat (mainly option 2), whereas Swift lets you lean on your own judgement more often. I'll talk about each case, when you might want to use it, and when you might not.
1) Global functions
These can be useful for one-of utility functions, where there isn't much benefit to " grouping" them in a particular way.
Pros:
- Short names due to unqualified access (can access
foo
, rather thanFooUtils.foo
) - Short to write
Cons:
- Pollutes the global name space, and makes auto-completion less useful.
- Not grouped in a way that aids discoverability
- Cannot be dependancy injected
- Any accessed state must be global state, which is almost always a recipe for a disaster
2) Instance functions
Pros:
- Group related operations under a common namespace
- Have access to localized state (the members of
self
), which is almost always preferable over global state. - Can be dependancy injected
- Can be overridden by subclasses
Cons:
- Longer to write than global functions
- Sometimes instances don't make sense. E.g. if you have to make an empty
MathUtils
object, just to use itspow
instance method, which doesn't actually use any instance data (e.g.MathUtils().pow(2, 2)
)
3) Static functions
Pros:
- Group related operations under a common namespace
- Can be dependancy in Swift (where protocols can support the requirement for static functions, subscripts, and properties)
Cons:
- Longer to write than global functions
- It's hard to extend these to be stateful in the future. Once a function is written as static, it requires an API-breaking change to turn it into an instance function, which is necessary if the need for instance state ever arrises.
4) Class functions
For classes, static func
is like final class func
. These are supported in Java, but in Swift you can also have non-finals class functions. The only difference here is that they support overriding (by a subclass). All other pro/cons are shared with static functions.
Which one should I use?
It depends.
If the piece you're programming is one that would like to isolate for testing, then global functions aren't a candidate. You have to use either protocol or inheritance based dependancy injection. Static functions could be appropriate if the code does not nned to have some sort of instance state (and is never expected to need it), whereas an instance function should be when instance state is needed. If you're not sure, you should opt for an instance function, because as mentioned earlier, transitioning a function from static to instance is an API breaking change, and one that you would want to avoid if possible.
If the new piece is really simple, perhaps it could be a global function. E.g. print
, min
, abs
, isKnownUniquelyReferenced
, etc. But only if there's no grouping that makes sense. There are exceptions to look out for:
If your code repeats a common prefix, naming pattern, etc., that's a strong indication that a logical grouping exists, which could be better expressed as unification under a common namespace. For example:
func formatDecimal(_: Decimal) -> String { ... }
func formatCurrency(_: Price) -> String { ... }
func formatDate(_: Date) -> String { ... }
func formatDistance(_: Measurement<Distance>) -> String { ... }Could be better expressed if these functions were grouped under a common umbrella. In this case, we don't need instance state, so we don't need to use instance methods. Additionally, it would make sense to have an instance of
FormattingUtils
(since it has no state, and nothing that could use that state), so outlawing the creation of instances is probably a good idea. An emptyenum
does just that.enum FormatUtils {
func formatDecimal(_: Decimal) -> String { ... }
func formatCurrency(_: Price) -> String { ... }
func formatDate(_: Date) -> String { ... }
func formatDistance(_: Measurement<Distance>) -> String { ... }
}Not only does this logical grouping "make sense", it also has the added benefit of bringing you one step closer to supporting dependancy injection for this type. All you would need is to extract the interface into a new
FormatterUtils
protocol, rename this type toProdFormatterUtils
, and change dependant code to rely on the protocol rather than the concrete type.If you find that you have code like in case 1, but also find yourself repeating the same parameter in each function, that's a very strong indication that you have a type abstraction just waiting to be discovered. Consider this example:
func enableLED(pin: Int) { ... }
func disableLED(pin: Int) { ... }
func checkLEDStatus(pin: Int) -> Bool { ... }Not only can we apply the refactoring from point 1 above, but we can also notice that
pin: Int
is a repeated parameter, which could be better expressed as an instance of a type. Compare:class LED { // or struct/enum, depending on the situation.
let pin: Int
init(pin: Int)? {
guard pinNumberIsValid(pin) else { return nil }
self.pin = pin
}
func enable() { ... }
func disable() { ... }
func status() -> Bool { ... }
}Compared to the refactoring from point 1, this changes the call site from
LEDUtils.enableLED(pin: 1)`
LEDUtils.disableLED(pin: 1)`to
guard let redLED = LED(pin: 1) else { fatalError("Invalid LED pin!") }
redLED.enable();
redLED.disable();Not only is this nicer, but now we have a way to clearly distinguish functions that expect any old integer, and those which expect an LED pin's number, by using
Int
vsLED
. We also give a central place for all LED-related operations, and a central point at which we can validate that a pin number is indeed valid. You know that if you have an instance ofLED
provided to you, thepin
is valid. You don't need to check it for yourself, because you can rely on it already having been checked (otherwise thisLED
instance wouldn't exist).
What is the difference between static func and class func in Swift?
Is it simply that static is for static functions of structs and enums, and class for classes and protocols?
That's the main difference. Some other differences are that class functions are dynamically dispatched and can be overridden by subclasses.
Protocols use the class keyword, but it doesn't exclude structs from implementing the protocol, they just use static instead. Class was chosen for protocols so there wouldn't have to be a third keyword to represent static or class.
From Chris Lattner on this topic:
We considered unifying the syntax (e.g. using "type" as the keyword), but that doesn't actually simply things. The keywords "class" and "static" are good for familiarity and are quite descriptive (once you understand how + methods work), and open the door for potentially adding truly static methods to classes. The primary weirdness of this model is that protocols have to pick a keyword (and we chose "class"), but on balance it is the right tradeoff.
And here's a snippet that shows some of the override behavior of class functions:
class MyClass {
class func myFunc() {
println("myClass")
}
}
class MyOtherClass: MyClass {
override class func myFunc() {
println("myOtherClass")
}
}
var x: MyClass = MyOtherClass()
x.dynamicType.myFunc() //myOtherClass
x = MyClass()
x.dynamicType.myFunc() //myClass
How do I declare a class level function in Swift?
Yes, you can create class functions like this:
class func someTypeMethod() {
//body
}
Although in Swift, they are called Type methods.
Why can't I use a private,internal,fileprivate method within a class/static method?
You are seeing an error because the privateMethod
in your screenshot is not a static method.
Static methods cannot call instance (non-static) methods of the class.
privateMethod
is static in your initial example code though? Changing ClassA to this should work, while keeping the private method hidden from ClassB:
class ClassA {
private static func privateMethod(append aString: String) -> String {
return "Appended String:" + aString
}
static func classMethod() -> String {
let theString = privateMethod(append: "random string")
return theString
}
}
Static Structs that Mutate Data in Swift
There seems to be a major misunderstanding of why one would use a struct, a singleton, etc.
These two statements from your post contradict each other:
First you say that you use structs "because copying data is safer than having multiple reference points to a single instance".
Then, however, you say that you "decided to make the properties and functions in the class static so that I did not have to instantiate and pass around".
So you chose a struct to make passing data around safer and then decided to avoid passing things around altogether.
Maybe that's what confused them: you used a language feature in a very untypical way.
There is not difference inbetweenusing a class
or a struct
when all all members are static. (Forget performance considerations, that's totally negligible in such a simple example.)
Coming back to your question, what they wanted to see is probably this:
struct Calculation {
var firstNumber: Double? = nil
var secondNumber: Double? = nil
var type: CalculationType = .none
}
extension Calculation {
var value: Double? {
// ... calculate it
}
}
That's a simple struct, just holding all the data that belongs to a calculation, plus a var to perform the calculation.
In your view controller, you would then define a didSet
handler in order to update the visualization:
var calculation = Calculation() {
didSet {
outputLabel.calculatedValue = calculation.value
}
}
The Swift magic that comes into play here is that whenever you reassign a member of self.calculation
the didSet
handler will automatically be executed (behind the scenes, a new Calculation
instance is created and assigned); so in your UI handlers you only need to forward the entered data (e.g. self.calculation.firstNumber = number
), which will in turn cause the output to get updated.
As I said, it's just a guess that that's what they meant with their critique. You can only know for sure when you ask ;)
Related Topics
Swift How to Cast from Int? to String
Speed Up Xcode Swift Build Times
Swift: Dictionary Access via Index
iOS 10 App Crashes When Trying to Save Image to Photo Library
How to Create a Hud on Top of My Scenekit.Scene
How to Use Environment Map in Arkit
Swift 3D Touch iOS 10 Home Screen Quick Actions Share Item Missing
Swift Package Manager - Swift 4 Syntax
Swift - How to Save Audio from Avaudioengine, or from Audioplayernode? If Yes, How
How to Install Package in Xcode via Swift Package Manager
Why Are Iboutlets Optionals After Swift 5 Migration
Difference Between Dispatchqueue Types in Swift
Checking If a Swift Class Conforms to a Protocol and Implements an Optional Function