【问题标题】:Why, Sending nil as parameters from Objc C to swift class initializer, replaces nil parameters with new objects为什么,将 nil 作为参数从 Objc C 发送到 swift 类初始化程序,用新对象替换 nil 参数
【发布时间】:2018-02-09 10:03:39
【问题描述】:

我创建了这个 Swift 类:

@objc public class Tester: NSObject {
    private var name: String
    private var user: Users
    init(string:String, user: Users) {
        print(user.empId)
        print(user.name)
        self.user = user
        self.name = string
        super.init()
    }
}

我像这样从 Obj C 调用初始化程序:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    NSString * nilString = nil;
    Users * nilUser = nil;
    Tester * test = [[Tester alloc] initWithString:nilString user:nilUser];

    return YES;
}

在这里,我将 nil 的参数传递给 Swift 初始化程序。理想情况下,我希望这会崩溃,因为初始化程序只接受非nil 值。

但实际发生的是,当执行点到达初始化器内部时,会创建新的参数对象:

nil 字符串变为"",而以前为nilUser 变量现在指向一个对象。

但是对于这样的属性

@property(nonatomic,strong) SomeClass * object;

在哪里

object = nil;

当我从 swift 调用这个对象时,

let x = object.someGetter;

这会崩溃。

在某一时刻,如果我将 nil 传递给某个非 null,它会起作用,而在另一时刻,它会崩溃。为什么会存在这种奇怪的行为?如果由于某些原因,我的参数为 nil,并传递给 nonnull,我希望它崩溃。这样我就可以解决问题。

编辑:这变得如此出乎意料,进一步尝试使用此代码。

字符串参数实际上是字符串,但用户显示未初始化,因此对字符串的操作效果很好,但用户对象接受了更改但没有显示回来。

【问题讨论】:

    标签: ios objective-c swift


    【解决方案1】:

    所以有三个问题:

    • 首先,为什么访问用户属性不会崩溃,
    • 第二个,为什么会有一个空字符串而不是nil一个,
    • 第三,为什么分配(自定义类的)属性会崩溃

    我会一一回答:-)

    1。访问用户属性

    Swift 在访问Users 类的属性时使用Objective C 消息传递,(我假设-Users 是调试器输出中看到的ObjC 类;基类NSObject)。 在反汇编视图中,可以看到:

    0x1000018be <+78>:   movq   0x3e6e2b(%rip), %rsi      ; "empId"
       ....
    0x1000018d7 <+103>:  callq  0x100361b10               ; symbol stub for: objc_msgSend
    

    由于objc_msgSend支持nil消息传递,调用不会失败。

    2。空字符串魔法

    从 Objective C 调用 Swift 初始化程序时,桥接代码会创建以下内容:

    0x100001f45 <+53>: callq  0x100021f50               
    ; static (extension in Foundation):
    ;Swift.String._unconditionallyBridgeFromObjectiveC (Swift.Optional<__ObjC.NSString>) -> Swift.String
       ....
    0x100001f5b <+75>: callq  0x100001870               
    ; TestCalling.Tester.init (string : Swift.String, user : __ObjC.ObjCUser) -> TestCalling.Tester at SwiftClass.swift:14
    

    这里有趣的部分是_unconditionallyBridgeFromObjectiveC 调用。这会在内部调用Swift.String函数_cocoaStringToSwiftString_NonASCII,查看源码(here, line 48),可以看到如下:

    @inline(never) @_semantics("stdlib_binary_only") // Hide the CF dependency
    func _cocoaStringToSwiftString_NonASCII(
      _ source: _CocoaString
    ) -> String {
      let cfImmutableValue = _stdlib_binary_CFStringCreateCopy(source)
      let length = _stdlib_binary_CFStringGetLength(cfImmutableValue)
      let start = _stdlib_binary_CFStringGetCharactersPtr(cfImmutableValue)
    
      return String(_StringCore(
        baseAddress: start,
        count: length,
        elementShift: 1,
        hasCocoaBuffer: true,
        owner: unsafeBitCast(cfImmutableValue, to: Optional<AnyObject>.self)))
    }
    

    该函数总是返回一个新的Swift.String 对象,在我们的例子中是一个空对象!所以,再次没有崩溃。

    3。属性访问

    访问自定义属性时,例如将其分配给变量:

    let x:SomeClass = object.someGetter;
    

    会发生以下情况:

    1. someGetter 的返回值将被保留 (objc_retainAutoreleasedReturnValue) -- 这不会崩溃

    2. 返回的对象将被隐式解包——然后崩溃

    如果x 是弱属性或可选属性,则代码不会崩溃。即使使用类型推断,它不会崩溃(在我的机器上,swift 4):

    let x = object.someGetter;
    

    这是因为x 的推断类型是可选的SomeClass?,除非属性本身被声明为nonnull

    @property(nonatomic,strong, nonnull) SomeClass * object;
    

    【讨论】:

    • 您能详细说明一下属性访问部分吗?我不完全理解“someGetter 的返回值将被保留(objc_retainAutoreleasedReturnValue)——这不会崩溃”和“返回的对象将被隐式解包——然后崩溃”。为什么 someGetter 的返回值会被保留,为什么会被隐式解包?
    【解决方案2】:

    当你说字符串是"" 并且用户是一个对象时,看起来你只是依赖于调试器的变量视图,对吗?因为控制台输出清楚地显示了nil 两次。

    这里发生的情况是,如果值为nil,初始化程序的生成代码实际上并没有做任何会崩溃的事情。从语义上讲,是的,它是无效的,但是实际的机器代码并没有做依赖于非nil 假设的事情。对print() 的调用有效,因为我假设将参数转换为Any 存在的代码恰好可以工作,即使值意外地为nil,并产生nil Any

    至于调试器视图,它是一个调试器,它只是尽其所能解释变量,但它实际上并没有运行 Swift 标准库。我不完全确定为什么string 变量显示为"",我猜是因为它总是显示一个字符串表示并且它确定它没有数据(并且没有数据的字符串是"")。至于user,它实际上显示为0x0000000000000000,这是正确的,这意味着它是空指针(即nil)。

    最后,let x = object.someGetter 确实崩溃了,因为生成的代码取决于非nil 的值(具体来说,它可能在值上调用swift_retain()swift_unknownRetain(),这需要参数为非nil,因为它取消引用指针)。

    【讨论】:

    • “因为控制台输出清楚地显示了 nil 两次。”这是第 15、16 行,打印用户对象的一些属性,而不是参数。
    • 我猜是因为它总是显示一个字符串表示并且它确定它没有数据(并且没有数据的字符串是“”) - 它试图玩这个,我认为这里的字符串被初始化。检查问题的更新。
    猜你喜欢
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 2011-07-22
    • 1970-01-01
    • 2014-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多