Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Objective-C compatibility; adopt Swift language features #2230

Merged
merged 57 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
14df7b1
WIP: Starting out with a blanket wipe of @objc. 90 build errors.
Sep 16, 2019
87434c6
WIP: Refactoring. Single digit errors.
Sep 20, 2019
717794b
Fixing UnimplementedLogging protocol issues
Sep 20, 2019
91a43c2
Adding stubs for LegacyRouteController
Sep 20, 2019
6016b34
Continuing to churn through this. 142 errors.
Sep 20, 2019
9ac081a
adding defaults for CarPlayManagerDelegate
Sep 20, 2019
6add446
Continuing to go fast. 84 remain.
Sep 20, 2019
8d4bee5
Really whittling it down now. 9 errors remain.
Sep 20, 2019
b055108
Fixing extension issues, can't override from extensions in swift. 5 i…
Sep 20, 2019
903b12e
Moving to proper place in file
Sep 20, 2019
83f3bbe
adding default handling for TopBannerViewControllerDelegate
Sep 20, 2019
b03a9fd
Took care of all remaining issues on first-pass. Ready for second phase.
Sep 20, 2019
9da8da3
Removing OBJ-C example.
Sep 23, 2019
484a320
All build errors fixed. Kobe's Guidance cards are not working, and th…
Sep 23, 2019
ccab3eb
Fixing integration tests with Instruction Cards.
Sep 24, 2019
ed9c2bf
Fixing last remaining test failure.
Sep 24, 2019
e74d9bb
Apparently CI doesn't like the unified logging module. Lets see if we…
Sep 24, 2019
6a5b021
Fixing UserCourseView and some other issues that were causing the CI …
Sep 25, 2019
159664e
Removing Orphaned class.
Sep 25, 2019
98b5a22
Fixing a warning that CI is complaining about
Sep 25, 2019
c1332e1
Replacing janky `delegateIdentifier` property (supposed to tell you w…
Sep 25, 2019
1bf136d
Removing mention of OBJ-C from documentation. Also adding cover.md to…
Sep 26, 2019
05fa803
Replacing all OBJ-C constants and enumerations with swift patterns.
Sep 26, 2019
0aab920
Fixing lingering issues.
Sep 30, 2019
293a0da
Fixed default protocol method implementation visibility
1ec5 Oct 15, 2019
745448d
Getting rid of `Self.` in type extensions, which only works in swift 5.1
Oct 15, 2019
b610d1f
Fixed indentation
1ec5 Oct 16, 2019
37f2c60
Fixed Xcode 10 syntax error
1ec5 Oct 16, 2019
20fb6a0
Rewrote String.md5 for Swift 5 compatibility
1ec5 Oct 16, 2019
512db91
Restored user course view to map view
1ec5 Oct 18, 2019
3dd9c7c
Testing CI Build Failure
Oct 28, 2019
aafc146
PR Comments
Nov 1, 2019
4a0d77b
PR comment: documentation for notification user info keys
Nov 1, 2019
80d6e44
PR Comment: Adding note about unimplemented feature
Nov 1, 2019
02549c4
Updating with better copy
Nov 1, 2019
c1535b1
WIP: adding proper swift error for voice/speech issues
Nov 1, 2019
b92fec8
Implementing swift speech errors
Nov 4, 2019
e659378
There actually aren't any unknown speech errors.
Nov 4, 2019
aaf83d5
Adding documentation and changelog entries
Nov 5, 2019
0e29a77
whoopsie
Nov 5, 2019
20a91b5
Fixing `testDefaultUserInterfaceUsage` test
Nov 5, 2019
b14e936
Adding interface deprecations
Nov 8, 2019
6d36650
Adding testing for `UnimplementedLogging` that _hopefully_ should sat…
Nov 8, 2019
1aa6f21
Final changelog and documentation entries.
Nov 8, 2019
d025291
Taking minh's suggestion to make `userDistanceToManeuverLocation` ini…
Nov 8, 2019
ff2abea
Doh!
Nov 8, 2019
8f7819e
Fixed misleading indentation; removed extraneous blank lines
1ec5 Nov 12, 2019
f6548b6
Fixed documentation comment syntax
1ec5 Nov 12, 2019
73555ac
Fixed documentation comment indentation
1ec5 Nov 12, 2019
4e9b7d6
Apply suggestions from code review
JThramer Nov 13, 2019
93eedcc
Update MapboxNavigation/CarPlayManagerDelegate.swift
JThramer Nov 13, 2019
69220fc
Wrapping up final outstanding issues.
Nov 19, 2019
9147d60
Merge branch 'master' into jerrad/objc-delenda-est
Nov 19, 2019
755e450
Other PR suggestions
Nov 19, 2019
3a7813f
Updating test to reflect new uniquing mechanism on UnimplmenetedLogging.
Nov 20, 2019
0f9ef94
Fixing indendation issue.
Nov 20, 2019
2a1ac22
PR Suggestion: Replace `engine` with `synthesizer`
Nov 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@

## master

### Architechture
### Packaging
* This library can no longer be used in applications written in pure Objective-C. If you need to use this library’s public API from Objective-C code, you will need to implement a wrapper in Swift that bridges the subset of the API you need from Swift to Objective-C. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))

### UI
### User interface
* `UserCourseView` is now a type alias of the `UIView` class and the `CourseUpdatable` protocol rather than a protocol in its own right. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* `NavigationMapview.showRoutes(_:legIndex:)` has been renaamed to `NavigationMapView.show(_:legIndex:)`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* `NavigationMapview.showWayoints(_:legIndex:)` has been renaamed to `NavigationMapView.showWaypoints(on:legIndex:)`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* `MapboxVoiceController.play(_:)` has been renaamed to `MapboxVoiceController.play(instruction:data:)`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* Fixed an issue where user notifications displayed right turn arrows for left turn maneuvers. ([#2270](https://github.com/mapbox/mapbox-navigation-ios/pull/2270))
* Renamed `NavigationMapView.showRoutes(_:legIndex:)` to `NavigationMapView.show(_:legIndex:)`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* Renamed `NavigationMapView.showWaypoints(_:legIndex:)` to `NavigationMapView.showWaypoints(on:legIndex:)`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* Renamed `MapboxVoiceController.play(_:)` to `MapboxVoiceController.play(instruction:data:)`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))

### Error Handling
### Error handling
* The `MapboxVoiceController` and `RouteVoiceController` now emit `SpeechError`s instead of an `NSError` object. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* There is a new `VoiceControllerDelegate.voiceController(_:didFallBackTo:becauseOf:)` method that informs the client that the speech engine was forced to fall-back to a native speech engine. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))
* Added the `VoiceControllerDelegate.voiceController(_:didFallBackTo:becauseOf:)` method for detecting when the voice controller falls back to `AVSpeechSynthesizer`. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))

### Messaging
### Other changes
* Since pure Swift protocols cannot have optional methods, various delegate protocols now provide default no-op implementations for all their methods and conform to the `UnimplementedLogging` protocol, which can inform you at runtime when a delegate method is called but has not been implemented. Messages are sent through Apple Unified Logging and can be disabled globally through [Unifed Logging](https://developer.apple.com/documentation/os/logging#2878594), or by overriding the delegate function with a no-op implementation. ([#2230](https://github.com/mapbox/mapbox-navigation-ios/pull/2230))

## v0.38.0
Expand Down
6 changes: 3 additions & 3 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
binary "https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK.json" "5.2.0"
binary "https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK.json" "5.5.0"
binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" "6.2.1"
github "AndriiDoroshko/SnappyShrimp" "1.6.4"
github "CedarBDD/Cedar" "v1.0"
github "Quick/Nimble" "v8.0.2"
github "Quick/Quick" "v2.1.0"
github "Quick/Nimble" "v8.0.4"
github "Quick/Quick" "v2.2.0"
github "ceeK/Solar" "2.1.0"
github "mapbox/MapboxDirections.swift" "v0.30.0"
github "mapbox/MapboxGeocoder.swift" "v0.10.2"
Expand Down
2 changes: 1 addition & 1 deletion Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ extension ViewController: NavigationMapViewDelegate {
// To use these delegate methods, set the `VoiceControllerDelegate` on your `VoiceController`.
extension ViewController: VoiceControllerDelegate {
// called when there is an error that requires the speech controller to fall back to a native engine.
func voiceController(_ voiceController: RouteVoiceController, didFallBackTo engine: AVSpeechSynthesizer, becauseOf error: SpeechError) {
func voiceController(_ voiceController: RouteVoiceController, didFallBackTo engine: AVSpeechSynthesizer, error: SpeechError) {
print(error)
}

Expand Down
2 changes: 1 addition & 1 deletion MapboxCoreNavigation/NavigationServiceDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public extension NavigationServiceDelegate {
}

func navigationService(_ service: NavigationService, didArriveAt waypoint: Waypoint) -> Bool {
logUnimplemented(protocolType: NavigationServiceDelegate.self, level: .info)
logUnimplemented(protocolType: NavigationServiceDelegate.self, level: .debug)
return MapboxNavigationService.Default.didArriveAtWaypoint
}

Expand Down
22 changes: 20 additions & 2 deletions MapboxCoreNavigation/UnimplementedLogging.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import os.log
import Dispatch

/**
Protocols that provide no-op default method implementations can use this protocol to log a message to the console whenever an unimplemented delegate method is called.
Expand All @@ -13,14 +14,31 @@ public protocol UnimplementedLogging {
}

public extension UnimplementedLogging {

func logUnimplemented(protocolType: Any, level: OSLogType, function: String = #function) {

let protocolDescription = String(describing: protocolType)
let selfDescription = String(describing: type(of: self))

let description = (selfDescription, function)

let alreadyWarned = warned.contains { elem -> Bool in
elem == description
}

guard !alreadyWarned else {
return
}

let log = OSLog(subsystem: "com.mapbox.navigation", category: "delegation.\(selfDescription)")
let formatted: StaticString = "Unimplemented Delegate Method in %@: %@.%@"
os_log(formatted, log: log, type: level, selfDescription, protocolDescription, function)
let formatted: StaticString = "Unimplemented Delegate Method in %@: %@.%@. This message will only be logged once."
os_log(formatted, log: log, type: level, selfDescription, protocolDescription, function)
unimplementedTestLogs?.append((selfDescription, function))
warned.append(description)
}
}

fileprivate var warned: [(String, String)] = []


var unimplementedTestLogs: [(String, String)]? = nil
2 changes: 1 addition & 1 deletion MapboxCoreNavigationTests/NavigationServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ class NavigationServiceTests: XCTestCase {

let ourLogs = logs.filter { $0.0 == "EmptyNavigationServiceDelegate" }

XCTAssertEqual(ourLogs.count, 520, "Expected logs to be populated and expected number of messages sent")
XCTAssertEqual(ourLogs.count, 4, "Expected logs to be populated and expected number of messages sent")
unimplementedTestLogs = nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion MapboxNavigation/CarPlayManagerDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public protocol CarPlayManagerDelegate: class, UnimplementedLogging {
- parameter waypoints: the waypoints for which a route could not be retrieved.
- parameter options: The route options that were attached to the route request.
- parameter error: The error returned from the directions API.
- returns: Optionally, a CPNavigationAlert to present to the user. If an alert is returned, Carplay will transition back to the map template and display the alert. If `nil` is returned, nothing is done.
- returns: Optionally, a `CPNavigationAlert` to present to the user. If this method returns an alert, the CarPlay manager will transition back to the map template and display the alert. If it returns `nil`, the CarPlay manager will do nothing.
- note: This delegate method includes a default implementation that prints a warning to the console when this method is called. See `UnimplementedLogging` for details.
*/
func carPlayManager(_ carPlayManager: CarPlayManager, didFailToFetchRouteBetween waypoints: [Waypoint]?, options: RouteOptions, error: NSError) -> CPNavigationAlert?
Expand Down
63 changes: 17 additions & 46 deletions MapboxNavigation/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,28 @@ import MapboxSpeech
import AVKit

/**
A enum representing the reason why the speech API request failed.
- seealso: SpeechError
*/
public enum SpeechRequestFailureReason: String {
/**
No data was returned from the service.
*/
case noData
/**
An erroneous response was returned from the server.
*/
case apiError
}

/**
An enum representing the action that failed to complete successfully.
The speech-related action that failed.
- seealso: SpeechError
*/
public enum SpeechFailureAction: String {
/**
A failure was encountered while attempting to mix audio.
A failure occurred while attempting to mix audio.
*/
case mix
/**
A failure was encountered while attempting to duck audio.
A failure occurred while attempting to duck audio.
*/
case duck
/**
A failure was encountered while attempting to unduck audio.
A failure occurred while attempting to unduck audio.
*/
case unduck
/**
A failure was encountered while attempting to play audio.
A failure occurred while attempting to play audio.
*/
case play
}

/**
The speech engine that encountered issues with audio control.
- seealso: SpeechError
*/
public enum SpeechEngine {
/**
An API Audio Engine. Associated with a `SpeechSynthesizer`.
*/
case api(_: SpeechSynthesizer?)
/**
A native speech engine. Associated with an `AVSpeechSynthesizer`.
*/
case native(_: AVSpeechSynthesizer)

/**
An unkown speech engine. Associated with an unknown object.
*/
case unknown(_: AnyObject)
}

/**
A error type returned when encountering errors in the speech engine.
*/
Expand All @@ -70,28 +35,34 @@ public enum SpeechError: LocalizedError {
The Speech API Did not successfully return a response.
- parameter instruction: the instruction that failed.
- parameter options: the SpeechOptions that were used to make the API request.
- parameter reason: a `SpeechRequestFailureReason` describing why the request failed.
- parameter underlying: the underlying `Error` returned by the API.
*/
case apiRequestFailed(instruction: SpokenInstruction, options: SpeechOptions, reason: SpeechRequestFailureReason, underlying: Error?)
case apiError(instruction: SpokenInstruction, options: SpeechOptions, underlying: Error?)

/**
The Speech API Did not return any data.
- parameter instruction: the instruction that failed.
- parameter options: the SpeechOptions that were used to make the API request.
*/
case noData(instruction: SpokenInstruction, options: SpeechOptions)

/**
The speech engine was unable to perform an action on the system audio service.
- parameter instruction: The instruction that failed.
- parameter action: a `SpeechFailureAction` that describes the action attempted
- parameter engine: the `SpeechEngine` that tried to perform the action.
- parameter engine: the speech engine that tried to perform the action.
- parameter underlying: the `Error` that was optrionally returned by the audio service.
*/
case unableToControlAudio(instruction: SpokenInstruction?, action: SpeechFailureAction, engine: SpeechEngine, underlying: Error?)
case unableToControlAudio(instruction: SpokenInstruction?, action: SpeechFailureAction, engine: Any?, underlying: Error?)

/**
The speech engine was unable to initalize an audio player.
- parameter playerType: the type of `AVAudioPlayer` that failed to initalize.
- parameter instruction: The instruction that failed.
- parameter engine: The `SpeechEngine` that attempted the initalization.
- parameter engine: The speech engine that attempted the initalization.
- parameter underlying: the `Error` that was returned by the system audio service.
*/
case unableToInitalizePlayer(playerType: AVAudioPlayer.Type, instruction: SpokenInstruction, engine: SpeechEngine, underlying: Error)
case unableToInitializePlayer(playerType: AVAudioPlayer.Type, instruction: SpokenInstruction, engine: Any?, underlying: Error)

/**
The active `RouteProgress` did not contain a speech locale.
Expand Down
22 changes: 11 additions & 11 deletions MapboxNavigation/MapboxVoiceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ open class MapboxVoiceController: RouteVoiceController, AVAudioPlayerDelegate {
self?.audioPlayer?.stop()

guard let strongSelf = self else { return }
strongSelf.safeUnduckAudio(instruction: nil, engine: .api(self?.speech)) {
strongSelf.safeUnduckAudio(instruction: nil, engine: self?.speech) {
strongSelf.voiceControllerDelegate?.voiceController(strongSelf, spokenInstructionsDidFailWith: $0)
}
}
Expand All @@ -68,15 +68,15 @@ open class MapboxVoiceController: RouteVoiceController, AVAudioPlayerDelegate {
deinit {
audioPlayer?.stop()

safeUnduckAudio(instruction: nil, engine: .api(speech)) {
safeUnduckAudio(instruction: nil, engine: speech) {
voiceControllerDelegate?.voiceController(self, spokenInstructionsDidFailWith: $0)
}

audioPlayer?.delegate = nil
}

public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
safeUnduckAudio(instruction: nil, engine: .api(speech)) {
safeUnduckAudio(instruction: nil, engine: speech) {
voiceControllerDelegate?.voiceController(self, spokenInstructionsDidFailWith: $0)
}
}
Expand Down Expand Up @@ -139,7 +139,7 @@ open class MapboxVoiceController: RouteVoiceController, AVAudioPlayerDelegate {
audioTask?.cancel()

if let error = error {
voiceControllerDelegate?.voiceController(self, didFallBackTo: speechSynth, becauseOf: error)
voiceControllerDelegate?.voiceController(self, didFallBackTo: speechSynth, error: error)
}

guard !(audioPlayer?.isPlaying ?? false) else {
Expand All @@ -165,13 +165,13 @@ open class MapboxVoiceController: RouteVoiceController, AVAudioPlayerDelegate {
if let error = error as? URLError, error.code == .cancelled {
return
} else if let error = error {
let wrapped = SpeechError.apiRequestFailed(instruction: instruction, options: options, reason: .apiError, underlying: error)
let wrapped = SpeechError.apiError(instruction: instruction, options: options, underlying: error)
strongSelf.speakWithDefaultSpeechSynthesizer(instruction, error: wrapped)
return
}

guard let data = data else {
let wrapped = SpeechError.apiRequestFailed(instruction: instruction, options: options, reason: .noData, underlying: nil)
let wrapped = SpeechError.noData(instruction: instruction, options: options)
strongSelf.speakWithDefaultSpeechSynthesizer(instruction, error: wrapped)
return
}
Expand Down Expand Up @@ -216,12 +216,12 @@ open class MapboxVoiceController: RouteVoiceController, AVAudioPlayerDelegate {
return cachedDataForKey(key) != nil
}

func safeInitalizeAudioPlayer(playerType: AVAudioPlayer.Type, data: Data, instruction: SpokenInstruction, engine: SpeechEngine, failure: AudioControlFailureHandler) -> AVAudioPlayer? {
func safeInitalizeAudioPlayer(playerType: AVAudioPlayer.Type, data: Data, instruction: SpokenInstruction, engine: Any?, failure: AudioControlFailureHandler) -> AVAudioPlayer? {
do {
let player = try playerType.init(data: data)
return player
} catch {
let wrapped = SpeechError.unableToInitalizePlayer(playerType: playerType, instruction: instruction, engine: engine, underlying: error)
let wrapped = SpeechError.unableToInitializePlayer(playerType: playerType, instruction: instruction, engine: engine, underlying: error)
failure(wrapped)
return nil
}
Expand All @@ -239,16 +239,16 @@ open class MapboxVoiceController: RouteVoiceController, AVAudioPlayerDelegate {

audioQueue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.audioPlayer = strongSelf.safeInitalizeAudioPlayer(playerType: strongSelf.audioPlayerType, data: data, instruction: instruction, engine: .api(strongSelf.speech), failure: fallback)
strongSelf.audioPlayer = strongSelf.safeInitalizeAudioPlayer(playerType: strongSelf.audioPlayerType, data: data, instruction: instruction, engine: strongSelf.speech, failure: fallback)
strongSelf.audioPlayer?.prepareToPlay()
strongSelf.audioPlayer?.delegate = strongSelf

strongSelf.safeDuckAudio(instruction: instruction, engine: .api(strongSelf.speech), failure: fallback)
strongSelf.safeDuckAudio(instruction: instruction, engine: strongSelf.speech, failure: fallback)

let played = strongSelf.audioPlayer?.play() ?? false

guard played else {
strongSelf.safeUnduckAudio(instruction: instruction, engine: .api(strongSelf.speech), failure: fallback)
strongSelf.safeUnduckAudio(instruction: instruction, engine: strongSelf.speech, failure: fallback)
return
}
}
Expand Down
Loading