I wanted to declare a factory to help me create instances of singleton classes for testing, without the overhead of clearing the class-level state between test runs. My thought was to use a factory to return a class declared inside a function.
My assumption was that the class would be created on the fly, and would be retained only for as long as the returned reference was maintained. For example:
protocol TestClassFactory {
static func make() -> TestClass.Type
}
protocol TestClass {
static var testValue: String { get set }
}
class Factory: TestClassFactory {
static func make() -> TestClass.Type {
class Tester: TestClass {
static var testValue = "Unmodified"
}
return Tester.self
}
}
However, in practice, this didn't work as expected. Specifically, a static value set on the first make() return value persisted into the second.
var testClass1 = Factory.make()
print(testClass1.testValue) // "Unmodified"
testClass1.testValue = "Modified"
print(testClass1.testValue) // "Modified"
var testClass2 = Factory.make()
print(testClass2.testValue) // "Modified" 🤨
The Swift runtime appeared to retain the class in its just like a class declared and referenced at compile time.
My questions:
Swift is capable of creating new types at runtime, but I don't think that it's currently possible to use that capability as a language user.
In Swift, not all statements are executable: there is code that is evaluated at compile-time only and results in no executable code. Class statements are one of these. The class scope is not evaluated at runtime to create a new class: the compiler sees a class statement, builds that class, and gives you a static reference to that class going forward. That way, no code is executed at your program's startup (or at any other time, for that matter) when you create a class. This contrasts to other languages, such as Python, where every statement is inherently executable, and executing a class statement actually creates a class.
This behavior is emphasized by errors when you try to use local variables inside of function-local classes:
func foo(int: Int) {
class Bar {
let f = int
// error: class declaration cannot close over value 'int' defined in outer
// scope
}
}