Compare commits

..

20 Commits

Author SHA1 Message Date
Jørgen Henrichsen d1bbc94bdd Update Readme. Update podspec.
Bump version to 0.9.1.
2019-06-11 13:48:16 +02:00
Jørgen Henrichsen fb5a8dde6c Merge pull request #61 from minhtc/fix-exclusive-access
Fix crash on modification requires exclusive access
2019-06-11 13:47:03 +02:00
minhtcx 9b71040f43 fix crash on modification requires exclusive access 2019-06-10 12:02:46 +07:00
Jørgen Henrichsen 5e50ea48d6 Merge pull request #60 from jorgenhenrichsen/recover-from-AVPlayer-failed
Recover from AVPlayer failed
2019-05-11 19:41:12 +02:00
Jørgen Henrichsen 92a804e9e4 Update Readme. Update podspec.
Bump version to v0.9.0.
2019-05-11 19:30:59 +02:00
Jørgen Henrichsen af4635d047 Remove AVPlayer parameter from init in both AudioPlayer and AVPlayerWrapper. 2019-05-11 19:29:36 +02:00
Jørgen Henrichsen addebef7f9 Add more detailed description on the fail event. 2019-05-11 18:14:54 +02:00
Jørgen Henrichsen 46022ef0d6 Add event when AVPlayer is recreated.
The AudioSession category will need to be set again when this event is emitted.
2019-05-11 17:56:43 +02:00
Jørgen Henrichsen 386dc5202c Recreate the AVPlayer when loading an item, if the previous item failed. 2019-05-11 17:46:55 +02:00
Jørgen Henrichsen 17166093c2 Correctly set isObserving when stopping the observation. 2019-05-11 17:46:27 +02:00
Jørgen Henrichsen c06b8ce64c Made the player observer not retain the AVPlayer.
They will also stop observing the current AVPlayer if a new one is going to be set.
2019-05-11 17:34:24 +02:00
Jørgen Henrichsen 5c8c83f914 Merge pull request #54 from jorgenhenrichsen/swift5
Swift 5
2019-04-18 17:18:00 +02:00
Jørgen Henrichsen ef2d9f5a90 Update README. Update podspec.
Bump version to 0.8.0.
2019-04-18 17:06:57 +02:00
Jørgen Henrichsen 08ebb473f2 Add a custom case to the TimeEventFrequency enum.
Will fix #52.
2019-04-18 16:53:07 +02:00
Jørgen Henrichsen d32a041159 Updated to recommended project settings. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen 1057a7fca6 Add new handler for non-frozen enums. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen cee2be50e2 Removed the AudioPlayerDelegate.
Events must be used instead.
2019-04-18 16:53:07 +02:00
Jørgen Henrichsen 8161c3cf02 Update to Swift 5. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen bb0f301383 Update dependencies. 2019-04-18 16:53:07 +02:00
Jørgen Henrichsen 9da1347d12 Update issue templates
Added issue templates based on Githubs default templates
2019-04-18 12:45:07 +02:00
84 changed files with 1670 additions and 1504 deletions
+32
View File
@@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
If needed, include code examples for the problem or steps to reproduce.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Version [e.g. 0.7.0]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6, Simulator iPhone6 ...]
- OS: [e.g. iOS8.1]
- Version [e.g. 0.7.0]
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution or feature you would like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+2 -2
View File
@@ -7,8 +7,8 @@ target 'SwiftAudio_Example' do
target 'SwiftAudio_Tests' do
inherit! :search_paths
pod 'Quick', '~> 1.3.0'
pod 'Nimble' , '~> 7.3.0'
pod 'Quick', '~> 2.0.0'
pod 'Nimble' , '~> 8.0.0'
end
end
+9 -9
View File
@@ -1,11 +1,11 @@
PODS:
- Nimble (7.3.1)
- Quick (1.3.2)
- SwiftAudio (0.3.3)
- Nimble (8.0.1)
- Quick (2.0.0)
- SwiftAudio (0.7.2)
DEPENDENCIES:
- Nimble (~> 7.3.0)
- Quick (~> 1.3.0)
- Nimble (~> 8.0.0)
- Quick (~> 2.0.0)
- SwiftAudio (from `../`)
SPEC REPOS:
@@ -18,10 +18,10 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
SwiftAudio: 2e712c3e04cf172d05639d7bb1516db7afd195da
Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0
Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d
SwiftAudio: a7bb22e98fd48fd908f7ffca992166e0057ce8ea
PODFILE CHECKSUM: 8a75946cbc65d8d98176f80a88d8363a28d118ce
PODFILE CHECKSUM: 08ea4075437f8ff3c4f84ed70827a8132461373f
COCOAPODS: 1.5.3
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "SwiftAudio",
"version": "0.3.3",
"version": "0.7.2",
"summary": "Easy audio streaming for iOS",
"description": "SwiftAudio is an audio player written in Swift, making it simpler to work with audio playback from streams and files.",
"homepage": "https://github.com/jorgenhenrichsen/SwiftAudio",
@@ -13,7 +13,7 @@
},
"source": {
"git": "https://github.com/jorgenhenrichsen/SwiftAudio.git",
"tag": "0.3.3"
"tag": "0.7.2"
},
"platforms": {
"ios": "10.0"
+9 -9
View File
@@ -1,11 +1,11 @@
PODS:
- Nimble (7.3.1)
- Quick (1.3.2)
- SwiftAudio (0.3.3)
- Nimble (8.0.1)
- Quick (2.0.0)
- SwiftAudio (0.7.2)
DEPENDENCIES:
- Nimble (~> 7.3.0)
- Quick (~> 1.3.0)
- Nimble (~> 8.0.0)
- Quick (~> 2.0.0)
- SwiftAudio (from `../`)
SPEC REPOS:
@@ -18,10 +18,10 @@ EXTERNAL SOURCES:
:path: "../"
SPEC CHECKSUMS:
Nimble: 04f732da099ea4d153122aec8c2a88fd0c7219ae
Quick: 2623cb30d7a7f41ca62f684f679586558f483d46
SwiftAudio: 2e712c3e04cf172d05639d7bb1516db7afd195da
Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0
Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d
SwiftAudio: a7bb22e98fd48fd908f7ffca992166e0057ce8ea
PODFILE CHECKSUM: 8a75946cbc65d8d98176f80a88d8363a28d118ce
PODFILE CHECKSUM: 08ea4075437f8ff3c4f84ed70827a8132461373f
COCOAPODS: 1.5.3
+7 -22
View File
@@ -4,6 +4,7 @@
[![CocoaPods](https://img.shields.io/cocoapods/v/Nimble.svg)](https://cocoapods.org/pods/Nimble)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platforms](https://img.shields.io/cocoapods/p/Nimble.svg)](https://cocoapods.org/pods/Nimble)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
Use Nimble to express the expected outcomes of Swift
or Objective-C expressions. Inspired by
@@ -306,8 +307,7 @@ In Nimble, it's easy to make expectations on values that are updated
asynchronously. Just use `toEventually` or `toEventuallyNot`:
```swift
// Swift 3.0 and later
// Swift
DispatchQueue.main.async {
ocean.add("dolphins")
ocean.add("whales")
@@ -316,17 +316,6 @@ expect(ocean).toEventually(contain("dolphins", "whales"))
```
```swift
// Swift 2.3 and earlier
dispatch_async(dispatch_get_main_queue()) {
ocean.add("dolphins")
ocean.add("whales")
}
expect(ocean).toEventually(contain("dolphins", "whales"))
```
```objc
// Objective-C
@@ -857,11 +846,7 @@ Notes:
## Swift Error Handling
If you're using Swift 2.0 or newer, you can use the `throwError` matcher to check if an error is thrown.
Note:
The following code sample references the `Swift.Error` protocol.
This is `Swift.ErrorProtocol` in versions of Swift prior to version 3.0.
You can use the `throwError` matcher to check if an error is thrown.
```swift
// Swift
@@ -1277,7 +1262,7 @@ public func equal<T: Equatable>(expectedValue: T?) -> Predicate<T> {
// Predicate { actual in ... }
//
// But shown with types here for clarity.
return Predicate { (actual: Expression<T>) throws -> PredicateResult in
return Predicate { (actualExpression: Expression<T>) throws -> PredicateResult in
let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>")
if let actualValue = try actualExpression.evaluate() {
return PredicateResult(
@@ -1673,11 +1658,11 @@ backported.
The deprecating plan is a 3 major versions removal. Which is as follows:
1. Introduce new `Predicate` API, deprecation warning for old matcher APIs.
(Nimble `v7.x.x`)
(Nimble `v7.x.x` and `v8.x.x`)
2. Introduce warnings on migration-path features (`.predicate`,
`Predicate`-constructors with similar arguments to old API). (Nimble
`v8.x.x`)
3. Remove old API. (Nimble `v9.x.x`)
`v9.x.x`)
3. Remove old API. (Nimble `v10.x.x`)
# Installing Nimble
@@ -13,5 +13,6 @@ public protocol AssertionHandler {
///
/// @see AssertionHandler
public var NimbleAssertionHandler: AssertionHandler = { () -> AssertionHandler in
// swiftlint:disable:previous identifier_name
return isXCTestAvailable() ? NimbleXCTestHandler() : NimbleXCTestUnavailableHandler()
}()
@@ -37,21 +37,48 @@ public class AssertionRecorder: AssertionHandler {
}
}
extension NMBExceptionCapture {
internal func tryBlockThrows(_ unsafeBlock: () throws -> Void) throws {
var catchedError: Error?
tryBlock {
do {
try unsafeBlock()
} catch {
catchedError = error
}
}
if let error = catchedError {
throw error
}
}
}
/// Allows you to temporarily replace the current Nimble assertion handler with
/// the one provided for the scope of the closure.
///
/// Once the closure finishes, then the original Nimble assertion handler is restored.
///
/// @see AssertionHandler
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler, closure: () throws -> Void) {
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
file: FileString = #file,
line: UInt = #line,
closure: () throws -> Void) {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
let capturer = NMBExceptionCapture(handler: nil, finally: ({
environment.assertionHandler = oldRecorder
}))
environment.assertionHandler = tempAssertionHandler
capturer.tryBlock {
try! closure()
do {
try capturer.tryBlockThrows {
try closure()
}
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
let location = SourceLocation(file: file, line: line)
tempAssertionHandler.assert(false, message: failureMessage, location: location)
}
}
@@ -1,6 +1,6 @@
import Foundation
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
private func from(objcPredicate: NMBPredicate) -> Predicate<NSObject> {
return Predicate { actualExpression in
@@ -15,6 +15,7 @@ internal struct ObjCMatcherWrapper: Matcher {
func matches(_ actualExpression: Expression<NSObject>, failureMessage: FailureMessage) -> Bool {
return matcher.matches(
// swiftlint:disable:next force_try
({ try! actualExpression.evaluate() }),
failureMessage: failureMessage,
location: actualExpression.location)
@@ -22,6 +23,7 @@ internal struct ObjCMatcherWrapper: Matcher {
func doesNotMatch(_ actualExpression: Expression<NSObject>, failureMessage: FailureMessage) -> Bool {
return matcher.doesNotMatch(
// swiftlint:disable:next force_try
({ try! actualExpression.evaluate() }),
failureMessage: failureMessage,
location: actualExpression.location)
@@ -30,11 +32,13 @@ internal struct ObjCMatcherWrapper: Matcher {
// Equivalent to Expectation, but for Nimble's Objective-C interface
public class NMBExpectation: NSObject {
// swiftlint:disable identifier_name
internal let _actualBlock: () -> NSObject?
internal var _negative: Bool
internal let _file: FileString
internal let _line: UInt
internal var _timeout: TimeInterval = 1.0
// swiftlint:enable identifier_name
@objc public init(actualBlock: @escaping () -> NSObject?, negative: Bool, file: FileString, line: UInt) {
self._actualBlock = actualBlock
@@ -1,6 +1,6 @@
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// swiftlint:disable line_length
public typealias MatcherBlock = (_ actualExpression: Expression<NSObject>, _ failureMessage: FailureMessage) throws -> Bool
@@ -8,8 +8,10 @@ public typealias FullMatcherBlock = (_ actualExpression: Expression<NSObject>, _
// swiftlint:enable line_length
public class NMBObjCMatcher: NSObject, NMBMatcher {
// swiftlint:disable identifier_name
let _match: MatcherBlock
let _doesNotMatch: MatcherBlock
// swiftlint:enable identifier_name
let canMatchNil: Bool
public init(canMatchNil: Bool, matcher: @escaping MatcherBlock, notMatcher: @escaping MatcherBlock) {
@@ -3,7 +3,7 @@ import Foundation
/// "Global" state of Nimble is stored here. Only DSL functions should access / be aware of this
/// class' existence
internal class NimbleEnvironment {
internal class NimbleEnvironment: NSObject {
static var activeInstance: NimbleEnvironment {
get {
let env = Thread.current.threadDictionary["NimbleEnvironment"]
@@ -20,6 +20,7 @@ internal class NimbleEnvironment {
}
}
// swiftlint:disable:next todo
// TODO: eventually migrate the global to this environment value
var assertionHandler: AssertionHandler {
get { return NimbleAssertionHandler }
@@ -29,17 +30,14 @@ internal class NimbleEnvironment {
var suppressTVOSAssertionWarning: Bool = false
var awaiter: Awaiter
init() {
let timeoutQueue: DispatchQueue
if #available(OSX 10.10, *) {
timeoutQueue = DispatchQueue.global(qos: .userInitiated)
} else {
timeoutQueue = DispatchQueue.global(priority: .high)
}
override init() {
let timeoutQueue = DispatchQueue.global(qos: .userInitiated)
awaiter = Awaiter(
waitLock: AssertionWaitLock(),
asyncQueue: .main,
timeoutQueue: timeoutQueue)
timeoutQueue: timeoutQueue
)
super.init()
}
}
@@ -64,7 +64,7 @@ class NimbleXCTestUnavailableHandler: AssertionHandler {
#endif
func isXCTestAvailable() -> Bool {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// XCTest is weakly linked and so may not be present
return NSClassFromString("XCTestCase") != nil
#else
@@ -77,15 +77,14 @@ public func recordFailure(_ message: String, location: SourceLocation) {
XCTFail("\(message)", file: location.file, line: location.line)
#else
if let testCase = CurrentTestCaseTracker.sharedInstance.currentTestCase {
#if swift(>=4)
let line = Int(location.line)
#else
let line = location.line
#endif
testCase.recordFailure(withDescription: message, inFile: location.file, atLine: line, expected: true)
} else {
let msg = "Attempted to report a test failure to XCTest while no test case was running. " +
"The failure was:\n\"\(message)\"\nIt occurred at: \(location.file):\(location.line)"
let msg = """
Attempted to report a test failure to XCTest while no test case was running. The failure was:
\"\(message)\"
It occurred at: \(location.file):\(location.line)
"""
NSException(name: .internalInconsistencyException, reason: msg, userInfo: nil).raise()
}
#endif
+10 -4
View File
@@ -14,7 +14,7 @@ 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
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc
internal class func until(
timeout: TimeInterval,
@@ -87,13 +87,19 @@ internal class NMBWait: NSObject {
}
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(untilFile:line:action:)
internal class func until(_ file: FileString = #file, line: UInt = #line, action: @escaping (() -> Void) -> Void) {
internal class func until(
_ file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
until(timeout: 1, file: file, line: line, action: action)
}
#else
internal class func until(_ file: FileString = #file, line: UInt = #line, action: @escaping (() -> Void) -> Void) {
internal class func until(
_ file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
until(timeout: 1, file: file, line: line, action: action)
}
#endif
+11 -7
View File
@@ -43,12 +43,13 @@ internal func nimblePrecondition(
line: UInt = #line) {
let result = expr()
if !result {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let e = NSException(
#if canImport(Darwin)
let exception = NSException(
name: NSExceptionName(name()),
reason: message(),
userInfo: nil)
e.raise()
userInfo: nil
)
exception.raise()
#else
preconditionFailure("\(name()) - \(message())", file: file, line: line)
#endif
@@ -56,9 +57,12 @@ internal func nimblePrecondition(
}
internal func internalError(_ msg: String, file: FileString = #file, line: UInt = #line) -> Never {
// swiftlint:disable line_length
fatalError(
"Nimble Bug Found: \(msg) at \(file):\(line).\n" +
"Please file a bug to Nimble: https://github.com/Quick/Nimble/issues with the " +
"code snippet that caused this error."
"""
Nimble Bug Found: \(msg) at \(file):\(line).
Please file a bug to Nimble: https://github.com/Quick/Nimble/issues with the code snippet that caused this error.
"""
)
// swiftlint:enable line_length
}
+16 -13
View File
@@ -75,6 +75,7 @@ public indirect enum ExpectationMessage {
}
internal func visitLeafs(_ f: (ExpectationMessage) -> ExpectationMessage) -> ExpectationMessage {
// swiftlint:disable:previous identifier_name
switch self {
case .fail, .expectedTo, .expectedActualValueTo, .expectedCustomValueTo:
return f(self)
@@ -90,6 +91,7 @@ public indirect enum ExpectationMessage {
/// Replaces a primary expectation with one returned by f. Preserves all composite expectations
/// that were built upon it (aka - all appended(message:) and appended(details:).
public func replacedExpectation(_ f: @escaping (ExpectationMessage) -> ExpectationMessage) -> ExpectationMessage {
// swiftlint:disable:previous identifier_name
func walk(_ msg: ExpectationMessage) -> ExpectationMessage {
switch msg {
case .fail, .expectedTo, .expectedActualValueTo, .expectedCustomValueTo:
@@ -124,6 +126,7 @@ public indirect enum ExpectationMessage {
return visitLeafs(walk)
}
// swiftlint:disable:next todo
// TODO: test & verify correct behavior
internal func prepended(message: String) -> ExpectationMessage {
return .prepends(message, self)
@@ -183,32 +186,32 @@ public indirect enum ExpectationMessage {
extension FailureMessage {
internal func toExpectationMessage() -> ExpectationMessage {
let defaultMsg = FailureMessage()
if expected != defaultMsg.expected || _stringValueOverride != nil {
let defaultMessage = FailureMessage()
if expected != defaultMessage.expected || _stringValueOverride != nil {
return .fail(stringValue)
}
var msg: ExpectationMessage = .fail(userDescription ?? "")
var message: ExpectationMessage = .fail(userDescription ?? "")
if actualValue != "" && actualValue != nil {
msg = .expectedCustomValueTo(postfixMessage, actualValue ?? "")
} else if postfixMessage != defaultMsg.postfixMessage {
message = .expectedCustomValueTo(postfixMessage, actualValue ?? "")
} else if postfixMessage != defaultMessage.postfixMessage {
if actualValue == nil {
msg = .expectedTo(postfixMessage)
message = .expectedTo(postfixMessage)
} else {
msg = .expectedActualValueTo(postfixMessage)
message = .expectedActualValueTo(postfixMessage)
}
}
if postfixActual != defaultMsg.postfixActual {
msg = .appends(msg, postfixActual)
if postfixActual != defaultMessage.postfixActual {
message = .appends(message, postfixActual)
}
if let m = extendedMessage {
msg = .details(msg, m)
if let extended = extendedMessage {
message = .details(message, extended)
}
return msg
return message
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
public class NMBExpectationMessage: NSObject {
private let msg: ExpectationMessage
+2
View File
@@ -24,8 +24,10 @@ internal func memoizedClosure<T>(_ closure: @escaping () throws -> T) -> (Bool)
/// This provides a common consumable API for matchers to utilize to allow
/// Nimble to change internals to how the captured closure is managed.
public struct Expression<T> {
// swiftlint:disable identifier_name
internal let _expression: (Bool) throws -> T?
internal let _withoutCaching: Bool
// swiftlint:enable identifier_name
public let location: SourceLocation
public let isClosure: Bool
+1
View File
@@ -28,6 +28,7 @@ public class FailureMessage: NSObject {
}
}
// swiftlint:disable:next identifier_name
internal var _stringValueOverride: String?
internal var hasOverriddenStringValue: Bool {
return _stringValueOverride != nil
+2 -1
View File
@@ -63,7 +63,7 @@ private func createPredicate<S>(_ elementMatcher: Predicate<S.Iterator.Element>)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func allPassMatcher(_ matcher: NMBMatcher) -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -103,6 +103,7 @@ extension NMBObjCMatcher {
} else {
let failureMessage = FailureMessage()
let result = matcher.matches(
// swiftlint:disable:next force_try
({ try! expr.evaluate() }),
failureMessage: failureMessage,
location: expr.location
+10 -4
View File
@@ -23,14 +23,18 @@ private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout:
}
switch result {
case .completed: return lastPredicateResult!
case .timedOut: return PredicateResult(status: .fail, message: lastPredicateResult!.message)
case .timedOut:
let message = lastPredicateResult?.message ?? .fail("timed out before returning a value")
return PredicateResult(status: .fail, message: message)
case let .errorThrown(error):
return PredicateResult(status: .fail, message: .fail("unexpected error thrown: <\(error)>"))
case let .raisedException(exception):
return PredicateResult(status: .fail, message: .fail("unexpected exception raised: \(exception)"))
case .blockedRunLoop:
// swiftlint:disable:next line_length
return PredicateResult(status: .fail, message: lastPredicateResult!.message.appended(message: " (timed out, but main thread was unresponsive)."))
let message = lastPredicateResult?.message.appended(message: " (timed out, but main run loop was unresponsive).") ??
.fail("main run loop was unresponsive")
return PredicateResult(status: .fail, message: message)
case .incomplete:
internalError("Reached .incomplete state for \(fnName)(...).")
}
@@ -38,8 +42,10 @@ private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout:
}
private let toEventuallyRequiresClosureError = FailureMessage(
// swiftlint:disable:next line_length
stringValue: "expect(...).toEventually(...) requires an explicit closure (eg - expect { ... }.toEventually(...) )\nSwift 1.2 @autoclosure behavior has changed in an incompatible way for Nimble to function"
stringValue: """
expect(...).toEventually(...) requires an explicit closure (eg - expect { ... }.toEventually(...) )
Swift 1.2 @autoclosure behavior has changed in an incompatible way for Nimble to function
"""
)
extension Expectation {
+1 -1
View File
@@ -29,7 +29,7 @@ public func beAKindOf<T>(_ expectedType: T.Type) -> Predicate<Any> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
/// A Nimble matcher that succeeds when the actual value is an instance of the given class.
/// @see beAnInstanceOf if you want to match against the exact class
@@ -33,7 +33,7 @@ public func beAnInstanceOf(_ expectedClass: AnyClass) -> Predicate<NSObject> {
} else {
actualString = "<nil>"
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
let matches = instance != nil && instance!.isMember(of: expectedClass)
#else
let matches = instance != nil && type(of: instance!) == expectedClass
@@ -45,7 +45,7 @@ public func beAnInstanceOf(_ expectedClass: AnyClass) -> Predicate<NSObject> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beAnInstanceOfMatcher(_ expected: AnyClass) -> NMBMatcher {
return NMBPredicate { actualExpression in
+8 -1
View File
@@ -1,5 +1,6 @@
import Foundation
// swiftlint:disable:next identifier_name
public let DefaultDelta = 0.0001
internal func isCloseTo(_ actualValue: NMBDoubleConvertible?,
@@ -34,10 +35,12 @@ public func beCloseTo(_ expectedValue: NMBDoubleConvertible, within delta: Doubl
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher {
// swiftlint:disable identifier_name
var _expected: NSNumber
var _delta: CDouble
// swiftlint:enable identifier_name
init(expected: NSNumber, within: CDouble) {
_expected = expected
_delta = within
@@ -110,14 +113,17 @@ public func beCloseTo(_ expectedValues: [Double], within delta: Double = Default
infix operator : ComparisonPrecedence
// swiftlint:disable:next identifier_name
public func (lhs: Expectation<[Double]>, rhs: [Double]) {
lhs.to(beCloseTo(rhs))
}
// swiftlint:disable:next identifier_name
public func (lhs: Expectation<NMBDoubleConvertible>, rhs: NMBDoubleConvertible) {
lhs.to(beCloseTo(rhs))
}
// swiftlint:disable:next identifier_name
public func (lhs: Expectation<NMBDoubleConvertible>, rhs: (expected: NMBDoubleConvertible, delta: Double)) {
lhs.to(beCloseTo(rhs.expected, within: rhs.delta))
}
@@ -133,6 +139,7 @@ precedencegroup PlusMinusOperatorPrecedence {
}
infix operator ± : PlusMinusOperatorPrecedence
// swiftlint:disable:next identifier_name
public func ±(lhs: NMBDoubleConvertible, rhs: Double) -> (expected: NMBDoubleConvertible, delta: Double) {
return (expected: lhs, delta: rhs)
}
+25 -4
View File
@@ -4,15 +4,36 @@ import Foundation
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty<S: Sequence>() -> Predicate<S> {
return Predicate.simple("be empty") { actualExpression in
let actualSeq = try actualExpression.evaluate()
if actualSeq == nil {
guard let actual = try actualExpression.evaluate() else {
return .fail
}
var generator = actualSeq!.makeIterator()
var generator = actual.makeIterator()
return PredicateStatus(bool: generator.next() == nil)
}
}
/// A Nimble matcher that succeeds when a value is "empty". For collections, this
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty<S: SetAlgebra>() -> Predicate<S> {
return Predicate.simple("be empty") { actualExpression in
guard let actual = try actualExpression.evaluate() else {
return .fail
}
return PredicateStatus(bool: actual.isEmpty)
}
}
/// A Nimble matcher that succeeds when a value is "empty". For collections, this
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty<S: Sequence & SetAlgebra>() -> Predicate<S> {
return Predicate.simple("be empty") { actualExpression in
guard let actual = try actualExpression.evaluate() else {
return .fail
}
return PredicateStatus(bool: actual.isEmpty)
}
}
/// A Nimble matcher that succeeds when a value is "empty". For collections, this
/// means the are no items in that collection. For strings, it is an empty string.
public func beEmpty() -> Predicate<String> {
@@ -61,7 +82,7 @@ public func beEmpty() -> Predicate<NMBCollection> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beEmptyMatcher() -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -30,12 +30,12 @@ public func > (lhs: Expectation<NMBComparable>, rhs: NMBComparable?) {
lhs.to(beGreaterThan(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beGreaterThanMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beGreaterThanMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beGreaterThan(expected).matches(expr, failureMessage: failureMessage)
return try beGreaterThan(expected).satisfies(expr).toObjectiveC()
}
}
}
@@ -32,12 +32,12 @@ public func >=<T: NMBComparable>(lhs: Expectation<T>, rhs: T) {
lhs.to(beGreaterThanOrEqualTo(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beGreaterThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beGreaterThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beGreaterThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
return try beGreaterThanOrEqualTo(expected).satisfies(expr).toObjectiveC()
}
}
}
@@ -4,14 +4,14 @@ import Foundation
/// as the expected instance.
public func beIdenticalTo(_ expected: Any?) -> Predicate<Any> {
return Predicate.define { actualExpression in
#if os(Linux)
#if os(Linux) && !swift(>=4.1.50)
let actual = try actualExpression.evaluate() as? AnyObject
#else
let actual = try actualExpression.evaluate() as AnyObject?
#endif
let bool: Bool
#if os(Linux)
#if os(Linux) && !swift(>=4.1.50)
bool = actual === (expected as? AnyObject) && actual !== nil
#else
bool = actual === (expected as AnyObject?) && actual !== nil
@@ -41,12 +41,12 @@ public func be(_ expected: Any?) -> Predicate<Any> {
return beIdenticalTo(expected)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beIdenticalToMatcher(_ expected: NSObject?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beIdenticalToMatcher(_ expected: NSObject?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let aExpr = actualExpression.cast { $0 as Any? }
return try beIdenticalTo(expected).matches(aExpr, failureMessage: failureMessage)
return try beIdenticalTo(expected).satisfies(aExpr).toObjectiveC()
}
}
}
@@ -29,12 +29,12 @@ public func < (lhs: Expectation<NMBComparable>, rhs: NMBComparable?) {
lhs.to(beLessThan(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beLessThanMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beLessThanMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beLessThan(expected).matches(expr, failureMessage: failureMessage)
return try beLessThan(expected).satisfies(expr).toObjectiveC()
}
}
}
@@ -29,12 +29,12 @@ public func <=<T: NMBComparable>(lhs: Expectation<T>, rhs: T) {
lhs.to(beLessThanOrEqualTo(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beLessThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beLessThanOrEqualToMatcher(_ expected: NMBComparable?) -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { $0 as? NMBComparable }
return try beLessThanOrEqualTo(expected).matches(expr, failureMessage: failureMessage)
return try beLessThanOrEqualTo(expected).satisfies(expr).toObjectiveC()
}
}
}
+17 -30
View File
@@ -100,14 +100,6 @@ public func beTruthy<T: ExpressibleByBooleanLiteral & Equatable>() -> Predicate<
return Predicate.simpleNilable("be truthy") { actualExpression in
let actualValue = try actualExpression.evaluate()
if let actualValue = actualValue {
// FIXME: This is a workaround to SR-2290.
// See:
// - https://bugs.swift.org/browse/SR-2290
// - https://github.com/norio-nomura/Nimble/pull/5#issuecomment-237835873
if let number = actualValue as? NSNumber {
return PredicateStatus(bool: number.boolValue == true)
}
return PredicateStatus(bool: actualValue == (true as T))
}
return PredicateStatus(bool: actualValue != nil)
@@ -120,47 +112,42 @@ public func beFalsy<T: ExpressibleByBooleanLiteral & Equatable>() -> Predicate<T
return Predicate.simpleNilable("be falsy") { actualExpression in
let actualValue = try actualExpression.evaluate()
if let actualValue = actualValue {
// FIXME: This is a workaround to SR-2290.
// See:
// - https://bugs.swift.org/browse/SR-2290
// - https://github.com/norio-nomura/Nimble/pull/5#issuecomment-237835873
if let number = actualValue as? NSNumber {
return PredicateStatus(bool: number.boolValue == false)
}
return PredicateStatus(bool: actualValue == (false as T))
}
return PredicateStatus(bool: actualValue == nil)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beTruthyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
@objc public class func beTruthyMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beTruthy().matches(expr, failureMessage: failureMessage)
return try beTruthy().satisfies(expr).toObjectiveC()
}
}
@objc public class func beFalsyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
@objc public class func beFalsyMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beFalsy().matches(expr, failureMessage: failureMessage)
return try beFalsy().satisfies(expr).toObjectiveC()
}
}
@objc public class func beTrueMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
@objc public class func beTrueMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beTrue().matches(expr, failureMessage: failureMessage)
return try beTrue().satisfies(expr).toObjectiveC()
}
}
@objc public class func beFalseMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let expr = actualExpression.cast { ($0 as? NSNumber)?.boolValue ?? false }
return try beFalse().matches(expr, failureMessage: failureMessage)
@objc public class func beFalseMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
let expr = actualExpression.cast { value -> Bool? in
guard let value = value else { return nil }
return (value as? NSNumber)?.boolValue ?? false
}
return try beFalse().satisfies(expr).toObjectiveC()
}
}
}
+4 -4
View File
@@ -8,11 +8,11 @@ public func beNil<T>() -> Predicate<T> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualExpression, failureMessage in
return try beNil().matches(actualExpression, failureMessage: failureMessage)
@objc public class func beNilMatcher() -> NMBMatcher {
return NMBPredicate { actualExpression in
return try beNil().satisfies(actualExpression).toObjectiveC()
}
}
}
+7 -5
View File
@@ -8,10 +8,12 @@ public func beVoid() -> Predicate<()> {
}
}
public func == (lhs: Expectation<()>, rhs: ()) {
lhs.to(beVoid())
}
extension Expectation where T == () {
public static func == (lhs: Expectation<()>, rhs: ()) {
lhs.to(beVoid())
}
public func != (lhs: Expectation<()>, rhs: ()) {
lhs.toNot(beVoid())
public static func != (lhs: Expectation<()>, rhs: ()) {
lhs.toNot(beVoid())
}
}
+8 -8
View File
@@ -35,24 +35,24 @@ public func beginWith(_ startingElement: Any) -> Predicate<NMBOrderedCollection>
public func beginWith(_ startingSubstring: String) -> Predicate<String> {
return Predicate.simple("begin with <\(startingSubstring)>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let range = actual.range(of: startingSubstring)
return PredicateStatus(bool: range != nil && range!.lowerBound == actual.startIndex)
return PredicateStatus(bool: actual.hasPrefix(startingSubstring))
}
return .fail
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func beginWithMatcher(_ expected: Any) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func beginWithMatcher(_ expected: Any) -> NMBMatcher {
return NMBPredicate { actualExpression in
let actual = try actualExpression.evaluate()
if (actual as? String) != nil {
if actual is String {
let expr = actualExpression.cast { $0 as? String }
return try beginWith(expected as! String).matches(expr, failureMessage: failureMessage)
// swiftlint:disable:next force_cast
return try beginWith(expected as! String).satisfies(expr).toObjectiveC()
} else {
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return try beginWith(expected).matches(expr, failureMessage: failureMessage)
return try beginWith(expected).satisfies(expr).toObjectiveC()
}
}
}
+67 -18
View File
@@ -1,16 +1,17 @@
import Foundation
/// A Nimble matcher that succeeds when the actual sequence contains the expected value.
/// A Nimble matcher that succeeds when the actual sequence contains the expected values.
public func contain<S: Sequence, T: Equatable>(_ items: T...) -> Predicate<S>
where S.Iterator.Element == T {
where S.Element == T {
return contain(items)
}
/// A Nimble matcher that succeeds when the actual sequence contains the expected values.
public func contain<S: Sequence, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Iterator.Element == T {
where S.Element == T {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = items.all {
let matches = items.allSatisfy {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
@@ -19,6 +20,46 @@ public func contain<S: Sequence, T: Equatable>(_ items: [T]) -> Predicate<S>
}
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: SetAlgebra, T: Equatable>(_ items: T...) -> Predicate<S>
where S.Element == T {
return contain(items)
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: SetAlgebra, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Element == T {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = items.allSatisfy {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
}
return .fail
}
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: Sequence & SetAlgebra, T: Equatable>(_ items: T...) -> Predicate<S>
where S.Element == T {
return contain(items)
}
/// A Nimble matcher that succeeds when the actual set contains the expected values.
public func contain<S: Sequence & SetAlgebra, T: Equatable>(_ items: [T]) -> Predicate<S>
where S.Element == T {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = items.allSatisfy {
return actual.contains($0)
}
return PredicateStatus(bool: matches)
}
return .fail
}
}
/// A Nimble matcher that succeeds when the actual string contains the expected substring.
public func contain(_ substrings: String...) -> Predicate<String> {
return contain(substrings)
@@ -27,7 +68,7 @@ public func contain(_ substrings: String...) -> Predicate<String> {
public func contain(_ substrings: [String]) -> Predicate<String> {
return Predicate.simple("contain <\(arrayAsString(substrings))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = substrings.all {
let matches = substrings.allSatisfy {
let range = actual.range(of: $0)
return range != nil && !range!.isEmpty
}
@@ -45,7 +86,7 @@ public func contain(_ substrings: NSString...) -> Predicate<NSString> {
public func contain(_ substrings: [NSString]) -> Predicate<NSString> {
return Predicate.simple("contain <\(arrayAsString(substrings))>") { actualExpression in
if let actual = try actualExpression.evaluate() {
let matches = substrings.all { actual.range(of: $0.description).length != 0 }
let matches = substrings.allSatisfy { actual.range(of: $0.description).length != 0 }
return PredicateStatus(bool: matches)
}
return .fail
@@ -60,17 +101,17 @@ public func contain(_ items: Any?...) -> Predicate<NMBContainer> {
public func contain(_ items: [Any?]) -> Predicate<NMBContainer> {
return Predicate.simple("contain <\(arrayAsString(items))>") { actualExpression in
guard let actual = try actualExpression.evaluate() else { return .fail }
let matches = items.all { item in
let matches = items.allSatisfy { item in
return item.map { actual.contains($0) } ?? false
}
return PredicateStatus(bool: matches)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func containMatcher(_ expected: [NSObject]) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func containMatcher(_ expected: [NSObject]) -> NMBMatcher {
return NMBPredicate { actualExpression in
let location = actualExpression.location
let actualValue = try actualExpression.evaluate()
if let value = actualValue as? NMBContainer {
@@ -78,17 +119,25 @@ extension NMBObjCMatcher {
// A straightforward cast on the array causes this to crash, so we have to cast the individual items
let expectedOptionals: [Any?] = expected.map({ $0 as Any? })
return try contain(expectedOptionals).matches(expr, failureMessage: failureMessage)
return try contain(expectedOptionals).satisfies(expr).toObjectiveC()
} else if let value = actualValue as? NSString {
let expr = Expression(expression: ({ value as String }), location: location)
return try contain(expected as! [String]).matches(expr, failureMessage: failureMessage)
} else if actualValue != nil {
// swiftlint:disable:next line_length
failureMessage.postfixMessage = "contain <\(arrayAsString(expected))> (only works for NSArrays, NSSets, NSHashTables, and NSStrings)"
} else {
failureMessage.postfixMessage = "contain <\(arrayAsString(expected))>"
// swiftlint:disable:next force_cast
return try contain(expected as! [String]).satisfies(expr).toObjectiveC()
}
return false
let message: ExpectationMessage
if actualValue != nil {
message = ExpectationMessage.expectedActualValueTo(
// swiftlint:disable:next line_length
"contain <\(arrayAsString(expected))> (only works for NSArrays, NSSets, NSHashTables, and NSStrings)"
)
} else {
message = ExpectationMessage
.expectedActualValueTo("contain <\(arrayAsString(expected))>")
.appendedBeNilHint()
}
return NMBPredicateResult(status: .fail, message: message.toObjectiveC())
}
}
}
@@ -24,20 +24,22 @@ public func containElementSatisfying<S: Sequence, T>(_ predicate: @escaping ((T)
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func containElementSatisfyingMatcher(_ predicate: @escaping ((NSObject) -> Bool)) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func containElementSatisfyingMatcher(_ predicate: @escaping ((NSObject) -> Bool)) -> NMBMatcher {
return NMBPredicate { actualExpression in
let value = try actualExpression.evaluate()
guard let enumeration = value as? NSFastEnumeration else {
// swiftlint:disable:next line_length
failureMessage.postfixMessage = "containElementSatisfying must be provided an NSFastEnumeration object"
failureMessage.actualValue = nil
failureMessage.expected = ""
failureMessage.to = ""
return false
let message = ExpectationMessage.fail(
"containElementSatisfying must be provided an NSFastEnumeration object"
)
return NMBPredicateResult(status: .fail, message: message.toObjectiveC())
}
let message = ExpectationMessage
.expectedTo("find object in collection that satisfies predicate")
.toObjectiveC()
var iterator = NSFastEnumerationIterator(enumeration)
while let item = iterator.next() {
guard let object = item as? NSObject else {
@@ -45,13 +47,11 @@ public func containElementSatisfying<S: Sequence, T>(_ predicate: @escaping ((T)
}
if predicate(object) {
return true
return NMBPredicateResult(status: .matches, message: message)
}
}
failureMessage.actualValue = nil
failureMessage.postfixMessage = "find object in collection that satisfies predicate"
return false
return NMBPredicateResult(status: .doesNotMatch, message: message)
}
}
}
@@ -0,0 +1,16 @@
/// A Nimble matcher that succeeds when the actual sequence contain the same elements in the same order to the exepected sequence.
public func elementsEqual<S: Sequence>(_ expectedValue: S?) -> Predicate<S> where S.Element: Equatable {
// A matcher abstraction for https://developer.apple.com/documentation/swift/sequence/2949668-elementsequal
return Predicate.define("elementsEqual <\(stringify(expectedValue))>") { (actualExpression, msg) in
let actualValue = try actualExpression.evaluate()
switch (expectedValue, actualValue) {
case (nil, _?):
return PredicateResult(status: .fail, message: msg.appendedBeNilHint())
case (nil, nil), (_, nil):
return PredicateResult(status: .fail, message: msg)
case (let expected?, let actual?):
let matches = expected.elementsEqual(actual)
return PredicateResult(bool: matches, message: msg)
}
}
}
+7 -6
View File
@@ -50,17 +50,18 @@ public func endWith(_ endingSubstring: String) -> Predicate<String> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func endWithMatcher(_ expected: Any) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func endWithMatcher(_ expected: Any) -> NMBMatcher {
return NMBPredicate { actualExpression in
let actual = try actualExpression.evaluate()
if (actual as? String) != nil {
if actual is String {
let expr = actualExpression.cast { $0 as? String }
return try endWith(expected as! String).matches(expr, failureMessage: failureMessage)
// swiftlint:disable:next force_cast
return try endWith(expected as! String).satisfies(expr).toObjectiveC()
} else {
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return try endWith(expected).matches(expr, failureMessage: failureMessage)
return try endWith(expected).satisfies(expr).toObjectiveC()
}
}
}
+46 -118
View File
@@ -7,103 +7,30 @@ import Foundation
public func equal<T: Equatable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
let matches = actualValue == expectedValue && expectedValue != nil
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
switch (expectedValue, actualValue) {
case (nil, _?):
return PredicateResult(status: .fail, message: msg.appendedBeNilHint())
case (nil, nil), (_, nil):
return PredicateResult(status: .fail, message: msg)
case (let expected?, let actual?):
let matches = expected == actual
return PredicateResult(bool: matches, message: msg)
}
return PredicateResult(status: PredicateStatus(bool: matches), message: msg)
}
}
/// A Nimble matcher that succeeds when the actual value is equal to the expected value.
/// Values can support equal by supporting the Equatable protocol.
///
/// @see beCloseTo if you want to match imprecise types (eg - floats, doubles).
public func equal<T, C: Equatable>(_ expectedValue: [T: C]?) -> Predicate<[T: C]> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
return PredicateResult(status: .fail, message: msg)
}
return PredicateResult(
status: PredicateStatus(bool: expectedValue! == actualValue!),
message: msg
)
}
}
/// A Nimble matcher that succeeds when the actual collection is equal to the expected collection.
/// Items must implement the Equatable protocol.
public func equal<T: Equatable>(_ expectedValue: [T]?) -> Predicate<[T]> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
return PredicateResult(
status: .fail,
message: msg
)
}
return PredicateResult(
bool: expectedValue! == actualValue!,
message: msg
)
}
}
/// A Nimble matcher allowing comparison of collection with optional type
public func equal<T: Equatable>(_ expectedValue: [T?]) -> Predicate<[T?]> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
if let actualValue = try actualExpression.evaluate() {
let doesNotMatch = PredicateResult(
status: .doesNotMatch,
message: msg
)
if expectedValue.count != actualValue.count {
return doesNotMatch
}
for (index, item) in actualValue.enumerated() {
let otherItem = expectedValue[index]
if item == nil && otherItem == nil {
continue
} else if item == nil && otherItem != nil {
return doesNotMatch
} else if item != nil && otherItem == nil {
return doesNotMatch
} else if item! != otherItem! {
return doesNotMatch
}
}
return PredicateResult(
status: .matches,
message: msg
)
} else {
guard let actualValue = try actualExpression.evaluate() else {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
let matches = expectedValue == actualValue
return PredicateResult(bool: matches, message: msg)
}
}
@@ -128,44 +55,45 @@ private func equal<T>(_ expectedValue: Set<T>?, stringify: @escaping (Set<T>?) -
var errorMessage: ExpectationMessage =
.expectedActualValueTo("equal <\(stringify(expectedValue))>")
if let expectedValue = expectedValue {
if let actualValue = try actualExpression.evaluate() {
errorMessage = .expectedCustomValueTo(
"equal <\(stringify(expectedValue))>",
"<\(stringify(actualValue))>"
)
if expectedValue == actualValue {
return PredicateResult(
status: .matches,
message: errorMessage
)
}
let missing = expectedValue.subtracting(actualValue)
if missing.count > 0 {
errorMessage = errorMessage.appended(message: ", missing <\(stringify(missing))>")
}
let extra = actualValue.subtracting(expectedValue)
if extra.count > 0 {
errorMessage = errorMessage.appended(message: ", extra <\(stringify(extra))>")
}
return PredicateResult(
status: .doesNotMatch,
message: errorMessage
)
}
return PredicateResult(
status: .fail,
message: errorMessage.appendedBeNilHint()
)
} else {
guard let expectedValue = expectedValue else {
return PredicateResult(
status: .fail,
message: errorMessage.appendedBeNilHint()
)
}
guard let actualValue = try actualExpression.evaluate() else {
return PredicateResult(
status: .fail,
message: errorMessage.appendedBeNilHint()
)
}
errorMessage = .expectedCustomValueTo(
"equal <\(stringify(expectedValue))>",
"<\(stringify(actualValue))>"
)
if expectedValue == actualValue {
return PredicateResult(
status: .matches,
message: errorMessage
)
}
let missing = expectedValue.subtracting(actualValue)
if missing.count > 0 {
errorMessage = errorMessage.appended(message: ", missing <\(stringify(missing))>")
}
let extra = actualValue.subtracting(expectedValue)
if extra.count > 0 {
errorMessage = errorMessage.appended(message: ", extra <\(stringify(extra))>")
}
return PredicateResult(
status: .doesNotMatch,
message: errorMessage
)
}
}
@@ -209,7 +137,7 @@ public func !=<T, C: Equatable>(lhs: Expectation<[T: C]>, rhs: [T: C]?) {
lhs.toNot(equal(rhs))
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func equalMatcher(_ expected: NSObject) -> NMBMatcher {
return NMBPredicate { actualExpression in
+18 -9
View File
@@ -7,7 +7,7 @@ import Foundation
/// A Nimble matcher that succeeds when the actual Collection's count equals
/// the expected value
public func haveCount<T: Collection>(_ expectedValue: T.IndexDistance) -> Predicate<T> {
public func haveCount<T: Collection>(_ expectedValue: Int) -> Predicate<T> {
return Predicate.define { actualExpression in
if let actualValue = try actualExpression.evaluate() {
let message = ExpectationMessage
@@ -45,20 +45,29 @@ public func haveCount(_ expectedValue: Int) -> Predicate<NMBCollection> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func haveCountMatcher(_ expected: NSNumber) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
@objc public class func haveCountMatcher(_ expected: NSNumber) -> NMBMatcher {
return NMBPredicate { actualExpression in
let location = actualExpression.location
let actualValue = try actualExpression.evaluate()
if let value = actualValue as? NMBCollection {
let expr = Expression(expression: ({ value as NMBCollection}), location: location)
return try haveCount(expected.intValue).matches(expr, failureMessage: failureMessage)
} else if let actualValue = actualValue {
failureMessage.postfixMessage = "get type of NSArray, NSSet, NSDictionary, or NSHashTable"
failureMessage.actualValue = "\(String(describing: type(of: actualValue)))"
return try haveCount(expected.intValue).satisfies(expr).toObjectiveC()
}
return false
let message: ExpectationMessage
if let actualValue = actualValue {
message = ExpectationMessage.expectedCustomValueTo(
"get type of NSArray, NSSet, NSDictionary, or NSHashTable",
"\(String(describing: type(of: actualValue)))"
)
} else {
message = ExpectationMessage
.expectedActualValueTo("have a collection with count \(stringify(expected.intValue))")
.appendedBeNilHint()
}
return NMBPredicateResult(status: .fail, message: message.toObjectiveC())
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ public func match(_ expectedValue: String?) -> Predicate<String> {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func matchMatcher(_ expected: NSString) -> NMBMatcher {
@@ -1,6 +1,6 @@
import Foundation
// `CGFloat` is in Foundation (swift-corelibs-foundation) on Linux.
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
import CoreGraphics
#endif
@@ -28,7 +28,7 @@ extension Matcher {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
/// Objective-C interface to the Swift variant of Matcher.
@objc public protocol NMBMatcher {
func matches(_ actualBlock: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool
@@ -41,7 +41,8 @@ public protocol NMBContainer {
func contains(_ anObject: Any) -> Bool
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// swiftlint:disable:next todo
// FIXME: NSHashTable can not conform to NMBContainer since swift-DEVELOPMENT-SNAPSHOT-2016-04-25-a
//extension NSHashTable : NMBContainer {} // Corelibs Foundation does not include this class yet
#endif
@@ -54,7 +55,7 @@ public protocol NMBCollection {
var count: Int { get }
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NSHashTable: NMBCollection {} // Corelibs Foundation does not include these classes yet
extension NSMapTable: NMBCollection {}
#endif
@@ -131,7 +132,7 @@ extension NSDate: TestOutputStringConvertible {
/// beGreaterThan(), beGreaterThanOrEqualTo(), and equal() matchers.
///
/// Types that conform to Swift's Comparable protocol will work implicitly too
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc public protocol NMBComparable {
func NMB_compare(_ otherObject: NMBComparable!) -> ComparisonResult
}
@@ -144,11 +145,13 @@ public protocol NMBComparable {
extension NSNumber: NMBComparable {
public func NMB_compare(_ otherObject: NMBComparable!) -> ComparisonResult {
// swiftlint:disable:next force_cast
return compare(otherObject as! NSNumber)
}
}
extension NSString: NMBComparable {
public func NMB_compare(_ otherObject: NMBComparable!) -> ComparisonResult {
// swiftlint:disable:next force_cast
return compare(otherObject as! String)
}
}
@@ -1,36 +1,9 @@
import Foundation
// A workaround to SR-6419.
extension NotificationCenter {
#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
#if swift(>=4.0)
#if swift(>=4.0.2)
#else
func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
return addObserver(forName: name, object: obj, queue: queue, usingBlock: block)
}
#endif
#elseif swift(>=3.2)
#if swift(>=3.2.2)
#else
// swiftlint:disable:next line_length
func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
return addObserver(forName: name, object: obj, queue: queue, usingBlock: block)
}
#endif
#else
// swiftlint:disable:next line_length
func addObserver(forName name: Notification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
return addObserver(forName: name, object: obj, queue: queue, usingBlock: block)
}
#endif
#endif
}
internal class NotificationCollector {
private(set) var observedNotifications: [Notification]
private let notificationCenter: NotificationCenter
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
private var token: AnyObject?
#else
private var token: NSObjectProtocol?
@@ -43,14 +16,14 @@ internal class NotificationCollector {
func startObserving() {
// swiftlint:disable:next line_length
self.token = self.notificationCenter.addObserver(forName: nil, object: nil, queue: nil, using: { [weak self] n in
self.token = self.notificationCenter.addObserver(forName: nil, object: nil, queue: nil) { [weak self] notification in
// linux-swift gets confused by .append(n)
self?.observedNotifications.append(n)
})
self?.observedNotifications.append(notification)
}
}
deinit {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
if let token = self.token {
self.notificationCenter.removeObserver(token)
}
+3 -2
View File
@@ -218,6 +218,7 @@ extension Predicate: Matcher {
extension Predicate {
// Someday, make this public? Needs documentation
internal func after(f: @escaping (Expression<T>, PredicateResult) throws -> PredicateResult) -> Predicate<T> {
// swiftlint:disable:previous identifier_name
return Predicate { actual -> PredicateResult in
let result = try self.satisfies(actual)
return try f(actual, result)
@@ -241,7 +242,7 @@ extension Predicate {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
public typealias PredicateBlock = (_ actualExpression: Expression<NSObject>) throws -> NMBPredicateResult
public class NMBPredicate: NSObject {
@@ -311,7 +312,7 @@ final public class NMBPredicateStatus: NSObject {
public static let doesNotMatch: NMBPredicateStatus = NMBPredicateStatus(status: 1)
public static let fail: NMBPredicateStatus = NMBPredicateStatus(status: 2)
public override var hashValue: Int { return self.status.hashValue }
public override var hash: Int { return self.status.hashValue }
public override func isEqual(_ object: Any?) -> Bool {
guard let otherPredicate = object as? NMBPredicateStatus else {
@@ -1,7 +1,7 @@
import Foundation
// This matcher requires the Objective-C, and being built by Xcode rather than the Swift Package Manager
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
/// A Nimble matcher that succeeds when the actual expression raises an
/// exception with the specified name, reason, and/or userInfo.
@@ -23,8 +23,12 @@ public func raiseException(
exception = e
}), finally: nil)
capture.tryBlock {
_ = try! actualExpression.evaluate()
do {
try capture.tryBlockThrows {
_ = try actualExpression.evaluate()
}
} catch {
return PredicateResult(status: .fail, message: .fail("unexpected error thrown: <\(error)>"))
}
let failureMessage = FailureMessage()
@@ -118,10 +122,12 @@ internal func exceptionMatchesNonNilFieldsOrClosure(
}
public class NMBObjCRaiseExceptionMatcher: NSObject, NMBMatcher {
// swiftlint:disable identifier_name
internal var _name: String?
internal var _reason: String?
internal var _userInfo: NSDictionary?
internal var _block: ((NSException) -> Void)?
// swiftlint:enable identifier_name
internal init(name: String?, reason: String?, userInfo: NSDictionary?, block: ((NSException) -> Void)?) {
_name = name
@@ -39,7 +39,7 @@ public func && <T>(left: Predicate<T>, right: Predicate<T>) -> Predicate<T> {
return satisfyAllOf(left, right)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func satisfyAllOfMatcher(_ matchers: [NMBMatcher]) -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -60,8 +60,12 @@ extension NMBObjCMatcher {
return predicate.satisfies({ try expression.evaluate() }, location: actualExpression.location).toSwift()
} else {
let failureMessage = FailureMessage()
// swiftlint:disable:next line_length
let success = matcher.matches({ try! expression.evaluate() }, failureMessage: failureMessage, location: actualExpression.location)
let success = matcher.matches(
// swiftlint:disable:next force_try
{ try! expression.evaluate() },
failureMessage: failureMessage,
location: actualExpression.location
)
return PredicateResult(bool: success, message: failureMessage.toExpectationMessage())
}
}
@@ -47,7 +47,7 @@ public func || <T>(left: MatcherFunc<T>, right: MatcherFunc<T>) -> Predicate<T>
return satisfyAnyOf(left, right)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
extension NMBObjCMatcher {
@objc public class func satisfyAnyOfMatcher(_ matchers: [NMBMatcher]) -> NMBPredicate {
return NMBPredicate { actualExpression in
@@ -68,8 +68,12 @@ extension NMBObjCMatcher {
return predicate.satisfies({ try expression.evaluate() }, location: actualExpression.location).toSwift()
} else {
let failureMessage = FailureMessage()
// swiftlint:disable:next line_length
let success = matcher.matches({ try! expression.evaluate() }, failureMessage: failureMessage, location: actualExpression.location)
let success = matcher.matches(
// swiftlint:disable:next force_try
{ try! expression.evaluate() },
failureMessage: failureMessage,
location: actualExpression.location
)
return PredicateResult(bool: success, message: failureMessage.toExpectationMessage())
}
}
@@ -2,7 +2,7 @@ import Foundation
public func throwAssertion() -> Predicate<Void> {
return Predicate { actualExpression in
#if arch(x86_64) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if arch(x86_64) && canImport(Darwin) && !SWIFT_PACKAGE
let message = ExpectationMessage.expectedTo("throw an assertion")
var actualError: Error?
@@ -44,9 +44,8 @@ public func throwAssertion() -> Predicate<Void> {
" conditional statement")
#else
fatalError("The throwAssertion Nimble matcher can only run on x86_64 platforms with " +
"Objective-C (e.g. Mac, iPhone 5s or later simulators). You can silence this error " +
"by placing the test case inside an #if arch(x86_64) or (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) conditional statement")
// swiftlint:disable:previous line_length
"Objective-C (e.g. macOS, iPhone 5s or later simulators). You can silence this error " +
"by placing the test case inside an #if arch(x86_64) or canImport(Darwin) conditional statement")
#endif
}
}
+13 -19
View File
@@ -2,7 +2,7 @@ import CoreFoundation
import Dispatch
import Foundation
#if !(os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
#if canImport(CDispatch)
import CDispatch
#endif
@@ -32,7 +32,7 @@ internal class AssertionWaitLock: WaitLock {
func acquireWaitingLock(_ fnName: String, file: FileString, line: UInt) {
let info = WaitingInfo(name: fnName, file: file, lineNumber: line)
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
let isMainThread = Thread.isMainThread
#else
let isMainThread = _CFIsMainThread()
@@ -45,10 +45,15 @@ internal class AssertionWaitLock: WaitLock {
nimblePrecondition(
currentWaiter == nil,
"InvalidNimbleAPIUsage",
"Nested async expectations are not allowed to avoid creating flaky tests.\n\n" +
"The call to\n\t\(info)\n" +
"triggered this exception because\n\t\(currentWaiter!)\n" +
"is currently managing the main run loop."
"""
Nested async expectations are not allowed to avoid creating flaky tests.
The call to
\t\(info)
triggered this exception because
\t\(currentWaiter!)
is currently managing the main run loop.
"""
)
currentWaiter = info
}
@@ -180,25 +185,18 @@ internal class AwaitPromiseBuilder<T> {
// 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
)
#endif
trigger.timeoutSource.setEventHandler {
guard self.promise.asyncResult.isIncomplete() else { return }
let timedOutSem = DispatchSemaphore(value: 0)
let semTimedOutOrBlocked = DispatchSemaphore(value: 0)
semTimedOutOrBlocked.signal()
let runLoop = CFRunLoopGetMain()
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
let runLoopMode = CFRunLoopMode.defaultMode.rawValue
#else
let runLoopMode = kCFRunLoopDefaultMode
@@ -263,7 +261,7 @@ internal class AwaitPromiseBuilder<T> {
self.trigger.timeoutSource.resume()
while self.promise.asyncResult.isIncomplete() {
// Stopping the run loop does not work unless we run only 1 mode
#if swift(>=4.2)
#if (swift(>=4.2) && canImport(Darwin)) || compiler(>=5.0)
_ = RunLoop.current.run(mode: .default, before: .distantFuture)
#else
_ = RunLoop.current.run(mode: .defaultRunLoopMode, before: .distantFuture)
@@ -333,11 +331,7 @@ 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() {
+4 -2
View File
@@ -1,12 +1,14 @@
import Foundation
#if !swift(>=4.2)
extension Sequence {
internal func all(_ fn: (Iterator.Element) -> Bool) -> Bool {
internal func allSatisfy(_ predicate: (Element) throws -> Bool) rethrows -> Bool {
for item in self {
if !fn(item) {
if try !predicate(item) {
return false
}
}
return true
}
}
#endif
+3 -2
View File
@@ -2,7 +2,7 @@ import Foundation
internal func identityAsString(_ value: Any?) -> String {
let anyObject: AnyObject?
#if os(Linux)
#if os(Linux) && !swift(>=4.1.50)
anyObject = value as? AnyObject
#else
anyObject = value as AnyObject?
@@ -122,6 +122,7 @@ extension String: TestOutputStringConvertible {
extension Data: TestOutputStringConvertible {
public var testDescription: String {
#if os(Linux)
// swiftlint:disable:next todo
// FIXME: Swift on Linux triggers a segfault when calling NSData's hash() (last checked on 03-11-16)
return "Data<length=\(count)>"
#else
@@ -158,7 +159,7 @@ public func stringify<T>(_ value: T?) -> String {
return String(describing: value)
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc public class NMBStringer: NSObject {
@objc public class func stringify(_ obj: Any?) -> String {
return Nimble.stringify(obj)
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -4,6 +4,7 @@
[![CocoaPods](https://img.shields.io/cocoapods/v/Quick.svg)](https://cocoapods.org/pods/Quick)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Platforms](https://img.shields.io/cocoapods/p/Quick.svg)](https://cocoapods.org/pods/Quick)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
Quick is a behavior-driven development framework for Swift and Objective-C.
Inspired by [RSpec](https://github.com/rspec/rspec), [Specta](https://github.com/specta/specta), and [Ginkgo](https://github.com/onsi/ginkgo).
@@ -44,7 +45,8 @@ Certain versions of Quick and Nimble only support certain versions of Swift. Dep
|Swift version |Quick version |Nimble version |
|:--------------------|:---------------|:--------------|
|Swift 3 |v1.0.0 or later |v5.0.0 or later|
|Swift 4.2 |v1.3.2 or later |v7.3.2 or later|
|Swift 3 / Swift 4 |v1.0.0 or later |v5.0.0 or later|
|Swift 2.2 / Swift 2.3|v0.9.3 |v4.1.0 |
## Documentation
+1 -1
View File
@@ -4,7 +4,7 @@
open class Behavior<Context> {
open static var name: String { return String(describing: self) }
public static var name: String { return String(describing: self) }
/**
override this method in your behavior to define a set of reusable examples.
+3 -9
View File
@@ -1,14 +1,8 @@
import Foundation
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _CallsiteBase: NSObject {}
#else
public class _CallsiteBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _CallsiteBase: NSObject {}
#else
public class _CallsiteBase: NSObject {}
#endif
@@ -72,7 +72,7 @@ final public class Configuration: NSObject {
provided with metadata on the example that the closure is being run
prior to.
*/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc(beforeEachWithMetadata:)
public func beforeEach(_ closure: @escaping BeforeExampleWithMetadataClosure) {
exampleHooks.appendBefore(closure)
@@ -109,7 +109,7 @@ final public class Configuration: NSObject {
is provided with metadata on the example that the closure is being
run after.
*/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc(afterEachWithMetadata:)
public func afterEach(_ closure: @escaping AfterExampleWithMetadataClosure) {
exampleHooks.appendAfter(closure)
+3 -3
View File
@@ -56,7 +56,7 @@ extension World {
currentExampleGroup.hooks.appendBefore(closure)
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(beforeEachWithMetadata:)
internal func beforeEach(closure: @escaping BeforeExampleWithMetadataClosure) {
currentExampleGroup.hooks.appendBefore(closure)
@@ -74,7 +74,7 @@ extension World {
currentExampleGroup.hooks.appendAfter(closure)
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(afterEachWithMetadata:)
internal func afterEach(closure: @escaping AfterExampleWithMetadataClosure) {
currentExampleGroup.hooks.appendAfter(closure)
@@ -172,7 +172,7 @@ extension World {
self.itBehavesLike(behavior, context: context, flags: pendingFlags, file: file, line: line)
}
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
#if canImport(Darwin) && !SWIFT_PACKAGE
@objc(itWithDescription:flags:file:line:closure:)
internal func objc_it(_ description: String, flags: FilterFlags, file: String, line: UInt, closure: @escaping () -> Void) {
it(description, flags: flags, file: file, line: line, closure: closure)
+1 -1
View File
@@ -1,7 +1,7 @@
import Foundation
internal func raiseError(_ message: String) -> Never {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
NSException(name: .internalInconsistencyException, reason: message, userInfo: nil).raise()
#endif
+3 -9
View File
@@ -3,15 +3,9 @@ import Foundation
private var numberOfExamplesRun = 0
private var numberOfIncludedExamples = 0
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleBase: NSObject {}
#else
public class _ExampleBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleBase: NSObject {}
#else
public class _ExampleBase: NSObject {}
#endif
+3 -9
View File
@@ -1,14 +1,8 @@
import Foundation
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleMetadataBase: NSObject {}
#else
public class _ExampleMetadataBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _ExampleMetadataBase: NSObject {}
#else
public class _ExampleMetadataBase: NSObject {}
#endif
+3 -9
View File
@@ -1,14 +1,8 @@
import Foundation
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
public class _FilterBase: NSObject {}
#else
public class _FilterBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
public class _FilterBase: NSObject {}
#else
public class _FilterBase: NSObject {}
#endif
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import Foundation
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import Foundation
extension NSString {
@@ -21,7 +21,7 @@ extension NSString {
return invalidCharacters
}()
/// This API is not meant to be used outside Quick, so will be unavaialbe in
/// This API is not meant to be used outside Quick, so will be unavailable in
/// a next major version.
@objc(qck_c99ExtendedIdentifier)
public var c99ExtendedIdentifier: String {
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import Foundation
/**
+1 -1
View File
@@ -1,4 +1,4 @@
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
#if canImport(Darwin)
import XCTest
+5 -11
View File
@@ -12,15 +12,9 @@ public typealias SharedExampleContext = () -> [String: Any]
*/
public typealias SharedExampleClosure = (@escaping SharedExampleContext) -> Void
// `#if swift(>=3.2) && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE`
// does not work as expected.
#if swift(>=3.2)
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS)) && !SWIFT_PACKAGE
@objcMembers
internal class _WorldBase: NSObject {}
#else
internal class _WorldBase: NSObject {}
#endif
#if canImport(Darwin) && !SWIFT_PACKAGE
@objcMembers
internal class _WorldBase: NSObject {}
#else
internal class _WorldBase: NSObject {}
#endif
@@ -57,7 +51,7 @@ final internal class World: _WorldBase {
within this test suite. This is only true within the context of Quick
functional tests.
*/
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
// Convention of generating Objective-C selector has been changed on Swift 3
@objc(isRunningAdditionalSuites)
internal var isRunningAdditionalSuites = false
@@ -158,7 +152,7 @@ final internal class World: _WorldBase {
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
#if canImport(Darwin)
@objc(examplesForSpecClass:)
internal func objc_examples(_ specClass: AnyClass) -> [Example] {
return examples(specClass)
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>7.3.1</string>
<string>8.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.3.2</string>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.3.3</string>
<string>0.7.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+7 -7
View File
@@ -307,7 +307,7 @@
607FACCF1AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = HPNZWPB9JK;
LastSwiftMigration = 1010;
LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
@@ -317,14 +317,14 @@
607FACE41AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = HPNZWPB9JK;
LastSwiftMigration = 1010;
LastSwiftMigration = 1020;
TestTargetID = 607FACCF1AFB9204008FA782;
};
};
};
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "SwiftAudio" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -630,7 +630,7 @@
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
@@ -647,7 +647,7 @@
MODULE_NAME = ExampleApp;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
@@ -669,7 +669,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
};
name = Debug;
@@ -687,7 +687,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftAudio_Example.app/SwiftAudio_Example";
};
name = Release;
+5
View File
@@ -31,6 +31,7 @@ class ViewController: UIViewController {
controller.player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
controller.player.event.seek.addListener(self, handleAudioPlayerDidSeek)
controller.player.event.updateDuration.addListener(self, handleAudioPlayerUpdateDuration)
controller.player.event.didRecreateAVPlayer.addListener(self, handleAVPlayerRecreated)
}
@IBAction func togglePlay(_ sender: Any) {
@@ -116,4 +117,8 @@ class ViewController: UIViewController {
}
}
func handleAVPlayerRecreated() {
try? controller.audioSessionController.set(category: .playback)
}
}
+17 -1
View File
@@ -20,10 +20,15 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
beforeEach {
player = AVPlayer()
player.volume = 0.0
observer = AVPlayerObserver(player: player)
observer = AVPlayerObserver()
observer.player = player
observer.delegate = self
}
it("should not be observing", closure: {
expect(observer.isObserving).to(beFalse())
})
context("when observing has started", {
beforeEach {
observer.startObserving()
@@ -54,6 +59,17 @@ class AVPlayerObserverTests: QuickSpec, AVPlayerObserverDelegate {
expect(observer.isObserving).toEventually(beTrue())
})
})
context("when stopping observing", closure: {
beforeEach {
observer.stopObserving()
}
it("should not be observing", closure: {
expect(observer.isObserving).to(beFalse())
})
})
})
}
@@ -17,7 +17,8 @@ class AVPlayerTimeObserverTests: QuickSpec {
player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.volume = 0
observer = AVPlayerTimeObserver(player: player, periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime())
observer = AVPlayerTimeObserver(periodicObserverTimeInterval: TimeEventFrequency.everyQuarterSecond.getTime())
observer.player = player
}
context("has started boundary time observing", {
+7 -4
View File
@@ -14,10 +14,9 @@ class AVPlayerWrapperTests: QuickSpec {
var wrapper: AVPlayerWrapper!
beforeEach {
let player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = false
player.volume = 0.0
wrapper = AVPlayerWrapper(avPlayer: player)
wrapper = AVPlayerWrapper()
wrapper.automaticallyWaitsToMinimizeStalling = false
wrapper.volume = 0.0
wrapper.bufferDuration = 0.0001
}
@@ -235,6 +234,10 @@ class AVPlayerWrapperTests: QuickSpec {
}
class AVPlayerWrapperDelegateHolder: AVPlayerWrapperDelegate {
func AVWrapperDidRecreateAVPlayer() {
}
func AVWrapperItemDidPlayToEndTime() {
}
+2 -2
View File
@@ -23,13 +23,13 @@ SwiftAudio is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'SwiftAudio', '~> 0.7.2'
pod 'SwiftAudio', '~> 0.9.1'
```
### Carthage
SwiftAudio supports [Carthage](https://github.com/Carthage/Carthage). Add this to your Cartfile:
```ruby
github "jorgenhenrichsen/SwiftAudio" ~> 0.7.2
github "jorgenhenrichsen/SwiftAudio" ~> 0.9.1
```
Then follow the rest of Carthage instructions on [adding a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application).
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = 'SwiftAudio'
s.version = '0.7.2'
s.version = '0.9.1'
s.summary = 'Easy audio streaming for iOS'
# This description is used to generate tags and improve search results.
@@ -26,7 +26,7 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
// MARK: - Properties
let avPlayer: AVPlayer
var avPlayer: AVPlayer
let playerObserver: AVPlayerObserver
let playerTimeObserver: AVPlayerTimeObserver
let playerItemNotificationObserver: AVPlayerItemNotificationObserver
@@ -46,10 +46,12 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
}
}
public init(avPlayer: AVPlayer = AVPlayer()) {
self.avPlayer = avPlayer
self.playerObserver = AVPlayerObserver(player: avPlayer)
self.playerTimeObserver = AVPlayerTimeObserver(player: avPlayer, periodicObserverTimeInterval: timeEventFrequency.getTime())
public init() {
self.avPlayer = AVPlayer()
self.playerObserver = AVPlayerObserver()
self.playerObserver.player = avPlayer
self.playerTimeObserver = AVPlayerTimeObserver(periodicObserverTimeInterval: timeEventFrequency.getTime())
self.playerTimeObserver.player = avPlayer
self.playerItemNotificationObserver = AVPlayerItemNotificationObserver()
self.playerItemObserver = AVPlayerItemObserver()
@@ -142,6 +144,8 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
pause()
case .paused:
play()
@unknown default:
fatalError("Unknown AVPlayer.timeControlStatus")
}
}
@@ -165,6 +169,10 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
func load(from url: URL, playWhenReady: Bool) {
reset(soft: true)
_playWhenReady = playWhenReady
if currentItem?.status == .failed {
recreateAVPlayer()
}
// Set item
let currentAsset = AVURLAsset(url: url)
@@ -197,6 +205,16 @@ class AVPlayerWrapper: AVPlayerWrapperProtocol {
}
}
/// Will recreate the AVPlayer instance. Used when the current one fails.
private func recreateAVPlayer() {
let player = AVPlayer()
playerObserver.player = player
playerTimeObserver.player = player
playerTimeObserver.registerForPeriodicTimeEvents()
avPlayer = player
delegate?.AVWrapperDidRecreateAVPlayer()
}
}
extension AVPlayerWrapper: AVPlayerObserverDelegate {
@@ -216,6 +234,8 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
self._state = .loading
case .playing:
self._state = .playing
@unknown default:
break
}
}
@@ -240,6 +260,8 @@ extension AVPlayerWrapper: AVPlayerObserverDelegate {
case .unknown:
break
@unknown default:
break
}
}
@@ -16,5 +16,6 @@ protocol AVPlayerWrapperDelegate: class {
func AVWrapper(seekTo seconds: Int, didFinish: Bool)
func AVWrapper(didUpdateDuration duration: Double)
func AVWrapperItemDidPlayToEndTime()
func AVWrapperDidRecreateAVPlayer()
}
@@ -9,7 +9,7 @@ import Foundation
import AVFoundation
protocol AVPlayerWrapperProtocol {
protocol AVPlayerWrapperProtocol: class {
var state: AVPlayerWrapperState { get }
+6 -29
View File
@@ -10,23 +10,6 @@ import MediaPlayer
public typealias AudioPlayerState = AVPlayerWrapperState
@available(*, deprecated, message: "Delegates will be removed in future versions of SwiftAudio. Use event handlers instead.")
public protocol AudioPlayerDelegate: class {
func audioPlayer(playerDidChangeState state: AudioPlayerState)
func audioPlayer(itemPlaybackEndedWithReason reason: PlaybackEndedReason)
func audioPlayer(secondsElapsed seconds: Double)
func audioPlayer(failedWithError error: Error?)
func audioPlayer(seekTo seconds: Int, didFinish: Bool)
func audioPlayer(didUpdateDuration duration: Double)
}
public class AudioPlayer: AVPlayerWrapperDelegate {
private var _wrapper: AVPlayerWrapperProtocol
@@ -38,9 +21,7 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
public let nowPlayingInfoController: NowPlayingInfoControllerProtocol
public let remoteCommandController: RemoteCommandController
public let event = EventHolder()
public weak var delegate: AudioPlayerDelegate?
var _currentItem: AudioItem?
public var currentItem: AudioItem? {
@@ -146,10 +127,9 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
- parameter infoCenter: The InfoCenter to update. Default is `MPNowPlayingInfoCenter.default()`.
*/
public init(avPlayer: AVPlayer = AVPlayer(),
nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(),
public init(nowPlayingInfoController: NowPlayingInfoControllerProtocol = NowPlayingInfoController(),
remoteCommandController: RemoteCommandController = RemoteCommandController()) {
self._wrapper = AVPlayerWrapper(avPlayer: avPlayer)
self._wrapper = AVPlayerWrapper()
self.nowPlayingInfoController = nowPlayingInfoController
self.remoteCommandController = remoteCommandController
@@ -226,7 +206,6 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
self.reset()
self.wrapper.stop()
self.event.playbackEnd.emit(data: .playerStopped)
self.delegate?.audioPlayer(itemPlaybackEndedWithReason: .playerStopped)
}
/**
@@ -336,17 +315,14 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
default: break
}
self.event.stateChange.emit(data: state)
self.delegate?.audioPlayer(playerDidChangeState: state)
}
func AVWrapper(secondsElapsed seconds: Double) {
self.event.secondElapse.emit(data: seconds)
self.delegate?.audioPlayer(secondsElapsed: seconds)
}
func AVWrapper(failedWithError error: Error?) {
self.event.fail.emit(data: error)
self.delegate?.audioPlayer(failedWithError: error)
}
func AVWrapper(seekTo seconds: Int, didFinish: Bool) {
@@ -354,17 +330,18 @@ public class AudioPlayer: AVPlayerWrapperDelegate {
updateNowPlayingCurrentTime(currentTime)
}
self.event.seek.emit(data: (seconds, didFinish))
self.delegate?.audioPlayer(seekTo: seconds, didFinish: didFinish)
}
func AVWrapper(didUpdateDuration duration: Double) {
self.event.updateDuration.emit(data: duration)
self.delegate?.audioPlayer(didUpdateDuration: duration)
}
func AVWrapperItemDidPlayToEndTime() {
self.event.playbackEnd.emit(data: .playedUntilEnd)
self.delegate?.audioPlayer(itemPlaybackEndedWithReason: .playedUntilEnd)
}
func AVWrapperDidRecreateAVPlayer() {
self.event.didRecreateAVPlayer.emit(data: ())
}
}
+10 -1
View File
@@ -15,6 +15,7 @@ extension AudioPlayer {
public typealias FailEventData = (Error?)
public typealias SeekEventData = (seconds: Int, didFinish: Bool)
public typealias UpdateDurationEventData = (Double)
public typealias DidRecreateAVPlayerEventData = ()
public struct EventHolder {
@@ -37,7 +38,8 @@ extension AudioPlayer {
public let secondElapse: AudioPlayer.Event<SecondElapseEventData> = AudioPlayer.Event()
/**
Emitted when the player encounters an error.
Emitted when the player encounters an error. This will ultimately result in the AVPlayer instance to be recreated.
If this event is emitted, it means you will need to load a new item in some way. Calling play() will not resume playback.
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
*/
public let fail: AudioPlayer.Event<FailEventData> = AudioPlayer.Event()
@@ -54,6 +56,13 @@ extension AudioPlayer {
*/
public let updateDuration: AudioPlayer.Event<UpdateDurationEventData> = AudioPlayer.Event()
/**
Emitted when the underlying AVPlayer instance is recreated. Recreation happens if the current player fails.
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
- Note: It can be necessary to set the AVAudioSession's category again when this event is emitted.
*/
public let didRecreateAVPlayer: AudioPlayer.Event<()> = AudioPlayer.Event()
}
public typealias EventClosure<EventData> = (EventData) -> Void
@@ -36,39 +36,43 @@ class AVPlayerObserver: NSObject {
static let timeControlStatus = #keyPath(AVPlayer.timeControlStatus)
}
let player: AVPlayer
private let statusChangeOptions: NSKeyValueObservingOptions = [.new, .initial]
private let timeControlStatusChangeOptions: NSKeyValueObservingOptions = [.new]
var isObserving: Bool = false
weak var delegate: AVPlayerObserverDelegate?
init(player: AVPlayer) {
self.player = player
weak var player: AVPlayer? {
willSet {
self.stopObserving()
}
}
deinit {
if self.isObserving {
self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context)
self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context)
}
self.stopObserving()
}
/**
Start receiving events from this observer.
- Important: If this observer is already receiving events, it will first be removed. Never remove this observer manually.
*/
func startObserving() {
main.async {
if self.isObserving {
self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context)
self.player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context)
}
self.isObserving = true
self.player.addObserver(self, forKeyPath: AVPlayerKeyPath.status, options: self.statusChangeOptions, context: &AVPlayerObserver.context)
self.player.addObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, options: self.timeControlStatusChangeOptions, context: &AVPlayerObserver.context)
guard let player = player else {
return
}
self.stopObserving()
self.isObserving = true
player.addObserver(self, forKeyPath: AVPlayerKeyPath.status, options: self.statusChangeOptions, context: &AVPlayerObserver.context)
player.addObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, options: self.timeControlStatusChangeOptions, context: &AVPlayerObserver.context)
}
func stopObserving() {
guard let player = player, self.isObserving else {
return
}
player.removeObserver(self, forKeyPath: AVPlayerKeyPath.status, context: &AVPlayerObserver.context)
player.removeObserver(self, forKeyPath: AVPlayerKeyPath.timeControlStatus, context: &AVPlayerObserver.context)
self.isObserving = false
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
@@ -27,7 +27,12 @@ class AVPlayerTimeObserver {
var boundaryTimeStartObserverToken: Any?
var periodicTimeObserverToken: Any?
private let player: AVPlayer
weak var player: AVPlayer? {
willSet {
unregisterForBoundaryTimeEvents()
unregisterForPeriodicEvents()
}
}
/// The frequence to receive periodic time events.
/// Setting this to a new value will trigger a re-registering to the periodic events of the player.
@@ -41,8 +46,7 @@ class AVPlayerTimeObserver {
weak var delegate: AVPlayerTimeObserverDelegate?
init(player: AVPlayer, periodicObserverTimeInterval: CMTime) {
self.player = player
init(periodicObserverTimeInterval: CMTime) {
self.periodicObserverTimeInterval = periodicObserverTimeInterval
}
@@ -50,9 +54,11 @@ class AVPlayerTimeObserver {
Will register for the AVPlayer BoundaryTimeEvents, to trigger start and complete events.
*/
func registerForBoundaryTimeEvents() {
guard let player = player else {
return
}
unregisterForBoundaryTimeEvents()
let startBoundaryTimes: [NSValue] = [AVPlayerTimeObserver.startBoundaryTime].map({NSValue(time: $0)})
boundaryTimeStartObserverToken = player.addBoundaryTimeObserver(forTimes: startBoundaryTimes, queue: nil, using: { [weak self] in
self?.delegate?.audioDidStart()
})
@@ -62,10 +68,11 @@ class AVPlayerTimeObserver {
Unregister from the boundary events of the player.
*/
func unregisterForBoundaryTimeEvents() {
if let boundaryTimeStartObserverToken = boundaryTimeStartObserverToken {
player.removeTimeObserver(boundaryTimeStartObserverToken)
self.boundaryTimeStartObserverToken = nil
guard let player = player, let boundaryTimeStartObserverToken = boundaryTimeStartObserverToken else {
return
}
player.removeTimeObserver(boundaryTimeStartObserverToken)
self.boundaryTimeStartObserverToken = nil
}
/**
@@ -73,6 +80,9 @@ class AVPlayerTimeObserver {
Will trigger unregisterForPeriodicEvents() first to avoid multiple subscriptions.
*/
func registerForPeriodicTimeEvents() {
guard let player = player else {
return
}
unregisterForPeriodicEvents()
periodicTimeObserverToken = player.addPeriodicTimeObserver(forInterval: periodicObserverTimeInterval, queue: nil, using: { (time) in
self.delegate?.timeEvent(time: time)
@@ -83,12 +93,11 @@ class AVPlayerTimeObserver {
Unregister for periodic events.
*/
func unregisterForPeriodicEvents() {
if let periodicTimeObserverToken = periodicTimeObserverToken {
self.player.removeTimeObserver(periodicTimeObserverToken)
self.periodicTimeObserverToken = nil
guard let player = player, let periodicTimeObserverToken = periodicTimeObserverToken else {
return
}
player.removeTimeObserver(periodicTimeObserverToken)
self.periodicTimeObserverToken = nil
}
}
@@ -120,7 +120,6 @@ public class QueuedAudioPlayer: AudioPlayer {
*/
public func next() throws {
event.playbackEnd.emit(data: .skippedToNext)
delegate?.audioPlayer(itemPlaybackEndedWithReason: .skippedToNext)
let nextItem = try queueManager.next()
try self.load(item: nextItem, playWhenReady: true)
}
@@ -130,7 +129,6 @@ public class QueuedAudioPlayer: AudioPlayer {
*/
public func previous() throws {
event.playbackEnd.emit(data: .skippedToPrevious)
delegate?.audioPlayer(itemPlaybackEndedWithReason: .skippedToPrevious)
let previousItem = try queueManager.previous()
try self.load(item: previousItem, playWhenReady: true)
}
@@ -154,7 +152,6 @@ public class QueuedAudioPlayer: AudioPlayer {
*/
public func jumpToItem(atIndex index: Int, playWhenReady: Bool = true) throws {
event.playbackEnd.emit(data: .jumpedToIndex)
delegate?.audioPlayer(itemPlaybackEndedWithReason: .jumpedToIndex)
let item = try queueManager.jump(to: index)
try self.load(item: item, playWhenReady: playWhenReady)
}
@@ -13,12 +13,14 @@ public enum TimeEventFrequency {
case everySecond
case everyHalfSecond
case everyQuarterSecond
case custom(time: CMTime)
func getTime() -> CMTime {
switch self {
case .everySecond: return CMTime(value: 1, timescale: 1)
case .everyHalfSecond: return CMTime(value: 1, timescale: 2)
case .everyQuarterSecond: return CMTime(value: 1, timescale: 4)
case .custom(let time): return time
}
}
}