Upon migrating a project to Swift 3.1, I was greeted by several warnings. None of them seemed particularly hard to fix, except for this one:
Method ‘initialize()’ defines Objective-C class method ‘initialize’, which is not guaranteed to be invoked by Swift and will be disallowed in future versions.
Ouch. Several classes were overriding NSObject’s initialize()
function, and the warning makes it clear that this is no longer OK (or at least, it won’t be very soon). A very similar method, load()
, hasn’t been available since Swift 1.2, so it seems that initialize will shortly follow suit. It performs a unique role, as outlined by the NSObject
documentation:
The runtime sends
initialize()
to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program.
Why use initialize()?
If you’re in the same situation, the first question to ask is probably ‘do I actually need this?’. If you don’t, move the logic somewhere else, save yourself the trouble, and call it a day. It’s not often that you really need the behaviour it provides, but without support for load()
since Swift 1.2, there are no longer any obvious alternatives. In my case, initialize()
was being used as an entry point for method swizzling (probably the most popular use case for initialize()
). Swizzling allowed me to patch a bug in iOS that had been the cause of several crashes. If you’ve never heard of method swizzling, don’t stress… I’d only suggest using this if exceptional circumstances require it.
A simple solution
Before taking this too far, a simple alternative to both load()
and initialize()
would be to execute your logic from application(_:didFinishLaunchingWithOptions:)
. I’d suggest keeping the logic contained in the relevant class, and you this from the application delegate. Functionally, this solution is more similar to load()
than initialize()
, but for most cases this is perfectly sufficient.
This is reasonably straight forward, and in most cases what I’d suggest doing for sake of simplicity. However, there are one or two small caveats. You may have a lot of classes you’d like to do this for, so doing this all from the application delegate would be a little unwieldy. Perhaps you don’t have access to the application delegate, or maybe you’d like the solution to look a little more structured than the current suggestion.
A not-so-simple solution
The following is also functionally closer to load()
than initialize()
. The goal was to define an easy way for classes to adopt a function, and have that function called before the class is used. Although not so simple internally, it scales well, and is completely self contained - negating any requirement for adding code to the application delegate.
First, define the following Swift code. The purpose is to provide a simple entry point for any class that you would like to imbue with initialize-like behaviour - this can now be done simply by conforming to SelfAware
. It also provides a single function to initiate this behaviour for every conforming class.
protocol SelfAware: class {
static func awake()
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass?>.allocate(capacity: typeCount)
let safeTypes = AutoreleasingUnsafeMutablePointer<AnyClass?>(types)
objc_getClassList(safeTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
We still need a way to run the function we defined, i.e. NothingToSeeHere.harmlessFunction()
, on application startup. You could call the this from your application delegate, but if you’d like to ensure that the solution is self contained, the following offers a way to do this for iOS. For macOS or other platforms where UIApplication
is not available, a variation of the following will be needed.
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
}
With this code in place, conforming to the protocol SelfAware
will grant any class the desired behaviour. Doing this is reasonably easy and structured, so although the inner-workings of this solution are ‘not-so-simple’, we now have a really neat way to add initialize-like behaviour to any class.