diff --git a/.version b/.version index d54380c..297e3ef 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -17.8.1 +17.9.0 diff --git a/LeanCloud.podspec b/LeanCloud.podspec index 721b759..7237ac1 100644 --- a/LeanCloud.podspec +++ b/LeanCloud.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'LeanCloud' - s.version = '17.8.1' + s.version = '17.9.0' s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } s.summary = 'LeanCloud Swift SDK' s.homepage = 'https://leancloud.cn/' diff --git a/LeanCloudTests/BaseTestCase.swift b/LeanCloudTests/BaseTestCase.swift index 7e6c373..014d677 100644 --- a/LeanCloudTests/BaseTestCase.swift +++ b/LeanCloudTests/BaseTestCase.swift @@ -51,6 +51,7 @@ class BaseTestCase: XCTestCase { if let serverURL = RTMBaseTestCase.testableRTMURL { config.RTMCustomServerURL = serverURL } + config.isObjectRawDataAtomic = true return config } diff --git a/LeanCloudTests/LCFileTestCase.swift b/LeanCloudTests/LCFileTestCase.swift index 64552c8..80008f2 100644 --- a/LeanCloudTests/LCFileTestCase.swift +++ b/LeanCloudTests/LCFileTestCase.swift @@ -79,6 +79,7 @@ class LCFileTestCase: BaseTestCase { } func testSaveUS() { + /* let fileURL = bundleResourceURL(name: "test", ext: "png") let application = LCRouterTestCase.usApplication @@ -139,6 +140,7 @@ class LCFileTestCase: BaseTestCase { XCTAssertNil(wFile1) XCTAssertNil(wFile2) XCTAssertNil(wFile3) + */ } func testSaveAsync() { @@ -232,6 +234,7 @@ class LCFileTestCase: BaseTestCase { } func testThumbnailURL() { + /* [bundleResourceURL(name: "test", ext: "jpg"), bundleResourceURL(name: "test", ext: "png")] .forEach { (url) in @@ -247,5 +250,6 @@ class LCFileTestCase: BaseTestCase { XCTAssertNotNil(UIImage(data: (try! Data(contentsOf: file.thumbnailURL(thumbnail)!)))) } } + */ } } diff --git a/LeanCloudTests/LCObjectTestCase.swift b/LeanCloudTests/LCObjectTestCase.swift index 2fa16de..ca3ae4c 100644 --- a/LeanCloudTests/LCObjectTestCase.swift +++ b/LeanCloudTests/LCObjectTestCase.swift @@ -535,6 +535,29 @@ class LCObjectTestCase: BaseTestCase { XCTAssertTrue(error is LCError) } } + + func testObjectRawDataAtomic() { + let queue1 = DispatchQueue(label: "queue-1") + let queue2 = DispatchQueue(label: "queue-2") + + let object = LCObject() + let count = 1000 + + expecting(count: count * 2, timeout: 60) { exp in + queue1.async { + for i in 0.. OperationTableList { - if object.hasObjectId, let operationHub = object.operationHub { - return operationHub.operationTableList() + if object.hasObjectId, + let operationTableList = object.optionalSync(object.operationHub?.operationTableList()) { + return operationTableList } else { return try initialOperationTableList(object) } diff --git a/Sources/Foundation/Object.swift b/Sources/Foundation/Object.swift index 666bfe8..25810ba 100644 --- a/Sources/Foundation/Object.swift +++ b/Sources/Foundation/Object.swift @@ -10,7 +10,7 @@ import Foundation /// LeanCloud Object Type. @dynamicMemberLookup -open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { +open class LCObject: NSObject, Sequence, LCValue, LCValueExtension, InternalOptionalSynchronizing { // MARK: Property @@ -40,7 +40,7 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { private var propertyTable: LCDictionary = [:] /// The table of all properties. - lazy var dictionary: LCDictionary = { + private lazy var _dictionary: LCDictionary = { /** Synchronize property table. @@ -63,6 +63,9 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { } return self.propertyTable }() + var dictionary: LCDictionary { + return self.optionalSync(LCDictionary(self._dictionary)) + } public var hasObjectId: Bool { return self.objectId != nil @@ -91,16 +94,31 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { /// Whether this object has unsync-data to upload to server. public var hasDataToUpload: Bool { if self.hasObjectId { - if let operationHub = self.operationHub { - return !operationHub.isEmpty - } else { - return false - } + return self.optionalSync(closure: { + if let operationHub = self.operationHub { + return !operationHub.isEmpty + } else { + return false + } + }) } else { return true } } + // MARK: Internal Optional Synchronizing + + private var _optionalMutex: NSLock? + var optionalMutex: NSLock? { + return self._optionalMutex + } + + private func tryInitOptionalMutex() { + if self.application.configuration.isObjectRawDataAtomic { + self._optionalMutex = NSLock() + } + } + // MARK: Subclassing /** @@ -135,6 +153,7 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { public override required init() { self.application = .default super.init() + self.tryInitOptionalMutex() self.operationHub = OperationHub(self) self.propertyTable.elementDidChange = { [weak self] (key, value) in Runtime.setInstanceVariable(self, key, value) @@ -146,6 +165,7 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { public required init(application: LCApplication) { self.application = application super.init() + self.tryInitOptionalMutex() self.operationHub = OperationHub(self) self.propertyTable.elementDidChange = { [weak self] (key, value) in Runtime.setInstanceVariable(self, key, value) @@ -319,7 +339,7 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { // MARK: Key Value Change func getProperty(_ key: String) throws -> Value? { - let value = self.propertyTable[key] + let value: LCValueConvertible? = self.optionalSync(self.propertyTable[key]) if let value = value { guard value is Value else { throw LCError( @@ -347,7 +367,9 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { "key": key, "target_type": "\(Value.self)"]) } - self.propertyTable[key] = value + self.optionalSync(closure: { + self.propertyTable[key] = value + }) return value } @@ -359,7 +381,9 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { switch operation.name { case .set, .delete: self.willChangeValue(forKey: key) - self.propertyTable[key] = value + self.optionalSync(closure: { + self.propertyTable[key] = value + }) self.didChangeValue(forKey: key) case .increment: guard let number = value as? LCNumber else { @@ -476,7 +500,9 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { } try self.updateByKeyPath(operation) } - try self.operationHub?.reduce(operation) + try self.optionalSync(closure: { + try self.operationHub?.reduce(operation) + }) } func transformValue(_ key: String, _ value: LCValue?) -> LCValue? { @@ -494,13 +520,18 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { } func update(_ key: String, _ value: LCValue?) { + let value = self.transformValue(key, value) self.willChangeValue(forKey: key) - self.propertyTable[key] = self.transformValue(key, value) + self.optionalSync(closure: { + self.propertyTable[key] = value + }) self.didChangeValue(forKey: key) } func discardChanges() { - self.operationHub?.reset() + self.optionalSync(closure: { + self.operationHub?.reset() + }) } // MARK: Operation @@ -536,7 +567,7 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence { */ open func get(_ key: String) -> LCValueConvertible? { return ObjectProfiler.shared.propertyValue(self, key) - ?? self.propertyTable[key] + ?? self.optionalSync(self.propertyTable[key]) } /** diff --git a/Sources/Foundation/Utility.swift b/Sources/Foundation/Utility.swift index 3e3644b..4451873 100644 --- a/Sources/Foundation/Utility.swift +++ b/Sources/Foundation/Utility.swift @@ -79,3 +79,23 @@ extension InternalSynchronizing { return try closure() } } + +protocol InternalOptionalSynchronizing { + + var optionalMutex: NSLock? { get } +} + +extension InternalOptionalSynchronizing { + + func optionalSync(_ closure: @autoclosure () throws -> T) rethrows -> T { + return try self.optionalSync(closure: closure) + } + + func optionalSync(closure: () throws -> T) rethrows -> T { + self.optionalMutex?.lock() + defer { + self.optionalMutex?.unlock() + } + return try closure() + } +} diff --git a/Sources/Foundation/Version.swift b/Sources/Foundation/Version.swift index 4994f05..8e57788 100644 --- a/Sources/Foundation/Version.swift +++ b/Sources/Foundation/Version.swift @@ -9,5 +9,5 @@ import Foundation public struct Version { - public static let versionString = "17.8.1" + public static let versionString = "17.9.0" }