My iOS app stalls on setDelegate for about 15 seconds after about 100,000 setDelegate calls. Changing the delegate property from weak to assign fixes the problem. Any idea why the weak property has so much overhead and stalls the app?
I suspect the weak references are maintained in an array so the runtime can loop thru them and set the pointers to nil when the delegate object gets deallocated. Does the array have a max size? The stall gets much longer as n nears 100,000.
Sample code is below:
Header file:
#import <Foundation/Foundation.h>
@protocol GraphDataPointDelegate <NSObject>
- (BOOL)drawGraphByDistance;
@end
@interface GraphDataPoint : NSObject
- (id)initWithYValue:(NSNumber *)yValue withTime:(NSNumber *)time withDistance:(NSNumber *)distance withDelegate:(id <GraphDataPointDelegate> )delegate;
@end
@interface Graph : NSObject <GraphDataPointDelegate>
@end
M File
#import "Graph.h"
@interface GraphDataPoint ()
@property (nonatomic, weak, readwrite) id <GraphDataPointDelegate> delegate;
@property (nonatomic, strong, readwrite) NSNumber *yValue;
@property (nonatomic, strong, readwrite) NSNumber *time;
@property (nonatomic, strong, readwrite) NSNumber *distance;
@end
@implementation GraphDataPoint
- (id)initWithYValue:(NSNumber *)yValue withTime:(NSNumber *)time withDistance:(NSNumber *)distance withDelegate:(id<GraphDataPointDelegate>)delegate {
self = [super init];
if (self) {
self.yValue = yValue;
self.time = time;
self.distance = distance;
self.delegate = delegate;
}
return self;
}
- (id)graphXValue {
if ([_delegate drawGraphByDistance]) {
return _distance;
} else {
return _time;
}
}
@end
@implementation Graph
- (id)init {
self = [super init];
if (self) {
NSMutableArray *array = [NSMutableArray array];
NSLog(@"before");
for (int i = 0; i < 100000; i++) {
GraphDataPoint *graphData = [[GraphDataPoint alloc] initWithYValue:@1 withTime:@1 withDistance:@1 withDelegate:self];
[array addObject:graphData];
}
NSLog(@"after");
}
return self;
}
- (BOOL)drawGraphByDistance {
return YES;
}
@end
The system needs to keep track of every memory address where a weak pointer to an object is stored. Why? Because if the object is to be dealloc'ed (its memory will be freed), all those pointers must be set to nil
first. That's what makes weak pointers special: They do not retain objects (not keep them alive), but they are also never dangling pointers (never pointing to memory addresses of former, now dead objects); if the object dies, they become nil
. So whenever a weak reference is changed in value, the system must first tell a global weak pointer tracking manager to delete the information recorded for this memory address in the past (if any) and then record the new information after the object was changed. Needless to say that the whole thing must be thread-safe and thus involves (slightly expensive) locking.
__weak id x;
// ...
x = anObject;
// ...
x = anotherObject;
// ....
x = nil;
is in fact (not really, just to get the concept across):
__weak id x;
// ...
[WeakPointerTrackingManager lock];
x = anObject;
[WeakPointerTrackingManager var:&x pointsTo:anObject];
[WeakPointerTrackingManager unlock];
// ...
[WeakPointerTrackingManager lock];
x = anotherObject;
[WeakPointerTrackingManager var:&x pointsTo:anotherObject];
[WeakPointerTrackingManager unlock];
// ...
[WeakPointerTrackingManager lock];
x = nil;
[WeakPointerTrackingManager deleteInfoForVar:&x];
[WeakPointerTrackingManager unlock];
assign
does nothing like that. Is just stores a reference to the object without increasing the object retain counter. However, if the object dies, the assign
variable still points to the memory address where the object used to live. If you now send a message to this non-existing object, your app may crash or other undefined things may happen.
But seriously, every app that performs 100,000 setDelegate calls is broken by design IMHO. I can't think of any serious use case where this would be meaningful. There is probably a much better way to do whatever you intend to do here.
Just for the records, accessing a weak variable is expensive, too.
__weak id x;
// ...
[x sendMessage];
// ...
__strong id y; // Strong is optional,
// vars are strong by default
y = x;
is in fact (not really):
__weak id x;
// ...
__strong id tmp;
[WeakPointerTrackingManager lock];
tmp = [x retain];
[WeakPointerTrackingManager unlock];
[tmp sendMessage];
[tmp release];
// ...
__strong id y;
[WeakPointerTrackingManager lock];
y = [x retain];
[WeakPointerTrackingManager unlock];
weak
has some overhead that assign
does not.
They're both different from strong
in that they don't increase the retain count for ARC.
But as for the difference between weak
and assign
, when an object pointed to by a weak
reference is deallocated, the weak
pointer automatically zeros out to nil
. This doesn't happen with assign
. This means if you try to call a method on a deallocated delegate that is an assign
property, you'll get a crash, but assign
also doesn't have that extra overhead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With