Jor*_*ith 41 objective-c ios swift
Objective-C声明了一个类函数initialize()
,在使用之前对每个类运行一次.它经常被用作交换方法实现(混合)等的入口点.
Swift 3.1弃用了这个函数并带有警告:
方法'initialize()'定义了Objective-C类方法'initialize',它不能保证被Swift调用,并且在将来的版本中将被禁止
如何解决这个问题,同时仍保持我目前使用initialize()
入口点实现的相同行为和功能?
Jor*_*ith 31
常见的应用程序入口点是应用程序委托applicationDidFinishLaunching
.我们可以简单地向我们想要在初始化时通知的每个类添加一个静态函数,并从这里调用它.
第一个解决方案简单易懂.对于大多数情况,这是我建议的.虽然下一个解决方案提供的结果与原始initialize()
功能更相似,但它也会导致应用启动时间略长.在大多数情况下,我不再认为值得努力,性能下降或代码复杂.简单的代码是很好的代码.
请继续阅读另一个选项.您可能有理由需要它(或者可能是它的一部分).
第一种解决方案不一定能很好地扩展.如果您正在构建一个框架,您希望在没有任何人需要从应用程序委托中调用代码的情况下运行代码,该怎么办?
定义以下Swift代码.目的是为任何想要与行为类似的类提供一个简单的入口点initialize()
- 现在可以简单地通过符合来实现SelfAware
.它还提供了一个函数来为每个符合要求的类运行此行为.
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)
}
}
Run Code Online (Sandbox Code Playgroud)
这一切都很好,但是我们仍然需要一种实际运行我们定义的函数的方法,即NothingToSeeHere.harmlessFunction()
在应用程序启动时.以前,这个答案建议使用Objective-C代码来执行此操作.但是,我们似乎只能使用Swift来完成我们需要的工作.对于没有UIApplication的macOS或其他平台,将需要以下变体.
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
}
Run Code Online (Sandbox Code Playgroud)
我们现在有一个应用程序启动的入口点,以及从您选择的类中挂钩的方法.剩下要做的就是:而不是实现initialize()
,遵守SelfAware
和实现定义的方法,awake()
.
我的方法与adib基本相同.以下是使用Core Data的桌面应用程序的示例; 这里的目标是在任何代码提到它之前注册我们的自定义变换器:
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
AppDelegate.doInitialize
}
static let doInitialize : Void = {
// set up transformer
ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer)
}()
// ...
}
Run Code Online (Sandbox Code Playgroud)
好的一点是,这适用于任何类,只要initialize
你覆盖所有的基础 - 也就是说,你必须实现每个初始化程序.这是一个文本视图的示例,它在任何实例有机会出现在屏幕上之前配置一次自己的外观代理; 这个例子是人为的,但封装非常好:
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
}()
}
Run Code Online (Sandbox Code Playgroud)
这表明这种方法的优势比app委托更好.只有一个app delegate实例,所以问题不是很有趣; 但是可以有很多CustomTextView实例.然而,在创建第一个实例时,该行CustomTextView.appearance().backgroundColor = .green
只会执行一次,因为它是静态属性的初始化程序的一部分.这与类方法的行为非常相似.initialize
如果你想以Pure Swift的方式修复你的方法Swizzling :
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
}
}
Run Code Online (Sandbox Code Playgroud)
由于objc_getClassList
是Objective-C并且它不能获得超类(例如UILabel)而只能获得所有子类,但是对于UIKit相关的swizzling,我们只想在超类上运行一次.只需在每个目标类上运行inject(),而不是循环整个项目类.
归档时间: |
|
查看次数: |
10849 次 |
最近记录: |