iOS/Mac OS fluent matching framework that works with Swift? - swift

Is there a fluent matching API that works for Swift code? The leading Objective-C matcher candidates seem to be OCHamcrest and Expecta, both of which rely on complex macros that (as per the docs) aren't available to Swift code, e.g.
#define HC_assertThat(actual, matcher) \
HC_assertThatWithLocation(self, actual, matcher, __FILE__, __LINE__)
(OCHamcrest)
#define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, ^id{ return EXPObjectify((actual)); })
(Expecta)
Is there another alternative that does work with Swift, or some way to wrap one or the other of these so they can be used with Swift?
ETA: For future readers -- I looked at SwiftHamcrest (per Jon Reid's answer) but for the time being I've settled on Quick/Nimble.

Complex preprocessor macros aren't translated to their Swift alternatives. Apple has a Blog discussing Swift, in which they described on how they made the assert function.
The first macro is easy to make into a Swift function
#define HC_assertThat(actual, matcher) \
HC_assertThatWithLocation(self, actual, matcher, __FILE__, __LINE__)
In Swift:
func HC_assertThat(caller: AnyObject, actual: String, matcher: String, file: String = __FILE__, line: UWord = __LINE__) {
HC_assertThatWithLocation(caller, actual, matcher, file.fileSystemRepresentation, Int32(line))
}
#define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, ^id{ return EXPObjectify((actual)); })
In Swift:
func EXP_expect(caller: AnyObject, actual: String, line: UWord = __LINE__, file: String = __FILE__) {
_EXP_expect(caller, Int32(line), file.fileSystemRepresentation, ({
return EXPObjectify(caller)
})
}
Note that I am guessing on what you want passed to the preprocessor functions.

https://github.com/nschum/SwiftHamcrest is a Swift-native implementation of Hamcrest

Related

How to implement Custom Log Levels in CocoaLumberJack from Swift?

I am using CocoaLumberjack for a Swift project. I would like to implement custom log levels/flags, as I would like to use 6 rather than the default 5, and would prefer different names.
The documentation for doing this is not helpful. It is only a solution for Objective-C.
The fact that DDLogFlag is defined as NS_OPTIONS means I actually could simply ignore the pre-defined values here, create my own constants, and just write some wrapping code to convert from one to the other.
However, DDLogLevel is defined as NS_ENUM, which means Swift won't be very happy with me trying to instantiate something to say 0b111111, which isn't an existing value in the enum. If it were an NS_OPTIONS, like DDLogFlag, I could just ignore the pre-existing definitions from the library and use whatever valid UInt values I wanted to.
As far as I can tell, I just have to write some Objective-C code to define my own replacements for DDLogLevel, DDLogFlag, and write a custom function to pass this in to and access these properties on DDLogMessage. But this feels bad.
How can I use my own custom logging levels in Swift with CocoaLumberjack?
This is indeed only possible in Objective-C right now - and there also only for the #define Log Macros. Even then I could imagine that the "modern" ObjC compiler will warn about the types that are passed to DDLogMessage.
The docs are indeed a bit outdated here and stem from a time where Objective-C was closer to C that it is to Swift nowadays... :-)
Nevertheless, in the end DDLogLevel and DDLogFlag are both stored as NSUInteger. Which means it can theoretically take any NSUInteger value (aka UInt in Swift).
To define your own levels, you would simply create an enum MyLogLevel: UInt { /*...*/ } and then write your own logging functions.
Those functions can actually forward to the existing functions:
extension DDLogFlag {
public static let fatal = DDLogFlag(rawValue: 0x0001)
public static let failure = DDLogFlag(rawValue: 0x0010)
}
public enum MyLogLevel: UInt {
case fatal = 0x0001
case failure = 0x0011
}
extension MyLogLevel {
public static var defaultLevel: MyLogLevel = .fatal
}
#inlinable
public func LogFatal(_ message: #autoclosure () -> Any,
level: MyLogLevel = .defaultLevel,
context: Int = 0,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil,
asynchronous async: Bool = asyncLoggingEnabled,
ddlog: DDLog = .sharedInstance) {
_DDLogMessage(message(), level: unsafeBitCast(level, to: DDLogLevel.self), flag: .fatal, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}
The unsafeBitCast here works, because in the end it's just an UInt and _DDLogMessage does not switch over the level, but instead does a bit mask check against the flag.
Disclaimer: I'm a CocoaLumberjack maintainer myself.
We don't recommend using a custom log level in Swift. There's not much benefit from it and logging frameworks like swift-log also use predefined log levels.
However, I personally could also imagine declaring DDLogLevel with NS_OPTIONS instead of NS_ENUM. The OSLog Swift overlay also uses an extensible OSLogType.
If this is something you'd like to see, please open a PR so we can discuss it with the team. We need to be a bit careful with API compatibility, but like I said it's totally doable.
On a side-note: May I ask what you need custom levels for?

Swift C interop: Passing Swift Argv to C argv

I am working on a swift wrapper for a C library. One such function in this library expects the command line arguments, in the form of char const *const *. This is linked to swift as Optional<UnsafePointer<UnsafePointer<Int8>?>> From swift I can obtain the command line arguments as CommandLine.unsafeArgv, of type UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>. How can I convert this to the expected immutable type? I know UnsafePointer has a constructor that takes an UnsafeMutablePointer, but I'm unsure of how to handle the nested types. Suggestions on how to correctly convert this?
Try the following (tested with Xcode 12 / swift 5.3)
let values: UnsafePointer<UnsafePointer<Int8>?> =
UnsafeRawPointer(CommandLine.unsafeArgv).bindMemory(
to: UnsafePointer<Int8>?.self,
capacity: Int(CommandLine.argc)
)
your_api_func(Int(CommandLine.argc), values)
Alternate: (on #MartinR comment) tested & worked as well.
CommandLine.unsafeArgv.withMemoryRebound(
to: UnsafePointer<Int8>?.self,
capacity: Int(CommandLine.argc)) { ptr in
your_api_func(Int(CommandLine.argc), ptr)
}

How to use Strings as Printables in Swift

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.

Multiline statement in Swift

I was working on a Swift tutorial and found that Swift has a strange way to handle multi-line statement.
First, I defined some extension to the standard String class:
extension String {
func replace(target: String, withString: String) -> String {
return self.stringByReplacingOccurrencesOfString(target, withString: withString)
}
func toLowercase() -> String {
return self.lowercaseString
}
}
This works as expected:
let str = "HELLO WORLD"
let s1 = str.lowercaseString.replace("hello", withString: "goodbye") // -> goodbye world
This doesn't work:
let s2 = str
.lowercaseString
.replace("hello", withString: "goodbye")
// Error: could not find member 'lowercaseString'
If I replace the reference to the lowercaseString property with a function call, it works again:
let s3 = str
.toLowercase()
.replace("hello", withString: "goodbye") // -> goodbye world
Is there anything in the Swift language specifications that prevent a property to be broken onto its own line?
Code at Swift Stub.
This is definitely a compiler bug. Issue has been resolved in Xcode 7 beta 3.
This feels like a compiler bug, but it relates to the fact that you can define prefix, infix, and postfix operators in Swift (but not the . operator, ironically enough). I don't know why it only gives you grief on the property and not the function call, but is a combination of two things:
the whitespace before and after the . (dot) operator for properties (only)
some nuance of this ever growing language that treats properties differently than function calls (even though functions are supposed to first class types).
I would file a bug to see what comes out of it, Swift is not supposed to by pythonic this way. That said, to work around it, you can either not break the property from the type, or you can add a white space before and after the . .
let s2 = str.lowercaseString
.replace("hello", withString: "goodbye")
let s3 = str
. lowercaseString
.replace("hello", withString: "goodbye")
Using semicolons is not mandatory in swift. And I think that the problems with multiline statements in swift are because of optional semicolons.
Note that swift does not support multiline strings. Check here: Swift - Split string over multiple lines
So maybe swift cannot handle multiline statements. I am not sure about this and this could be one of the reasons so I would appreciate if anyone else can help regarding this issue.

How do I access program arguments in Swift?

C and derivatives have argc and argv (and envp) parameters to their entry point functions, but Swift doesn't have one proper: top-level code is just code and it doesn't have parameters.
How can one access the equivalent of argc and argv in a Swift program?
Process was just renamed into CommandLine (since Swift 3.0 August 4 snapshot)
let arguments = CommandLine.arguments
(for some reason this wasn't mentioned on the changelog)
Process.arguments is your friend!
Fortunately this is much easier, and built in: no importing anything, no getting your hands dirty with C, objective or otherwise.
Consider this, let's call it args.swift:
Swift 2 version:
var c = 0;
for arg in Process.arguments {
println("argument \(c) is: \(arg)")
c++
}
Swift 3 version:
var c = 0;
for arg in CommandLine.arguments {
print("argument \(c) is: \(arg)")
c += 1
}
We can compile and run it like this:
$ swift -o args args.swift && ./args fee fi fo fum
argument 0 is: ./args
argument 1 is: fee
argument 2 is: fi
argument 3 is: fo
argument 4 is: fum
Note that the first argument is the program name, as you might expect.
It seems every argument is a String, as you might also expect.
I hope very much that Process becomes more useful as Swift matures, but right now it seems to only give you the arguments. Which is a lot, if you're trying to write a pure-Swift program.
As soon as your app is up I'd use the process info:
let args = NSProcessInfo.processInfo().arguments
print(args)
Nothing unsafe there, very convenient.
Note that you have to import Foundation (or Cocoa / UIKit).
For Swift 3 you can use this code:
let argc = CommandLine.argc
let argv = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
which is equivalent of argc and argv parameters used in Objective-C main function:
int main(int argc, char *argv[])
For older versions of Swift, you can use Process.argc and Process.unsafeArgv or C_ARGC and C_ARGV.
You can pass this variables to UIApplicationMain function in iOS app:
Swift 3:
let argc = CommandLine.argc
let argv = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
UIApplicationMain(argc, argv, nil, NSStringFromClass(AppDelegate.self))
previous Swift versions:
UIApplicationMain(Process.argc, Process.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
or:
UIApplicationMain(C_ARGC, C_ARGC, nil, NSStringFromClass(AppDelegate.self))
Objective-C:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
import Foundation
println(C_ARGC) //CInt
println(C_ARGV) // CString
As in the above code, you can use C_ARGC to get number of arguments. C_ARGV to get this arguments.
An elegant alternative to CommandLine.arguments is the Swift Argument Parser. ArgumentParser will do the parsing of the command line arguments for you, mapping the arguments into a struct. It provides all the features you’d expect from a command line app including type safe parsing of arguments, automatic help features, short and long options, etc.
Just use the “Swift Package Manager” to add the ArgumentParser package to your command line project (e.g., in Xcode’s “File” » “Add Package” command).