Objective-C declares a class function, initialize()
, that is run once for each class, before it is used. It is often used as an entry point for exchanging method implementations (swizzling), among other things.
Swift 3.1 deprecates this function with a warning:
Method 'initialize()' defines Objective-C class method 'initialize', which is not guaranteed to be invoked by Swift and will be disallowed in future versions
How can this be resolved, while still maintaining the same behaviour and features that I currently implement using the initialize()
entry point?
A common app entry point is an application delegate's applicationDidFinishLaunching
. We could simply add a static function to each class that we want to notify on initialization, and call it from here.
This first solution is simple and easy to understand. For most cases, this is what I'd recommend. Although the next solution provides results that are more similar to the original initialize()
function, it also results in slightly longer app start up times. I no longer think it is worth the effort, performance degradation, or code complexity in most cases. Simple code is good code.
Read on for another option. You may have reason to need it (or perhaps parts of it).
The first solution doesn't necessarily scale so well. And what if you are building a framework, where you'd like your code to run without anyone needing to call it from the application delegate?
Define the following Swift code. The purpose is to provide a simple entry point for any class that you would like to imbue with behavior akin to initialize()
- this can now be done simply by conforming to SelfAware
. It also provides a single function to run this behavior 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 autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
That's all good and well, but we still need a way to actually run the function we defined, ie NothingToSeeHere.harmlessFunction()
, on application startup. Previously, this answer suggested using the Objective-C code to do this. However, it seems that we can do what we need using only Swift. 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
}
}
We now have an entry point at application startup, and a way to hook into this from classes of your choice. All that is left to do: instead of implementing initialize()
, conform to SelfAware
and implement the defined method, awake()
.
My approach is essentially the same as adib's. Here's an example from a desktop application that uses Core Data; the goal here is to register our custom transformer before any code mentions it:
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
AppDelegate.doInitialize
}
static let doInitialize : Void = {
// set up transformer
ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer)
}()
// ...
}
The nice thing is that this works for any class, just as initialize
did, provided you cover all your bases — that is, you must implement every initializer. Here's an example of a text view that configures its own appearance proxy once before any instances have a chance to appear onscreen; the example is artificial but the encapsulation is extremely nice:
class CustomTextView : UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame:frame, textContainer: textContainer)
CustomTextView.doInitialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
CustomTextView.doInitialize
}
static let doInitialize : Void = {
CustomTextView.appearance().backgroundColor = .green
}()
}
That demonstrates the advantage of this approach much better than the app delegate does. There is only one app delegate instance, so the problem isn't very interesting; but there can be many CustomTextView instances. Nevertheless, the line CustomTextView.appearance().backgroundColor = .green
will be executed only once , as the first instance is created, because it is part of the initializer for a static property. That is very similar to the behavior of the class method initialize
.
If you want to fix your Method Swizzling in Pure Swift way:
public protocol SwizzlingInjection: class {
static func inject()
}
class SwizzlingHelper {
private static let doOnce: Any? = {
UILabel.inject()
return nil
}()
static func enableInjection() {
_ = SwizzlingHelper.doOnce
}
}
extension UIApplication {
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
SwizzlingHelper.enableInjection()
return super.next
}
}
extension UILabel: SwizzlingInjection
{
public static func inject() {
// make sure this isn't a subclass
guard self === UILabel.self else { return }
// Do your own method_exchangeImplementations(originalMethod, swizzledMethod) here
}
}
Since the objc_getClassList
is Objective-C and it cannot get the superclass (eg UILabel) but all the subclasses only, but for UIKit related swizzling we just want to run it once on the superclass. Just run inject() on each target class instead of for-looping your whole project classes.
A slight addition to @JordanSmith's excellent class which ensures that each awake()
is only called once:
protocol SelfAware: class {
static func awake()
}
@objc class NothingToSeeHere: NSObject {
private static let doOnce: Any? = {
_harmlessFunction()
}()
static func harmlessFunction() {
_ = NothingToSeeHere.doOnce
}
private static func _harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
You can also use static variables since those are already lazy and refer them in your top-level objects' initializers. This would be useful for app extensions and the like which doesn't have an application delegate.
class Foo {
static let classInit : () = {
// do your global initialization here
}()
init() {
// just reference it so that the variable is initialized
Foo.classInit
}
}
If you prefer Pure Swift™! then my solution to this kind of thing is running at _UIApplicationMainPreparations
time to kick things off:
@UIApplicationMain
private final class OurAppDelegate: FunctionalApplicationDelegate {
// OurAppDelegate() constructs these at _UIApplicationMainPreparations time
private let allHandlers: [ApplicationDelegateHandler] = [
WindowHandler(),
FeedbackHandler(),
...
Pattern here is I'm avoiding the Massive Application Delegate problem by decomposing UIApplicationDelegate
into various protocols that individual handlers can adopt, in case you're wondering. But the important point is that a pure-Swift way to get to work as early as possible is dispatch your +initialize
type tasks in the initialization of your @UIApplicationMain
class, like the construction of allHandlers
here. _UIApplicationMainPreparations
time ought to be early enough for pretty much anybody!
@objc
NSObject
initialize
in categoryExample
Swift files:
//MyClass.swift
@objc class MyClass : NSObject
{
}
Objc files:
//MyClass+ObjC.h
#import "MyClass-Swift.h"
@interface MyClass (ObjC)
@end
//MyClass+ObjC.m
#import "MyClass+ObjC.h"
@implement MyClass (ObjC)
+ (void)initialize {
[super initialize];
}
@end
Here is a solution that does work on swift 3.1+
@objc func newViewWillAppear(_ animated: Bool) {
self.newViewWillAppear(animated) //Incase we need to override this method
let viewControllerName = String(describing: type(of: self)).replacingOccurrences(of: "ViewController", with: "", options: .literal, range: nil)
print("VIEW APPEAR", viewControllerName)
}
static func swizzleViewWillAppear() {
//Make sure This isn't a subclass of UIViewController, So that It applies to all UIViewController childs
if self != UIViewController.self {
return
}
let _: () = {
let originalSelector = #selector(UIViewController.viewWillAppear(_:))
let swizzledSelector = #selector(UIViewController.newViewWillAppear(_:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod!, swizzledMethod!);
}()
}
Then on AppDelegate:
UIViewController.swizzleViewWillAppear()
Init static stored property with closure
[static stored property with closure]
One more example to execute something once using
extension MyClass {
static let shared: MyClass = {
//create an instance and setup it
let myClass = MyClass(parameter: "parameter")
myClass.initialize()//setup
return myClass
}()
//() to execute the closure.
func initialize() {
//is called once
}
}
//using
let myClass = MyClass.shared
I think that is a workaround way.
Also we can write initialize()
function in objective-c code, then use it by bridge reference
Hope the best way.....
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.