The code below will crash inside of NSKVOUnionSetAndNotify calling CFDictionaryGetValue with what appears to be a bogus dictionary.
It seems to be a race between the swizzled addFoos / NSKVOUnionSetAndNotify code and the act of adding and removing KVO observers.
#import <Foundation/Foundation.h>
#interface TestObject : NSObject
#property (readonly) NSSet *foos;
#end
#implementation TestObject {
NSMutableSet *_internalFoos;
dispatch_queue_t queue;
BOOL observed;
}
- (id)init {
self = [super init];
_internalFoos = [NSMutableSet set];
queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
return self;
}
- (void)start {
// Start a bunch of work hitting the unordered collection mutator
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (YES) {
#autoreleasepool {
[self addFoos:[NSSet setWithObject:#(rand() % 100)]];
}
}
});
}
// Start work that will constantly observe and unobserve the unordered collection
[self observe];
}
- (void)observe {
dispatch_async(dispatch_get_main_queue(), ^{
observed = YES;
[self addObserver:self forKeyPath:#"foos" options:0 context:NULL];
});
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
dispatch_async(dispatch_get_main_queue(), ^{
if (observed) {
observed = NO;
[self removeObserver:self forKeyPath:#"foos"];
[self observe];
}
});
}
// Public unordered collection property
- (NSSet *)foos {
__block NSSet *result;
dispatch_sync(queue, ^{
result = [_internalFoos copy];
});
return result;
}
// KVO compliant mutators for unordered collection
- (void)addFoos:(NSSet *)objects {
dispatch_barrier_sync(queue, ^{
[_internalFoos unionSet:objects];
});
}
- (void)removeFoos:(NSSet *)objects {
dispatch_barrier_sync(queue, ^{
[_internalFoos minusSet:objects];
});
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
TestObject *t = [[TestObject alloc] init];
[t start];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000, false);
}
return 0;
}
The actual crash you get is an EXC_BAD_ACCESS when the key value observing dictionary is accesed. The stack trace is as follows:
* thread #2: tid = 0x1ade39, 0x00007fff92f8e097 libobjc.A.dylib`objc_msgSend + 23, queue = 'com.apple.root.default-priority', stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
frame #0: 0x00007fff92f8e097 libobjc.A.dylib`objc_msgSend + 23
frame #1: 0x00007fff8ffe2b11 CoreFoundation`CFDictionaryGetValue + 145
frame #2: 0x00007fff8dc55750 Foundation`NSKVOUnionSetAndNotify + 147
* frame #3: 0x0000000100000f85 TestApp`__19-[TestObject start]_block_invoke(.block_descriptor=<unavailable>) + 165 at main.m:34
frame #4: 0x000000010001832d libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #5: 0x0000000100014925 libdispatch.dylib`_dispatch_client_callout + 8
frame #6: 0x0000000100016c3d libdispatch.dylib`_dispatch_root_queue_drain + 601
frame #7: 0x00000001000182e6 libdispatch.dylib`_dispatch_worker_thread2 + 52
frame #8: 0x00007fff9291eef8 libsystem_pthread.dylib`_pthread_wqthread + 314
frame #9: 0x00007fff92921fb9 libsystem_pthread.dylib`start_wqthread + 13
If you set a symbolic breakpoint with the symbol NSKVOUnionSetAndNotify the debugger will stop where this method is being invoked.
The crash you are seeing is because automatic key-value notifications are being sent from one thread when you invoke your [addFoos:] method, but then the change dictionary is being accessed from another thread. This is stimulated by your use of the global dispatch queue when calling this method, as that will execute the block in many different threads.
There are mulitple ways to fix this crash, and I will try to walk you through this to give you a more thourough understanding of what is going on.
In the simplest case, you can fix the crash by using the key-value coding mutable proxy object for this key:
NSMutableSet *someSet = [self mutableSetValueForKey:#"foos"];
[someSet unionSet:[NSSet setWithObject:#(rand() % 100)]];
That will stop this particular crash. What's happening here? When mutableSetValueForKey: is called, the result is a proxy object that forwards messages to your KVC-compliant accessor methods for the key "foos". The author's object does not actually fully conform to the required pattern for a KVC compliant property of this type. If other KVC accessor methods are messaged for this key, they may go through non-thread safe accessors provided by Foundation, which can result in this crash all over again. We'll get to how to fix that in a moment.
The crash is being triggered by automatic KVO change notifications crossing threads. Automatic KVO notifications work by swizzling classes and methods at runtime. You can read a more in-depth explanation here and here. KVC accessor methods are essentially wrapped at runtime with KVO-supplied methods. This is in fact where the crash in the original application is happening. This is the KVO inserted code disassembled from Foundation:
int _NSKVOUnionSetAndNotify(int arg0, int arg1, int arg2) {
r4 = object_getIndexedIvars(object_getClass(arg0));
OSSpinLockLock(_NSKVONotifyingInfoPropertyKeysSpinLock);
r6 = CFDictionaryGetValue(*(r4 + 0xc), arg1);
OSSpinLockUnlock(_NSKVONotifyingInfoPropertyKeysSpinLock);
var_0 = arg2;
[arg0 willChangeValueForKey:r6 withSetMutation:0x1 usingObjects:STK-1];
r0 = *r4;
r0 = class_getInstanceMethod(r0, arg1);
method_invoke(arg0, r0);
var_0 = arg2;
r0 = [arg0 didChangeValueForKey:r6 withSetMutation:0x1 usingObjects:STK-1];
Pop();
Pop();
Pop();
return r0;
}
As you can see, this is wrapping a KVC compliant accessor method with willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey: withSetMutation:usingObjects:. These are the methods that send out KVO notifications. KVO will insert this wrapper at runtime if the object has opted into automatic key value observer notification. In between these calls you can see class_getInstanceMethod. This is getting a reference to the KVC compliant accessor being wrapped, and then calling it. In the case of the original code, this is being triggered from inside NSSet's unionSet:, which was happening across threads and causing the crash when it accessed the change dictionary.
Automatic notifications are sent by the thread where the change occured, and are intended to be received on the same thread. This being Teh IntarWebs, there is a lot of bad or misleading information out there about KVO. Not all objects and not all properties emit automatic KVO notifications, and in your classes you can control which do and don't. From the Key Value Observing Programming Guide: Automatic Change Notification :
NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods. Automatic notification is also supported by the collection proxy objects returned by, for example, mutableArrayValueForKey:
This may lead one to believe that all descendants of NSObject emit automatic notifications by default. This is not the case - may framework classes do not, or implement special behavior. Core Data is an example. From the Core Data Programming Guide :
NSManagedObject disables automatic key-value observing (KVO) change notifications for modeled properties, and the primitive accessor methods do not invoke the access and change notification methods. For unmodeled properties, on OS X v10.4 Core Data also disables automatic KVO; on OS X v10.5 and later, Core Data adopts to NSObject’s behavior.
As a developer, you can ensure that automatic key value observer notifications are on or off for a particular property by implementing a method with the correct naming convention, +automaticallyNotifiesObserversOf<Key>. When this method returns NO, automatic key value notifications are not emitted for this property. When automatic change notifications are disabled KVO also does not have to swizzle the accessor method at runtime, as this is done primarily to support automatic change notifications. For example:
+ (BOOL) automaticallyNotifiesObserversOfFoos {
return NO;
}
In a comment the author stated that the reason he was using dispatch_barrier_sync for his accessor methods is that if he did not, KVO notifications would arrive before changes occured. With automatic notifications disabled for a property, you still have the option of sending these notifications manually. This is done by using the methods willChangeValueForKey: and didChangeValueForKey:. Not only does this give you control of when these notifications are sent (if at all), but on what thread. Automatic change notifications, as you recall, are sent from and received on the thread where the change occured.
For example, if you wanted change notifications to happen only on the main queue, you could do so using recursive decomposition:
- (void)addFoos:(NSSet *)objects {
dispatch_async(dispatch_get_main_queue(), ^{
[self willChangeValueForKey:#"foos"];
dispatch_barrier_sync(queue, ^{
[_internalFoos unionSet:objects];
dispatch_async(dispatch_get_main_queue(), ^{
[self didChangeValueForKey:#"foos"];
});
});
});
}
The original class in the author's question was forcing KVO observation to start and stop on the main queue, which seems have been an attempt to emit notifications on the main queue. The above example demonstrates a solution that not only addresses that concern, but ensures that the KVO notifications are correctly sent before and after the data changes.
In the example above I modified the author's original method as an illustrative example - this class is still not correctly KVC compliant for the key "foos". To be Key-Value Observing compliant, an object must first be Key-Value Coding compliant. To address this, first create the correct Key-value coding compliant accessors for an unordered mutable collection :
Immutable:
countOfFoos
enumeratorOfFoos
memberOfFoos:
Mutable:
addFoosObject:
removeFoosObject:
These are just the minimum, there are additional methods that can be implemented for performance or data integrity reasons.
The original application was using a concurrent queue and dispatch_barrier_sync. This was dangerous, for many reasons. The approach recommended by the Concurrency Programming Guide is to instead use a serial queue. This ensures that only one thing can be touching the protected resource at a time, and it is from a consistent context. For example, two of the above methods would look like this:
- (NSUInteger)countOfFoos {
__block NSUInteger result = 0;
dispatch_sync([self serialQueue], ^{
result = [[self internalFoos] count];
});
return result;
}
- (void) addFoosObject:(id)object {
id addedObject = [object copy];
dispatch_async([self serialQueue], ^{
[[self internalFoos] addObject:addedObject];
});
}
Note that in this example and the next, I am not including manual KVO change notifications for brevity and clarity. If you want manual change notifications to be sent, that code should be added to these methods like what you saw in the previous example.
Unlike using dispatch_barrier_sync with a concurrent queue, this will not allow a deadlock.
The WWDC 2011 Session 210 Mastering Grand Central Dispatch showed the correct use of the dispatch barrier API for implementing a reader/writer lock for a collection using a concurrent queue. This would be implemented like this:
- (id) memberOfFoos:(id)object {
__block id result = nil;
dispatch_sync([self concurrentQueue], ^{
result = [[self internalFoos] member:object];
});
return result;
}
- (void) addFoosObject:(id)object {
id addedObject = [object copy];
dispatch_barrier_async([self concurrentQueue], ^{
[[self internalFoos] addObject:addedObject];
});
}
Note that the dispatch barrier is accessed asynchronously for the write operation, while the read operation uses dispatch_sync. The original application used dispatch_barrier_sync for both reads and writes, which the author stated was done to control when automatic change notifications were sent. Using manual change notifications would address that concern (again, not shown in this example for brevity and clarity).
There are still issues with the KVO implementation in the original. It does not use the context pointer to determine ownership of an observation. This is a recommended practice, and can use a pointer to self as a value. The value should have the same address as the objected used to add and remove the observer:
[self addObserver:self forKeyPath:#"foos" options:NSKeyValueObservingOptionNew context:(void *)self];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (__bridge void *)self){
// check the key path, etc.
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
From the NSKeyValueObserving.h header:
You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong.
If you are interested in a further understanding of applying and implementing Key Value Observing, I suggest the video KVO Considered Awesome
In summary:
• Implement the required key-value coding accessor pattern (unordered mutable collection)
• Make those accessors thread safe (using a serial queue with dispatch_sync/dispatch_async, or a concurrent queue with dispatch_sync/dispatch_barrier_async)
• Decide wether you want automatic KVO notifications or not, implement automaticallyNotifiesObserversOfFoos accordingly
• Add manual change notifications appropriately to accessor methods
• Make sure that code which accesses your property does so through the correct KVC accessor methods (i.e. mutableSetValueForKey:)
Related
I'm trying to build an array of dictionaries in a background thread while keeping access to the current array until the background operation is done. Here's a simplified version of my code:
#property (nonatomic, strong) NSMutableArray *data;
#property (nonatomic, strong) NSMutableArray *dataInProgress;
- (void)loadData {
self.dataInProgress = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
[self loadDataWorker];
});
}
- (void)loadDataWorker {
for (int i=0; i<10000; i++) {
[self addDataItem];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self loadDataFinish]; // the crash occurs before we get to this point
});
}
- (void)addDataItem {
// first check some previously added data
int currentCount = (int)[self.dataInProgress count];
if (currentCount > 0) {
NSDictionary *lastItem = [self.dataInProgress objectAtIndex:(currentCount - 1)];
NSDictionary *checkValue = [lastItem objectForKey:#"key3"]; // this line crashes with EXC_BAD_ACCESS
}
// then add another item
NSDictionary *dictionaryValue = [NSDictionary dictionaryWithObjectsAndKeys:#"bar", #"foo", nil];
NSDictionary *item = [NSDictionary dictionaryWithObjectsAndKeys:#"value1", #"key1", #"value2", #"key2", dictionaryValue, #"key3", nil];
// as described in UPDATE, I think this is the problem
dispatch_async(dispatch_get_main_queue(), ^{
[dictionaryValue setObject:[self makeCustomView] forKey:#"customView"];
});
[self.dataInProgress addObject:item];
}
- (UIView *)makeCustomView {
return [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
}
- (void)loadDataFinish {
self.data = [NSMutableArray arrayWithArray:self.dataInProgress];
}
This works fine in most cases, but when the dataset is large, I start to get crashes on the line indicated above. The likelihood of a crash is greater with more data or a device with less memory. On an iPhone 6 with 10,000 items, it happens about one in five times. So it looks like when memory gets tight, the dictionaries inside the data array are destroyed before I access them.
If I do everything in the main thread there are no crashes. I originally had this problem with non-ARC code, then I converted my project to ARC and the same problem remains.
Is there a way to ensure the objects added earlier in the build process are retained until I'm done? Or is there a better way to do what I'm doing?
Here's a stack trace:
thread #17: tid = 0x9c586, 0x00000001802d1b90 libobjc.A.dylib`objc_msgSend + 16, queue = 'com.apple.root.background-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x10)
frame #0: 0x00000001802d1b90 libobjc.A.dylib`objc_msgSend + 16
frame #1: 0x0000000180b42384 CoreFoundation`-[__NSDictionaryM objectForKey:] + 148
frame #2: 0x00000001002edd58 MyApp`-[Table addDataItem](self=0x000000014fd44600, _cmd="addDataItem", id=0x00000001527650d0, section=3, cellData=0x0000000152765050) + 1232 at Table.m:392
frame #4: 0x00000001002eca28 MyApp`__25-[Table loadData]_block_invoke(.block_descriptor=0x000000015229efd0) + 52 at Table.m:265
frame #5: 0x0000000100705a7c libdispatch.dylib`_dispatch_call_block_and_release + 24
frame #6: 0x0000000100705a3c libdispatch.dylib`_dispatch_client_callout + 16
frame #7: 0x0000000100714c9c libdispatch.dylib`_dispatch_root_queue_drain + 2344
frame #8: 0x0000000100714364 libdispatch.dylib`_dispatch_worker_thread3 + 132
frame #9: 0x00000001808bd470 libsystem_pthread.dylib`_pthread_wqthread + 1092
frame #10: 0x00000001808bd020 libsystem_pthread.dylib`start_wqthread + 4
UPDATE
I traced through my full code with the answers below in mind, particularly those about locking while multithreading, and realized that part of the data I'm adding to my data array is a UIView that I'm creating during the build process. Since it's bad to build views in a background thread, and I did see problems when doing that, I'm jumping back to the main thread for makeCustomView. See the lines of code I added above with "UPDATE" in the comment. This must be the problem now; when I skip adding the custom view, I have no more crashes.
I could rework the build workflow so that all the data except the custom views are added on the background thread, then I could make a second pass and add the custom views on the main thread. But is there a way to manage the threads in this workflow? I tried locking with NSLock before and after calling makeCustomView, but that made no difference. I also found an SO answer saying NSLock is basically outdated, so I didn't go further with that.
If I understood you correctly, concurrent accesses to the dataInProgress array causes the problem, because the array is filled in a background thread and used in the main thread. But NSMutableArray is not thread safe. This fits to my intention that the array itself is corrupted.
You could solve that with NSLock to serialize the accesses to the array, but this is akin of outdated and does not fit to the rest of your code, which uses the more modern (and better) GCD.
A. Situation
What you have:
a builder control flow, which has to run in background
a creation of views control flow, which has to run in the main queue (thread). (I'm not completely sure, whether the pure creation of a view has to be done in main thread, but i would do.)
both control flows accesses the same resource (dataInProgress)
B. GCD
With classical thread/lock approach you start async control flows and serialize them with locks, when they access concurrently a shared resource.
With GCD you start control flows concurrently to each other, but serialized for a given shared resource. (Basically, there are more features, more complexity, but this is, what we need here.)
C. Serializing
It is correct to start the builder in a background queue ("thread") to run it without blocking the main thread. Done.
It is correct to switch back to main thread, if you want to do something with UI elements, esp. creating a view.
Since both control flows accesses the same resource, you have to serialize the accesses. You do this by creating a (serial) queue for that resource:
…
#property dispatch_queue_t dataInProgressAccessQ;
…
// In init or whatever
self. dataInProgressAccessQ = dispatch_queue_create("com.yourcompany.dataInProgressQ", NULL);
After doing that, you put every access to the dataInProgress array in that queue. There is a simple example for that:
// [self.dataInProgress addObject:item];
dispatch_async( self.dataInProgressAccessQ,
^{
[self.dataInProgress addObject:item];
});
In this case it is very easy, because you have to switch the queue at the and of the code. If it is in the middle, you have two options:
a) Use the queue similar to a lock. Let's have an example:
// NSInteger currentCount = [self.dataInProgress count]; // Why int?
NSInteger currentCount;
dispatch_sync( self.dataInProgressAccessQ,
^{
currentCount = [self.dataInProgress count];
});
// More code using currentCount
Using dispatch_sync() will let the code execution wait, until accesses from other control flows are finished. (It is like a lock.)
Edit: As with locks, the access is guaranteed to be serialized. But there might be the problem, that another thread removes objects from the array. Let's have a look to such a situation:
// NSInteger currentCount = [self.dataInProgress count]; // Why int?
NSInteger currentCount;
dispatch_sync( self.dataInProgressAccessQ,
^{
currentCount = [self.dataInProgress count];
});
// More code using currentCount
// Imagine that the execution is stopped here
// Imagine that -makeCustomView removes the last item in meanwhile
// Imagine that the execution continues here
// -> currentCount is not valid anymore.
id lastItem = [self.dataInProgress objectAtIndex:currentCount]; // crash: index out of bounds
To prevent this, you really have to isolate your concurrent code. This highly depends on your code. However, in my example:
id lastItem;
dispatch_sync( self.dataInProgressAccessQ,
^{
NSInteger currentCount;
currentCount = [self.dataInProgress count];
lastItem = [self.dataInProgress objectAtIndex:currentCount]; // don't crash: bounds are not changed
});
// Continue with lastItem
As you can imagine, when getting the last item, if can be removed from the array in the very next moment after you read it. Maybe this causes problems of inconsistency in your code. It really depends on your code.
End of edit
b) Maybe you get performance problems, because is works like a lock (synch). If so, you have to analyze your code and extract parts, that can run concurrently again. The pattern looks like this:
// NSInteger currentCount = [self.dataInProgress count]; // Why int?
dispatch_async( self.dataInProgressAccessQ, // <-- becomes asynch
^{
NSInteger currentCount = [self.dataInProgress count];
// go back to the background queue to leave the access queue fastly
dispatch_async( dispatch_get_global_queue(),
^{
// use current count here.
});
});
dispatch_async( self.dataInProgressAccessQ,
^{
// Another task, that can run concurrently to the above
});
What you can do there, is a matter of your concrete code. Maybe it is a help for you, to have your own private builder queue instead of using the global queue.
But this is the basic approach: Move a task into a queue and do not wait, until it is finished, but add code at the end, that completes the task in another control flow.
Instead of
Code
--lock--
var = Access code
--unlock--
More Code using var
it is
Code
asynch {
var Access Code
asynch {
More code using var
}
}
Of course, you have to do the same inside -makeCustomView.
I agree with Phillip Mills. This looks like a thread safety issue around your self.dataInProgress object.
From Apple docs https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html :
Mutable objects are generally not thread-safe. To use mutable objects in a threaded application, the application must synchronize access to them using locks. (For more information, see Atomic Operations). In general, the collection classes (for example, NSMutableArray, NSMutableDictionary) are not thread-safe when mutations are concerned. That is, if one or more threads are changing the same array, problems can occur. You must lock around spots where reads and writes occur to assure thread safety.
If addDataItem is being called from various background threads, you need to lock around reading and writing to self.dataInProgress.
I don't think you need deep copies. If the dictionaries aren't mutable, all you need is for them to not be released...and a copy of the array they're in will do that for you.
What I believe you need is synchronization around any access to self.data. I suggest creating a NSLock object for your class and wrapping each of the following two lines with lock/unlock method calls:
self.data = [NSMutableArray arrayWithArray:self.dataInProgress];
//...
NSDictionary *item = [self.data objectAtIndex:index];
Also, why does self.data need to be mutable? If it doesn't, self.data = [self.dataInProgress copy]; is simpler...and quite possibly more efficient for memory and performance.
The one thing that's worrying me is, what about the caller of getData. It may not know that the self.data array has changed. If the array becomes shorter, you're headed for an "index out of bounds" crash.
It would be good to only call getData when you know the array is going to be stable. (In other words, synchronize the data gets at a higher level.)
I would attempt at passing in a weak reference to self. I bet if you might have a strong retain cycle happening there somewhere. If I remember correctly, __weak doesn't up the retain count, and __block allows you to change the variable
- (void)loadData {
self.dataInProgress = [NSMutableArray array];
__weak __block SelfClassName *weakSelf = self;
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
[weakSelf loadDataWorker];
});
}
- (void)loadDataWorker {
for (int i=0; i<10000; i++) {
[self addDataItem];
}
__weak __block SelfClassName *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf loadDataFinish];
});
}
A number of Cocoa Touch classes leverage a design pattern of coalescing events. UIViews, for example, have a method setNeedsLayout which causes layoutSubviews to be called in the very near future. This is especially useful in situations where a number of properties influence the layout. In the setter for each property you can call [self setNeedsLayout] which will ensure the layout will be updated, but will prevent many (potentially expensive) updates to the layout if multiple properties are changed at once or even if a single property were modified multiple times within one iteration of the run loop. Other expensive operations like the setNeedsDisplay and drawRect: pair of methods follow the same pattern.
What's the best way to implement pattern like this? Specifically I'd like to tie a number of dependent properties to an expensive method that needs to be called once per iteration of the run loop if a property has changed.
Possible Solutions:
Using a CADisplayLink or NSTimer you could get something working like this, but both seem more involved than necessary and I'm not sure what the performance implications of adding this to lots of objects (especially timers) would be. After all, performance is the only reason to do something like this.
I've used something like this in some cases:
- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil];
[self performSelector:sel withObject:nil afterDelay:delay];
}
This works great in situations where a user input should only trigger some event when a continuous action, or things like that. It seems clunky when we want to ensure there is no delay in triggering the event, instead we just want to coalesce calls within the same run loop.
NSNotificationQueue has just the thing you're looking for. See the documentation on Coalescing Notifications
Here a simple example in a UIViewController:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(configureView:)
name:#"CoalescingNotificationName"
object:self];
[self setNeedsReload:#"viewDidLoad1"];
[self setNeedsReload:#"viewDidLoad2"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNeedsReload:#"viewWillAppear1"];
[self setNeedsReload:#"viewWillAppear2"];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsReload:#"viewDidAppear1"];
[self setNeedsReload:#"viewDidAppear2"];
}
- (void)setNeedsReload:(NSString *)context
{
NSNotification *notification = [NSNotification notificationWithName:#"CoalescingNotificationName"
object:self
userInfo:#{#"context":context}];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:nil];
}
- (void)configureView:(NSNotification *)notification
{
NSString *text = [NSString stringWithFormat:#"configureView called: %#", notification.userInfo];
NSLog(#"%#", text);
self.detailDescriptionLabel.text = text;
}
You can checkout the docs and play with the postingStyle to get the behavior you desired. Using NSPostASAP, in this example, will give us output:
configureView called: {
context = viewDidLoad1;
}
configureView called: {
context = viewDidAppear1;
}
meaning that back-to-back calls to setNeedsReload have been coalesced.
I've implemented something like this using custom dispatch sources. Basically, you setup a dispatch source using DISPATCH_SOURCE_TYPE_DATA_OR as such:
dispatch_source_t source = dispatch_source_create( DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, dispatch_get_main_queue() );
dispatch_source_set_event_handler( source, ^{
// UI update logic goes here
});
dispatch_resume( source );
After that, every time you want to notify that it's time to update, you call:
dispatch_source_merge_data( __source, 1 );
The event handler block is non-reentrant, so updates that occur while the event handler is running will coalesce.
This is a pattern I use a fair bit in my framework, Conche (https://github.com/djs-code/Conche). If you're looking for other examples, poke around CNCHStateMachine.m and CNCHObjectFeed.m.
This borders on "primarily opinion based", but I'll throw out my usual method of handling this:
Set a flag and then queue processing with performSelector.
In your #interface put:
#property (nonatomic, readonly) BOOL needsUpdate;
And then in your #implementation put:
-(void)setNeedsUpdate {
if(!_needsUpdate) {
_needsUpdate = true;
[self performSelector:#selector(_performUpdate) withObject:nil afterDelay:0.0];
}
}
-(void)_performUpdate {
if(_needsUpdate) {
_needsUpdate = false;
[self performUpdate];
}
}
-(void)performUpdate {
}
The double check of _needsUpdate is a little redundant, but cheap. The truly paranoid would wrap all the relevant pieces in #synchronized, but that's really only necessary if setNeedsUpdate can be invoked from threads other than the main thread. If you're going to do that you also need to make changes to setNeedsUpdate to get to the main thread before calling performSelector.
It's my understanding that calling performSelector:withObject:afterDelay: using a delay value of 0 causes the method to be called on the next pass through the event loop.
If you want your actions to be queued up until the next pass through the event loop, that should work fine.
If you want to coalesce multiple different actions and only want one "do everything that accumulated since the last pass through the event loop" call, you could add single call to performSelector:withObject:afterDelay: in your app delegate (or some other single instance object) at launch, and invoke your method again at the end of each call. You could then add an NSMutableSet of things to do, and add an entry to the set each time you trigger an action that you want to coalesce. If you created a custom action object and overrode the isEqual (and hash) methods on your action object, you could set it up so there would only ever be a single action object of each type in your set of actions. Adding the same action type multiple times in a pass through the event loop would add one and only one action of that type).
Your method might look something like this:
- (void) doCoalescedActions;
{
for (CustomActionObject *aCustomAction in setOfActions)
{
//Do whatever it takes to handle coalesced actions
}
[setOfActions removeAllObjects];
[self performSelector: #selector(doCoalescedActions)
withObject: nil
afterDelay: 0];
}
It's hard to get into details on how to do this without specific details of what you want to do.
I have this very strange error happening when I'm changing view controllers in my iOs app.
First some background info.
I am retrieving an image from the web in a background NSOperation. This operation is started from another (background) operation that is instantiated in an collection view cell. The way this works is that the cell creates an object , then sets itself as an observer for that object, then creates an NSOperation with that object as a parameter. This first level operation will start the second operation that will get the image from the web and another NSOperation that will try to get the data from a file (if possible) and report it via delegation to the first operation. That first level operation will change a property on the observed object thus triggering the KVO. The collection/tableView cell will update from the - observeValueChange method.
Here is the problem:
Sometime the cell disappears (reused or deallocated) and when the background thread tries to set the value on the observed object it triggers an EXC_BREAKPOINT exception ([collectionViewCell message retain sent to deallocated instance]).
In order to prevent this I tried implementing -prepareForReuse and -dealloc on the cells. But the error keeps happening.
The flow seem like this:
-User loads VC that has collectionViewWithCells
-cell creates object and NSOperation 1
NSoperation 1 creates NSOperation2 (this is of two types get from web or get from file)
NSOpeartion 2 get image from internet or from a local file
NSoperation 2 sends data to NSOperation1
User has left this screen
NSOperation 1 tries to set data on observed object
-- CRASH
Here is the code inside the cell:
#interface CustomCollectionViewCell ()
#property (strong, nonatomic) NSOperationQueue *imagesOperationQueue;
#property (strong, nonatomic) ImageObject *imgObj;
#end
#implementation CustomCollectionViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)prepareForReuse{
[self clearDelegatesAndObservers];
[super prepareForReuse];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (void) getImage {
self.imgObj = [ImageObject newRefrenceWithId:obj_ref];
[self.imgObj addObserver:self forKeyPath:#"data" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
TaskImageReqCache *imgReq = [[TaskImageReqCache alloc] initWithUrl:imgUrl andImageObject:self.imgObj];
[self.imagesOperationQueue addOperation:imgReq];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.imgObj) {
UIImage *img = [UIImage imageWithData:self.imgObj.data];
self.thumbnailImage.image = img;
}
}
- (void)dealloc
{
[self clearDelegatesAndObservers];
}
- (void)clearDelegatesAndObservers
{
[self.imagesOperationQueue cancelAllOperations];
self.thumbnailImage.image = nil;
[self.imgObj removeObserver:self forKeyPath:#"data"];
[self.pageListAdapter removeDelegateAtIndex:self.myIndexInCollection];
self.imgObj = nil;
}
In the first Level NSOperation this is where the exception breakpoint shows the crash happening:
- (void)didLoadDataFromFile:(NSData *)data
{
if (self.isCancelled) {
[self.opQueue cancelAllOperations];
[self completeOperation];
return;
}
if (!fileDownloadedFromWeb) {
self.observedObject.data = data; // CRASH
}
dataFromDisk = data;
fileReadDone = YES;
if (debugLog) {
NSLog(#"image loaded from local cache (%#)",self.sUrl);
}
}
Any suggestion on how to prevent this crash?
Thanks.
Edited to add:
what I am trying to achieve is: When a tableView cell is displayed a nsoperation is activated to get an image from the net. If the user scrolls quickly and the operation has not finished I need to end it and deallocate any data, and when the cell is reused start a new operation to get the appropriate image from the internet...
Based on comments below, we know that:
- (void)didLoadDataFromFile:(NSData *)data
is called on a different thread to dealloc, so there is a race condition. You need to access self.observedObject on the same thread as the thread it is deallocated on. I'm presuming "observedObject" is a weak reference?
dispatch_sync(dispatch_get_main_queue(), ^{
if (!fileDownloadedFromWeb) {
// Get a strong reference. This will retain observedObject - we must do this
// on the same thread as observedObject:dealloc is called, to prevent retaining
// an object during (or after) dealloc.
ObservedObject *strongRef = self.observedObject;
// This will do nothing if strongRef is nil.
strongRef.data = data;
}
});
A more structured approach would be to have the cell fetch all its images from a singleton cache (it looks as though at the moment there is no caching). The cell would obviously need to register itself as an observer for a particular URL in the cache, and the cache would notify the cell when the URL had downloaded. The cache should post that notification on the main thread.
The cache itself would manage all downloads, and there would be no background deallocation problem because it would be a singleton.
If you don't want to cache, that's fine. Use the same architecture, but call the cache an image fetcher instead. You can always add caching later if you want to.
EDIT - if your objects may be reused, rather than deallocated, as is the case for UITableViewCells, then the cell needs to be careful to ignore notifications about images that relate to a previous fetch. Either of these models will work
a) The cell retains a reference to the NSOperation until the NSOperation calls it back, or until prepareForReuse is called. Any callback from an unrecognised NSOperation must be a previous fetch (that we tried to cancel), and should be ignored. I don't really recommend this model, having the cell know about the operation AND vice versa seems silly.
b) The NSOperation sends a notification when it completes (on the main thread), and in the user info specifies the url/path that was requested. The UITableViewCell remembers what url/path it was trying to fetch, and ignores notifications that relate to other images. It unobserved that path in dealloc/prepareForReuse.
This was getting to long to be a comment so I'll make it an answer.
The reason why it's crashing has to do with the fact that UICollectionViewCells get recycled and deallocated. ARC is has put a [cvcell retain] in the wrong place. So, there are a few options:
One way to fix this is to just not create a NSOperation from a UICollectionViewCell.
Force the users to stay on the UICollectionViewController / UICollectionView so that it stays in memory.
Keep a property / pointer to the UICollectionViewController / UICollectionView so that it stays in memory even when the user has left it. (Make sure you retain it as strong or retain).
NOTE: All of these solutions do the same thing, force ARC to put the retain call somewhere else or to remove it entirely.
Cells get reused and reassigned frequently without your control so you should avoid assigning pending requests or operations to them.
Instead handle operations in your collection view data source (the view controller), and keep track of the operations not per cell but per indexPath's in a dictionary.
Even better keep this as a good experience and use something trusted and tested such as SDwebImage.
I'm trying to make a little binding system between my UILabel and my object Data using KVO. If my UI change, my data have to change, and if my data change my UI should refresh to display the new value.
The biggest issue I have is that I need to cast a custom object to a void* (context) with __bridge_retained void* - or CFBridgingRetain() - but I don't know where I should call CFBridgingRelease(). If call it in observeValueForKeyPath method I get a bad access error (I guess because my Reference Count to the object pointed by context is 0)
// viewDidLoad
// binding my label text with a custom data object
[self bindObject:_myLabel withPath:#"text" toObject:_user path:#"name"];
-(void) bindObject:(id)uiObj withPath:(NSString *)uiPath toObject:(id)dataObj path:(NSString *)dataPath
{
// custom object storing the object I want to bind and his path
PLSObjectPath* op = [[PLSObjectPath alloc] init];
op.theObj = dataObj;
op.thePath = dataPath;
PLSObjectPath* ob = [[PLSObjectPath alloc] init];
ob.theObj = uiObj;
ob.thePath = uiPath;
/* possible leak because I don't know where to call CFBridgingRelease */
[uiObj addObserver:self forKeyPath:uiPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(op)];
[dataObj addObserver:self forKeyPath:dataPath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(ob)];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
PLSObjectPath *obj = (__bridge PLSObjectPath*) context;
PLSObjectPath* pairObj = [[PLSObjectPath alloc] init];
pairObj.theObj = object;
pairObj.thePath = keyPath;
// avoid infinite loop
[obj.theObj removeObserver:self forKeyPath:obj.thePath];
[obj.theObj setValue:change[#"new"] forKeyPath:obj.thePath];
[obj.theObj addObserver:self forKeyPath:obj.thePath options:NSKeyValueObservingOptionNew context:(__bridge_retained void*)(pairObj)];
}
Traditionally users of this have used a static char * as the context parameter, so as to differentiate the different observeValueForKeyPath messages. That said, it should be possible to do something as you are attempting.
What I would suggest is to switch from a custom object to a Core Foundation one, where you can do you own memory management explicitly. Thus I'd suggest changing PLSObjectPath to CFDictionary. You can first create a NSDictionary, then "transfer" it to the CF domain with the appropriate cast, and pass that CFDictionary object for context (which is now a retained CF object). Recast it in observeValueForKeyPath to a CFDictionary, properly ARC cast it to a NSDictionary, use that, then it should get released if you've done the ARC correctly. This is all a well understood paradyme - moving objects in and out of ARC.
Another way you could do it is us a static NSMutableDictionary, and use the context pointer to go to a int value, which when converted to a NSNumber is the key to the dictionary. If all KVO occurs on the main thread, you don't need to protect the dictionary, but if not then you will need to put all access to the dictionary behind a serial dispatch queue that in forces serial access on one thread.
The memory leak is from [obj.theObj removeObserver:self forKeyPath:obj.thePath]. You are removing the observer but as the system doesn't treat the context as an object, it will not release it. Also, there is no way for you to get the context from the observed object itself.
At some point you will need to stop all observation to allow your view to be deallocated. This should happen from viewDid(Will)Disappear:. To be able to release the PLSObjectPath:s at that point, you will need to store them somewhere, possibly an NSMutableArray. If you will store these anyway for this purpose, you don't need to use __bridge_retained. Also, in this case your PLSObjectPath:s might/should contain a void* pointing to the other context. This way, you save the creation of PLSObject in observeValueForKeyPath:ofObject:change:context:.
Just a comment, you should start the KVO from viewWill(Did)Appear: and not from viewDidLoad. It gives you a better KVO start/stop management and also removes the unnecessary observation while your view is not on the screen.
Under manual memory management, I use this pattern fairly often:
NSString * myStr = /* some local object */
[UIView beginAnimation:#"foo" context:(void *)[myStr retain]];
And then, later and asynchronously:
- (void)animationDidStop:(NSString *)anim finished:(NSNumber *)num context:(void *)context
{
NSString * contextStr = (NSString *)context;
// ...
[contextStr release];
}
i.e. I manually managed the lifetime of an object used as an opaque context. (This is true for the old UIView animations but also for other kinds of API that I use.)
Under ARC, my instinct is that I want to __bridge_retained going in and __bridge_transfer in the handler, as suggested here. But this treats a Cocoa object as a CFType not because it's really bridged, but just for the purpose of shoving a retain down its throat.
Is this valid, and is this stylistically acceptable? If not, what's the better* solution?
(The accepted answer in this question gives a different answer, saying that __bridge alone is OK, but that seems to me to be wrong, since the original string would be at risk of being deallocated as soon as it goes out of scope in the first function. Right?)
*Please don't say "use block-based animations instead". (That's not what I'm asking about.)
Go with your instinct. __bridge_retained transfers management of an object to you from ARC, while __bridge_transfer does the reverse, don't worry about treating the object as a CFType - you're not really doing that just taking over management.
The other approach you see recommended is to construct your code so that ARC retains management, but this can easily come across as contrived (and get messy). Having the API you're using maintain the value as it is designed to do is clean; just comment the code appropriately where management is handed to the API and returned back to ARC.
Even if using __bridge_retained/__bridge_transfer seems fine to me (transferring ownership to CoreFoundation or to any C code or to yourself is quite the same you just tell ARC that you are responsible for the object ownership at some point and give the ownership back to ARC later), you can instead keep a strong reference on your object you use as your context somewhere if you prefer, so that ARC does not reclaim its memory.
This can be achieved by using a #property(strong) in your class for example, affecting it to the value when you previously did your retain and assigning it to nil when you previously did your release to let the string go.
Note that if you need to keep around multiple contexts in the same class, you may opt for the option to use an NSMutableArray that keeps your context strings around instead of declaring a property for each context.
#interface YourClass ()
#property(strong) NSMutableArray* runningAnimationContexts;
#end
#implementation YourClass
-(id)init {
self = [super init];
if (self) {
self.runningAnimationContexts = [NSMutableArray array];
}
return self;
}
-(void)someMethod
{
// Example with two different parallel animations using old API
NSString * myStr = /* some local object */
[self.runningAnimationContexts addObject:myStr]; // ~ retain
[UIView beginAnimation:#"foo" context:(__bridge)myStr];
...
[UIView commitAnimations];
NSString * myStr2 = /* some other local object */
[self.runningAnimationContexts addObject:myStr2]; // ~ retain
[UIView beginAnimation:#"foo2" context:(__bridge)myStr2];
...
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)anim finished:(NSNumber *)num context:(void *)context
{
NSString * contextStr = (__bridge NSString *)context;
// ...
[self.runningAnimationContexts removeObject:contextStr]; // ~ release
}
#end