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

RouteOptions subclass type is erased when rerouting #3191

Closed
1ec5 opened this issue Jul 20, 2021 · 1 comment · Fixed by #3192
Closed

RouteOptions subclass type is erased when rerouting #3191

1ec5 opened this issue Jul 20, 2021 · 1 comment · Fixed by #3192
Assignees
Labels
bug Something isn’t working
Milestone

Comments

@1ec5
Copy link
Contributor

1ec5 commented Jul 20, 2021

We allow and sometimes encourage developers to subclass NavigationRouteOptions to fine-tune the Directions API requests that come from the application. For example, mapbox/mapbox-navigation-ios-examples#91 illustrates how to use beta query parameters that the MapboxDirections library doesn’t formally support yet. When either RouteController or LegacyRouteController reroutes the user, the SDK stops using the developer’s subclass, so any customizations implemented in a urlQueryItems override get blown away.

Diagnosis

RouteOptions.without(waypoint:) and RouteProgress.reroutingOptions(with:) effectively replace the subclass instance with a RouteOptions instance (not even a NavigationRouteOptions instance), so that it can modify the set of waypoints without affecting the original options object.

func getDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping Directions.RouteCompletionHandler) {
routeTask?.cancel()
let options = progress.reroutingOptions(with: location)
let newWaypoints = [user] + remainingWaypointsForCalculatingRoute()
let newOptions = oldOptions.copy() as! RouteOptions
newOptions.waypoints = newWaypoints
extension RouteOptions: NSCopying {
public func copy(with zone: NSZone? = nil) -> Any {
do {
let encodedOptions = try JSONEncoder().encode(self)
return try JSONDecoder().decode(RouteOptions.self, from: encodedOptions)
} catch {
preconditionFailure("Unable to copy RouteOptions by round-tripping it through JSON")
}
}

This is an awkward mix of Objective-C and Swift paradigms for dealing with value types: traditionally, in Objective-C, RouteOptions would be immutable and a separate MutableRouteOptions class would have mutable properties, but that complicates subclassing. In Swift, a struct would be the correct way to implement the pass-by-value semantics we’re going for: mapbox/mapbox-directions-swift#564.

In practice, RouteOptions itself has mutable properties, so as a workaround, 70bd68f for #2275 implemented NSCopying on RouteOptions, hard-coding the RouteOptions type in the implementation of copy(with:). This means any subclass of RouteOptions, including NavigationRouteOptions, would need to override copy(with:) to preserve any customizations after rerouting. NavigationRouteOptions happens to be unaffected, because all it does is set a few good default values in its initializer – values that RouteOptions’ Codable implementation would naturally preserve when round-tripping through JSON.

Next steps

While we await the bright future after mapbox/mapbox-directions-swift#564 is fixed, RouteOptions.copy(with:) should dynamically decode the current object’s type rather than hard-coding RouteOptions per se. Documentation about RouteOptions and NavigationRouteOptions should include a subclassing note that discusses overriding init(from:) and encode(to:). The modified RouteOptions.copy(with:) would only work to the extent that RouteOptions subclasses are resilient to being round-tripped through JSON.

/cc @mapbox/navigation-ios

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn’t working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant