Objective-C中primefaces/非primefaces的证据

在阅读Apple的文档之后 ,我试图在Objective-C中certificate属性的primefaces性或非primefaces性。 要做到这一点,我创build了一个具有名字和姓氏的人。

Person.h

@interface Person : NSObject @property (nonatomic, strong) NSString *firstName; @property (nonatomic, strong) NSString *lastName; - (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln; @end 

Person.m

 @implementation Person - (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln { if (self = [super init]) { self.firstName = fn; self.lastName = ln; } return self; } - (NSString *)description { return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; } @end 

在另一个类中,这里是我的AppDelegate,我有一个非primefaces属性,它是Person的一个实例。

 @property (strong, nonatomic) Person *p; 

在实现文件中,我创build了三个并发队列。 在第一个队列中,我读取了属性,在另外两个队列中我写入了不同的值。

据我所知,我可以在我的日志中输出Bob Frost或者Jack Sponge ,因为我宣布我的财产是非primefaces的 。 但是那没有发生。 我不明白为什么。 我错过了什么或误解了什么?

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. Person *bob = [[Person alloc] initWithFirstName:@"Bob" lastName:@"Sponge"]; Person *jack = [[Person alloc] initWithFirstName:@"Jack" lastName:@"Frost"]; self.p = bob; dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue1, ^{ while (YES) { NSLog(@"%@", self.p); } }); dispatch_async(queue2, ^{ while (YES) { self.p = bob; } }); dispatch_async(queue3, ^{ while (YES) { self.p = jack; } }); return YES; } 

具有非primefaces属性使得部分写入成为可能,但决不是肯定的。

在你的Person类中,设置名字和姓氏的唯一方法是在init方法中,然后在之后立即设置名字和姓氏。 设置名字和姓氏将发生非常接近的彼此,很less有机会让另一个线程在操作之间搞砸了。

此外,您在运行并发操作之前,在主线程中创buildPerson对象。 在当前代码运行时,对象已经存在,并且不再更改它们的名称值,所以没有竞争条件或部分写入名称值的机会。 你只是简单地改变两个对象之间的self.p,一旦创build就不会改变。

也就是说,什么是不可预测的关于你的代码是什么人对象将在任何时候self.p。 您应该看到鲍勃海绵和杰克弗罗斯特之间交替显示的值是不可预测的。

一个更好的testing会是这样的:

(假设每个TestObject的x1和x2值应始终保持相同。)

 @interface TestObject : NSObject @property (nonatomic, assign) int x1; @property (nonatomic, assign) int x2; @end @interface AppDelegate @property (nonatomic, strong) TestObject *thing1; @property (nonatomic, strong) TestObject *thing2; @property (nonatomic, strong) NSTimer *aTimer; @property (nonatomic, strong) NSTimer *secondTimer; @end 

然后这样的代码:

 #include <stdlib.h> - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); self.thing1 = [[TestObject alloc] init]; self.thing2 = [[TestObject alloc] init]; dispatch_async(queue1, ^ { for (int x = 0; x < 100; x++) { usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds int thing1Val = arc4random_uniform(10000); int thing2Val = arc4random_uniform(10000); _thing1.x1 = thing1Val; usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x1 = thing2Val; _thing1.x2 = thing1Val; //thing1's x1 and x2 should now match usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match } }); //Do the same thing on queue2 dispatch_async(queue2, ^ { for (int x = 0; x < 100; x++) { usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds int thing1Val = arc4random_uniform(10000); int thing2Val = arc4random_uniform(10000); _thing1.x1 = thing1Val; usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x1 = thing2Val; _thing1.x2 = thing1Val; //thing1's x1 and x2 should now match usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds _thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match } }); //Log the values in thing1 and thing2 every .1 second self.aTimer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(logThings:) userInfo:nil repeats:YES]; //After 5 seconds, kill the timer. self.secondTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(stopRepeatingTimer:) userInfo:nil repeats:NO]; return YES; } - (void)stopRepeatingTimer:(NSTimer *)timer { [self.aTimer invalidate]; } - (void)logThings:(NSTimer *)timer { NSString *equalString; if (_thing1.x1 == _thing1.x2) { equalString = @"equal"; } else { equalString = @"not equal"; } NSLog(@"%@ : thing1.x1 = %d, thing1.x2 = %d", equalString, _thing1.x1, _thing1.x2); if (_thing2.x1 == _thing2.x2) { equalString = @"equal"; } else { equalString = @"not equal"; } NSLog(@"%@ : thing2.x1 = %d, thing2.x2 = %d", equalString, _thing2.x1, _thing2.x2); } 

在上面的代码中,每个队列都会创build一系列随机值,并将一对对象的x1和x2属性设置为重复循环中的这些随机值。 它会延迟设置每个对象的x1和x2属性之间的一个小的随机间隔。 这个延迟模拟一个后台任务需要花费一定的时间来完成应该是primefaces的工作。 它还引入了一个窗口,其中另一个线程可以在当前线程能够设置第二个值之前更改第二个值。

如果你运行上面的代码,你几乎肯定会发现thing1和thing2的x1和x2值有时是不同的。

上面的代码不会被primefaces属性所帮助。 您需要在设置每个对象的x1和x2属性(可能使用@synchronized指令)之间断言某种locking。

(注意,我在论坛编辑器中把上面的代码一起敲了出来,我没有尝试编译它,更不用说debugging它了,无疑有一些错别字)。

(注2,编辑我的代码的人:代码格式化是一个风格和个人品味的问题,我使用“Allman indentation”的变体。我很欣赏错别字的修正,但是我鄙视K&R式的缩进。 你的风格对我的代码。

作为atomic的属性意味着读取所执行的所有操作以及写入所执行的所有操作都是以primefaces方式完成的。 (这与两个独立属性之间的一致性是完全无关的,就像在你的例子中一样,不能简单地通过添加(atomic)来实现(atomic)

这在两种情况下尤其重要:

  1. 对于对象指针,隐式[_property release]; [newValue retain]; _property = newValue [_property release]; [newValue retain]; _property = newValue [_property release]; [newValue retain]; _property = newValue ARC在您存储新值时执行的[_property release]; [newValue retain]; _property = newValue操作,隐式value = _property; [value retain]; value = _property; [value retain]; 当您加载该值时会发生这种情况。

  2. 无论保留/释放语义如何,其实际值无法以primefaces方式加载/存储的大型数据types。

这是一个例子,说明了两个潜在的问题:

 typedef struct { NSUInteger x; NSUInteger xSquared; // cached value of x*x } Data; @interface Producer : NSObject @property (nonatomic) Data latestData; @property (nonatomic) NSObject *latestObject; @end @implementation Producer - (void)startProducing { // Produce new Data structs. dispatch_async(dispatch_get_global_queue(0, 0), ^{ for (NSUInteger x = 0; x < NSUIntegerMax; x++) { Data newData; newData.x = x; newData.xSquared = x * x; // Since the Data struct is too large for a single store, // the setter actually updates the two fields separately. self.latestData = newData; } }); // Produce new objects. dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (true) { // Release the previous value; retain the new value. self.latestObject = [NSObject new]; } }); [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(logStatus) userInfo:nil repeats:YES]; } - (void)logStatus { // Implicitly retain the current object for our own uses. NSObject *o = self.latestObject; NSLog(@"Latest object: %@", o); // Validate the consistency of the data. Data latest = self.latestData; NSAssert(latest.x * latest.x == latest.xSquared, @"WRONG: %lu^2 != %lu", latest.x, latest.xSquared); NSLog(@"Latest data: %lu^2 = %lu", latest.x, latest.xSquared); } @end int main(int argc, const char * argv[]) { @autoreleasepool { [[Producer new] startProducing]; [[NSRunLoop mainRunLoop] run]; } return 0; } 

使用nonatomic ,对于对象属性,您偶尔会得到EXC_BAD_ACCESS崩溃,并logging如下消息:

AtomicTest [2172:57275]最新的对象:<NSObject:0x100c04a00>
objc [2172]:NSObject对象0x100c04a00已经释放时已经过度释放; 打开objc_overrelease_during_dealloc_error进行debugging

而对于数据结构,断言有时会失败:

AtomicTest [2240:59304] ***在 – [Producer logStatus],main.m:58中断言失败
AtomicTest [2240:59304] ***终止应用程序由于未捕获的exception“NSInternalInconsistencyException”,原因:'错误:55937112 ^ 2!= 3128960610774769'

(请注意, xSquared的值, xSquared实际上是55937113 2而不是55937112 2。

使属性(atomic)而不是(nonatomic)避免了这两个问题,代价是稍微慢一点的执行。


注意:即使在Swift中也会出现同样的问题,因为没有primefaces属性的概念:

 class Object { } var obj = Object() dispatch_async(dispatch_get_global_queue(0, 0)) { while true { obj = Object() } } while true { // This sometimes crashes, and sometimes deadlocks let o = obj print("Current object: \(o)") } 

据我所知,我可以在我的日志中输出Bob Frost或者Jack Sponge,因为我宣布我的财产是非primefaces的。 但是那没有发生。 我不明白为什么。 我错过了什么或误解了什么?

如果引发竞争条件,这不会发生什么。 几乎肯定会发生的是,你会崩溃,或者你会得到真正令人惊讶的东西。

primefaces意味着你将始终得到一个一致的价值,我的意思是“你实际投入的价值”。 没有primefaces,有可能得到一个价值,这不是任何线程写的。 考虑这个程序,它必须针对32位体系结构进行编译(这也意味着ARC必须被禁用,并且您需要声明您的ivars才能使其在Mac上运行;或者您可以在32位iPhone上进行testing) 。

 // clang -arch i386 -framework Foundation atomic.m -o atomic ; ./atomic #import <Foundation/Foundation.h> @interface MyObject : NSObject { long long i; } @property (nonatomic) long long i; @end @implementation MyObject @synthesize i; @end int main(int argc, const char * argv[]) { @autoreleasepool { dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT); MyObject *obj = [MyObject new]; long long value1 = 0; long long value2 = LLONG_MAX; dispatch_async(queue2, ^{ while (YES) { obj.i = value1; } }); dispatch_async(queue3, ^{ while (YES) { obj.i = value2; } }); while (YES) { long long snapshot = obj.i; if (snapshot != value1 && snapshot != value2) { printf("***PANIC*** Got %lld (not %lld or %lld)\n", snapshot, value1, value2); } } } return 0; } 

如果你运行这个几秒钟,你会得到很多消息,如:

 ***PANIC*** Got 4294967295 (not 0 or 9223372036854775807) ***PANIC*** Got 9223372032559808512 (not 0 or 9223372036854775807) 

你会注意到4294967295和9223372032559808512都不出现在程序的任何地方。 他们如何显示在输出中? 因为我正在使用32位代码编写一个64位数字。 没有一个机器指令可以同时写入所有的64位数据。 上半部分将写入,然后是另一半。 如果另一个队列正在同时写入,则可以从一个写入结束最高的32位,而另一个写入最低的32位。 atomic通过locking内存直到写入所有单词来防止这种情况。

对象可能会发生不同的问题。 在ARC之前,这个问题尤其严重,但仍然可以发生。 考虑以下非常常见的ObjC-1代码(即在属性之前):

 @interface MyObject : NSObject { id _something; } - (id)something; - (void)setSomething:(id)newSomething; @end @implementation MyObject - (id)something { return _something; } - (void)setSomething:(id)newSomething { [newSomething retain]; [_something release]; _something = newSomething; } @end 

这是编写访问器的一种非常常见的方式。 在设置过程中处理保留新/释放旧。 get过程中返回指针。 这基本上是今天nonatomic的实现。 问题是内存pipe理不是线程安全的。 考虑如果你在一个线程上调用了[_something release] ,然后在另一个线程上调用getter。 你会得到_something的旧值,它已经被释放,并且可能已经被释放。 所以你可能在看无效的记忆,你会崩溃。

一个常见的解决scheme是保留/自动释放获取器:

 - (id)something { return [[_something retain] autorelease]; } 

这就确定了至less在目前的自动释放池(如果你想超出这个范围的话,那是你的责任就是保留它)。 这比微不足道的getter慢很多。 atomic也可以解决这个问题,确保在设置过程中没有人抓到。

尽pipe如此,在less数情况下,这可能是有价值的,几乎总是如果你在多个队列中访问数据, atomic是不够的,反正慢(至less它曾经是;我还没有近期版本,因为我从来没有使用atomic )。 如果你想要的只是单一财产primefaces, GCD访问者通常更好。 如果您需要完全primefaces事务(您经常这样做),那么GCD访问器也可以很容易地进行调整。

可能最好的讨论是bbum的博客文章: http ://www.friday.com/bbum/2008/01/13/objectivce-c-atomic-properties-threading-andor-custom-settergetter/。 简单的答案是, atomic是非常有用的。 如果你认为你需要atomic ,你通常需要的不仅仅是给你的东西,而且通常可以使用GCD访问器来获得更便宜的东西。

atomic作为默认设置是苹果在ObjC2中犯下的重大失误之一。