Câu chuyện dài ngắn, tôi mệt mỏi với các quy tắc đồng thời vô lý liên quan đến NSManagedObjectContext
(hoặc đúng hơn, thiếu hoàn toàn hỗ trợ đồng thời và xu hướng phát nổ hoặc làm những việc không chính xác khác nếu bạn cố gắng chia sẻ một số NSManagedObjectContext
trên toàn bộ chủ đề) và đang cố gắng triển khai một biến thể an toàn chủ đề.Làm cho dữ liệu cốt lõi an toàn chủ đề
Về cơ bản những gì tôi đã làm được tạo ra một phân lớp theo dõi chuỗi mà nó được tạo ra, và sau đó ánh xạ tất cả các lời gọi phương thức trở lại chủ đề đó. Cơ chế để làm điều này là hơi phức tạp, nhưng mấu chốt của nó là tôi đã có một số phương pháp helper như:
- (NSInvocation*) invocationWithSelector:(SEL)selector {
//creates an NSInvocation for the given selector
NSMethodSignature* sig = [self methodSignatureForSelector:selector];
NSInvocation* call = [NSInvocation invocationWithMethodSignature:sig];
[call retainArguments];
call.target = self;
call.selector = selector;
return call;
}
- (void) runInvocationOnContextThread:(NSInvocation*)invocation {
//performs an NSInvocation on the thread associated with this context
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
[self performSelector:@selector(runInvocationOnContextThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
- (id) runInvocationReturningObject:(NSInvocation*) call {
//returns object types only
[self runInvocationOnContextThread:call];
//now grab the return value
__unsafe_unretained id result = nil;
[call getReturnValue:&result];
return result;
}
... và sau đó là lớp con thực hiện các giao diện NSManagedContext
sau một mô hình như:
- (NSArray*) executeFetchRequest:(NSFetchRequest *)request error:(NSError *__autoreleasing *)error {
//if we're on the context thread, we can directly call the superclass
if ([NSThread currentThread] == myThread) {
return [super executeFetchRequest:request error:error];
}
//if we get here, we need to remap the invocation back to the context thread
@synchronized(self) {
//execute the call on the correct thread for this context
NSInvocation* call = [self invocationWithSelector:@selector(executeFetchRequest:error:) andArg:request];
[call setArgument:&error atIndex:3];
return [self runInvocationReturningObject:call];
}
}
... và sau đó tôi thử nghiệm nó với một số mã mà đi như:
- (void) testContext:(NSManagedObjectContext*) context {
while (true) {
if (arc4random() % 2 == 0) {
//insert
MyEntity* obj = [NSEntityDescription insertNewObjectForEntityForName:@"MyEntity" inManagedObjectContext:context];
obj.someNumber = [NSNumber numberWithDouble:1.0];
obj.anotherNumber = [NSNumber numberWithDouble:1.0];
obj.aString = [NSString stringWithFormat:@"%d", arc4random()];
[context refreshObject:obj mergeChanges:YES];
[context save:nil];
}
else {
//delete
NSArray* others = [context fetchObjectsForEntityName:@"MyEntity"];
if ([others lastObject]) {
MyEntity* target = [others lastObject];
[context deleteObject:target];
[context save:nil];
}
}
[NSThread sleepForTimeInterval:0.1];
}
}
Vì vậy, về cơ bản, tôi quay lên một số đề nhắm mục tiêu các điểm nhập cảnh trên, và họ chạy domly tạo và xóa các thực thể. Điều này gần như hoạt động theo cách nó nên.
Vấn đề là mỗi thường một trong các chủ đề sẽ nhận được EXC_BAD_ACCESS
khi gọi obj.<field> = <value>;
. Nó không rõ ràng với tôi vấn đề là gì, bởi vì nếu tôi in obj
trong trình gỡ lỗi, mọi thứ đều tốt. Bất kỳ đề xuất nào về vấn đề có thể là gì (khác với thực tế là Apple khuyến nghị chống lại phân lớp NSManagedObjectContext) và cách khắc phục sự cố?
P.S. Tôi biết về GCD và NSOperationQueue
và các kỹ thuật khác thường được sử dụng để "giải quyết" sự cố này. Không ai trong số những người cung cấp những gì tôi muốn. Những gì tôi đang tìm kiếm là NSManagedObjectContext
có thể được tự do, an toàn và được sử dụng trực tiếp bởi bất kỳ số luồng nào để xem và thay đổi trạng thái ứng dụng mà không yêu cầu bất kỳ đồng bộ hóa bên ngoài nào.
Có phải vấn đề mà bạn đang thao tác các thuộc tính trên một luồng khác với ngữ cảnh và do đó có thể đồng thời với các thao tác khác trên ngữ cảnh đó, bao gồm lưu và xóa? Bạn có thể thử ghi đè setSomeNumber, setAnotherNumber, setAString để chạy trên chuỗi ngữ cảnh và xem liệu điều đó có ảnh hưởng đến kết quả của bạn hay không. – paulmelnikow
Có, dường như đã ổn định nó. Vì vậy, bây giờ câu hỏi là, làm thế nào để tôi tạo ra một lớp con 'NSManagedObject' tự động tiêm triển khai setter thuộc tính thread-safe? – aroth
Tôi nhận được công cụ tiêm setter hoạt động. Nó thậm chí còn phức tạp hơn những thay đổi 'NSManagedObjectContext'. Nhưng điều quan trọng là nó hoạt động. Nếu có ai quan tâm, tôi sẽ chia sẻ phần mã tương ứng. – aroth