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

Use CoreDataStack in MediaThumbnailService #20294

Merged
merged 3 commits into from
Mar 14, 2023

Conversation

crazytonyli
Copy link
Contributor

Unlike other similar PR, this PR has changes that are not just simply swapping NSManagedObjectContext with CoreDataStack. I'll add some PR comments for detailed reasons.

Test instructions

Mainly the uploading feature in the "Media" screen is impacted. Verifying uploading media still works should be sufficient.

Regression Notes

  1. Potential unintended areas of impact
    None.

  2. What I did to test those areas of impact (or what existing automated tests I relied on)
    What are described in the "Test instructions" above.

  3. What automated tests I added (or what prevented me from doing so)
    None.

PR submission checklist:

  • I have completed the Regression Notes.
  • I have considered adding unit tests for my changes.
  • I have considered adding accessibility improvements for my changes.
  • I have considered if this change warrants user-facing release notes and have added them to RELEASE-NOTES.txt if necessary.

@crazytonyli crazytonyli added the Core Data Issues related to Core Data label Mar 9, 2023
@crazytonyli crazytonyli added this to the 22.0 milestone Mar 9, 2023
@crazytonyli crazytonyli requested a review from mokagio March 9, 2023 20:40
@crazytonyli crazytonyli self-assigned this Mar 9, 2023
}
let mediaService = MediaService(managedObjectContext: coreDataStack.mainContext)
mediaService.getMediaWithID(mediaID, in: media.blog, success: { (loadedMedia) in
onCompletion(loadedMedia, nil)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since a Media object is leaked out of MediaService's lifecycle(or MediaService.managedObjectContext's to be exact), it's only safe to use the main context as the MediaService instance's context.

///
/// - Note: Images may be downloaded and resized if required, avoid requesting multiple explicit preferredSizes
/// as several images could be downloaded, resized, and cached, if there are several variations in size.
///
@objc func thumbnailURL(forMedia media: Media, preferredSize: CGSize, onCompletion: @escaping OnThumbnailURL, onError: OnError?) {
managedObjectContext.perform {
let context = coreDataStack.mainContext
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using the main context here because this function only read the Media instance, without changing it.

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, there is no time consuming work done in the block that's passed to the perform function, all heavy lifting are done in background queues.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let context = coreDataStack.mainContext
// We can use the main context here because we only read the `Media` instance, without changing it, and all
// the time consuming work is done in background queues.
let context = coreDataStack.mainContext

/// - onCompletion: Completion handler passing the URL once available, or nil if unavailable.
/// - onError: Error handler.
/// - onCompletion: Completion handler passing the URL once available, or nil if unavailable. This closure is called on the `exportQueue`.
/// - onError: Error handler. This closure is called on the `exportQueue`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Originally, these closures are called on the managedObjectContext queue. Since we don't have a long living context object anymore, they are changed to use the exportQueue.

remoteURL = WPImageURLHelper.imageURLWithSize(preferredSize, forImageURL: remoteAssetURL)
} else {
remoteURL = PhotonImageURLHelper.photonURL(with: preferredSize, forImageURL: remoteAssetURL)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The above changes are mainly for removing the onCompletion(nil) calls above, which are now covered by the guard let imageURL = remoteURL case below.

private let onSuccess: (UIImage) -> ()
private let onFailure: (Error) -> ()

init(url: URL, blog: Blog, onSuccess: @escaping (UIImage) -> (), onFailure: @escaping (Error) -> ()) {
init(url: URL, blog: Blog, callbackQueue: DispatchQueue, onSuccess: @escaping (UIImage) -> (), onFailure: @escaping (Error) -> ()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Originally the onSuccess closure is called on the main queue, and the onFailure closure is called on a background thread that runs this operation. Adding this calbackQueue to unify their calling queue. Also, it makes the enforcing callback queue code in MediaThumbnailService nicer, without requiring additional async calls there.

@@ -140,6 +145,7 @@ class EditorMediaUtility {
let imageDownload = AuthenticatedImageDownload(
url: requestURL,
blog: post.blog,
callbackQueue: .main,
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 should be an improvement to the existing behaviour, since onFailure wasn't called on the main thread, but I believe this function requires the error handling to be on the main thread.

@@ -121,8 +121,7 @@ class MediaLibraryPickerDataSourceTests: CoreDataTestCase {

let mediaService = MediaService(managedObjectContext: mainContext)
let expect = self.expectation(description: "Media should be create with success")
mediaService.createMedia(with: url as NSURL, blog: blog, post: post, progress: nil, thumbnailCallback: { (media, url) in
}, completion: { (media, error) in
mediaService.createMedia(with: url as NSURL, blog: blog, post: post, progress: nil, thumbnailCallback: nil, completion: { (media, error) in
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 just a small thing I noticed—no point passing an empty closure if nil is an acceptable argument.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Mar 9, 2023

WordPress Alpha📲 You can test the changes from this Pull Request in WordPress Alpha by scanning the QR code below to install the corresponding build.
App NameWordPress Alpha WordPress Alpha
ConfigurationRelease-Alpha
Build Numberpr20294-62f47cc
Version21.9
Bundle IDorg.wordpress.alpha
Commit62f47cc
App Center BuildWPiOS - One-Offs #5191
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

@wpmobilebot
Copy link
Contributor

wpmobilebot commented Mar 9, 2023

Jetpack Alpha📲 You can test the changes from this Pull Request in Jetpack Alpha by scanning the QR code below to install the corresponding build.
App NameJetpack Alpha Jetpack Alpha
ConfigurationRelease-Alpha
Build Numberpr20294-62f47cc
Version21.9
Bundle IDcom.jetpack.alpha
Commit62f47cc
App Center Buildjetpack-installable-builds #4216
Automatticians: You can use our internal self-serve MC tool to give yourself access to App Center if needed.

Copy link
Contributor

@mokagio mokagio left a comment

Choose a reason for hiding this comment

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

Unlike other similar PR, this PR has changes that are not just simply swapping NSManagedObjectContext with CoreDataStack. I'll add some PR comments for detailed reasons.

Thanks!

Mainly the uploading feature in the "Media" screen is impacted. Verifying uploading media still works should be sufficient.

image

Upload successful and the thumbnails appear alright.

Comment on lines 81 to 83
let mediaService = MediaService(managedObjectContext: coreDataStack.mainContext)
mediaService.getMediaWithID(mediaID, in: media.blog, success: { (loadedMedia) in
onCompletion(loadedMedia, nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think of tracking what your wrote in the comment above in the code?

Suggested change
let mediaService = MediaService(managedObjectContext: coreDataStack.mainContext)
mediaService.getMediaWithID(mediaID, in: media.blog, success: { (loadedMedia) in
onCompletion(loadedMedia, nil)
// It's only safe to use the main context as the MediaService instance's context because a Media object is
// leaked out of MediaService's lifecycle (MediaService.managedObjectContext's to be exact.
let mediaService = MediaService(managedObjectContext: coreDataStack.mainContext)
mediaService.getMediaWithID(mediaID, in: media.blog, success: { (loadedMedia) in
onCompletion(loadedMedia, nil)

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, SwiftLint might at some point complain about the redundant () around loadedMedia. 🤷‍♂️

///
/// - Note: Images may be downloaded and resized if required, avoid requesting multiple explicit preferredSizes
/// as several images could be downloaded, resized, and cached, if there are several variations in size.
///
@objc func thumbnailURL(forMedia media: Media, preferredSize: CGSize, onCompletion: @escaping OnThumbnailURL, onError: OnError?) {
managedObjectContext.perform {
let context = coreDataStack.mainContext
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let context = coreDataStack.mainContext
// We can use the main context here because we only read the `Media` instance, without changing it, and all
// the time consuming work is done in background queues.
let context = coreDataStack.mainContext

private func downloadThumbnail(
forMedia media: Media,
preferredSize: CGSize,
callbackQueue: DispatchQueue,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
callbackQueue: DispatchQueue,
callbackQueue: DispatchQueue,

@@ -121,8 +121,7 @@ class MediaLibraryPickerDataSourceTests: CoreDataTestCase {

let mediaService = MediaService(managedObjectContext: mainContext)
let expect = self.expectation(description: "Media should be create with success")
mediaService.createMedia(with: url as NSURL, blog: blog, post: post, progress: nil, thumbnailCallback: { (media, url) in
}, completion: { (media, error) in
mediaService.createMedia(with: url as NSURL, blog: blog, post: post, progress: nil, thumbnailCallback: nil, completion: { (media, error) in
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@crazytonyli crazytonyli enabled auto-merge March 14, 2023 03:01
@crazytonyli crazytonyli merged commit 152708d into trunk Mar 14, 2023
@crazytonyli crazytonyli deleted the tonyli-refactor-media-service-types branch March 14, 2023 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Core Data Issues related to Core Data
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants