some NSArrayController fixes: there's probably still a lot of stuff broken, but now addObject: should be a lot faster

This commit is contained in:
Johannes Fortmann 2008-10-11 11:00:58 +00:00
parent 73953849e3
commit aa1dbec7f6
8 changed files with 222 additions and 37 deletions

View File

@ -16,6 +16,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
#import <Foundation/NSKeyValueObserving.h>
#import <Foundation/NSKeyValueCoding.h>
#import <Foundation/NSString+KVCAdditions.h>
#import <Foundation/NSRaise.h>
#import "NSObservationProxy.h"
@interface NSObjectController(private)
@ -30,15 +31,21 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
- (void)_setContentArray:(id)value;
@end
@interface NSArray (private)
-(NSUInteger)_insertObject:(id)obj inArraySortedByDescriptors:(id)desc;
@end
@implementation NSArrayController
+(void)initialize
{
[self setKeys:[NSArray arrayWithObjects:@"contentArray", @"selectionIndexes", nil]
[self setKeys:[NSArray arrayWithObjects:@"content", nil]
triggerChangeNotificationsForDependentKey:@"contentArray"];
[self setKeys:[NSArray arrayWithObjects:@"content", @"contentArray", @"selectionIndexes", nil]
triggerChangeNotificationsForDependentKey:@"selection"];
[self setKeys:[NSArray arrayWithObjects:@"contentArray", @"selectionIndexes", @"selection", nil]
[self setKeys:[NSArray arrayWithObjects:@"content", @"contentArray", @"selectionIndexes", @"selection", nil]
triggerChangeNotificationsForDependentKey:@"selectionIndex"];
[self setKeys:[NSArray arrayWithObjects:@"contentArray", @"selectionIndexes", @"selection", nil]
[self setKeys:[NSArray arrayWithObjects:@"content", @"contentArray", @"selectionIndexes", @"selection", nil]
triggerChangeNotificationsForDependentKey:@"selectedObjects"];
[self setKeys:[NSArray arrayWithObjects:@"selectionIndexes", nil]
@ -103,17 +110,22 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
_flags.preservesSelection=value;
}
- (void)_setContentArray:(id)value
-(void)setContent:(id)value
{
if(![value isKindOfClass:[NSArray class]])
value=[NSArray arrayWithObject:value];
id oldSelection=nil;
id oldSelectionIndexes=[[[self selectionIndexes] copy] autorelease];
if([self preservesSelection])
oldSelection=[self selectedObjects];
[self setContent:value];
[super setContent:[[value mutableCopy] autorelease]];
if(_flags.clearsFilterPredicateOnInsertion)
[self setFilterPredicate:nil];
[self rearrangeObjects];
if(oldSelection)
{
[self setSelectedObjects:oldSelection];
@ -124,6 +136,16 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
}
}
- (void)_setContentArrayForMultipleSelection:(id)value
{
NSUnimplementedMethod();
}
- (void)_setContentArray:(id)value
{
[self setContent:value];
}
- (id)contentArray {
return [self content];
}
@ -145,9 +167,9 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
- (void)_setArrangedObjects:(id)value {
if (_arrangedObjects != value)
{
[_arrangedObjects release];
_arrangedObjects = [[_NSObservableArray alloc] initWithArray:value];
{
[_arrangedObjects release];
_arrangedObjects = [[_NSObservableArray alloc] initWithArray:value];
}
}
@ -307,7 +329,7 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
-(void)_setContentSet:(NSSet*)set
{
[self _setContentArray:[set allObjects]];
[self setContent:[set allObjects]];
}
#pragma mark -
@ -317,7 +339,21 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
{
if(![self canAdd])
return;
[[self mutableArrayValueForKey:@"contentArray"] addObject:object];
[self willChangeValueForKey:@"content"];
[_content addObject:object];
[self didChangeValueForKey:@"content"];
if(_flags.clearsFilterPredicateOnInsertion)
[self setFilterPredicate:nil];
if([_filterPredicate evaluateWithObject:object])
{
[self willChangeValueForKey:@"selectionIndexes"];
NSUInteger pos=[_arrangedObjects _insertObject:object inArraySortedByDescriptors:_sortDescriptors];
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
[self didChangeValueForKey:@"selectionIndexes"];
}
}
@ -325,7 +361,19 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
{
if(![self canRemove])
return;
[[self mutableArrayValueForKey:@"contentArray"] removeObject:object];
[self willChangeValueForKey:@"content"];
[_content removeObject:object];
[self didChangeValueForKey:@"content"];
if([_filterPredicate evaluateWithObject:object])
{
NSUInteger pos=[_arrangedObjects indexOfObject:object];
[self willChangeValueForKey:@"selectionIndexes"];
[_arrangedObjects removeObject:object];
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:-1];
[self didChangeValueForKey:@"selectionIndexes"];
}
}
-(void)add:(id)sender
@ -349,14 +397,16 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
-(void)remove:(id)sender
{
[self removeObjectsAtArrangedObjectIndexes:[self selectionIndexes]];
[self removeObjects:[[self contentArray] objectsAtIndexes:[self selectionIndexes]]];
}
-(void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet*)indexes
{
// FIXME: this should remove no matter what canRemove returns
[self removeObjects:[[self contentArray] objectsAtIndexes:indexes]];
}
- (void)addObjects:(NSArray *)objects
{
if(![self canAdd])
@ -366,7 +416,7 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
int i;
for(i=0; i<count; i++)
[contentArray addObject:[objects objectAtIndex:i]];
[self _setContentArray:contentArray];
[self setContent:contentArray];
}
@ -381,7 +431,7 @@ triggerChangeNotificationsForDependentKey:@"selectionIndex"];
for(i=0; i<count; i++)
[contentArray removeObject:[objects objectAtIndex:i]];
[self _setContentArray:contentArray];
[self setContent:contentArray];
}
-(BOOL)canInsert;

View File

@ -14,7 +14,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
@interface NSControllerSelectionProxy : NSObject
{
id _controller;
NSMutableDictionary *_values;
NSMutableDictionary *_cachedValues;
NSMutableArray *_observationProxies;
id _keys;
}

View File

@ -22,7 +22,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
{
if((self=[super init]))
{
_values=[NSMutableDictionary new];
_cachedValues=[NSMutableDictionary new];
_controller = [cont retain];
_observationProxies = [NSMutableArray new];
}
@ -32,7 +32,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
-(void)dealloc
{
[_keys release];
[_values release];
[_cachedValues release];
[_controller release];
if([_observationProxies count]>0)
@ -47,7 +47,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
-(id)valueForKey:(NSString*)key
{
id val=[_values objectForKey:key];
id val=[_cachedValues objectForKey:key];
if(val)
return val;
id allValues=[[_controller selectedObjects] valueForKeyPath:key];
@ -81,19 +81,19 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
}
}
[_values setValue:val forKey:key];
[_cachedValues setValue:val forKey:key];
return val;
}
-(int)count
{
return [_values count];
return [_cachedValues count];
}
-(id)keyEnumerator
{
return [_values keyEnumerator];
return [_cachedValues keyEnumerator];
}
-(void)setValue:(id)value forKey:(NSString *)key
@ -112,17 +112,17 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
-(void)controllerWillChange
{
[_keys autorelease];
_keys=[[_values allKeys] retain];
_keys=[[_cachedValues allKeys] retain];
for(id key in _keys)
{
[self willChangeValueForKey:key];
}
[_values removeAllObjects];
[_cachedValues removeAllObjects];
}
-(void)controllerDidChange
{
[_values removeAllObjects];
[_cachedValues removeAllObjects];
for(id key in _keys)
{
[self didChangeValueForKey:key];
@ -136,12 +136,14 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
change:(NSDictionary *)change
context:(void *)context
{
[_values removeObjectForKey:keyPath];
// remove cached value for this key path
[_cachedValues removeObjectForKey:keyPath];
}
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath observer:observer object:self];
[proxy setNotifiyObject:YES];
[_observationProxies addObject:proxy];
[[_controller selectedObjects] addObserver:proxy forKeyPath:keyPath options:options context:context];

View File

@ -6,7 +6,7 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
#import <AppKit/NSController.h>
@class NSString,NSPredicate,NSFetchRequest,NSManagedObjectContext,NSMenuItem,NSError,NSArray;
@class NSString,NSPredicate,NSFetchRequest,NSManagedObjectContext,NSMenuItem,NSError,NSArray, NSCountedSet;
@interface NSObjectController : NSController {
NSString* _objectClassName;
@ -14,6 +14,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
id _selection;
BOOL _editable;
BOOL _automaticallyPreparesContent;
NSCountedSet* _observedKeys;
}
-(void)setContent:content;
-content;

View File

@ -36,6 +36,7 @@ triggerChangeNotificationsForDependentKey:@"canRemove"];
_objectClassName=[[coder decodeObjectForKey:@"NSObjectClassName"] retain];
_editable = [coder decodeBoolForKey:@"NSEditable"];
_automaticallyPreparesContent = [coder decodeBoolForKey:@"NSAutomaticallyPreparesContent"];
_observedKeys=[[NSCountedSet alloc] init];
}
return self;
}
@ -45,7 +46,8 @@ triggerChangeNotificationsForDependentKey:@"canRemove"];
}
- (void)setContent:(id)value {
if (_content != value) {
if(value!=_content)
{
[self _selectionWillChange];
[_content release];
@ -55,6 +57,15 @@ triggerChangeNotificationsForDependentKey:@"canRemove"];
}
}
-(void)_setContentWithoutKVO:(id)value
{
if (_content != value) {
[_content release];
_content = [value retain];
}
}
-(NSArray *)selectedObjects
{
return [_NSObservableArray arrayWithObject:_content];
@ -96,6 +107,7 @@ triggerChangeNotificationsForDependentKey:@"canRemove"];
[_selection release];
[_objectClassName release];
[_content release];
[_observedKeys release];
[super dealloc];
}
@ -127,4 +139,21 @@ triggerChangeNotificationsForDependentKey:@"canRemove"];
- (void)setAutomaticallyPreparesContent:(BOOL)value {
_automaticallyPreparesContent = value;
}
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
[_observedKeys addObject:keyPath];
[super addObserver:observer forKeyPath:keyPath options:options context:context];
}
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
[_observedKeys removeObject:keyPath];
[super removeObserver:observer forKeyPath:keyPath];
}
-(id)_observedKeys
{
return _observedKeys;
}
@end

View File

@ -1,6 +1,7 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSMutableArray.h>
#import <Foundation/NSKeyValueObserving.h>
@interface _NSObservationProxy : NSObject
{
@ -8,19 +9,24 @@
id _observer;
id _object;
BOOL _notifyObject;
// only as storage (context for _observer will be the one given in observeValueForKeyPath:)
// FIXME: write accessors, remove @public
@public
void* _context;
NSKeyValueObservingOptions _options;
}
-(id)initWithKeyPath:(id)keyPath observer:(id)observer object:(id)object;
-(id)observer;
-(id)keyPath;
-(void)setNotifiyObject:(BOOL)val;
@end
@interface _NSObservableArray : NSArray
@interface _NSObservableArray : NSMutableArray
{
NSArray *_array;
NSMutableArray *_array;
NSMutableArray *_observationProxies;
}
-initWithObjects:(id *)objects count:(unsigned)count;
@end

View File

@ -14,8 +14,6 @@
_keyPath=[keyPath retain];
_observer=observer;
_object=object;
if([object respondsToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)])
_notifyObject=YES;
}
return self;
}
@ -36,6 +34,21 @@
return _keyPath;
}
-(void*)context
{
return _context;
}
-(NSKeyValueObservingOptions)options
{
return _options;
}
-(void)setNotifiyObject:(BOOL)val
{
_notifyObject=val;
}
- (BOOL)isEqual:(id)other
{
if([other isMemberOfClass:isa])
@ -53,10 +66,12 @@
context:(void *)context
{
if(_notifyObject)
{
[_object observeValueForKeyPath:_keyPath
ofObject:_object
change:change
context:context];
}
[_observer observeValueForKeyPath:_keyPath
ofObject:_object
@ -70,8 +85,6 @@
}
@end
@implementation _NSObservableArray
-(id)objectAtIndex:(unsigned)idx
@ -117,6 +130,9 @@
_NSObservationProxy *proxy=[[_NSObservationProxy alloc] initWithKeyPath:keyPath
observer:observer
object:self];
proxy->_options=options;
proxy->_context=context;
[_observationProxies addObject:proxy];
[proxy release];
@ -170,5 +186,84 @@
forKeyPath:keyPath];
}
}
-(void)insertObject:(id)obj atIndex:(NSUInteger)idx
{
for(_NSObservationProxy *proxy in _observationProxies)
{
id keyPath=[proxy keyPath];
BOOL isOperator=NO;
if([keyPath hasPrefix:@"@"])
isOperator=YES;
if(isOperator)
[self willChangeValueForKey:keyPath];
[obj addObserver:proxy
forKeyPath:keyPath
options:[proxy options]
context:[proxy context]];
if(isOperator)
[self didChangeValueForKey:keyPath];
}
[_array insertObject:obj atIndex:idx];
}
-(void)removeObjectAtIndex:(NSUInteger)idx
{
id obj=[_array objectAtIndex:idx];
for(_NSObservationProxy *proxy in _observationProxies)
{
id keyPath=[proxy keyPath];
BOOL isOperator=NO;
if([keyPath hasPrefix:@"@"])
isOperator=YES;
if(isOperator)
[self willChangeValueForKey:keyPath];
[obj removeObserver:proxy
forKeyPath:keyPath];
if(isOperator)
[self didChangeValueForKey:keyPath];
}
[_array removeObjectAtIndex:idx];
}
-(void)addObject:(id)obj
{
[self insertObject:obj atIndex:[self count]];
}
-(void)removeLastObject
{
[self removeObjectAtIndex:[self count]];
}
-(void)replaceObjectAtIndex:(NSUInteger)idx withObject:(id)obj
{
id old=[_array objectAtIndex:idx];
for(_NSObservationProxy *proxy in _observationProxies)
{
id keyPath=[proxy keyPath];
BOOL isOperator=NO;
if([keyPath hasPrefix:@"@"])
isOperator=YES;
if(isOperator)
[self willChangeValueForKey:keyPath];
[old removeObserver:proxy
forKeyPath:[proxy keyPath]];
[obj addObserver:proxy
forKeyPath:[proxy keyPath]
options:[proxy options]
context:[proxy context]];
if(isOperator)
[self didChangeValueForKey:keyPath];
}
[_array replaceObjectAtIndex:idx withObject:obj];
}
@end

View File

@ -125,6 +125,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
-(BOOL)allowsEditingForRow:(int)row
{
if([[_rowValues objectAtIndex:row] classForCoder]==[NSDictionary class])
return NO;
return YES;
}