I'm looking to implement some simple string templating in Swift and am wondering what the built-in options are for replacing macros in a string with values at runtime.
It would be awesome if there were a way for me to use the string interpolation syntax "\(variable)" at runtime, but I'm guessing these are actually parsed at compile time since the macros can contain actual code.
I've also found a String constructor that accepts a format string in Objective-C style, using %#, etc.
let myString = String(format: "Hello %#", name)
That could work, though I like the syntax less. I'm just wondering if there are better approaches I should take, or if it would be better to just write my own.
Being able to execute commands (like operators or method/property calls) within the macro would be awesome, but not required (and considering how static a language Swift is, not expected).
import Foundation
let serverName = "Fantastic"
var resultString = "We are using $SERVERNAME$"
if let range = resultString.rangeOfString("$SERVERNAME$") {
resultString.replaceRange(range, with: serverName)
}
You could, of course, implement a String extension if you are going to be using this regularly.
extension String {
func replaceString(sourceString:String, withNewElements newElements:String) throws {
//implementation left for the reader
let userInfo = ["missingString": sourceString,
"message": "Substring '\(sourceString)' not found in '\(self)'"]
throw NSError(domain: "ReplaceStringFailure", code: 1, userInfo: userInfo)
}
}
Related
I’m learning Swift. How do I fix the following code to list the window names?
import CoreGraphics
let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID)
for i in 0..<CFArrayGetCount(windows) {
if let window = CFArrayGetValueAtIndex(windows, i) {
print(CFDictionaryGetValue(window, kCGWindowName))
}
}
The error:
main.swift:6:32: error: cannot convert value of type 'UnsafeRawPointer' to expected argument type 'CFDictionary?'
print(CFDictionaryGetValue(window, kCGWindowName))
^~~~~~
as! CFDictionary
It becomes easier if you avoid using the Core Foundation types and methods, and bridge the values to native Swift types as early as possible.
Here, CGWindowListCopyWindowInfo() returns an optional CFArray of CFDictionaries, and that can be bridged to the corresponding Swift type [[String : Any]]. Then you can access its values with the usual Swift methods (array enumeration and dictionary subscripting):
if let windowInfo = CGWindowListCopyWindowInfo(.optionAll, kCGNullWindowID) as? [[ String : Any]] {
for windowDict in windowInfo {
if let windowName = windowDict[kCGWindowName as String] as? String {
print(windowName)
}
}
}
You can use unsafeBitCast(_:to:) to convert the opaque raw pointer to a CFDictionary. Note that you'll also need to convert the second parameter, to a raw pointer:
CFDictionaryGetValue(unsafeBitCast(window, to: CFDictionary.self), unsafeBitCast(kCGWindowName, to: UnsafeRawPointer.self))
unsafeBitCast(_:to:) tells the compiler to treat that variable as another type, however it's not very safe (thus the unsafe prefix), recommending to read the documentation for more details, especially the following note:
Warning
Calling this function breaks the guarantees of the Swift type system; use with extreme care.
In your particular case there should not be any problems using the function, since you're working with the appropriate types, as declared in the documentation of the Foundation functions you're calling.
Complete, workable code could look something like this:
import CoreGraphics
let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID)
for i in 0..<CFArrayGetCount(windows) {
let windowDict = unsafeBitCast(CFArrayGetValueAtIndex(windows, i), to: CFDictionary.self)
let rawWindowNameKey = unsafeBitCast(kCGWindowName, to: UnsafeRawPointer.self)
let rawWindowName = CFDictionaryGetValue(windowDict, rawWindowNameKey)
let windowName = unsafeBitCast(rawWindowName, to: CFString?.self) as String?
print(windowName ?? "")
}
Update
You can bring the CoreFoundation array sooner to the Swift world by casting right from the start:
let windows = CGWindowListCopyWindowInfo(CGWindowListOption.optionAll, kCGNullWindowID) as? [[AnyHashable: Any]]
windows?.forEach { window in
print(window[kCGWindowName])
}
The code is much readable, however it might pose performance problems, as the cast to [[AnyHashable: Any]]` can be expensive for large array consisting of large dictionaries.
Why does this work (Example 1):
if let numString:String = Model.selectedLocation?.zip{
let callString:String = String(format:"tel:%#",numString)
//more code here
}
But not this (Example 2):
if let numString:String = String(format:"tel:%#",Model.selectedLocation?.zip){
//more code here
}
In the second example, Xcode throws an error and wants zip to be unwrapped like:
String(format:"tel:%#",(Model.selectedLocation?.zip)!)
but if I do that the app will crash when zip is nil.
QUESTION:
Is there a way to make the second example above work or is it not possible/correct?
Avoid redundant type annotations
Avoid String(format:) unless you need it. It's a method of NSString from the Foundation framework, which has several consequences:
It requires Foundation to be imported.
It implicitly bridges your String to NSString.
It won't work in Swift 3, because bridging was made explicit.
The root issue here is that String(format:) returns String? (since the format string could be invalid). You can avoid this entirely by using Swift's string interpolation:
if let numString = Model.selectedLocation?.zip {
let callString = "tel: \(numString)"
//more code here
}
...or simple concatination:
if let numString = Model.selectedLocation?.zip {
let callString = "tel: " + numString
//more code here
}
Strictly spoken Example 2 is neither optional binding nor optional chaining because String(format...) returns a non-optional String and the format parameter must be non-optional, too.
Example 1 is the correct and recommended syntax to handle the optionals.
Edit: I totally agree with Alexander's answer (except that String(format:) returns String?)
UPDATED
It is because in String(format: " ", ), the arguments must be not-nil, hence the !.
when using if-let check for optionals, statements must return optionals
// Assuming Model.selectionLocation.zip is of String type
if let numberString = Model.selectedLocation?.zip {
let formattedString = String(format:"tel:%#", numberString)
}
or use guard
guard let numberString = Model.selectedLocation?.zip else {
return
}
let numberString = String(format:"tel:%#", numberString)
I've got some Swift 2.0 code, for which I'm trying to achieve 100% code coverage. I'm doing some JSON handling, part of which looks like so:
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding) else {
throw ErrorCode.JSONEncodingFailure
}
I don't think there's a real-world case in which any string can't be encoded as UTF-8, but I don't want to open myself up to a crashing error either, so that code must remain. How can I mock the jsonText object to return nil for dataUsingEncoding()?
The closest I've come is to subclass NSString like so:
public class BadString: NSString {
public override var length: Int {
get {
return 5
}
}
public override func characterAtIndex(index: Int) -> unichar {
return 0
}
public override func dataUsingEncoding(encoding: NSStringEncoding) -> NSData? {
return nil
}
}
Here's the problem, though. In order for my mock implementation to be used, I have to define jsonText (a function parameter) as an NSString, rather than String, which feels wrong for an all-Swift codebase. With it defined as a Swift String, I have to cast my BadString to that type, and it uses the String implementation instead of my own.
Is there another (clean) way to achieve this?
You will be hard-pressed to find a string that cannot be encoded using UTF-8! As long as you know that is the encoding you will be using, I would suggest that you not worry about testing the "encoding failure" case.
However, if you still desire to test it then I recommend making one of the following changes in order to allow you to do so:
(1) change the way you are thinking about the 'failure': if you know that the string you are encoding will always be non-empty, then broaden the guard to also require that the encoded data has length > 0, e.g. =>
guard let jsonData = jsonText.dataUsingEncoding(NSUTF8StringEncoding)
where jsonData.length > 0 else {
throw ErrorCode.JSONEncodingFailure
}
...using this idea, you can now use an empty string for jsonText and trigger this code path (assuming that an empty string would also satisfy your definition of 'failure' here)
(2) store your string encoding value in a variable (let's call it stringEncoding) that you can access during your test setup, and then test this using incompatible values for jsonText and stringEncoding, e.g. =>
var jsonText = "🙈"
let stringEncoding = NSASCIIStringEncoding
...I guarantee that jsonText.dataUsingEncoding(stringEncoding) will return nil in this case :)
Happy Testing! I hope this helps!
I'm trying to write a function that takes a parameter of type Printable:
func logMessage(message: Printable) {
// ...
}
Strangely, this doesn't work as expected when passing in Strings.
This doesn't compile:
logMessage("some string \(someVariable)")
// Neither does this:
let aString = "aString"
logMessage(aString)
This however compiles:
logMessage("A string")
// This works too:
let aString: Printable = "a string"
logMessage(aString)
This is quite confusing. It seems that in some cases String implements Printable and in others not.
In addition, it seems that string interpolation always produces a String that does not implement Printable. This crashes at runtime with a cast error:
let aString = "a string"
let interpolatedString = "contains \(aString)"
Any idea what's going on here?
You're right that String doesn't conform to Printable. The reason this compiles:
let aString: Printable = "Ceci n'est pas une String"
is that you aren't creating a String with that literal – you're creating an NSString (which is Printable).
Generally, in Swift, it’s usually better to write generic functions constrained by protocols. So instead of
func logMessage(message: Printable) {
// ...
}
you would probably be better off writing:
func logMessage<T: Printable>(message: T) {
// ...
}
This approach has a number of advantages – better type-safety and avoiding type erasure, more performant etc. You can read more about this stuff here.
But you'll still hit a problem because you can't pass in a String. You have two options here. First, just don't constraint it at all:
func logMessage<T>(message: T) {
// ...then use toString(message) to create a String if you need one,
// or use string interpolation or print()
}
This will work with String, and in fact will also work with anything that isn’t Printable as well (though you'll get quite a unhelpful output involving the mangled classname).
Or, you could use Streamable which strings do conform to:
func logMessage<T: Streamable>(message: T) {
println(message)
}
let s: String = "hello"
logMessage(s)
I think I read a while back on twitter one of the Swift team mention that the reason String doesn't conform to Printable is exactly because they didn't want people using Printable directly like this and that it’s better to always use toString or similar.
I'm working with a third party c API I'm trying to call one of the functions with a simple string. Something like this:
some_c_func("aString");
I get a build error:
Type 'UnsafeMutablePointer<char_t>' does not conform to protocol 'StringLiteralConvertible'
I've seen some suggestions to use utf8 on String or similar conversions, which gets nearly there, but with the following error:
some_c_func("aString".cStringUsingEncoding(NSUTF8StringEncoding));
'UnsafePointer<Int8>' is not convertible to 'UnsafeMutablePointer<char_t>'
How can I create an UnsafeMutablePointer?
It all depends on what char_t is.
If char_t converts to Int8 then the following will work.
if let cString = str.cStringUsingEncoding(NSUTF8StringEncoding) {
some_c_func(strdup(cString))
}
This can be collapsed to
some_c_func(strdup(str.cStringUsingEncoding(NSUTF8StringEncoding)!))
WARNING! This second method will cause a crash if func cStringUsingEncoding(_:) returns nil.
Updating for Swift 3, and to fix memory leak
If the C string is only needed in a local scope, then no strdup() is needed.
guard let cString = str.cString(using: .utf8) else {
return
}
some_c_func(cString)
cString will have the same memory lifecycle as str (well similar at least).
If the C string needs to live outside the local scope, then you will need a copy. That copy will need to be freed.
guard let interimString = str.cString(using: .utf8), let cString = strdup(interimString) else {
return
}
some_c_func(cString)
//…
free(cString)
it may be simpler than that - many C APIs pass strings around as char * types, and swift treats these as unsafe.
try updating the C API (good) or hack it's header files (bad) to declare these as const char * instead.
in my experience this allows you to pass standard swift String types directly to the C API.
apparently a constant is required, in order to conform to the protocol.
I haven't tried passing strings like that, but I have a C function that I call from Swift, that takes a lot more parameters than shown here, among which is a reference to a Swift C typecast buffer to hold an error string. The compiler doesn't complain and the function call works. Hopefully this will steer you closer to the answer and you can provide an update with the final answer or someone else can.
var err = [CChar](count: 256, repeatedValue: 0)
var rv = somefunc((UnsafeMutablePointer<Int8>)(err))
if (rv < 0) {
println("Error \(err)")
return
}