【问题标题】:Check for uninitialized non optional value in Swift?检查 Swift 中未初始化的非可选值?
【发布时间】:2020-11-12 03:57:22
【问题描述】:

我有一些代码可以正常运行很长时间,但突然导致运行时崩溃。这可能是因为这是它第一次获得 nil 值(由于某些故障或其他原因),但无论如何我的应用程序现在在启动时崩溃了。

代码是:

 @objc func download(surl: NSString, completion : @escaping (NSData) -> Void ) {
print("surl to download is:",surl)//CRASHES HERE if surl is uninitialized.

它在错误指示的行崩溃:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)

这是运行时出现错误的控制台截图

这是调试器中显示崩溃时“单元化”的行。

如果 surl 是非可选的,是否有任何未初始化的检查?

我不愿意将 surl 更改为可选项,因为此方法在应用程序中被称为无数地方,我担心在方法中更改它会导致其他问题。

所以想找出一种方法来测试 surl 是否未初始化并在可能的情况下返回。

感谢您的任何建议。

编辑:

我在调用上述方法的方法中尝试了一个非零测试,并通过了非零测试。但是如果你尝试用它做点什么,它就会崩溃。此外,崩溃后在控制台中打印它会给出:(NSString) $R2 = <uninitialized>

这是截图:

【问题讨论】:

  • 你确定你的代码在那里崩溃了吗?你为什么首先使用 NSString/NSData?
  • 你的实现surl 不能是nil。如果是,编译器会抱怨(在编译时)。这就是 Swift 强类型系统的好处。错误必须在其他地方。正如 Leo 已经提到的,如果有原生 Swift 对应物,请停止使用 NS... 类。
  • @Leo Dabus 看起来代码在那里崩溃了。见截图。使用 NSString 因为这主要是一个 Objective-C 项目。然而,像这个这样的一些函数是用 Swift 编写的
  • @vadian。重新检查控制台,它显示“未初始化”。如前所述,此代码已在生产应用程序中运行了一年多,但不知何故,一个无效的值悄悄进入。不知道如何。可能是撞车什么的。将问题编辑为如何检查未初始化。归根结底,我需要测试未初始化的值,即使它们非常罕见,因为错误会阻止应用程序运行。

标签: ios swift string null optional


【解决方案1】:

可能是您的 Swift 代码是从 Objective-C 调用的,而 Objective-C 将 nil 发送给 Swift。这可以解释为什么你会在链的末端发生崩溃,只有在实际使用该值的地方。

注意,Objective-C 很乐意将一个 nil 值传递给一个不接受 nil 的 Swift 函数,而且 Swift 也会很高兴地将这个 nil 值转发1 到只有非 nil 存在的地方允许,仅在实际尝试使用该值时崩溃。

从我在您的堆栈跟踪中看到的情况来看,这似乎是问题所在:Objective-C -[IDModel updateCa..] 方法调用了 Utilities.downloadImag... Swift 方法,该方法将调用转发到 Utilities.download(surl..,并且崩溃发生在尝试使用该值时,链中的最后一个函数。

为了将更改的影响降到最低,您可以做的就是“重载”该函数,一个带有可选的 surl 用于 Objective-C 世界,一个用于 Swift 世界:

@objc(download) func objcDownload(surl: NSString?, completion : @escaping (NSData) -> Void ) {
    guard let surl = surl else {
        // handle this problems
        return
    }
    // forward to the worker function
    download(surl: surl, completion: completion)
}

// @nonobjc might not be needed, depending of the class declaration
// but being explicit doesn't hurt
@nonobjc func download(surl: NSString, completion : @escaping (NSData) -> Void ) {
    // same code, no changes needed
}

这边:

  • Objective-C 代码将看到相同的功能,无需更改
  • Objective-C 桥的 Swift 实现可以检查 nils
  • Swift 代码仍然可以使用,现在@nonobjc 下载功能。

如果你对从 Objective-C 传递给 Swift 的 nil 值不信任我,这里有一个例子来证明这一点:

// in a header file, imported in the bridging header
@interface ObjCWorker: NSObject

+ (void)sendNilToSwift;

@end

// in the corresponding .m file
@implementation ObjCWorker

+ (void)sendNilToSwift {
    NSString *shouldNotBeNil = nil;
    [SwiftWorker doWorkWithNonNull:shouldNotBeNil];
}
@end
// in a Swift file
class SwiftWorker: NSObject {
    
    @objc static func doWork(with string: NSString) {
        delegateWorkToSomeOtherMethod(string)
    }
    
    static func delegateWorkToSomeOtherMethod(_ string: NSString) {
        delegateWorkToAnotherMethod(string)
    }
    
    static func delegateWorkToAnotherMethod(_ string: NSString) {
        print(string)
    }
}

现在,只需写 ObjCWorker.sendNilToSwift(),您就会遇到完全相同的崩溃。

1请注意,这目前仅适用于 Objective-C 参数:NSString、NSData、...、桥接 Swift 类型(如 String、Data)在发送为来自 Objective-C 端的 nil 值。

【讨论】:

  • 但是 OP 一开始就提供这些参数 Objective-C 类型难道不是错误的吗? NSString 和 NSData 是错误的。
  • @matt 同意,感觉使用 NSString 是错误的,但 String 也能正常工作(或者甚至更好)。也许 OP 继承了这个代码库并且不想过多地触及它。
  • 感谢您的出色解释,您说得对,从 Obj-C 方法调用该方法失败。 NSString 和 NSData 的原因是 Objective-C 方法向它发送一个字符串(调用方法中的 NSString,然后使用返回的 NSData。在函数中(代码未显示)它确实使用字符串和数据。
猜你喜欢
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 1970-01-01
  • 2015-08-19
  • 2019-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多