1

I've been reading a lot about Swift's runtime lately, and became more and more interested in optimising my code using static method dispatch. This happens with the following methods:

  • struct methods
  • final class methods, i.e. declared with the final keyword, as private or in a final class
  • protocol methods that are defined in a protocol extension, without being declared in the protocol itself.

Problem is, non of these situations enables me to write testable code, at least not the way I do it now: injecting protocol entities that are replaced by mocks in unit testing.

So, is it possible to write testable code without giving up static method dispatch, and if so how does one go about it?

Thanks!

Yotam
  • 9,789
  • 13
  • 47
  • 68
  • 1
    Keep in mind that static dispatch is a compiler optimization, and tends to be very fragile. While it is a powerful tool for speeding up tight loops, adjusting your programming style to preserve across the whole program is a lot of work for very little payback. For the vast (overwhelming) number of method calls, it provides minimal performance improvement, and if you write your Swift simply and clearly, the compiler will do a very good job for you. If you find you need many mocks in the system, you likely have an overly-coupled system and should focus on that rather than static dispatch. – Rob Napier Nov 07 '18 at 14:52
  • The underlying point here is that attempts to second-guess the compiler to force static dispatch can backfire and lead to worse performance. Focusing on reducing the number of mocks will likely yield much better dividends than pursuing broad static dispatch with lots of mocks. – Rob Napier Nov 07 '18 at 14:54
  • That's very interesting, and somewhat disappointing at the same time, that you can combine the two. – Yotam Nov 13 '18 at 09:40

1 Answers1

2

Generics is what you look for. You can abstract over a protocol, but the compiler still knows what exact type you are using, so there's no need for dynamic dispatch.

protocol Dependency {
  func doSomething()
}

struct RealDependency: Dependency {
  func doSomething() {
    print("I'm doing real work")
  }
}

struct MockDependency: Dependency {
  func doSomething() {
    print("I'm the mock, so I do nothing")
  }
}

struct MyApp<D: Dependency> {
  let dependency: D

  func doSomething() {
    dependency.doSomething()
  }
}

let myAppReal = MyApp(dependency: RealDependency())
let myAppMock = MyApp(dependency: MockDependency())

myAppReal.doSomething() // Prints "I'm doing real work"
myAppMock.doSomething() // Prints "I'm the mock, so I do nothing"

However, note that in Swift, generics monomorphization is not guaranteed. So you might end with some form of dynamic dispatch anyway. See this link

  • This is a really good answer, and that last bit is so important. This may or may not even have better performance than a simpler dynamic dispatch solution using a class. (Large structs are in some cases slower to pass than large classes, overwhelming any benefits from static dispatch. On the other hand, structs can be faster than classes. There is no simple "always do this" rule.) – Rob Napier Nov 07 '18 at 15:01
  • A little late to the party, but you don't actually need Generics because you have the Protocol. Just declaring `let dependency: Dependency` works. – smat88dd Oct 11 '21 at 11:31
  • Also I was curious, @RobNapier mentioned that reducing coupling would result in less mocks. How would you de-couple? I found something about pure-functions, so of course that would work. Instead of having a class `NetworkAPI` you would just have variables for pure-functions? Like `var getUsers: ()->([User])` – smat88dd Oct 11 '21 at 11:36
  • But having those variables seems like a lot of boilerplate, though effective to reduce coupling and avoid mocks. Really curious about input! Thanks – smat88dd Oct 11 '21 at 11:38
  • 1
    @smat88dd https://www.youtube.com/watch?v=_m6DxTEisR8 – Rob Napier Oct 11 '21 at 12:56