Swift 3.1 depreciates initialize (). How can I achieve the same?

Objective-C declares an initialize() class function that runs 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 invalidates this function with a warning:

The 'initialize ()' method defines an Objective-C method of the 'initialize' class, which is not guaranteed to be called by Swift and will be prohibited in future versions

How can this be solved while maintaining the same behavioral functions and functions that I currently implement using the initialize() entry point?

+40
ios objective-c swift
Mar 16 '17 at 3:03 on
source share
9 answers

Simple / Easy Solution

A common entry point to the application is the delegate applicationDidFinishLaunching . We could just add a static function to each class that we want to notify about initialization, and call it from here.

This first solution is simple and easy to understand. In most cases, this is what I would recommend. Although the following solution provides results that look more like the original initialize() function, it also leads to a slightly longer application startup time. I no longer think it is worth the effort, reducing performance or code complexity in most cases. Simple code is good code.

Read on for another option. You may have a reason to need it (or perhaps part of it).




Not a very simple solution

The first solution does not necessarily scale so well. But what if you create a framework in which you would like your code to be executed without having to call it from the application delegate?

First step

Define the following Swift code. The goal is to provide a simple entry point for any class that you would like to populate with behavior similar to initialize() - this can now be done simply by following SelfAware . It also provides one function to trigger this behavior for each corresponding 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) } } 

Second step

This is all well and good, but we still need a way to actually run the function that we defined, i.e. NothingToSeeHere.harmlessFunction() , when the application starts. Earlier in this answer, it was suggested to use Objective-C code for this. However, it looks like we can do what we need using only Swift. For macOS or other platforms where UIApplication is not available, you will need the following options.

 extension UIApplication { private static let runOnce: Void = { NothingToSeeHere.harmlessFunction() }() override open var next: UIResponder? { // Called before applicationDidFinishLaunching UIApplication.runOnce return super.next } } 

Third step

Now we have an entry point when starting the application and a way to connect to it from the classes of your choice. All that remains to be done: instead of implementing initialize() , conform to SelfAware and implement the specific method, awake() .

+30
Mar 16 '17 at 3:03 on
source share

My approach is essentially the same as adib. Here is 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) }() // ... } 

It's nice that this works for any class, as initialize did, if you span all your bases, that is, you must implement each initializer. Here is an example of a text view that configures its own external proxy server once before any instances appear on the screen; this example is artificial, but encapsulation is extremely enjoyable:

 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 }() } 

This demonstrates the advantage of this approach much better than the application delegate. There is only one instance of the application delegate, so the problem is not very interesting; but there may be many instances of CustomTextView. However, the string CustomTextView.appearance().backgroundColor = .green will only run once since the first instance is created because it is part of the initializer for the static property. This is very similar to the behavior of a method of the initialize class.

+7
Mar 31 '17 at 2:29
source share

If you want to fix your Swizzling method in Pure Swift :

 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 objc_getClassList is Objective-C and it cannot get a superclass (like UILabel), but all subclasses are just, but for swizzling related UIKit, we just want to run it once on the superclass. Just run inject () for each target class, not a loop for all your project classes.

+5
Oct 31 '17 at 15:49
source share

You can also use static variables, because they are already lazy and refer to them in the initializers of top-level objects. This would be useful for application extensions and the like that do not 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 } } 
+2
Mar 28 '17 at 11:15
source share

A minor addition to the excellent @JordanSmith class, which ensures that each awake() is called only 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) } } 
+2
Mar 30 '17 at 18:22
source share

If you prefer Pure Swift ™! then my solution of this kind works in _UIApplicationMainPreparations time to drop things:

 @UIApplicationMain private final class OurAppDelegate: FunctionalApplicationDelegate { // OurAppDelegate() constructs these at _UIApplicationMainPreparations time private let allHandlers: [ApplicationDelegateHandler] = [ WindowHandler(), FeedbackHandler(), ... 

Sample here, I avoid the Massive Application delegate problem by decomposing the UIApplicationDelegate into various protocols that individual handlers can accept if you are interested. But the important point is that the pure-Swift method for working as early as possible sends your tasks of type +initialize to the initialization of your @UIApplicationMain class, for example, the allHandlers construct here. _UIApplicationMainPreparations time should be early enough for almost anyone!

+1
Mar 30 '17 at 10:51
source share
  1. Mark your class as @objc
  2. Inherit it from NSObject
  3. Add ObjC Category to Your Class
  4. Implement initialize in category

example

Swift files:

 //MyClass.swift @objc class MyClass : NSObject { } 

Object 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 
0
Aug 24 '18 at 9:31
source share

Here is a solution that works 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() 

From the next post

0
Jul 09 '19 at 7:46
source share

I think this is a workaround.

We can also write initialize() function in objective-c code, and then use it by the bridge link

Hope the best way .....

-four
Apr 23 '17 at 6:38 on
source share



All Articles