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

Hides delimiters in banner when surrounded by loaded shield images #1046

Merged
merged 31 commits into from
Feb 20, 2018

Conversation

akitchen
Copy link
Contributor

@akitchen akitchen commented Jan 19, 2018

This delivers #1027

Includes some refactoring to avoid use of disk cache under test, and adds some synchronous and asynchronous integrated tests of the desired behavior.

Presentation and domain logic are moved into an InstructionPresenter class, which already knows about too much but I think it's an incremental improvement on doing all this work in the view.

TO DO list before merging:

  • Update constants / namespace identifiers per @frederoni 's comments
  • Get feedback on appropriate visibility level for new classes (?)
  • Implement error handling in line with existing patterns
  • Remove SDWebImage from Cartfile, delete related extensions, etc.
  • Discover more test cases and update implementation so that delimiter hiding logic is more robust

Tail work:

  • Backfill some tests as indicated as TODOs int the code
  • Add background execution handler so that we can allow our download operations to continue when the containing app goes into the background (some investigation needed; want to test on a device in order to evaluate the need)
  • Force download / cache busting ?
  • Test in the wild (again)

}

private func instructionHasDownloadedAllShields() -> Bool {
for component in instruction! {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this seems to be safe considering the guard in attributedTextForLabel(_:) but the linter will complain about unwrapping when we decide to enable it.


SDImageCache.shared().clearMemory()

let clearDiskSemaphore = DispatchSemaphore.init(value: 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: .init can be dropped.

import UIKit
import MapboxDirections

class InstructionPresenter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Cool. Perhaps an Instructable protocol could be the next step.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm into the idea -- let's look out for a unified interface when we write/refactor the next view which might otherwise contain logic for setting up & displaying a complex domain object.

Copy link
Contributor

@bsudekum bsudekum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great. Let's hold off on finally merging until we can properly test this with the server. It looks like there is a slight bug that prevents delimiter: true from showing up in some cases.

@akitchen
Copy link
Contributor Author

Cool. I addressed the code nit and rebased on master again.

@akitchen akitchen force-pushed the delimiter branch 3 times, most recently from 25c4a07 to a748cd1 Compare February 1, 2018 00:42
@akitchen akitchen force-pushed the delimiter branch 5 times, most recently from 812935b to 45caabf Compare February 7, 2018 00:49
let diskCacheURL: URL = {
let fileManager = FileManager.default
let basePath = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
return basePath.appendingPathComponent("com.mapbox.directions.downloadedImages")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: directions is somewhat misleading, perhaps we could use Bundle.mapboxNavigation.bundleIdentifier+suffix? ×2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is helpful, you've answered one of my tail work questions.


if toDisk == true {
diskAccessQueue.async {
self.createCacheDirIfNeeded()
Copy link
Contributor

@frederoni frederoni Feb 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be mistaken but capturing self here should result in a retain cycle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is a transient retain cycle there. While this block is in the queue or executing, self will be retained which ensures it sticks around long enough to complete its work. Once the block exits it is disposed of, breaking the cycle.

Does this reasoning hold water or does the team prefer a weakSelf/strongSelf pattern in these disk access methods?

@akitchen
Copy link
Contributor Author

akitchen commented Feb 9, 2018

Looking forward to merging this. There's still a bit of work to do added to the OP

But I think it's looking good. If there are other PRs which collide, maybe we can integrate them together before the final merge into master. /cc @bsudekum @JThramer

@akitchen akitchen force-pushed the delimiter branch 4 times, most recently from d82aaed to 14de9cb Compare February 9, 2018 18:30
}

private func createCacheDirIfNeeded() {
if fileManager!.fileExists(atPath: diskCacheURL.absoluteString) == false {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we gently unwrap fileManager here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - also refactored to rely less on data from self in closures, addressing @frederoni 's previous observation about the transient retain cycles on the disk access queue.

completionHandler(.cancel)
return
}
operation.urlSession!(session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operation.urlSession! can also be unwrapped above in the guard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of unwrapping, I've opted to ask before telling in the latest commit ( which will ensure that the force unwrap is safe )

akitchen and others added 14 commits February 15, 2018 08:42
A few TODOs remain, specifically around handling memory warnings.
Also some DRY refactoring still needed
ImageCache dumps its in-memory cache if a memory warning is received
Provide a backwards-compatible shim for SDImageCache even though we're no longer using it
This only works for one request per URL at the moment. Next step is to encapsulate work into an operation which will allow for the addition of callbacks for an in-flight request (and avoid duplicate parallel requests for the same resource)
Co-Authored-By: Fredrik Karlsson <bjorn.fredrik.karlsson@gmail.com>
Remove redundant success flag from callbacks
A few test backfills still needed

Co-Authored-By: Fredrik Karlsson <bjorn.fredrik.karlsson@gmail.com>
Allow presenter to be thrown away when re-setting an instruction
Log error on image download error
Pad shield images with a space
Refactor InstructionPresenter to take its dependencies on init, removing some unnecessary optionality
Nil out the instructions in StepTableViewCell's prepareForReuse()
@akitchen
Copy link
Contributor Author

I just merged master into this branch as the semantic disconnects were a bit much. /cc @frederoni

Would love to merge this with a final 👍 and follow on with some tail work as enumerated in the OP

@@ -71,8 +71,8 @@ open class BaseInstructionsBannerView: UIControl {
delegate?.didTapInstructionsBanner(self)
}

func set(_ instruction: VisualInstruction) {
let secondaryInstruction = instruction.secondaryTextComponents
func set(_ instruction: VisualInstruction?) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This optionality is needed in order to unset the instruction in StepTableViewCell.prepareForReuse()

/cc @frederoni

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, should we use a different semantic here now that we've wrapped the primary and secondary components in the VisualInstruction class? Or does this feel idiomatic enough?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just set or set(_:) in this case almost feels like a reserved keyword but I don't have a strong opinion about that. Maybe setInstruction(_:) or updateInstruction(_:) would be more idiomatic?

Copy link
Contributor

@JThramer JThramer Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 % agree, @frederoni. A update(routeProgress:), update(legProgress:) or update(instruction:) pattern would be pretty straightforward.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this as tail work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants