(P)Objective-C declares a class function,EDOCX1 commercial 0,that is run once for each class,before it is used.它被用作交换方法执行(SWIZLING)的一个切入点,贯穿其他事项。(p)(P)Swift 3.1 precates this function with a warning:(p)布尔奇1(P)如何才能解决这一问题,同时保持同样的行为和特点,即我目前正在使用EDOCX1(英文)字母"Enry Point"进行工作?(p)
- 更精确地说,方法+initialize在使用类中的任何内容之前执行。实际上,它是在加载包含类的包时执行的,在应用程序启动之后会发生什么。
- @阿明尼根:我不确定这是否正确。也许您正在考虑使用load方法(Swift中没有这种方法)?
- 对于+initialize也是一样的。运行时将initialize发送到程序中的每个类,在该类或从该类继承的任何类从程序中发送其第一条消息之前。不能保证这是在程序启动时完成的。这样的保证将导致立即加载所有包以获得+initialize中的代码。–与+load的区别在于,它是在类和类别上执行的。(类和类别中具有相同选择器的方法!)但是,当然,对于类别而言,可能的延迟调用比类更重要。
- @阿明尼格姆阿瓦德:也许我误解了你的第一句话。你说过,在现实中,当包…已加载。这不是我所经历的。
- 也许吧。OP说,+initialize是在app start执行的。但是,如果在应用程序启动后加载了一个包,这就不是真的了。在这种情况下,+initialize的最早执行时间是加载包时。(显然,在加载包含+initialize的包之前,无法执行它。)我同意大多数开发人员从未有过这种体验。这是因为您需要在应用程序启动后加载捆绑包的情况。你很少有这种感觉。包的加载时间是一个实现细节。你不能仅仅依靠在应用程序启动时加载所有包。
- 举个例子:我为Objective-C编写了一个AOP框架。由于业务逻辑不应该涉及这些方面,所以我需要一种自动安装建议的方法。显然,+initialize(AOPSomeBaseClass)是一个很好的地方。我期望并体验到这个方法从未被执行过,因为业务逻辑故意不引用框架,并且框架从未被加载,导致这个方法从未被执行过。所以你需要推一下。既然你懂德语,我链接到谈话:macoun.de/video2009/ts6.php
- @aminnegm awad在技术上是非常正确的,在发送给它的第一条消息之前,对任何nsObject类调用initialize()。在方法转换(Swift中使用initialize()的常见原因)的情况下,在发送任何消息之前,方法转换都不会发生,因此一切都很好。
- 是的,就像我几小时前说的。但有时也很重要,比如我提到的情况。但是,不能保证它在应用程序启动时被发送到类对象(在Objective-C中,没有像initialize()这样的成员函数,并且它们不会被调用,而是发送消息)。
- @aminegm awad(很高兴见到您btw)-我认为您在初始化中要做的一个合法的做法是将nsApplicationRashonExceptions设置为true(nsUserDefaults)。否则,您的应用程序不会崩溃,因此,如果此标志设置得太晚,崩溃报告工具将无法正常工作。
- @克里斯蒂安基诺你好,克里斯:(-)是的,像这样或其他什么。在Q的早期版本中,声明+initialize在app start执行。我只是想纠正这一点,因为并非所有情况都是如此。(现在它在Q中被纠正了)想想一个动态链接的调试框架,它不是从应用程序引用的。如果你把某物放进一个+initialize中,它将永远不会被执行。然而,这只是一个评论,而不是一个回答。
- 对NSObject派生类进行简单的黑客攻击,只需引入一个只执行initialize的objective-c超类。我想这可以通过创造性地使用Objective-C运行时来实现——自动生成超类,并使initialize实现成为一个包装器来调用协议中声明的方法。
- 不,这不能通过NSObject的"简单黑客"来实现,因为RTE甚至不知道未加载包的类。这是因为它们没有加载。您必须搜索应用程序可以从中加载捆绑包的所有路径,才能在应用程序启动时加载所有捆绑包的负面影响。为何?唯一的积极效果是Q的前一个版本是正确的,显然没有设计目标。
简单/简单的解决方案
常见的应用程序入口点是应用程序委托的applicationDidFinishLaunching。我们可以简单地向每个类添加一个静态函数,我们希望在初始化时通知它,然后从这里调用它。
第一个解决方案简单易懂。对于大多数情况,这是我的建议。尽管下一个解决方案提供的结果与最初的initialize()功能更相似,但它也会导致应用程序启动时间稍长。我不再认为在大多数情况下,值得付出努力、性能降低或代码复杂性。简单代码就是好代码。
继续阅读另一个选项。你可能有理由需要它(或者它的一部分)。
不是那么简单的解决方案
第一个解决方案不一定能很好地扩展。如果您正在构建一个框架,您希望代码在其中运行,而无需任何人从应用程序委托调用它,该怎么办?
第一步
定义以下swift代码。目的是为您希望灌输类似于initialize()的行为的任何类提供一个简单的入口点-现在只需符合SelfAware即可实现。它还提供了一个单独的函数来为每个一致性类运行这个行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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)
}
} |
第二步
这一切都很好,但是我们仍然需要一种方法来在应用程序启动时实际运行我们定义的函数,即NothingToSeeHere.harmlessFunction()。在此之前,此答案建议使用Objective-C代码来完成此操作。然而,似乎我们只需要使用swift就可以做我们需要的事情。对于不能使用uiapplication的MacOS或其他平台,需要更改以下内容。
1 2 3 4 5 6 7 8 9 10 11 12 13
| extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
} |
步骤三
现在我们有了一个应用程序启动的入口点,以及一种从您选择的类中连接到这个入口点的方法。剩下要做的就是:不执行initialize(),而是遵循SelfAware并实现定义的方法awake()。
- 斯威夫特的优雅解决方案是通过使用Objective-C机制来实现的。
- 它之所以"优雅",是因为它可以是自包含的(不需要从应用程序委托进行初始化),并且因为它将目标C功能移植到了Swift,在那里它可以本地使用。
- 嗯,它是自给自足的,这是+initialize的本质。这就是它的目的。任何其他方法都不是一个解决方案,而不是一个不雅的解决方案。我不认为这是一个港口。如果它是从Objective-C的一个端口,它将在没有Objective-C的情况下工作。但是它没有。别误会我,我反对你的答案。然而,这不是一个优雅的解决方案,而是一个黑客,因为没有办法快速做到这一点。
- @Aminegmawad我建议优雅来自于对外部用户不变的行为。对于依赖于initialize()魔力的框架或类似的东西,这允许行为保持不变。你对我之前的评论是正确的,这样做不符合软件"端口"的定义。也许"桥"是个更好的词。
- 事实上,这是必要的。但是,它并不是完全不变的,因为您必须实现协议。另一种选择是向RTE请求特定的方法,而不是解包到协议类型。嗯,优雅这个词没有定义。有人应该为此写一个RFC…:-]
- 应用程序扩展并不总是有委托…
- @阿迪布,这是一个很好的观点。它不必是应用程序委托,也可能有其他方法。
- 这是很好的东西。可能建议进行小的编辑,以确保与dispatch_once的行为相同。哎呀-我看我不能在这里很好地格式化评论,所以我将在下面添加一个附录答案。
- 我认为回到目标C的想法是非常令人遗憾的。如果我想这样做,我就不会把我的应用程序转换成swift的所有工作都做完。
- @Matt它只适用于非常少量的代码,这与用目标C编写的整个项目非常不同。您的答案非常有效,除非您需要对类或初始值设定项进行快速调整(在我所用的情况下,这是一个合理的量)。虽然它很相似,但它并没有完全复制功能,因此不适用于每种情况。
- @Jordansmith是的,一个依赖于另一种语言的代码的一小部分……但是,这不是真的。响应链、核心数据……无止境的返回到Object-C的必要列表,似乎是一个设计目标,即快速喊出"静态安全性",并静默使用Object-C的动态方法的优势。
- @Matt这只是另一个例子,为什么将代码转换为swift是一项令人遗憾的工作。
- @aminegm awad答案已更新为仅使用swift。我不相信这比以前的Objective-C桥要好得多,但它解决了我在一个项目中遇到的问题,即在多个目标之间共享Objective-C文件。
- @Aminnegm Awad,你把可可装订删掉了。我刚转换了一个桌面应用程序,它大量使用了从Objective-C到Swift的绑定,Darned Near把我逼疯了。:)
- @马特确实,而且名单可能更长。
- @Jordansmith此代码不使用Objective-C?objc_getClassList()等人?
- @然而,乔丹史密斯,似乎我们可以做什么,我们只需要使用斯威夫特。似乎有误导性。
- 代码使用了Objective-C RTE。因此,"然而,似乎我们只需要使用swift就可以做我们需要的事情。"这不是真的。关键是仍然有一个对Objective-C的依赖,即使你试图混淆它。依赖于某个独立的事物是不好的,混淆它更糟。不过,我很确定您必须使用C头。我想,C不太快。
- 当类类别仅用作一个随机类别get调用时,这不起作用…有人能解决这个问题吗?
- 我怎么能把这个改编成MacOS?没有NSApplication#next…
- @Sindresorhus for a macos primary application,kickoff your global initializations in the app delegate's EDOCX1 niplication 0-that would be called before EDOCX1.For an app extension,call the global setup code inside a closure that initiatives a static property and then make a reference to the static property at your top-level objects'initializers(I.E.The extension's view controller).
- @Adib I'm aware of that.That's not a macos adaption of this answer though.这是一个不同的解决办法。I'm looking for a way to do it without need anything in the app delegate:Stackoverflow.com/questions/4902955/&Hellip;
- I need perform code on first access to class like initialize was working,not in app launch.
我的方法基本上与ADIB的方法相同。下面是一个使用核心数据的桌面应用程序的示例;这里的目标是在任何代码提到它之前注册我们的自定义转换器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
AppDelegate.doInitialize
}
static let doInitialize : Void = {
// set up transformer
ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer)
}()
// ...
} |
好的是,这对任何类都有效,就像initialize所做的那样,前提是您涵盖了所有的基础——也就是说,必须实现每个初始值设定项。下面是一个文本视图的示例,它在任何实例出现在屏幕上之前配置自己的外观代理;该示例是人工的,但封装非常好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
}()
} |
这说明了这种方法的优势比应用程序委托好得多。只有一个应用委托实例,所以这个问题不是很有趣;但是可以有很多CustomTextView实例。然而,行CustomTextView.appearance().backgroundColor = .green将只执行一次,因为它是静态属性的初始值设定项的一部分。这与类方法initialize的行为非常相似。
- I wonder if the optimizing compiler would be allowed to not initialize the static property since e EDOCX1 universific 8 napal evaluates to an unused expression–Just a thought,I have no idea if this is an issue or not.
- @Martinr well,if that were the case,would uldn't that same worry haunt the standard dispatch-once replacement pattern?
- Dispatch U11 executes a block exactly 11.这里的"11"部分是一个side-effect of the initialization of the static property。-The Swift migrator converts dispatch-11th to EDOCX1 original 9(as E.G.here:Stackoverflow.com/Q/39576848/1187415).是否需要向EDOCX1提供英文字母10?
- @Martinr all I can tell you that my value transformer initialization has never in fact failed.That's from a real application that I launch every day.
- 好的,谢谢这只是一个想法,我可能是在一个错误的跟踪完成。
- @Martinr I would never suggest that.如果我开始这一应用的一天和日-of-the-week fails呼吁在界面上,我会是第一个让你知道!
- 如果你有一个上诉代表团,为什么复杂的想法和错误的全球倡议包含上诉代表团的统计方法?An app delegate would only be instantited once per application run,Hence just setup your value transformers inside EDOCX1.
如果您想修复您的方法,以纯粹的快速方式旋转:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| 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
}
} |
由于objc_getClassList是objective-c,它不能得到超类(如uilabel),而只能得到所有子类,但是对于uikit相关的swizzing,我们只想在超类上运行一次。只需对每个目标类运行inject(),而不是循环整个项目类。
- Same issue for me:EDOCX1 plograph 12.Tells me uilabel does not conform to my Swizzlinginjection protocol.Only(internal)uilabel subclasses seems to be conforming to this Protocol…太坏了Cannot loop on runtime classes to perform auto swizing injection on conforming classes,need to manually inject swizing for each needed class.That's why,to me,this answer should be the accepted one
稍微增加@jordansmith的优秀课程,确保每个awake()只被称为一次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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)
}
} |
您还可以使用静态变量,因为这些变量已经是惰性的,并在顶级对象的初始值设定项中引用它们。这对于没有应用程序委托的应用程序扩展之类的应用程序很有用。
1 2 3 4 5 6 7 8 9 10
| class Foo {
static let classInit : () = {
// do your global initialization here
}()
init() {
// just reference it so that the variable is initialized
Foo.classInit
}
} |
- 如果你想在你的最高一级的目标中提及这些目标,那么为什么不直接呼吁EDOCX1。有什么优势吗?这并不是所有问题的解决办法。
- In any case EDOCX1 theographic 3 common should be internal to the class.
- @Aminegm-awad directly call EDOCX1 psychological 2.The code will be performed every time you create an instance of classed cx1 psychological 5.那不是我们想要的。
- 实际上,这类学校应该是更好的。
- @Ericchai an explicite call to EDOCX1 English 2 will be executed,when it is called.
- Note that EDOCX1 original 3 is not a method but a property.In turn it is initialized by a closure which will be executed only once the first time the static property is referred.
如果你喜欢纯迅捷?!那么我对这类事情的解决方案是在_UIApplicationMainPreparations时间开始:
1 2 3 4 5 6 7
| @UIApplicationMain
private final class OurAppDelegate: FunctionalApplicationDelegate {
// OurAppDelegate() constructs these at _UIApplicationMainPreparations time
private let allHandlers: [ApplicationDelegateHandler] = [
WindowHandler(),
FeedbackHandler(),
... |
这里的模式是,我通过将UIApplicationDelegate分解为各个处理程序可以采用的各种协议来避免大规模的应用程序委托问题,以防您感到疑惑。但重要的一点是,尽早开始工作的一个简单快捷的方法是在初始化@UIApplicationMain类时调度您的+initialize类型的任务,就像在这里构造allHandlers一样。对于大多数人来说,江户时间应该足够早!
- 有办法为Frameworks做这个吗?(which doesn't have an app delegate).
- Static class members as mentioned right below here are the best you can do in a framework,far as I know
把你的班级标记为@objc。
从NSObject继承
将objc类别添加到类中
分类实施initialize。
例子
SWIFT文件:
1 2 3 4
| //MyClass.swift
@objc class MyClass : NSObject
{
} |
Objc文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| //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 |
我认为这是一种权宜之计。
我们还可以在Objective-C代码中编写initialize()函数,然后通过桥接引用使用它。
希望最好的方法……