浏览代码

Integrating PR #495

Sam Spencer 5 年之前
父节点
当前提交
40cca01b79

+ 2 - 0
CHANGELOG.md

@@ -2,6 +2,8 @@
 
 All notable changes to this project will be documented in this file.
 
+* Make `ProductsInfoController`'s `retrieveProductsInfo` thread safe ([#405]https://github.com/bizz84/SwiftyStoreKit/pull/495), related issues: [#344](https://github.com/bizz84/SwiftyStoreKit/issues/344) and [#468](https://github.com/bizz84/SwiftyStoreKit/issues/468)
+
 ## [0.15.0](https://github.com/bizz84/SwiftyStoreKit/releases/tag/0.15.0) Update project to Swift 5, Xcode 10.2
 
 * Update project to Swift 5 ([#457](https://github.com/bizz84/SwiftyStoreKit/pull/457)), related issue: [#456](https://github.com/bizz84/SwiftyStoreKit/issues/456)

+ 2 - 2
Package.swift

@@ -8,7 +8,7 @@ let package = Package(
         // Products define the executables and libraries produced by a package, and make them visible to other packages.
         .library(
             name: "SwiftyStoreKit",
-            targets: ["SwiftyStoreKit"]),
+            targets: ["SwiftyStoreKit"])
     ],
     dependencies: [
         // Dependencies declare other packages that this package depends on.
@@ -22,6 +22,6 @@ let package = Package(
             dependencies: []),
         .testTarget(
             name: "SwiftyStoreKitTests",
-            dependencies: ["SwiftyStoreKit"]),
+            dependencies: ["SwiftyStoreKit"])
     ]
 )

+ 13 - 1
Sources/SwiftyStoreKit/ProductsInfoController.swift

@@ -44,8 +44,12 @@ class ProductsInfoController: NSObject {
     }
     
     let inAppProductRequestBuilder: InAppProductRequestBuilder
+
+    private var spinLock: OSSpinLock
+  
     init(inAppProductRequestBuilder: InAppProductRequestBuilder = InAppProductQueryRequestBuilder()) {
         self.inAppProductRequestBuilder = inAppProductRequestBuilder
+        self.spinLock = OSSpinLock()
     }
     
     // As we can have multiple inflight requests, we store them in a dictionary by product ids
@@ -53,10 +57,18 @@ class ProductsInfoController: NSObject {
 
     @discardableResult
     func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest {
+        OSSpinLockLock(&self.spinLock)
+        defer {
+          OSSpinLockUnlock(&self.spinLock)
+        }
 
         if inflightRequests[productIds] == nil {
             let request = inAppProductRequestBuilder.request(productIds: productIds) { results in
-                
+                OSSpinLockLock(&self.spinLock)
+                defer {
+                  OSSpinLockUnlock(&self.spinLock)
+                }
+              
                 if let query = self.inflightRequests[productIds] {
                     for completion in query.completionHandlers {
                         completion(results)

+ 0 - 1
Sources/SwiftyStoreKit/SKProductDiscount+LocalizedPrice.swift

@@ -60,4 +60,3 @@ public extension SKProductDiscount {
     }
     
 }
-

+ 5 - 15
SwiftyStoreKit.xcodeproj/project.pbxproj

@@ -71,9 +71,6 @@
 		2F2B8B7524A64CD700CEF088 /* SwiftyStoreKit-watchOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B6C24A64CD700CEF088 /* SwiftyStoreKit-watchOS.h */; };
 		2F2B8B7624A64CD700CEF088 /* SwiftyStoreKit-watchOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B6C24A64CD700CEF088 /* SwiftyStoreKit-watchOS.h */; };
 		2F2B8B7724A64CD700CEF088 /* SwiftyStoreKit-watchOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B6C24A64CD700CEF088 /* SwiftyStoreKit-watchOS.h */; };
-		2F2B8B7924A64CD700CEF088 /* Info-macOS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F2B8B6D24A64CD700CEF088 /* Info-macOS.plist */; };
-		2F2B8B7F24A64CD700CEF088 /* Info-watchOS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F2B8B6E24A64CD700CEF088 /* Info-watchOS.plist */; };
-		2F2B8B8024A64CD700CEF088 /* Info-iOS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F2B8B6F24A64CD700CEF088 /* Info-iOS.plist */; };
 		2F2B8B8424A64CD700CEF088 /* SwiftyStoreKit-tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B7024A64CD700CEF088 /* SwiftyStoreKit-tvOS.h */; };
 		2F2B8B8524A64CD700CEF088 /* SwiftyStoreKit-tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B7024A64CD700CEF088 /* SwiftyStoreKit-tvOS.h */; };
 		2F2B8B8624A64CD700CEF088 /* SwiftyStoreKit-tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B7024A64CD700CEF088 /* SwiftyStoreKit-tvOS.h */; };
@@ -86,7 +83,6 @@
 		2F2B8B8D24A64CD700CEF088 /* SwiftyStoreKit-macOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B7224A64CD700CEF088 /* SwiftyStoreKit-macOS.h */; };
 		2F2B8B8E24A64CD700CEF088 /* SwiftyStoreKit-macOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B7224A64CD700CEF088 /* SwiftyStoreKit-macOS.h */; };
 		2F2B8B8F24A64CD700CEF088 /* SwiftyStoreKit-macOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F2B8B7224A64CD700CEF088 /* SwiftyStoreKit-macOS.h */; };
-		2F2B8B9224A64CD700CEF088 /* Info-tvOS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F2B8B7324A64CD700CEF088 /* Info-tvOS.plist */; };
 		2F2B8BA024A64DE600CEF088 /* PaymentQueueControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9424A64DE600CEF088 /* PaymentQueueControllerTests.swift */; };
 		2F2B8BA124A64DE600CEF088 /* PaymentTransactionObserverFake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9524A64DE600CEF088 /* PaymentTransactionObserverFake.swift */; };
 		2F2B8BA224A64DE600CEF088 /* ProductsInfoControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9624A64DE600CEF088 /* ProductsInfoControllerTests.swift */; };
@@ -97,7 +93,6 @@
 		2F2B8BA724A64DE600CEF088 /* InAppReceiptVerificatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9B24A64DE600CEF088 /* InAppReceiptVerificatorTests.swift */; };
 		2F2B8BA824A64DE600CEF088 /* InAppReceiptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9C24A64DE600CEF088 /* InAppReceiptTests.swift */; };
 		2F2B8BA924A64DE600CEF088 /* PaymentsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9D24A64DE600CEF088 /* PaymentsControllerTests.swift */; };
-		2F2B8BAA24A64DE600CEF088 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9E24A64DE600CEF088 /* Info.plist */; };
 		2F2B8BAB24A64DE600CEF088 /* PaymentQueueSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2B8B9F24A64DE600CEF088 /* PaymentQueueSpy.swift */; };
 		654287F61E79F5A000F61800 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 654287F41E79F5A000F61800 /* Main.storyboard */; };
 		654287F81E79F5A000F61800 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 654287F71E79F5A000F61800 /* Assets.xcassets */; };
@@ -231,7 +226,7 @@
 		2F2B8B9B24A64DE600CEF088 /* InAppReceiptVerificatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppReceiptVerificatorTests.swift; path = Tests/SwiftyStoreKitTests/InAppReceiptVerificatorTests.swift; sourceTree = SOURCE_ROOT; };
 		2F2B8B9C24A64DE600CEF088 /* InAppReceiptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppReceiptTests.swift; path = Tests/SwiftyStoreKitTests/InAppReceiptTests.swift; sourceTree = SOURCE_ROOT; };
 		2F2B8B9D24A64DE600CEF088 /* PaymentsControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PaymentsControllerTests.swift; path = Tests/SwiftyStoreKitTests/PaymentsControllerTests.swift; sourceTree = SOURCE_ROOT; };
-		2F2B8B9E24A64DE600CEF088 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Tests/SwiftyStoreKitTests/Info.plist; sourceTree = SOURCE_ROOT; };
+		2F2B8B9E24A64DE600CEF088 /* Info-Tests.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-Tests.plist"; path = "Tests/SwiftyStoreKitTests/Info-Tests.plist"; sourceTree = SOURCE_ROOT; };
 		2F2B8B9F24A64DE600CEF088 /* PaymentQueueSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PaymentQueueSpy.swift; path = Tests/SwiftyStoreKitTests/PaymentQueueSpy.swift; sourceTree = SOURCE_ROOT; };
 		54C0D52C1CF7404500F90BCE /* SwiftyStoreKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyStoreKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		6502F5FE1B985833004E342D /* SwiftyStoreKit_iOSDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyStoreKit_iOSDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -388,7 +383,7 @@
 				2F2B8B9A24A64DE600CEF088 /* CompleteTransactionsControllerTests.swift */,
 				2F2B8B9C24A64DE600CEF088 /* InAppReceiptTests.swift */,
 				2F2B8B9B24A64DE600CEF088 /* InAppReceiptVerificatorTests.swift */,
-				2F2B8B9E24A64DE600CEF088 /* Info.plist */,
+				2F2B8B9E24A64DE600CEF088 /* Info-Tests.plist */,
 				2F2B8B9424A64DE600CEF088 /* PaymentQueueControllerTests.swift */,
 				2F2B8B9F24A64DE600CEF088 /* PaymentQueueSpy.swift */,
 				2F2B8B9D24A64DE600CEF088 /* PaymentsControllerTests.swift */,
@@ -650,7 +645,7 @@
 			isa = PBXProject;
 			attributes = {
 				LastSwiftUpdateCheck = 0820;
-				LastUpgradeCheck = 1020;
+				LastUpgradeCheck = 1160;
 				ORGANIZATIONNAME = musevisions;
 				TargetAttributes = {
 					54C0D52B1CF7404500F90BCE = {
@@ -720,7 +715,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				2F2B8B9224A64CD700CEF088 /* Info-tvOS.plist in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -738,7 +732,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				2F2B8B8024A64CD700CEF088 /* Info-iOS.plist in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -755,7 +748,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				2F2B8BAA24A64DE600CEF088 /* Info.plist in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -763,7 +755,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				2F2B8B7F24A64CD700CEF088 /* Info-watchOS.plist in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -771,7 +762,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				2F2B8B7924A64CD700CEF088 /* Info-macOS.plist in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1334,7 +1324,7 @@
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				INFOPLIST_FILE = SwiftyStoreKitTests/Info.plist;
+				INFOPLIST_FILE = "Tests/SwiftyStoreKitTests/Info-Tests.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 10.2;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.iOS.SwiftyStoreKitTests;
@@ -1354,7 +1344,7 @@
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
-				INFOPLIST_FILE = SwiftyStoreKitTests/Info.plist;
+				INFOPLIST_FILE = "Tests/SwiftyStoreKitTests/Info-Tests.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 10.2;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
 				PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.iOS.SwiftyStoreKitTests;

+ 10 - 14
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit-iOS-Demo.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -27,6 +27,15 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6502F5FD1B985833004E342D"
+            BuildableName = "SwiftyStoreKit_iOSDemo.app"
+            BlueprintName = "SwiftyStoreKit_iOSDemo"
+            ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <Testables>
          <TestableReference
             skipped = "NO">
@@ -39,17 +48,6 @@
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "6502F5FD1B985833004E342D"
-            BuildableName = "SwiftyStoreKit_iOSDemo.app"
-            BlueprintName = "SwiftyStoreKit_iOSDemo"
-            ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -71,8 +69,6 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 1 - 5
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit-iOS.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -29,8 +29,6 @@
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -51,8 +49,6 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 3 - 7
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit-macOS-Demo.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-      </Testables>
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <Testables>
+      </Testables>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 1 - 5
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit-macOS.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -29,8 +29,6 @@
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -51,8 +49,6 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 3 - 7
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit-tvOS-Demo.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -27,8 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-      </Testables>
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <Testables>
+      </Testables>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 1 - 5
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit-tvOS.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -29,8 +29,6 @@
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -51,8 +49,6 @@
             ReferencedContainer = "container:SwiftyStoreKit.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 1 - 5
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKitTests.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -23,8 +23,6 @@
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -36,8 +34,6 @@
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Release"

+ 1 - 1
SwiftyStoreKit.xcodeproj/xcshareddata/xcschemes/SwiftyStoreKit_watchOS.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1150"
+   LastUpgradeVersion = "1160"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"

+ 3 - 20
Tests/SwiftyStoreKitTests/InAppReceiptTests.swift

@@ -107,12 +107,11 @@ extension VerifyPurchaseResult: Equatable {
     }
 }
 
-// swiftlint:disable file_length
+// swiftlint: disable file_length
 class InAppReceiptTests: XCTestCase {
 
     // MARK: Verify Purchase
     func testVerifyPurchase_when_noPurchases_then_resultIsNotPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
@@ -121,8 +120,8 @@ class InAppReceiptTests: XCTestCase {
 
         XCTAssertEqual(verifyPurchaseResult, .notPurchased)
     }
+    
     func testVerifyPurchase_when_onePurchase_then_resultIsPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let item = ReceiptItem(productId: productId, purchaseDate: receiptRequestDate, subscriptionExpirationDate: nil, cancellationDate: nil, isTrialPeriod: false)
@@ -132,8 +131,8 @@ class InAppReceiptTests: XCTestCase {
 
         XCTAssertEqual(verifyPurchaseResult, .purchased(item: item))
     }
+    
     func testVerifyPurchase_when_oneCancelledPurchase_then_resultIsNotPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let item = ReceiptItem(productId: productId, purchaseDate: receiptRequestDate, subscriptionExpirationDate: nil, cancellationDate: receiptRequestDate, isTrialPeriod: false)
@@ -147,7 +146,6 @@ class InAppReceiptTests: XCTestCase {
     // MARK: Verify Subscription, single receipt item tests
     // auto-renewable, not purchased
     func testVerifyAutoRenewableSubscription_when_noSubscriptions_then_resultIsNotPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
@@ -160,7 +158,6 @@ class InAppReceiptTests: XCTestCase {
 
     // auto-renewable, expired
     func testVerifyAutoRenewableSubscription_when_oneExpiredSubscription_then_resultIsExpired() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
         let productId = "product1"
         let isTrialPeriod = false
@@ -177,7 +174,6 @@ class InAppReceiptTests: XCTestCase {
 
     // auto-renewable, purchased
     func testVerifyAutoRenewableSubscription_when_oneNonExpiredSubscription_then_resultIsPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -194,7 +190,6 @@ class InAppReceiptTests: XCTestCase {
 
     // auto-renewable, cancelled
     func testVerifyAutoRenewableSubscription_when_oneCancelledSubscription_then_resultIsNotPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -212,7 +207,6 @@ class InAppReceiptTests: XCTestCase {
 
     // non-renewing, non purchased
     func testVerifyNonRenewingSubscription_when_noSubscriptions_then_resultIsNotPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
@@ -225,7 +219,6 @@ class InAppReceiptTests: XCTestCase {
 
     // non-renewing, expired
     func testVerifyNonRenewingSubscription_when_oneExpiredSubscription_then_resultIsExpired() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 15)
         let productId = "product1"
         let isTrialPeriod = false
@@ -244,7 +237,6 @@ class InAppReceiptTests: XCTestCase {
 
     // non-renewing, purchased
     func testVerifyNonRenewingSubscription_when_oneNonExpiredSubscription_then_resultIsPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -263,7 +255,6 @@ class InAppReceiptTests: XCTestCase {
 
     // non-renewing, cancelled
     func testVerifyNonRenewingSubscription_when_oneCancelledSubscription_then_resultIsNotPurchased() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let productId = "product1"
         let isTrialPeriod = false
@@ -281,7 +272,6 @@ class InAppReceiptTests: XCTestCase {
 
     // MARK: Verify Subscription, multiple receipt item tests
     func testVerifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
 
         let productId = "product1"
@@ -312,7 +302,6 @@ class InAppReceiptTests: XCTestCase {
     }
 
     func testVerifyAutoRenewableSubscription_when_twoSubscriptions_sameProductId_bothExpired_then_resultIsExpired_itemsSorted() {
-        
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         
         let productId = "product1"
@@ -344,7 +333,6 @@ class InAppReceiptTests: XCTestCase {
     
     // MARK: Verify Subscriptions, multiple receipt item tests
     func testVerifyAutoRenewableSubscriptions_when_threeSubscriptions_twoMatchingProductIds_mostRecentNonExpired_then_resultIsPurchased_itemsSorted() {
-        
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         
         let productId1 = "product1"
@@ -386,7 +374,6 @@ class InAppReceiptTests: XCTestCase {
     
     // MARK: Get Distinct Purchase Identifiers, empty receipt item tests
     func testGetDistinctPurchaseIds_when_noReceipt_then_resultIsNil() {
-
         let receiptRequestDate = makeDateAtMidnight(year: 2017, month: 5, day: 14)
         let receipt = makeReceipt(items: [], requestDate: receiptRequestDate)
 
@@ -396,7 +383,6 @@ class InAppReceiptTests: XCTestCase {
     
     // MARK: Get Distinct Purchase Identifiers, multiple receipt item tests
     func testGetDistinctPurchaseIds_when_Receipt_then_resultIsNotNil() {
-
         let receiptRequestDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 20)
         let purchaseDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 1)
         let purchaseDateTwo = makeDateAtMidnight(year: 2020, month: 1, day: 1)
@@ -416,7 +402,6 @@ class InAppReceiptTests: XCTestCase {
     
     // MARK: Get Distinct Purchase Identifiers, multiple non unique product identifiers tests
     func testGetDistinctPurchaseIds_when_nonUniqueIdentifiers_then_resultIsUnique() {
-
         let receiptRequestDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 20)
         let purchaseDateOne = makeDateAtMidnight(year: 2020, month: 2, day: 1)
         let purchaseDateTwo = makeDateAtMidnight(year: 2020, month: 2, day: 2)
@@ -442,7 +427,6 @@ class InAppReceiptTests: XCTestCase {
 
     // MARK: Helper methods
     func makeReceipt(items: [ReceiptItem], requestDate: Date) -> [String: AnyObject] {
-
         let receiptInfos = items.map { $0.receiptInfo }
 
         // Creating this with NSArray results in __NSSingleObjectArrayI which fails the cast to [String: AnyObject]
@@ -462,7 +446,6 @@ class InAppReceiptTests: XCTestCase {
     }
 
     func makeDateAtMidnight(year: Int, month: Int, day: Int) -> Date {
-
         var dateComponents = DateComponents()
         dateComponents.day = day
         dateComponents.month = month

+ 0 - 0
Tests/SwiftyStoreKitTests/Info.plist → Tests/SwiftyStoreKitTests/Info-Tests.plist


+ 55 - 0
Tests/SwiftyStoreKitTests/ProductsInfoControllerTests.swift

@@ -23,6 +23,7 @@
 // THE SOFTWARE.
 
 import XCTest
+import Foundation
 @testable import SwiftyStoreKit
 
 class TestInAppProductRequest: InAppProductRequest {
@@ -50,8 +51,15 @@ class TestInAppProductRequest: InAppProductRequest {
 class TestInAppProductRequestBuilder: InAppProductRequestBuilder {
     
     var requests: [ TestInAppProductRequest ] = []
+    var os_unfair_lock_s = os_unfair_lock()
     
     func request(productIds: Set<String>, callback: @escaping InAppProductRequestCallback) -> InAppProductRequest {
+        // add locks to make sure the test does not fail in preparation
+        os_unfair_lock_lock(&self.os_unfair_lock_s)
+        defer {
+          os_unfair_lock_unlock(&self.os_unfair_lock_s)
+        }
+      
         let request = TestInAppProductRequest(productIds: productIds, callback: callback)
         requests.append(request)
         return request
@@ -68,6 +76,17 @@ class TestInAppProductRequestBuilder: InAppProductRequestBuilder {
 class ProductsInfoControllerTests: XCTestCase {
     
     let sampleProductIdentifiers: Set<String> = ["com.iap.purchase1"]
+    // Set of in app purchases to ask in different threads
+    let testProducts: Set<String> = ["com.iap.purchase01",
+                                     "com.iap.purchase02",
+                                     "com.iap.purchase03",
+                                     "com.iap.purchase04",
+                                     "com.iap.purchase05",
+                                     "com.iap.purchase06",
+                                     "com.iap.purchase07",
+                                     "com.iap.purchase08",
+                                     "com.iap.purchase09",
+                                     "com.iap.purchase10"]
 
     func testRetrieveProductsInfo_when_calledOnce_then_completionCalledOnce() {
         
@@ -117,4 +136,40 @@ class ProductsInfoControllerTests: XCTestCase {
         requestBuilder.fireCallbacks()
         XCTAssertEqual(completionCount, 2)
     }
+  
+  func testRetrieveProductsInfo_when_calledConcurrentlyInDifferentThreads_then_eachcompletionCalledOnce_noCrashes() {
+    let requestBuilder = TestInAppProductRequestBuilder()
+    let productInfoController = ProductsInfoController(inAppProductRequestBuilder: requestBuilder)
+    
+    var completionCallbackCount = 0
+    
+    // Create the expectation not to let the test finishes before the other threads complete
+    let expectation = XCTestExpectation(description: "Expect downloads of product informations")
+    
+    // Create the dispatch group to let the test verifies the assert only when
+    // everything else finishes.
+    let group = DispatchGroup()
+    
+    // Dispatch a request for every product in a different thread
+    for product in testProducts {
+      DispatchQueue.global().async {
+        group.enter()
+        productInfoController.retrieveProductsInfo([product]) { _ in
+          completionCallbackCount += 1
+          group.leave()
+        }
+      }
+    }
+    DispatchQueue.global().asyncAfter(deadline: .now()+0.1) {
+      requestBuilder.fireCallbacks()
+    }
+    // Fullfil the expectation when every thread finishes
+    group.notify(queue: DispatchQueue.global()) {
+      
+      XCTAssertEqual(completionCallbackCount, self.testProducts.count)
+      expectation.fulfill()
+    }
+    
+    wait(for: [expectation], timeout: 10.0)
+  }
 }