How to prove "copy-on-write" on String type in Swift - swift

As the title said, I tried to prove myself that COW(copy on write) is supported for String in Swift. But I cannot find a proof. I proved the COW on Array and Dictionary after trying the following codes:
func address(of object: UnsafeRawPointer) -> String {
let addr = Int(bitPattern: object)
return String(format: "%p", addr)
}
var xArray = [20, 30, 40, 50, 60]
var yArray = xArray
// These two addresses were the same
address(of: xArray)
address(of: yArray)
yArray[0] = 200
// The address of yArray got changed
address(of: yArray)
But for String type, it was not working.
var xString = "Hello World"
var yString = xString
// These two addresses were different
address(of: xString)
address(of: yString)
And I dumped the test function from the official Swift code repo.
func _rawIdentifier(s: String) -> (UInt, UInt) {
let tripe = unsafeBitCast(s, to: (UInt, UInt, UInt).self)
let minusCount = (tripe.0, tripe.2)
return minusCount
}
But this function seems to only cast the actual value pointed to not the address. So two different String variables with the same value would have the same rawIdentifier. Still cannot prove COW to me.
var xString = "Hello World"
var yString = "Hello" + " World"
// These two rawIdentifiers were the same
_rawIdentifier(s: xString)
_rawIdentifier(s: yString)
So how does COW work on String type in Swift?

The compiler creates only a single storage for both
"Hello World" and "Hello" + " World".
You can verify that for example by examining the assembly code
obtained from
swiftc -emit-assembly cow.swift
which defines only a single string literal
.section __TEXT,__cstring,cstring_literals
L___unnamed_1:
.asciz "Hello World"
As soon as the string is mutated, the address of the string storage
buffer (the first member of that "magic" tuple, actually _baseAddress
of struct _StringCore, defined in StringCore.swift) changes:
var xString = "Hello World"
var yString = "Hello" + " World"
print(_rawIdentifier(s: xString)) // (4300325536, 0)
print(_rawIdentifier(s: yString)) // (4300325536, 0)
yString.append("!")
print(_rawIdentifier(s: yString)) // (4322384560, 4322384528)
And why does your
func address(of object: UnsafeRawPointer) -> String
function show the same values for xArray and yArray, but
not for xString and yString?
Passing an array to a function taking a unsafe pointer passes the
address of the first array element, that is the same for both
arrays if they share the storage.
Passing a string to a function taking an unsafe pointer passes a
pointer to a temporary UTF-8 representation of the string.
That address can be different in each call, even for the same string.
This behavior is documented in the "Using Swift with Cocoa and
Objective-C" reference for UnsafePointer<T> arguments, but apparently
works the same for UnsafeRawPointer arguments.

Related

Converting a String to UnsafeMutablePointer<UInt16>

I'm trying to use a library which was written in C. I've imported .a and .h files at Xcode project, and checked it works properly. I've already made them working on Objective-C, and now for Swift.
A problem I've got is functions' arguments. There's a function requires an argument widechar(defined as typedef Unsigned short int in Library), which was UnsafeMutablePointer<UInt16> in Swift. The function translates it and return the result.
So I should convert a String to UnsafeMutablePointer<UInt16>. I tried to find the right way to converting it, but I've only got converting it to UnsafeMutablePointer<UInt8>. I couldn't find answer/information about converting String to UnsafeMutablePointer<UInt16>.
Here's a source code I've written.
extension String{
var utf8CString: UnsafePointer<Int8> {
return UnsafePointer((self as NSString).utf8String!)
}
}
func translate(toBraille: String, withTable: String) -> [String]? {
let filteredString = toBraille.onlyAlphabet
let table = withTable.utf8CString
var inputLength = CInt(filteredString.count)
var outputLength = CInt(maxBufferSize)
let inputValue = UnsafeMutablePointer<widechar>.allocate(capacity: Int(outputLength))
let outputValue = UnsafeMutablePointer<widechar>.allocate(capacity: Int(outputLength))
lou_translateString(table, inputValue, &inputLength, outputValue, &outputLength, nil, nil, 0)
//This is a function that I should use.
let result:[String] = []
return result
}
You have to create an array with the UTF-16 representation of the Swift
string that you can pass to the function, and on return create
a Swift string from the UTF-16 array result.
Lets assume for simplicity that the C function is imported to Swift as
func translateString(_ source: UnsafeMutablePointer<UInt16>, _ sourceLen: UnsafeMutablePointer<CInt>,
_ dest: UnsafeMutablePointer<UInt16>, _ destLen: UnsafeMutablePointer<CInt>)
Then the following should work (explanations inline):
// Create array with UTF-16 representation of source string:
let sourceString = "Hello world"
var sourceUTF16 = Array(sourceString.utf16)
var sourceLength = CInt(sourceUTF16.count)
// Allocate array for UTF-16 representation of destination string:
let maxBufferSize = 1000
var destUTF16 = Array<UInt16>(repeating: 0, count: maxBufferSize)
var destLength = CInt(destUTF16.count)
// Call translation function:
translateString(&sourceUTF16, &sourceLength, &destUTF16, &destLength)
// Create Swift string from UTF-16 representation in destination buffer:
let destString = String(utf16CodeUnits: destUTF16, count: Int(destLength))
I have assumed that the C function updates destLength to reflect
the actual length of the translated string on return.

Type Error in Swift :Cannot convert value of type '[UInt64]' to expected argument type 'inout UInt64'

New to Swift, I'm getting this error:
Cannot convert value of type '[UInt64]' to expected argument type 'inout UInt64'
I don't understand the "inout" typing
/// non crypto hash
func strHash(_ str: String) -> UInt64 {
var result = UInt64 (5381)
let buf = [UInt8](str.utf8)
for b in buf {
result = 127 * (result & 0x00ffffffffffffff) + UInt64(b)
}
return result
}
let myString: String = "Hello World"
let words = myString.components(separatedBy: " " )
print(words)
var hashArry = [UInt64]()
for w in words {
hashArry += strHash(w) // <<<<<<<<< Here
}
Well, you cannot use += as you might have expected.
Use
hashArray.append(strHash(w))
instead. And do not wonder about the sometimes very confusing compiler error messages :-)

Why does Swift return an unexpected pointer when converting an optional String into an UnsafePointer?

I noticed some unusual behaviour when working with a C library which took strings in as constĀ charĀ * (which is converted to Swift as UnsafePointer<Int8>!); passing a String worked as expected, but a String? seemed to corrupt the input. Consider the test I wrote:
func test(_ input: UnsafePointer<UInt8>?) {
if let string = input {
print(string[0], string[1], string[2], string[3], string[4], string[5])
} else {
print("nil")
}
}
let input: String = "Hello"
test(input)
This works as expected, printing a null-terminated list of UTF-8 bytes for the input string: 72 101 108 108 111 0
However, if I change the input to an optional string, so that it becomes:
let input: String? = "Hello"
I get a completely different set of values in the result (176 39 78 23 1 0), even though I would expect it to be the same. Passing in nil works as expected.
The C library's function allows NULL in place of a string, and I sometimes want to pass that in in Swift as well, so it makes sense for the input string to be an optional.
Is this a bug in Swift, or was Swift not designed to handle this case? Either way, what's the best way to handle this case?
Edit
It appears to have something to do with multiple arguments. The C function:
void multiString(const char *arg0, const char *arg1, const char *arg2, const char *arg3) {
printf("%p: %c %c %c\n", arg0, arg0[0], arg0[1], arg0[2]);
printf("%p: %c %c %c\n", arg1, arg1[0], arg1[1], arg1[2]);
printf("%p: %c %c %c\n", arg2, arg2[0], arg2[1], arg2[2]);
printf("%p: %c %c %c\n", arg3, arg3[0], arg3[1], arg3[2]);
}
Swift:
let input0: String? = "Zero"
let input1: String? = "One"
let input2: String? = "Two"
let input3: String? = "Three"
multiString(input0, input1, input2, input3)
Results in:
0x101003170: T h r
0x101003170: T h r
0x101003170: T h r
0x101003170: T h r
It appears that there's a bug with how Swift handles multiple arguments.
I didn't find anything useful on if this is desired behaviour or just a bug.
The pragmatic solution would probably be to just have a proxy method like this, but you probably did something similar already.
func proxy(_ str: String?, _ functionToProxy: (UnsafePointer<UInt8>?) -> ()) {
if let str = str {
functionToProxy(str)
} else {
functionToProxy(nil)
}
}
proxy(input, test)
Did you test if it was working in Swift 2? They changed something maybe related in Swift 3:
https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md
Just to be clear, there is a workaround until Apple fixes this. Unwrap your optional Strings before passing them and everything will work fine.
var anOptional: String?
var anotherOptional: String?
func mySwiftFunc() {
let unwrappedA = anOptional!
let unwrappedB = anotherOptional!
myCStringFunc(unwrappedA, unwrappedB)
}
As mentioned in the comments, this is a clear bug in Swift.
Here's a workaround I'm using. If you can't trust Swift to convert the strings to pointers for you, then you've got to do it yourself.
Assuming C function defined as:
void multiString(const char *arg0, const char *arg1, const char *arg2);
Swift code:
func callCFunction(arg0: String?, arg1: String?, arg2: String?) {
let dArg0 = arg0?.data(using: .utf8) as NSData?
let pArg0 = dArg0?.bytes.assumingMemoryBound(to: Int8.self)
let dArg1 = arg1?.data(using: .utf8) as NSData?
let pArg1 = dArg1?.bytes.assumingMemoryBound(to: Int8.self)
let dArg2 = arg2?.data(using: .utf8) as NSData?
let pArg2 = dArg2?.bytes.assumingMemoryBound(to: Int8.self)
multiString(pArg1, pArg2, pArg3)
}
Warning:
Don't be tempted to put this in a function like:
/* DO NOT USE -- BAD CODE */
func ocstr(_ str: String?) -> UnsafePointer<Int8>? {
guard let str = str else {
return nil
}
let nsd = str.data(using: .utf8)! as NSData
//This pointer is invalid on return:
return nsd.bytes.assumingMemoryBound(to: Int8.self)
}
which would remove repeated code. This doesn't work because the data object nsd gets deallocated at the end of the function. The pointer is therefore not valid on return.

Swift, Tuples issue #2

Sorry about the last thread where I posted this question by accident! Here it is. I received a response from someone (Their response is at bottom of page) and unfortunately it did not work. Thanks in advance!
Create a variable named result and assign it the tuple returned from function greeting. (Note: pass the string "Tom" to the greeting function.)
func greeting() -> (language: String, greeting: String, name: String) {
let language = "English"
let greeting = "Hello"
let name = "Tom"
return (language, greeting, name)
}
var result = greeting()
Error = Your function needs to return a tuple with elements named 'greeting' and 'language'.
Now this solution below tells me that my variable has the wrong value in it, but I can't figure out how to pass it "Tom" to the greeting function because I keep getting an error.
func greeting() -> (language: String, greeting: String) {
let language = "English"
let greeting = "Hello"
return (language, greeting)
}
var result = greeting("Tom")
Response I got in the last thread is below. I still get the error message saying that I need to assign the return value of greeting to result.
func greeting(name:String) -> (language: String, greeting: String) {
let language = "English"
let greeting = "Hello " + name + "!"
return (language, greeting)
}
var result = greeting("Tom").greeting
The task is
Create a variable named result and assign it the tuple returned from function greeting.
What you do in the last block is
var result = greeting("Tom").greeting
what I would expect you to do is
var result = greeting("Tom")

Interpolate String Loaded From File

I can't figure out how to load a string from a file and have variables referenced in that string be interpolated.
Let's say a text file at filePath that has these contents:
Hello there, \(name)!
I can load this file into a string with:
let string = String.stringWithContentsOfFile(filePath, encoding: NSUTF8StringEncoding, error: nil)!
In my class, I have loaded a name in: let name = "George"
I'd like this new string to interpolate the \(name) using my constant, so that its value is Hello there, George!. (In reality the text file is a much larger template with lots of strings that need to be swapped in.)
I see String has a convertFromStringInterpolation method but I can't figure out if that's the right way to do this. Does anyone have any ideas?
This cannot be done as you intend, because it goes against type safety at compile time (the compiler cannot check type safety on the variables that you are trying to refer to on the string file).
As a workaround, you can manually define a replacement table, as follows:
// Extend String to conform to the Printable protocol
extension String: Printable
{
public var description: String { return self }
}
var string = "Hello there, [firstName] [lastName]. You are [height]cm tall and [age] years old!"
let firstName = "John"
let lastName = "Appleseed"
let age = 33
let height = 1.74
let tokenTable: [String: Printable] = [
"[firstName]": firstName,
"[lastName]": lastName,
"[age]": age,
"[height]": height]
for (token, value) in tokenTable
{
string = string.stringByReplacingOccurrencesOfString(token, withString: value.description)
}
println(string)
// Prints: "Hello there, John Appleseed. You are 1.74cm tall and 33 years old!"
You can store entities of any type as the values of tokenTable, as long as they conform to the Printable protocol.
To automate things further, you could define the tokenTable constant in a separate Swift file, and auto-generate that file by using a separate script to extract the tokens from your string-containing file.
Note that this approach will probably be quite inefficient with very large string files (but not much more inefficient than reading the whole string into memory on the first place). If that is a problem, consider processing the string file in a buffered way.
There is no built in mechanism for doing this, you will have to create your own.
Here is an example of a VERY rudimentary version:
var values = [
"name": "George"
]
var textFromFile = "Hello there, <name>!"
var parts = split(textFromFile, {$0 == "<" || $0 == ">"}, maxSplit: 10, allowEmptySlices: true)
var output = ""
for index in 0 ..< parts.count {
if index % 2 == 0 {
// If it is even, it is not a variable
output += parts[index]
}
else {
// If it is odd, it is a variable so look it up
if let value = values[parts[index]] {
output += value
}
else {
output += "NOT_FOUND"
}
}
}
println(output) // "Hello there, George!"
Depending on your use case, you will probably have to make this much more robust.