From 31f4c709191695fcd66a7c6f38c9a9eaa313b4a2 Mon Sep 17 00:00:00 2001 From: Sho Ikeda Date: Thu, 24 Aug 2017 22:51:24 +0900 Subject: [PATCH 1/4] Support both Swift 3.x and Swift 4 --- Nimble.xcodeproj/project.pbxproj | 2 ++ Sources/Nimble/Adapters/NMBExpectation.swift | 26 +++++++++---------- Sources/Nimble/Matchers/BeCloseTo.swift | 6 ++--- Sources/Nimble/Matchers/RaisesException.swift | 12 ++++----- Sources/Nimble/Utils/Async.swift | 15 ++++++++++- Tests/NimbleTests/Helpers/utils.swift | 6 ++--- 6 files changed, 41 insertions(+), 26 deletions(-) diff --git a/Nimble.xcodeproj/project.pbxproj b/Nimble.xcodeproj/project.pbxproj index ed0beab80..dd11b1618 100644 --- a/Nimble.xcodeproj/project.pbxproj +++ b/Nimble.xcodeproj/project.pbxproj @@ -1796,6 +1796,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 9.0; @@ -1850,6 +1851,7 @@ METAL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 9.0; diff --git a/Sources/Nimble/Adapters/NMBExpectation.swift b/Sources/Nimble/Adapters/NMBExpectation.swift index dc52ac96d..642ec3f58 100644 --- a/Sources/Nimble/Adapters/NMBExpectation.swift +++ b/Sources/Nimble/Adapters/NMBExpectation.swift @@ -49,13 +49,13 @@ public class NMBExpectation: NSObject { } } - public var withTimeout: (TimeInterval) -> NMBExpectation { + @objc public var withTimeout: (TimeInterval) -> NMBExpectation { return ({ timeout in self._timeout = timeout return self }) } - public var to: (NMBMatcher) -> Void { + @objc public var to: (NMBMatcher) -> Void { return ({ matcher in if let pred = matcher as? NMBPredicate { self.expectValue.to(from(objcPredicate: pred)) @@ -65,7 +65,7 @@ public class NMBExpectation: NSObject { }) } - public var toWithDescription: (NMBMatcher, String) -> Void { + @objc public var toWithDescription: (NMBMatcher, String) -> Void { return ({ matcher, description in if let pred = matcher as? NMBPredicate { self.expectValue.to(from(objcPredicate: pred), description: description) @@ -75,7 +75,7 @@ public class NMBExpectation: NSObject { }) } - public var toNot: (NMBMatcher) -> Void { + @objc public var toNot: (NMBMatcher) -> Void { return ({ matcher in if let pred = matcher as? NMBPredicate { self.expectValue.toNot(from(objcPredicate: pred)) @@ -85,7 +85,7 @@ public class NMBExpectation: NSObject { }) } - public var toNotWithDescription: (NMBMatcher, String) -> Void { + @objc public var toNotWithDescription: (NMBMatcher, String) -> Void { return ({ matcher, description in if let pred = matcher as? NMBPredicate { self.expectValue.toNot(from(objcPredicate: pred), description: description) @@ -95,11 +95,11 @@ public class NMBExpectation: NSObject { }) } - public var notTo: (NMBMatcher) -> Void { return toNot } + @objc public var notTo: (NMBMatcher) -> Void { return toNot } - public var notToWithDescription: (NMBMatcher, String) -> Void { return toNotWithDescription } + @objc public var notToWithDescription: (NMBMatcher, String) -> Void { return toNotWithDescription } - public var toEventually: (NMBMatcher) -> Void { + @objc public var toEventually: (NMBMatcher) -> Void { return ({ matcher in if let pred = matcher as? NMBPredicate { self.expectValue.toEventually( @@ -117,7 +117,7 @@ public class NMBExpectation: NSObject { }) } - public var toEventuallyWithDescription: (NMBMatcher, String) -> Void { + @objc public var toEventuallyWithDescription: (NMBMatcher, String) -> Void { return ({ matcher, description in if let pred = matcher as? NMBPredicate { self.expectValue.toEventually( @@ -135,7 +135,7 @@ public class NMBExpectation: NSObject { }) } - public var toEventuallyNot: (NMBMatcher) -> Void { + @objc public var toEventuallyNot: (NMBMatcher) -> Void { return ({ matcher in if let pred = matcher as? NMBPredicate { self.expectValue.toEventuallyNot( @@ -153,7 +153,7 @@ public class NMBExpectation: NSObject { }) } - public var toEventuallyNotWithDescription: (NMBMatcher, String) -> Void { + @objc public var toEventuallyNotWithDescription: (NMBMatcher, String) -> Void { return ({ matcher, description in if let pred = matcher as? NMBPredicate { self.expectValue.toEventuallyNot( @@ -171,9 +171,9 @@ public class NMBExpectation: NSObject { }) } - public var toNotEventually: (NMBMatcher) -> Void { return toEventuallyNot } + @objc public var toNotEventually: (NMBMatcher) -> Void { return toEventuallyNot } - public var toNotEventuallyWithDescription: (NMBMatcher, String) -> Void { return toEventuallyNotWithDescription } + @objc public var toNotEventuallyWithDescription: (NMBMatcher, String) -> Void { return toEventuallyNotWithDescription } @objc public class func failWithMessage(_ message: String, file: FileString, line: UInt) { fail(message, location: SourceLocation(file: file, line: line)) diff --git a/Sources/Nimble/Matchers/BeCloseTo.swift b/Sources/Nimble/Matchers/BeCloseTo.swift index cd43bd655..dfb4e2853 100644 --- a/Sources/Nimble/Matchers/BeCloseTo.swift +++ b/Sources/Nimble/Matchers/BeCloseTo.swift @@ -43,7 +43,7 @@ public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher { _delta = within } - public func matches(_ actualExpression: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { + @objc public func matches(_ actualExpression: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { let actualBlock: () -> NMBDoubleConvertible? = ({ return actualExpression() as? NMBDoubleConvertible }) @@ -52,7 +52,7 @@ public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher { return try! matcher.matches(expr, failureMessage: failureMessage) } - public func doesNotMatch(_ actualExpression: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { + @objc public func doesNotMatch(_ actualExpression: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { let actualBlock: () -> NMBDoubleConvertible? = ({ return actualExpression() as? NMBDoubleConvertible }) @@ -61,7 +61,7 @@ public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher { return try! matcher.doesNotMatch(expr, failureMessage: failureMessage) } - public var within: (CDouble) -> NMBObjCBeCloseToMatcher { + @objc public var within: (CDouble) -> NMBObjCBeCloseToMatcher { return ({ delta in return NMBObjCBeCloseToMatcher(expected: self._expected, within: delta) }) diff --git a/Sources/Nimble/Matchers/RaisesException.swift b/Sources/Nimble/Matchers/RaisesException.swift index 6a0bfde85..0c13c45cc 100644 --- a/Sources/Nimble/Matchers/RaisesException.swift +++ b/Sources/Nimble/Matchers/RaisesException.swift @@ -114,7 +114,7 @@ public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher { _block = block } - public func matches(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { + @objc public func matches(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { let block: () -> Any? = ({ _ = actualBlock(); return nil }) let expr = Expression(expression: block, location: location) @@ -126,11 +126,11 @@ public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher { ).matches(expr, failureMessage: failureMessage) } - public func doesNotMatch(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { + @objc public func doesNotMatch(_ actualBlock: @escaping () -> NSObject!, failureMessage: FailureMessage, location: SourceLocation) -> Bool { return !matches(actualBlock, failureMessage: failureMessage, location: location) } - public var named: (_ name: String) -> NMBObjCRaiseExceptionMatcher { + @objc public var named: (_ name: String) -> NMBObjCRaiseExceptionMatcher { return ({ name in return NMBObjCRaiseExceptionMatcher( name: name, @@ -141,7 +141,7 @@ public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher { }) } - public var reason: (_ reason: String?) -> NMBObjCRaiseExceptionMatcher { + @objc public var reason: (_ reason: String?) -> NMBObjCRaiseExceptionMatcher { return ({ reason in return NMBObjCRaiseExceptionMatcher( name: self._name, @@ -152,7 +152,7 @@ public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher { }) } - public var userInfo: (_ userInfo: NSDictionary?) -> NMBObjCRaiseExceptionMatcher { + @objc public var userInfo: (_ userInfo: NSDictionary?) -> NMBObjCRaiseExceptionMatcher { return ({ userInfo in return NMBObjCRaiseExceptionMatcher( name: self._name, @@ -163,7 +163,7 @@ public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher { }) } - public var satisfyingBlock: (_ block: ((NSException) -> Void)?) -> NMBObjCRaiseExceptionMatcher { + @objc public var satisfyingBlock: (_ block: ((NSException) -> Void)?) -> NMBObjCRaiseExceptionMatcher { return ({ block in return NMBObjCRaiseExceptionMatcher( name: self._name, diff --git a/Sources/Nimble/Utils/Async.swift b/Sources/Nimble/Utils/Async.swift index d1417466b..44fa21668 100644 --- a/Sources/Nimble/Utils/Async.swift +++ b/Sources/Nimble/Utils/Async.swift @@ -180,9 +180,18 @@ internal class AwaitPromiseBuilder { // checked. // // In addition, stopping the run loop is used to halt code executed on the main run loop. + #if swift(>=4.0) + trigger.timeoutSource.schedule( + deadline: DispatchTime.now() + timeoutInterval, + repeating: .never, + leeway: timeoutLeeway + ) + #else trigger.timeoutSource.scheduleOneshot( deadline: DispatchTime.now() + timeoutInterval, - leeway: timeoutLeeway) + leeway: timeoutLeeway + ) + #endif trigger.timeoutSource.setEventHandler { guard self.promise.asyncResult.isIncomplete() else { return } let timedOutSem = DispatchSemaphore(value: 0) @@ -320,7 +329,11 @@ internal class Awaiter { let asyncSource = createTimerSource(asyncQueue) let trigger = AwaitTrigger(timeoutSource: timeoutSource, actionSource: asyncSource) { let interval = DispatchTimeInterval.nanoseconds(Int(pollInterval * TimeInterval(NSEC_PER_SEC))) + #if swift(>=4.0) + asyncSource.schedule(deadline: .now(), repeating: interval, leeway: pollLeeway) + #else asyncSource.scheduleRepeating(deadline: .now(), interval: interval, leeway: pollLeeway) + #endif asyncSource.setEventHandler { do { if let result = try closure() { diff --git a/Tests/NimbleTests/Helpers/utils.swift b/Tests/NimbleTests/Helpers/utils.swift index 976b2fb10..49eee8861 100644 --- a/Tests/NimbleTests/Helpers/utils.swift +++ b/Tests/NimbleTests/Helpers/utils.swift @@ -67,15 +67,15 @@ func deferToMainQueue(action: @escaping () -> Void) { } public class NimbleHelper: NSObject { - public class func expectFailureMessage(_ message: NSString, block: @escaping () -> Void, file: FileString, line: UInt) { + @objc public class func expectFailureMessage(_ message: NSString, block: @escaping () -> Void, file: FileString, line: UInt) { failsWithErrorMessage(String(describing: message), file: file, line: line, preferOriginalSourceLocation: true, closure: block) } - public class func expectFailureMessages(_ messages: [NSString], block: @escaping () -> Void, file: FileString, line: UInt) { + @objc public class func expectFailureMessages(_ messages: [NSString], block: @escaping () -> Void, file: FileString, line: UInt) { failsWithErrorMessage(messages.map({String(describing: $0)}), file: file, line: line, preferOriginalSourceLocation: true, closure: block) } - public class func expectFailureMessageForNil(_ message: NSString, block: @escaping () -> Void, file: FileString, line: UInt) { + @objc public class func expectFailureMessageForNil(_ message: NSString, block: @escaping () -> Void, file: FileString, line: UInt) { failsWithErrorMessageForNil(String(describing: message), file: file, line: line, preferOriginalSourceLocation: true, closure: block) } } From 09d7e67bbee84751cb5393238e1524a748326b7f Mon Sep 17 00:00:00 2001 From: Sho Ikeda Date: Sat, 26 Aug 2017 00:47:18 +0900 Subject: [PATCH 2/4] `NimbleHelper` is not needed for SwiftPM --- Tests/NimbleTests/Helpers/utils.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/NimbleTests/Helpers/utils.swift b/Tests/NimbleTests/Helpers/utils.swift index 49eee8861..6a5f544c0 100644 --- a/Tests/NimbleTests/Helpers/utils.swift +++ b/Tests/NimbleTests/Helpers/utils.swift @@ -66,6 +66,7 @@ func deferToMainQueue(action: @escaping () -> Void) { } } +#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE public class NimbleHelper: NSObject { @objc public class func expectFailureMessage(_ message: NSString, block: @escaping () -> Void, file: FileString, line: UInt) { failsWithErrorMessage(String(describing: message), file: file, line: line, preferOriginalSourceLocation: true, closure: block) @@ -79,6 +80,7 @@ public class NimbleHelper: NSObject { failsWithErrorMessageForNil(String(describing: message), file: file, line: line, preferOriginalSourceLocation: true, closure: block) } } +#endif extension Date { init(dateTimeString: String) { From 2a8dd83955bc4ed03831dffc1d1e388a97458d07 Mon Sep 17 00:00:00 2001 From: Sho Ikeda Date: Sat, 26 Aug 2017 10:07:44 +0900 Subject: [PATCH 3/4] Update `NMBWait` for Swift 4 support --- Sources/Nimble/DSL+Wait.swift | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Sources/Nimble/DSL+Wait.swift b/Sources/Nimble/DSL+Wait.swift index fdf450865..3b449dc4a 100644 --- a/Sources/Nimble/DSL+Wait.swift +++ b/Sources/Nimble/DSL+Wait.swift @@ -11,6 +11,8 @@ private enum ErrorResult { /// bridges to Objective-C via the @objc keyword. This class encapsulates callback-style /// asynchronous waiting logic so that it may be called from Objective-C and Swift. internal class NMBWait: NSObject { +#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE + @objc internal class func until( timeout: TimeInterval, file: FileString = #file, @@ -20,6 +22,17 @@ internal class NMBWait: NSObject { action(done) } } +#else + internal class func until( + timeout: TimeInterval, + file: FileString = #file, + line: UInt = #line, + action: @escaping (@escaping () -> Void) -> Void) { + return throwableUntil(timeout: timeout, file: file, line: line) { done in + action(done) + } + } +#endif // Using a throwable closure makes this method not objc compatible. internal class func throwableUntil( @@ -70,16 +83,16 @@ internal class NMBWait: NSObject { } } - #if SWIFT_PACKAGE +#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE + @objc(untilFile:line:action:) internal class func until(_ file: FileString = #file, line: UInt = #line, action: @escaping (() -> Void) -> Void) { until(timeout: 1, file: file, line: line, action: action) } - #else - @objc(untilFile:line:action:) +#else internal class func until(_ file: FileString = #file, line: UInt = #line, action: @escaping (() -> Void) -> Void) { until(timeout: 1, file: file, line: line, action: action) } - #endif +#endif } internal func blockedRunLoopErrorMessageFor(_ fnName: String, leeway: TimeInterval) -> String { From 2f28fd33d912cead8bdabc1fae2d0273b9a1b5ac Mon Sep 17 00:00:00 2001 From: Sho Ikeda Date: Tue, 29 Aug 2017 09:23:24 +0900 Subject: [PATCH 4/4] Add a comment about the `#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE` check [ci skip] --- Sources/Nimble/DSL+Wait.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/Nimble/DSL+Wait.swift b/Sources/Nimble/DSL+Wait.swift index 3b449dc4a..47a90287b 100644 --- a/Sources/Nimble/DSL+Wait.swift +++ b/Sources/Nimble/DSL+Wait.swift @@ -11,6 +11,9 @@ private enum ErrorResult { /// bridges to Objective-C via the @objc keyword. This class encapsulates callback-style /// asynchronous waiting logic so that it may be called from Objective-C and Swift. internal class NMBWait: NSObject { +// About these kind of lines, `@objc` attributes are only required for Objective-C +// support, so that should be conditional on Darwin platforms and normal Xcode builds +// (non-SwiftPM builds). #if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE @objc internal class func until(