@@ -10,14 +10,22 @@ final class ProductImageActionHandler {
1010
1111 private let siteID : Int64
1212
13+ /// The queue where internal states like `allStatuses` and `observations` are updated on to maintain thread safety.
14+ private let queue : DispatchQueue
15+
1316 var productImageStatuses : [ ProductImageStatus ] {
1417 return allStatuses. productImageStatuses
1518 }
1619
1720 private var allStatuses : AllStatuses {
1821 didSet {
19- observations. allStatusesUpdated. values. forEach { closure in
20- closure ( allStatuses)
22+ queue. async { [ weak self] in
23+ guard let self = self else {
24+ return
25+ }
26+ self . observations. allStatusesUpdated. values. forEach { closure in
27+ closure ( self . allStatuses)
28+ }
2129 }
2230 }
2331 }
@@ -27,37 +35,55 @@ final class ProductImageActionHandler {
2735 assetUploaded: [ UUID: OnAssetUpload] ( )
2836 )
2937
30- init ( siteID: Int64 , product: Product ) {
38+ /// - Parameters:
39+ /// - siteID: the ID of a site/store where the product belongs to.
40+ /// - product: the product whose image statuses and actions are of concern.
41+ /// - queue: the queue where the update callbacks are called on. Default to be the main queue.
42+ init ( siteID: Int64 , product: Product , queue: DispatchQueue = . main) {
3143 self . siteID = siteID
44+ self . queue = queue
3245 self . allStatuses = ( productImageStatuses: product. imageStatuses, error: nil )
3346 }
3447
3548 /// Observes when the image statuses have been updated.
3649 ///
3750 /// - Parameters:
3851 /// - observer: the observer that `onUpdate` is associated with.
39- /// - onUpdate: called when the image statuses have been updated, if `observer` is not nil.
52+ /// - onUpdate: called when the image statuses have been updated on the thread passed in the initializer (default to the main thread),
53+ /// if `observer` is not nil.
4054 @discardableResult
4155 func addUpdateObserver< T: AnyObject > ( _ observer: T ,
4256 onUpdate: @escaping OnAllStatusesUpdate ) -> ObservationToken {
4357 let id = UUID ( )
4458
45- observations. allStatusesUpdated [ id] = { [ weak self, weak observer] allStatuses in
46- // If the observer has been deallocated, we can
47- // automatically remove the observation closure.
48- guard observer != nil else {
49- self ? . observations. allStatusesUpdated. removeValue ( forKey: id)
59+ queue. async { [ weak self] in
60+ guard let self = self else {
5061 return
5162 }
5263
53- onUpdate ( allStatuses)
54- }
64+ self . observations. allStatusesUpdated [ id] = { [ weak self, weak observer] allStatuses in
65+ guard let self = self else {
66+ return
67+ }
68+
69+ // If the observer has been deallocated, we can
70+ // automatically remove the observation closure.
71+ guard observer != nil else {
72+ self . observations. allStatusesUpdated. removeValue ( forKey: id)
73+ return
74+ }
5575
56- // Sends the initial value.
57- onUpdate ( allStatuses)
76+ onUpdate ( self . allStatuses)
77+ }
78+
79+ // Sends the initial value.
80+ onUpdate ( self . allStatuses)
81+ }
5882
5983 return ObservationToken { [ weak self] in
60- self ? . observations. allStatusesUpdated. removeValue ( forKey: id)
84+ self ? . queue. async { [ weak self] in
85+ self ? . observations. allStatusesUpdated. removeValue ( forKey: id)
86+ }
6187 }
6288 }
6389
@@ -71,70 +97,103 @@ final class ProductImageActionHandler {
7197 onAssetUpload: @escaping OnAssetUpload ) -> ObservationToken {
7298 let id = UUID ( )
7399
74- observations. assetUploaded [ id] = { [ weak self, weak observer] asset, productImage in
75- // If the observer has been deallocated, we can
76- // automatically remove the observation closure.
77- guard observer != nil else {
78- self ? . observations. assetUploaded. removeValue ( forKey: id)
100+ queue. async { [ weak self] in
101+ guard let self = self else {
79102 return
80103 }
81104
82- onAssetUpload ( asset, productImage)
105+ self . observations. assetUploaded [ id] = { [ weak self, weak observer] asset, productImage in
106+ // If the observer has been deallocated, we can
107+ // automatically remove the observation closure.
108+ guard observer != nil else {
109+ self ? . observations. assetUploaded. removeValue ( forKey: id)
110+ return
111+ }
112+
113+ onAssetUpload ( asset, productImage)
114+ }
83115 }
84116
85117 return ObservationToken { [ weak self] in
86- self ? . observations. assetUploaded. removeValue ( forKey: id)
118+ self ? . queue. async { [ weak self] in
119+ self ? . observations. assetUploaded. removeValue ( forKey: id)
120+ }
87121 }
88122 }
89123
90124 func addSiteMediaLibraryImagesToProduct( mediaItems: [ Media ] ) {
91- let newProductImageStatuses = mediaItems. map { ProductImageStatus . remote ( image: $0. toProductImage) }
92- let imageStatuses = newProductImageStatuses + productImageStatuses
93- allStatuses = ( productImageStatuses: imageStatuses, error: nil )
125+ queue. async { [ weak self] in
126+ guard let self = self else {
127+ return
128+ }
129+
130+ let newProductImageStatuses = mediaItems. map { ProductImageStatus . remote ( image: $0. toProductImage) }
131+ let imageStatuses = newProductImageStatuses + self . productImageStatuses
132+ self . allStatuses = ( productImageStatuses: imageStatuses, error: nil )
133+ }
94134 }
95135
96136 func uploadMediaAssetToSiteMediaLibrary( asset: PHAsset ) {
97- let imageStatuses = [ . uploading( asset: asset) ] + allStatuses. productImageStatuses
98- allStatuses = ( productImageStatuses: imageStatuses, error: nil )
137+ queue. async { [ weak self] in
138+ guard let self = self else {
139+ return
140+ }
99141
100- let action = MediaAction . uploadMedia ( siteID: siteID,
101- mediaAsset: asset) { [ weak self] ( media, error) in
102- guard let self = self else {
103- return
104- }
142+ let imageStatuses = [ . uploading( asset: asset) ] + self . allStatuses. productImageStatuses
143+ self . allStatuses = ( productImageStatuses: imageStatuses, error: nil )
105144
106- guard let index = self . index ( of: asset) else {
107- return
108- }
145+ self . uploadMediaAssetToSiteMediaLibrary ( asset: asset) { [ weak self] ( media, error) in
146+ self ? . queue. async { [ weak self] in
147+ guard let self = self else {
148+ return
149+ }
109150
110- guard let media = media else {
111- DispatchQueue . main. async {
151+ guard let index = self . index ( of: asset) else {
152+ return
153+ }
154+
155+ guard let media = media else {
112156 self . updateProductImageStatus ( at: index, error: error)
157+ return
113158 }
114- return
115- }
116- let productImage = ProductImage ( imageID: media. mediaID,
117- dateCreated: media. date,
118- dateModified: media. date,
119- src: media. src,
120- name: media. name,
121- alt: media. alt)
122- DispatchQueue . main. async {
159+ let productImage = ProductImage ( imageID: media. mediaID,
160+ dateCreated: media. date,
161+ dateModified: media. date,
162+ src: media. src,
163+ name: media. name,
164+ alt: media. alt)
123165 self . updateProductImageStatus ( at: index, productImage: productImage)
124166 }
167+ }
168+ }
169+ }
170+
171+ private func uploadMediaAssetToSiteMediaLibrary( asset: PHAsset , onCompletion: @escaping ( _ uploadedMedia: Media ? , _ error: Error ? ) -> Void ) {
172+ DispatchQueue . main. async { [ weak self] in
173+ guard let siteID = self ? . siteID else {
174+ return
175+ }
176+
177+ let action = MediaAction . uploadMedia ( siteID: siteID, mediaAsset: asset, onCompletion: onCompletion)
178+ ServiceLocator . stores. dispatch ( action)
125179 }
126- ServiceLocator . stores. dispatch ( action)
127180 }
128181
129182 func deleteProductImage( _ productImage: ProductImage ) {
130- var imageStatuses = allStatuses. productImageStatuses
131- imageStatuses. removeAll { status -> Bool in
132- guard case . remote( let image) = status else {
133- return false
183+ queue. async { [ weak self] in
184+ guard let self = self else {
185+ return
134186 }
135- return image. imageID == productImage. imageID
187+
188+ var imageStatuses = self . allStatuses. productImageStatuses
189+ imageStatuses. removeAll { status -> Bool in
190+ guard case . remote( let image) = status else {
191+ return false
192+ }
193+ return image. imageID == productImage. imageID
194+ }
195+ self . allStatuses = ( productImageStatuses: imageStatuses, error: nil )
136196 }
137- allStatuses = ( productImageStatuses: imageStatuses, error: nil )
138197 }
139198
140199 /// Resets the product images to the ones from the given Product.
0 commit comments