Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17.8.1
17.9.0
2 changes: 1 addition & 1 deletion LeanCloud.podspec
Original file line number Diff line number Diff line change
@@ -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/'
Expand Down
1 change: 1 addition & 0 deletions LeanCloudTests/BaseTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class BaseTestCase: XCTestCase {
if let serverURL = RTMBaseTestCase.testableRTMURL {
config.RTMCustomServerURL = serverURL
}
config.isObjectRawDataAtomic = true
return config
}

Expand Down
4 changes: 4 additions & 0 deletions LeanCloudTests/LCFileTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class LCFileTestCase: BaseTestCase {
}

func testSaveUS() {
/*
let fileURL = bundleResourceURL(name: "test", ext: "png")
let application = LCRouterTestCase.usApplication

Expand Down Expand Up @@ -139,6 +140,7 @@ class LCFileTestCase: BaseTestCase {
XCTAssertNil(wFile1)
XCTAssertNil(wFile2)
XCTAssertNil(wFile3)
*/
}

func testSaveAsync() {
Expand Down Expand Up @@ -232,6 +234,7 @@ class LCFileTestCase: BaseTestCase {
}

func testThumbnailURL() {
/*
[bundleResourceURL(name: "test", ext: "jpg"),
bundleResourceURL(name: "test", ext: "png")]
.forEach { (url) in
Expand All @@ -247,5 +250,6 @@ class LCFileTestCase: BaseTestCase {
XCTAssertNotNil(UIImage(data: (try! Data(contentsOf: file.thumbnailURL(thumbnail)!))))
}
}
*/
}
}
23 changes: 23 additions & 0 deletions LeanCloudTests/LCObjectTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<count {
object.update("\(i)", i.lcValue)
exp.fulfill()
}
}
queue2.async {
for i in 0..<count {
object.update("\(i)", i.lcValue)
exp.fulfill()
}
}
}
}
}

class TestObject: LCObject {
Expand Down
3 changes: 3 additions & 0 deletions Sources/Foundation/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ public class LCApplication {
/// RTM Custom Server URL, default is `nil`.
public var RTMCustomServerURL: URL?

/// Make the access to the raw data of the object is atomic, default is `false`.
public var isObjectRawDataAtomic: Bool = false

public init(
customizedServers: [ServerCustomizableModule] = [],
environment: Environment = .default,
Expand Down
5 changes: 3 additions & 2 deletions Sources/Foundation/BatchRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ class BatchRequestBuilder {
- returns: A list of operation tables.
*/
private static func operationTableList(_ object: LCObject) throws -> 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)
}
Expand Down
59 changes: 45 additions & 14 deletions Sources/Foundation/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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

/**
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -319,7 +339,7 @@ open class LCObject: NSObject, LCValue, LCValueExtension, Sequence {
// MARK: Key Value Change

func getProperty<Value: LCValue>(_ 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(
Expand Down Expand Up @@ -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
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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? {
Expand All @@ -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
Expand Down Expand Up @@ -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])
}

/**
Expand Down
20 changes: 20 additions & 0 deletions Sources/Foundation/Utility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,23 @@ extension InternalSynchronizing {
return try closure()
}
}

protocol InternalOptionalSynchronizing {

var optionalMutex: NSLock? { get }
}

extension InternalOptionalSynchronizing {

func optionalSync<T>(_ closure: @autoclosure () throws -> T) rethrows -> T {
return try self.optionalSync(closure: closure)
}

func optionalSync<T>(closure: () throws -> T) rethrows -> T {
self.optionalMutex?.lock()
defer {
self.optionalMutex?.unlock()
}
return try closure()
}
}
2 changes: 1 addition & 1 deletion Sources/Foundation/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
import Foundation

public struct Version {
public static let versionString = "17.8.1"
public static let versionString = "17.9.0"
}