Search code examples
swiftmemoryunsafe-pointers

What difference in withUnsafePointer and Unmanaged.passUnretained


I want to see how swift String struct pointer changes when I append new string to original one, comparing to Objective C. For Objective C I use this code:

NSMutableString *st1 = [[NSMutableString alloc] initWithString:@"123"];
[st1 appendString:@"456"];

In Objective C st1 string object changes internaly (adds 456 and becomes 123456), but st1 pointer lefts the same and points to the same object. In Swift, since String is not mutable, var st1 must change its address after addition, because it will hold another string, with my strings summ (123456). Is this all correct?

This is my playground code for swift tests:

import Cocoa

var str = "123"
withUnsafePointer(to: &str) { print($0) }
str += "456"
withUnsafePointer(to: &str) { print($0) }

var str2 = "123"
print(Unmanaged<AnyObject>.passUnretained(str2 as AnyObject).toOpaque())
str2 += "456"
print(Unmanaged<AnyObject>.passUnretained(str2 as AnyObject).toOpaque())

and this is results:

0x000000010e228c40 // this are same
0x000000010e228c40

0x00007fb26ed5a790 // this are not
0x00007fb26ed3f6d0
// and they completly different from first two

Why I get same pointer when I use withUnsafePointer? Why I get different pointers when I use Unmanaged.passUnretained? And why pointers received from this methods are completly different?


Solution

  • To better explain the behaviour you're seeing, we can actually look at the String source code.

    Here's the full definition of String

    public struct String {
      /// Creates an empty string.
      public init() {
        _core = _StringCore()
      }
    
      public // @testable
      init(_ _core: _StringCore) {
        self._core = _core
      }
    
      public // @testable
      var _core: _StringCore
    }
    

    So String is just a wrapper around some type called _StringCore. We can find its definition here. Here are the relevant parts:

    public struct _StringCore {
      //...
    
      public var _baseAddress: UnsafeMutableRawPointer?
      var _countAndFlags: UInt
    
      //...
    }
    

    As you can see, the _StringCore doesn't directly contain the buffer of memory that stores the string's content. Instead, it references it externally, via a UnsafeMutableRawPointer.

    The first time you declare str, it was given some memory on the stack, at address 0x000000010e228c40. When you made a change to str, you actually had no effect on this String struct's location. Instead, you caused the _baseAddress of the String's _core to change. Array works a very similar way. This is how the string's copy-on-write behaviour is implemented, too.

    As for the Unmanaged behaviour, str2 as AnyObject creates a copy of str2, so you end up making 2 different copies, hence the difference in the printed addressed.