Reputation: 5287
I have UITextView logView
(tried both atomic
and nonatomic
) and NSOperationQueue uploadQueue
properties in my UIView subclass.
I've added KVO on my NSOperationQueue
property's operationsCount
like this:
[[self uploadQueue] addObserver:self
forKeyPath:@"operationCount"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
Observer function looks like this:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([object isKindOfClass: [NSOperationQueue class]]) {
NSLog(@"Keypath is: %@ change dictionary is: %@", keyPath, change);
NSInteger testnew = [[change objectForKey: @"new"] integerValue];
NSInteger testold = [[change objectForKey: @"old"] integerValue];
if (testnew > testold) {
[[self logView] setText: [NSString stringWithFormat: @"Uploading %d files", testnew]];
objc_setAssociatedObject([self logView], @"max_value_of_uploads", [change objectForKey: @"new"], OBJC_ASSOCIATION_COPY);
} else {
NSInteger value = [objc_getAssociatedObject([self logView], @"max_value_of_uploads") integerValue];
[[self logView] setText: [NSString stringWithFormat: @"Uploaded %d of %d files", testnew, value]];
}
}
}
uploadQueue
filling works like that:
...
NSDictionary *iter;
NSEnumerator *enumerator = [objects objectEnumerator];
while (iter = [enumerator nextObject]) {
Uploader *op = [[Uploader alloc] initWithFileName: [iter objectForKey: @"fileName"]
FileID: [[iter objectForKey: @"fileID"] integerValue]
AndSessionID: [self sess]];
//Uploader *op = [[Uploader alloc] init];
[[self uploadQueue] addOperation: op];
...
Without if-else
block, everything works just fine: I've got NSLog messages about number of operations in queue, numbers are ok and so on.
But with that if-else
block, I get crash, which looks like this:
bool _WebTryThreadLock(bool), 0x10617fb0: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
1 WebThreadLock
2 -[UITextView setText:]
3 -[SincViewController observeValueForKeyPath:ofObject:change:context:]
4 NSKeyValueNotifyObserver
5 NSKeyValueDidChange
6 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
7 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:usingBlock:]
8 ____NSOQDelayedFinishOperations_block_invoke_0
9 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
10 -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:usingBlock:]
11 __NSOQDelayedFinishOperations
12 _dispatch_after_timer_callback
13 _dispatch_source_invoke
14 _dispatch_queue_invoke
15 _dispatch_worker_thread2
16 _pthread_wqthread
17 start_wqthread
It crashes in else
block.
Moreover, I don't see any changes to my logView.
Thanks for future help and responses.
UPDATE: performSelectorOnMainThread
when setting text saved me.
Upvotes: 3
Views: 2634
Reputation: 8520
When updating your UI, make sure that you're in the main thread. A possible solution follows:
dispatch_async(dispatch_get_main_queue(), ^{
if (testnew > testold) {
[[self logView] setText: [NSString stringWithFormat: @"Uploading %d files", testnew]];
objc_setAssociatedObject([self logView], @"max_value_of_uploads", [change objectForKey: @"new"], OBJC_ASSOCIATION_COPY);
} else {
NSInteger value = [objc_getAssociatedObject([self logView], @"max_value_of_uploads") integerValue];
[[self logView] setText: [NSString stringWithFormat: @"Uploaded %d of %d files", testnew, value]];
}
});
Note that using dispatch_async
or dispatch_sync
depends on your desired implementation, but in the latter case, you risk deadlocking unless you check what thread you're in.
See also: NSOperation, observer and thread error where someone suggests moving your UI code to a separate method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self performSelectorOnMainThread:@selector(dataLoadingFinished:) withObject:nil waitUntilDone:YES];
}
.. which is a fine alternate route.
Upvotes: 4
Reputation: 10633
I would suggest you use the delegate pattern here. You seem to be mixing very advanced techniques with some loose code, which can lead to problems very quickly. I don't think you need the associative object for example.
Your error occurs because you are accessing UI from a background thread by the way. You should wrap you UI calls (setText:) inside performSelectorOnMainThread:withObject:waitUntilDone: or a block.
Upvotes: 1