Skip to content

Commit

Permalink
fixing racing condition between setup block and reading method (#810)
Browse files Browse the repository at this point in the history
* Fixing racing condition between `setupBlock` and `readMethod`
* Add multithread tests
  • Loading branch information
wolfcon committed Mar 22, 2021
1 parent dfc0f8a commit 7b635bb
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 30 deletions.
7 changes: 3 additions & 4 deletions MJExtension/NSObject+MJClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -130,17 +130,16 @@ + (NSMutableArray *)mj_totalAllowedCodingPropertyNames
}

#pragma mark - block和方法处理:存储block的返回值
+ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key
{
+ (void)mj_setupBlockReturnValue:(id (^)(void))block key:(const char *)key {
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
if (block) {
objc_setAssociatedObject(self, key, block(), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} else {
objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// 清空数据
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
[[self mj_classDictForKey:key] removeAllObjects];
MJ_UNLOCK(mje_signalSemaphore);
}
Expand Down
52 changes: 29 additions & 23 deletions MJExtension/NSObject+MJProperty.m
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,20 @@ + (Class)mj_propertyObjectClassInArray:(NSString *)propertyName
+ (void)mj_enumerateProperties:(MJPropertiesEnumeration)enumeration
{
// 获得成员变量
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
NSArray *cachedProperties = [self mj_properties];
MJ_UNLOCK(mje_signalSemaphore);
// 遍历成员变量
BOOL stop = NO;
for (MJProperty *property in [cachedProperties copy]) {
for (MJProperty *property in cachedProperties) {
enumeration(property, &stop);
if (stop) break;
}
}

#pragma mark - 公共方法
+ (NSMutableArray *)mj_properties
+ (NSArray *)mj_properties
{
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
NSMutableDictionary *cachedInfo = [self mj_propertyDictForKey:&MJCachedPropertiesKey];
NSMutableArray *cachedProperties = cachedInfo[NSStringFromClass(self)];
if (cachedProperties == nil) {
Expand Down Expand Up @@ -169,16 +168,20 @@ + (NSMutableArray *)mj_properties
free(properties);
}];

[self mj_propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
cachedInfo[NSStringFromClass(self)] = cachedProperties;
}
NSArray *properties = [cachedProperties copy];
MJ_UNLOCK(mje_signalSemaphore);

return cachedProperties;
return properties;
}

#pragma mark - 新值配置
+ (void)mj_setupNewValueFromOldValue:(MJNewValueFromOldValue)newValueFormOldValue
{
+ (void)mj_setupNewValueFromOldValue:(MJNewValueFromOldValue)newValueFormOldValue {
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
objc_setAssociatedObject(self, &MJNewValueFromOldValueKey, newValueFormOldValue, OBJC_ASSOCIATION_COPY_NONATOMIC);
MJ_UNLOCK(mje_signalSemaphore);
}

+ (id)mj_getNewValueFromObject:(__unsafe_unretained id)object oldValue:(__unsafe_unretained id)oldValue property:(MJProperty *__unsafe_unretained)property{
Expand All @@ -187,6 +190,8 @@ + (id)mj_getNewValueFromObject:(__unsafe_unretained id)object oldValue:(__unsafe
return [object mj_newValueFromOldValue:oldValue property:property];
}

MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
// 查看静态设置
__block id newValue = oldValue;
[self mj_enumerateAllClasses:^(__unsafe_unretained Class c, BOOL *stop) {
Expand All @@ -196,37 +201,38 @@ + (id)mj_getNewValueFromObject:(__unsafe_unretained id)object oldValue:(__unsafe
*stop = YES;
}
}];
MJ_UNLOCK(mje_signalSemaphore);
return newValue;
}

+ (void)mj_removeCachedProperties {
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJ_UNLOCK(mje_signalSemaphore);
}

#pragma mark - array model class配置
+ (void)mj_setupObjectClassInArray:(MJObjectClassInArray)objectClassInArray
{
[self mj_setupBlockReturnValue:objectClassInArray key:&MJObjectClassInArrayKey];

MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJ_UNLOCK(mje_signalSemaphore);
[self mj_removeCachedProperties];
}

#pragma mark - key配置
+ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName
{

+ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName {
[self mj_setupBlockReturnValue:replacedKeyFromPropertyName key:&MJReplacedKeyFromPropertyNameKey];

MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJ_UNLOCK(mje_signalSemaphore);
[self mj_removeCachedProperties];
}

+ (void)mj_setupReplacedKeyFromPropertyName121:(MJReplacedKeyFromPropertyName121)replacedKeyFromPropertyName121
{
objc_setAssociatedObject(self, &MJReplacedKeyFromPropertyName121Key, replacedKeyFromPropertyName121, OBJC_ASSOCIATION_COPY_NONATOMIC);

+ (void)mj_setupReplacedKeyFromPropertyName121:(MJReplacedKeyFromPropertyName121)replacedKeyFromPropertyName121 {
MJExtensionSemaphoreCreate
MJ_LOCK(mje_signalSemaphore);
objc_setAssociatedObject(self, &MJReplacedKeyFromPropertyName121Key, replacedKeyFromPropertyName121, OBJC_ASSOCIATION_COPY_NONATOMIC);

[[self mj_propertyDictForKey:&MJCachedPropertiesKey] removeAllObjects];
MJ_UNLOCK(mje_signalSemaphore);
}
Expand Down
4 changes: 4 additions & 0 deletions MJExtensionDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
01052EAD25F872D00049EC6F /* MultiThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01052EAC25F872D00049EC6F /* MultiThreadTests.swift */; };
0130EE80233C56D8008D2386 /* MJFrenchUser.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130EE7F233C56D8008D2386 /* MJFrenchUser.m */; };
0179886C24EFA460007F7FBC /* MJTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886B24EFA460007F7FBC /* MJTester.swift */; };
0179887024EFA58B007F7FBC /* SwiftModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */; };
Expand Down Expand Up @@ -87,6 +88,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
01052EAC25F872D00049EC6F /* MultiThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadTests.swift; sourceTree = "<group>"; };
0130EE7E233C56D8008D2386 /* MJFrenchUser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJFrenchUser.h; sourceTree = "<group>"; };
0130EE7F233C56D8008D2386 /* MJFrenchUser.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJFrenchUser.m; sourceTree = "<group>"; };
0179886A24EFA460007F7FBC /* MJExtensionTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MJExtensionTests-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -264,6 +266,7 @@
2D2DBA5F2317DBDF005A689E /* Model */,
2D2DBA572317DBB9005A689E /* MJExtensionTests.m */,
0179886F24EFA58B007F7FBC /* SwiftModelTests.swift */,
01052EAC25F872D00049EC6F /* MultiThreadTests.swift */,
2D2DBA592317DBB9005A689E /* Info.plist */,
2D2DBA872317DCCF005A689E /* PrefixHeader.pch */,
);
Expand Down Expand Up @@ -502,6 +505,7 @@
2D2DBA7F2317DBE0005A689E /* MJBaseObject.m in Sources */,
2D2DBA7D2317DBE0005A689E /* MJBook.m in Sources */,
2D2DBA7A2317DBE0005A689E /* MJDog.m in Sources */,
01052EAD25F872D00049EC6F /* MultiThreadTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
1 change: 1 addition & 0 deletions MJExtensionTests/MJExtensionTests-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
#import "MJExtension.h"

#import "MJUser.h"
#import "MJCat.h"
3 changes: 2 additions & 1 deletion MJExtensionTests/Model/MJCat.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ NS_ASSUME_NONNULL_BEGIN

@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, copy, nullable) NSArray<NSString *> *nicknames;
@property (nonatomic, copy, nullable) NSString *address;
@property (nonatomic, copy, nullable) NSString *address;
@property (nonatomic, copy, nullable) NSString *identifier;

@end

Expand Down
112 changes: 112 additions & 0 deletions MJExtensionTests/MultiThreadTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// MultiThreadTests.swift
// MJExtensionTests
//
// Created by Frank on 2021/3/10.
// Copyright © 2021 MJ Lee. All rights reserved.
//

import XCTest

class MultiThreadTests: XCTestCase {
private func testerJSON(_ id: Int) -> [String: Any] {
return
[
"isSpecialAgent": true,
"identifier": "\(id)",
"age": 22,
"name": "Juan"
]
}

private func catJSON(_ id: Int) -> [String: Any] {
return
[
"name": "Tom",
"identifier": "\(id)",
"nicknames" : [
"Jerry's Heart",
"Cowboy Tom",
],
]
}

func testMultiThread() throws {
let concurrentQueue = DispatchQueue.init(label: "MJExtension.MultiThread.UnitTests", qos: .default, attributes: [.concurrent], autoreleaseFrequency: .inherit)
let expectation4Tester = self.expectation(description: "Tester conversion successs")
let expectation4Cat = self.expectation(description: "Cat conversion successs")

for id in 0..<100 {
concurrentQueue.async {
sleep(1)
let testerDict = self.testerJSON(id)
guard let tester = MJTester.mj_object(withKeyValues: testerDict) else {
XCTAssert(false, "conversion failed")
return
}
print("tester: \(id)")
// XCTAssert(tester.isSpecialAgent)
// XCTAssert(tester.identifier == testerDict["identifier"] as? String)
// XCTAssert(tester.age == testerDict["age"] as! Int)
// XCTAssert(tester.name == testerDict["name"] as? String)

if id == 99 {
expectation4Tester.fulfill()
}
}

concurrentQueue.async {
sleep(1)
let catDict = self.catJSON(id)
guard let cat = MJCat.mj_object(withKeyValues: catDict) else {
XCTAssert(false, "convertion failed")
return
}
print("cat: \(id)")
// cat.nicknames?.forEach({ (nickname) in
// XCTAssert((catDict["nicknames"] as! [String]?)?.contains(nickname) ?? false)
// })
// XCTAssert(cat.identifier == catDict["identifier"] as? String)
// XCTAssert(cat.name == catDict["name"] as? String)

if id == 99 {
expectation4Cat.fulfill()
}
}

concurrentQueue.async {
sleep(1)
MJTester.mj_setupAllowedPropertyNames { () -> [Any]? in
["name", "identifier"]
}
print("change allowPropertyNames: (name, identifier) \(id)")
}

concurrentQueue.async {
sleep(1)
MJTester.mj_setupAllowedPropertyNames { () -> [Any]? in
["name"]
}
print("change allowPropertyNames: (name) \(id)")
}

concurrentQueue.async {
sleep(1)
MJTester.mj_setupAllowedPropertyNames { () -> [Any]? in
["isSpecialAgent", "age"]
}
print("change allowPropertyNames: (isSpecialAgent, age) \(id)")
}

concurrentQueue.async {
sleep(1)
MJUser.mj_setupAllowedPropertyNames { () -> [Any]? in
["name", "nicknames"]
}
print("change allowPropertyNames: (name, nicknames) \(id)")
}
}

waitForExpectations(timeout: 15, handler: nil)
}
}
4 changes: 2 additions & 2 deletions MJExtensionTests/SwiftModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class SwiftModelTests: XCTestCase {
]

guard let tester = MJTester.mj_object(withKeyValues: testerDict) else {
XCTAssert(false, "convertion failed")
XCTAssert(false, "conversion failed")
return
}
XCTAssert(tester.isSpecialAgent)
Expand All @@ -38,7 +38,7 @@ class SwiftModelTests: XCTestCase {
]

guard let user = MJUser.mj_object(withKeyValues: userDict) else {
XCTAssert(false, "convertion failed")
XCTAssert(false, "conversion failed")
return
}
XCTAssert(user.rich)
Expand Down

0 comments on commit 7b635bb

Please sign in to comment.