一个Swift初始化器中的感叹号代表什么意思?

14

我见过像这样的代码,是XCode从Objective-C初始化器中生成的:

init!(logMsg: String!, level logLevel: DDLogLevel, flag logFlag: DDLogFlag, context logContext: Int32, file: UnsafePointer<Int8>, function: UnsafePointer<Int8>, line: Int32, tag: AnyObject!, options optionsMask: DDLogMessageOptions)
init!(logMsg: String!, level logLevel: DDLogLevel, flag logFlag: DDLogFlag, context logContext: Int32, file: UnsafePointer<Int8>, function: UnsafePointer<Int8>, line: Int32, tag: AnyObject!, options optionsMask: DDLogMessageOptions, timestamp aTimestamp: NSDate!)

原始代码如下:

- (instancetype)initWithLogMsg:(NSString *)logMsg
                         level:(DDLogLevel)logLevel
                          flag:(DDLogFlag)logFlag
                       context:(int)logContext
                          file:(const char *)file
                      function:(const char *)function
                          line:(int)line
                           tag:(id)tag
                       options:(DDLogMessageOptions)optionsMask;
- (instancetype)initWithLogMsg:(NSString *)logMsg
                         level:(DDLogLevel)logLevel
                          flag:(DDLogFlag)logFlag
                       context:(int)logContext
                          file:(const char *)file
                      function:(const char *)function
                          line:(int)line
                           tag:(id)tag
                       options:(DDLogMessageOptions)optionsMask
                     timestamp:(NSDate *)aTimestamp;
init关键词后面的感叹号代表了一个非空断言

我在这里添加了一个可能有用的答案:https://dev59.com/b4Xca4cB1Zd3GeqPInek#31422223 - par
3个回答

19
当前被接受的答案给出了“what”,但没有“why”。我认为在这种情况下,理解为什么特别重要。
直接回答您的问题,它是一个返回implicitly-unwrapped optional的初始化程序。
使用init?表示初始化可能会失败是处理错误的有效方法。它返回一个“optional”(例如Type?),暗示着已经初始化了一个值,或者无法初始化任何内容,其内容为nil。但是,何时使用返回implicitly-unwrapped可选项的init!会很有用呢?
隐式解包的可选项表示您可以确信您当前正在使用的值不为nil,而无需检查它,但它在其生命周期的某个时刻可能为nil。这与非可选类型形成鲜明对比,后者永远不可能为nil。由于您从初始化程序获取值时就开始使用该值,因此几乎没有使用init!的用例。
它的存在主要是为了帮助Objective-C框架的转换,以避免手动检查每个自动转换的初始化器。 "这个东西可能是nil,但很可能不是" 是Objective-C的默认工作方式。在您的情况下,Xcode无法知道那些方法是否100%的时间返回已初始化的值。通过检查每个框架并确定初始化器是否应返回Type还是Type?需要付出相当大的努力,因此在此期间使用Type!是一个明智的选择。作为证明,Xcode足够聪明,可以将包含(NSError **)的初始化器转换为init?
另一个用例是委托给一个可失败的初始化器,而你知道这个委托永远不会导致失败条件。但除此之外,在您自己的Swift代码中编写init!时,可能应该尽可能避免(即使那种情况仍然非常棘手)。
来源:

1
谢谢Andrew,不过问题是关于“什么”,而不是“为什么”。一旦我知道它是什么,我就可以轻松地找到相关文档。 - Mike Lischke
1
我明白 :). 我只是希望为那些可能遇到这种情况的人提供一些文档中找不到的见解。在这种情况下,我觉得这很重要。 - Andrew
你说过:"由于你从初始化器中获取值时,就从其生命周期的一开始就在使用它了,因此init的用例不是很多。" 我不同意这个观点。有很多情况下,程序员知道init会成功(例如UIImage(named:)),但编译器不知道。在这些情况下使用!是完全适当的。 - Daniel T.
我明白你的意思,对于非空的init?初始化程序,第一次和以后每次强制解包都是合适的。我特别指的是隐式解包可失败初始化程序——init!。正如你提到的那样,UIImage(named:)是一个init?初始化程序,因为编译器不知道它是否成功。有趣的区别! - Andrew
@Andrew的回答是完整易懂的解决方案。干得好! - Cristi Habliuc

14

这是一个可失败的初始化程序,在Swift 1.1中引入(使用Xcode 6.1)

来自Apple开发者文档:

init!可失败初始化程序

通常,您可以通过在init关键字(init?)后面加上问号来定义一个可失败的初始化程序,以创建适当类型的可选实例。 或者,您可以定义一个可失败的初始化程序,通过在init关键字(init!)后面放置感叹号来创建隐式解包的可选实例

您可以从init?委托到init!,反之亦然,还可以用init!覆盖init?,反之亦然。 您还可以从init委托到init!,尽管这样做将在init!初始化程序导致初始化失败时触发断言。

(重点强调)


它们所说的“delegate from init? to init!”是什么意思?从init?调用init!? - Mike Lischke
1
也许可以补充说明,可失败的初始化器是在 Xcode 6.1 中引入的(据我所知)。 - Martin R
@MartinR 是的,使用 Swift 1.1。 - nicael
“Initializer delegation” 简单来说就是从另一个初始化程序中调用特定的初始化程序。它与委托协议无关。 - Andrew

1
这些被称为“隐式解包可选项”[1]。例如,logMsg的obj-c类型是NSString*,它可以为nil。它可以被用作可选项-String?,在这种情况下,您需要显式地解包才能获取该值。String!将直接给出该值,因此假设logMsg不会为空。
这种类型的可选项被定义为隐式解包可选项。通过在您想要使其成为可选项的类型之后放置一个感叹号(String!)而不是问号(String?),您可以编写隐式解包的可选项。
当确认可选项的值在可选项首次定义后立即存在,并且可以肯定地假定其在此后每个点都存在时,隐式解包的可选项非常有用。
--
1. https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html

2
哦,我误解了你的问题,错过了“init!”部分 :/ nicael的答案解释了这一点。 - radical

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接