Why, By sending null as parameters from Objc C to a fast class initializer, replace nil parameters with new objects

I created this Swift class:

@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() } } 

I call the initializer from Obj C as follows:

 - (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; } 

Here I pass nil for the parameters to the Swift initializer. Ideally, I expect this to work, as the initializer accepts non nil values.

enter image description here

But what actually happens when the execution point reaches inside the initializer, new parameter objects are created:

enter image description here

The string nil becomes "" , and the User variable, which was nil , now points to the object.

But for a property like this

 @property(nonatomic,strong) SomeClass * object; 

Where

 object = nil; 

When I call this object from swift,

 let x = object.someGetter; 

Failure.

At some point, if I pass nil to some nonempty ones, it works, and at another moment it works. Why does this strange behavior exist? If for some reason my parameters are null and passed to nonnull, I would like this to happen. So that I can fix it.

EDIT: It was so unexpected that it even tried to play with this code.

enter image description here

The string parameter was actually like a string, but the User is showing uninitialized, so manipulating the string worked well, but the user object accepted the changes, but did not show them.

+7
ios objective-c swift
source share
2 answers

So there are three questions:

  • firstly, why access to user properties does not crash,
  • second why there is an empty string instead of nil one,
  • thirdly, why the assignment of a property (user class) is crashing

I will answer all of them :-)

1. Access to the "Users" properties

Swift uses the Objective-C message when accessing the properties of the Users class (suppose that - Users is an ObjC class, as shown in the debugger output, the base class is NSObject ). In the disassembly view, you can see this:

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

Since objc_msgSend supports nil messaging, the call is not interrupted.

2. Empty line Magic

When you call the Swift initializer from Objective-C, the bridge code creates the following:

 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 

The interesting part here is calling _unconditionallyBridgeFromObjectiveC . This will internally call the Swift.String _cocoaStringToSwiftString_NonASCII function and check the source code ( here, line 48 ), you can see the following

 @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))) } 

The function always returns a new Swift.String object, in our case empty! So again, there are no glitches.

3. Access to properties

When accessing a custom property, for example. assigning it to a variable:

 let x:SomeClass = object.someGetter; 

the following happens:

  • The return value of someGetter will be saved ( objc_retainAutoreleasedReturnValue ) - this is not a failure

  • The returned object will be expanded implicitly, and then a message is issued

If x was a weak property or optional, the code will not crash. Even when using type inference, it doesn't crash (on my machine, fast 4):

 let x = object.someGetter; 

Is this because the type SomeClass? x is optional SomeClass? unless the property itself is declared as nonnull :

 @property(nonatomic,strong, nonnull) SomeClass * object; 
+1
source share

When you say that the string is "" and the user is an object, it looks like you are simply relying on the representation of a debugger variable for this, right? Since console output explicitly shows nil twice.

What happens here is the generated code for the initializer, in fact, does nothing that could crash if the values ​​are nil . Semantically, yes, this is not valid, but the actual machine code does not do what relies on the nil assumption. The print() call works because, I suppose, the code that converts the arguments to Any existence works even if the values ​​unexpectedly nil and creates nil Any .

As for the debugger view, this is a debugger, it just does everything it can to interpret the variables, but the standard Swift library doesn't actually work. I'm not quite sure why the string variable is displayed as "" , I think, because it always shows a string representation and determines that it has no data (and the string without data is "" ). As for user , it actually shows it as 0x0000000000000000 , which is correct, which means its null pointer (i.e. nil ).

And then finally let x = object.someGetter crashes because the code received depends on a value other than nil (in particular, it presumably calls swift_retain() or swift_unknownRetain() for the value, which requires that the argument is not nil because it is playing the pointer).

+1
source share

All Articles