mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 16:33:29 +00:00
fixup for removal from mutable array
This commit is contained in:
parent
46d19b5dd5
commit
bbd1f03183
3 changed files with 235 additions and 43 deletions
|
@ -1,3 +1,12 @@
|
|||
2025-01-01 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Source/NSKeyValueMutableArray.m: Fix mutable array support so that
|
||||
we can remove items from multiple indices at the same time and avoid
|
||||
problems where the array being accessed is set by copy rendering our
|
||||
cached infromation invalid (issue #480).
|
||||
* Tests/base/KVC/remove.m: adapted test by Rupert Daniel for removal
|
||||
of items from mutable array.
|
||||
|
||||
2024-12-31 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* Source/NSTimeZone.m: Add GNUSTEP_BUILTIN_TZ environment variable to
|
||||
|
|
|
@ -30,14 +30,15 @@
|
|||
@interface NSKeyValueMutableArray : NSMutableArray
|
||||
{
|
||||
@protected
|
||||
id object;
|
||||
NSString *key;
|
||||
id object;
|
||||
NSString *key;
|
||||
NSMutableArray *array;
|
||||
BOOL otherChangeInProgress;
|
||||
BOOL notifiesObservers;
|
||||
}
|
||||
|
||||
+ (NSKeyValueMutableArray *) arrayForKey: (NSString *)aKey ofObject: (id)anObject;
|
||||
+ (NSKeyValueMutableArray *) arrayForKey: (NSString*)aKey
|
||||
ofObject: (id)anObject;
|
||||
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject;
|
||||
|
||||
@end
|
||||
|
@ -51,10 +52,10 @@
|
|||
}
|
||||
|
||||
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
|
||||
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -65,10 +66,10 @@
|
|||
}
|
||||
|
||||
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
|
||||
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -84,6 +85,12 @@
|
|||
@end
|
||||
|
||||
|
||||
/* NB. For removal of objects we can remove multiple objects at the same
|
||||
* time and the notifications are sent with an NSIndexSet specifying the
|
||||
* indices which are being altered. We therefore funnuel all the other
|
||||
* removal methods through the -removeObjectsAtIndexes: method so that
|
||||
* the subclasses only need to implement that one themselves.
|
||||
*/
|
||||
@implementation NSKeyValueMutableArray
|
||||
|
||||
+ (NSKeyValueMutableArray *) arrayForKey: (NSString *)aKey
|
||||
|
@ -93,6 +100,7 @@
|
|||
unsigned size = [aKey maximumLengthOfBytesUsingEncoding:
|
||||
NSUTF8StringEncoding];
|
||||
char keybuf[size + 1];
|
||||
|
||||
[aKey getCString: keybuf
|
||||
maxLength: size + 1
|
||||
encoding: NSUTF8StringEncoding];
|
||||
|
@ -126,8 +134,8 @@
|
|||
object = anObject;
|
||||
key = [aKey copy];
|
||||
otherChangeInProgress = NO;
|
||||
notifiesObservers =
|
||||
[[anObject class] automaticallyNotifiesObserversForKey: aKey];
|
||||
notifiesObservers
|
||||
= [[anObject class] automaticallyNotifiesObserversForKey: aKey];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
@ -155,9 +163,21 @@
|
|||
[self insertObject: anObject atIndex: [self count]];
|
||||
}
|
||||
|
||||
- (void) removeFirstObject
|
||||
{
|
||||
NSUInteger count = [self count];
|
||||
|
||||
if (0 == count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
[self removeObjectAtIndex: 0];
|
||||
}
|
||||
|
||||
- (void) removeLastObject
|
||||
{
|
||||
NSUInteger count = [self count];
|
||||
|
||||
if (0 == count)
|
||||
{
|
||||
return;
|
||||
|
@ -165,19 +185,78 @@
|
|||
[self removeObjectAtIndex: (count - 1)];
|
||||
}
|
||||
|
||||
- (void) removeObject: (id)anObject
|
||||
{
|
||||
NSUInteger count = [self count];
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
NSMutableIndexSet *indexes = nil;
|
||||
|
||||
while (count-- > 0)
|
||||
{
|
||||
if ([[self objectAtIndex: count] isEqual: anObject])
|
||||
{
|
||||
if (nil == indexes)
|
||||
{
|
||||
indexes = [NSMutableIndexSet indexSet];
|
||||
}
|
||||
[indexes addIndex: count];
|
||||
}
|
||||
}
|
||||
if (indexes)
|
||||
{
|
||||
[self removeObjectsAtIndexes: indexes];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) removeObjectAtIndex: (NSUInteger)index
|
||||
{
|
||||
if (index != NSNotFound)
|
||||
{
|
||||
[self removeObjectsAtIndexes: [NSIndexSet indexSetWithIndex: index]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) removeObjectsFromIndices: (NSUInteger*)indices
|
||||
numIndices: (NSUInteger)count
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
NSMutableIndexSet *indexes = nil;
|
||||
|
||||
while (count-- > 0)
|
||||
{
|
||||
if (indices[count] != NSNotFound)
|
||||
{
|
||||
if (nil == indexes)
|
||||
{
|
||||
indexes = [NSMutableIndexSet indexSet];
|
||||
}
|
||||
[indexes addIndex: indices[count]];
|
||||
}
|
||||
}
|
||||
if (indexes)
|
||||
{
|
||||
[self removeObjectsAtIndexes: indexes];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSKeyValueFastMutableArray
|
||||
|
||||
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized
|
||||
withCapitalizedKey: (const char *)capitalized
|
||||
{
|
||||
return [[[self alloc] initWithKey: aKey ofObject: anObject
|
||||
withCapitalizedKey: capitalized] autorelease];
|
||||
}
|
||||
|
||||
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized
|
||||
withCapitalizedKey: (const char *)capitalized
|
||||
{
|
||||
SEL insert;
|
||||
SEL remove;
|
||||
|
@ -227,24 +306,33 @@
|
|||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) removeObjectAtIndex: (NSUInteger)index
|
||||
- (void) removeObjectsAtIndexes: (NSIndexSet*)indexes
|
||||
{
|
||||
NSIndexSet *indexes = nil;
|
||||
NSUInteger index = [indexes lastIndex];
|
||||
|
||||
if (nil == indexes || NSNotFound == index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
indexes = [NSIndexSet indexSetWithIndex: index];
|
||||
[object willChange: NSKeyValueChangeRemoval
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
[removeObjectInvocation setArgument: &index atIndex: 2];
|
||||
[removeObjectInvocation invoke];
|
||||
while ((index = [indexes indexLessThanIndex: index]) != NSNotFound)
|
||||
{
|
||||
[removeObjectInvocation setArgument: &index atIndex: 2];
|
||||
[removeObjectInvocation invoke];
|
||||
}
|
||||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeRemoval
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
forKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,8 +344,8 @@
|
|||
{
|
||||
indexes = [NSIndexSet indexSetWithIndex: index];
|
||||
[object willChange: NSKeyValueChangeInsertion
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
[insertObjectInvocation setArgument: &anObject atIndex: 2];
|
||||
[insertObjectInvocation setArgument: &index atIndex: 3];
|
||||
|
@ -265,7 +353,7 @@
|
|||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeInsertion
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
}
|
||||
|
@ -279,8 +367,8 @@
|
|||
otherChangeInProgress = YES;
|
||||
indexes = [NSIndexSet indexSetWithIndex: index];
|
||||
[object willChange: NSKeyValueChangeReplacement
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
if (replaceObjectInvocation)
|
||||
{
|
||||
|
@ -296,26 +384,25 @@
|
|||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeReplacement
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
otherChangeInProgress = NO;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSKeyValueSlowMutableArray
|
||||
|
||||
+ (id) arrayForKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized
|
||||
withCapitalizedKey: (const char *)capitalized
|
||||
{
|
||||
return [[[self alloc] initWithKey: aKey ofObject: anObject
|
||||
withCapitalizedKey: capitalized] autorelease];
|
||||
withCapitalizedKey: capitalized] autorelease];
|
||||
}
|
||||
|
||||
- (id) initWithKey: (NSString *)aKey ofObject: (id)anObject
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
withCapitalizedKey: (const char *)capitalized;
|
||||
|
||||
{
|
||||
SEL set = NSSelectorFromString([NSString stringWithFormat:
|
||||
|
@ -337,21 +424,29 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void) removeObjectAtIndex: (NSUInteger)index
|
||||
- (void) removeObjectsAtIndexes: (NSIndexSet*)indexes
|
||||
{
|
||||
NSIndexSet *indexes = nil;
|
||||
NSMutableArray *temp;
|
||||
NSUInteger index = [indexes lastIndex];
|
||||
NSMutableArray *temp;
|
||||
|
||||
if (nil == indexes || NSNotFound == index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
indexes = [NSIndexSet indexSetWithIndex: index];
|
||||
[object willChange: NSKeyValueChangeRemoval
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
|
||||
temp = [NSMutableArray arrayWithArray: [object valueForKey: key]];
|
||||
[temp removeObjectAtIndex: index];
|
||||
while ((index = [indexes indexLessThanIndex: index]) != NSNotFound)
|
||||
{
|
||||
[temp removeObjectAtIndex: index];
|
||||
}
|
||||
|
||||
[setArrayInvocation setArgument: &temp atIndex: 2];
|
||||
[setArrayInvocation invoke];
|
||||
|
@ -359,21 +454,21 @@
|
|||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeRemoval
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) insertObject: (id)anObject atIndex: (NSUInteger)index
|
||||
{
|
||||
NSIndexSet *indexes = nil;
|
||||
NSIndexSet *indexes = nil;
|
||||
NSMutableArray *temp;
|
||||
|
||||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
indexes = [NSIndexSet indexSetWithIndex: index];
|
||||
[object willChange: NSKeyValueChangeInsertion
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
|
||||
|
@ -386,7 +481,7 @@
|
|||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeInsertion
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +495,7 @@
|
|||
{
|
||||
indexes = [NSIndexSet indexSetWithIndex: index];
|
||||
[object willChange: NSKeyValueChangeReplacement
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
|
||||
|
@ -414,12 +509,11 @@
|
|||
if (notifiesObservers && !otherChangeInProgress)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeReplacement
|
||||
valuesAtIndexes: indexes
|
||||
valuesAtIndexes: indexes
|
||||
forKey: key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
@ -447,11 +541,11 @@
|
|||
maxLength: size + 1
|
||||
encoding: NSUTF8StringEncoding];
|
||||
|
||||
if (!GSObjCFindVariable (anObject, cKeyPtr, &type, &size, &offset))
|
||||
found = GSObjCFindVariable (anObject, ++cKeyPtr, &type, &size, &offset);
|
||||
if (!GSObjCFindVariable(anObject, cKeyPtr, &type, &size, &offset))
|
||||
found = GSObjCFindVariable(anObject, ++cKeyPtr, &type, &size, &offset);
|
||||
if (found)
|
||||
{
|
||||
array = GSObjCGetVal (anObject, cKeyPtr, NULL, type, size, offset);
|
||||
array = GSObjCGetVal(anObject, cKeyPtr, NULL, type, size, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -482,9 +576,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void) removeObjectAtIndex: (NSUInteger)index
|
||||
- (void) removeObjectsAtIndexes: (NSIndexSet*)indexes
|
||||
{
|
||||
NSIndexSet *indexes = nil;
|
||||
NSUInteger index = [indexes lastIndex];
|
||||
|
||||
if (nil == indexes || NSNotFound == index)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (notifiesObservers)
|
||||
{
|
||||
|
@ -494,6 +593,10 @@
|
|||
forKey: key];
|
||||
}
|
||||
[array removeObjectAtIndex: index];
|
||||
while ((index = [indexes indexLessThanIndex: index]) != NSNotFound)
|
||||
{
|
||||
[array removeObjectAtIndex: index];
|
||||
}
|
||||
if (notifiesObservers)
|
||||
{
|
||||
[object didChange: NSKeyValueChangeRemoval
|
||||
|
@ -526,6 +629,7 @@
|
|||
{
|
||||
NSIndexSet *indexes = nil;
|
||||
NSUInteger count = [array count];
|
||||
|
||||
if (0 == count)
|
||||
{
|
||||
return;
|
||||
|
|
79
Tests/base/KVC/remove.m
Normal file
79
Tests/base/KVC/remove.m
Normal file
|
@ -0,0 +1,79 @@
|
|||
#import "ObjectTesting.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface TestObject : NSObject
|
||||
{
|
||||
NSArray *items;
|
||||
}
|
||||
|
||||
@property (nonatomic, copy) NSArray *items;
|
||||
- (void) addItem: (id)item;
|
||||
- (NSArray*) items;
|
||||
- (void) removeItem: (id)item;
|
||||
- (void) setItems: (NSArray*)a;
|
||||
@end
|
||||
|
||||
@implementation TestObject
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
DESTROY(items);
|
||||
DEALLOC
|
||||
}
|
||||
|
||||
- (instancetype) init
|
||||
{
|
||||
if ((self = [super init]) != nil)
|
||||
{
|
||||
ASSIGN(items, [NSMutableArray array]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) addItem: (id)item
|
||||
{
|
||||
[[self mutableArrayValueForKeyPath: @"items"] addObject: item];
|
||||
}
|
||||
|
||||
- (NSArray*) items
|
||||
{
|
||||
return items;
|
||||
}
|
||||
|
||||
- (void) removeItem: (id)item
|
||||
{
|
||||
[[self mutableArrayValueForKeyPath: @"items"] removeObject: item];
|
||||
}
|
||||
|
||||
- (void) setItems: (NSArray*)a
|
||||
{
|
||||
ASSIGNCOPY(items, a);
|
||||
}
|
||||
@end
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
ENTER_POOL
|
||||
NSString *s1 = [NSString stringWithFormat: @"Moose1"];
|
||||
NSString *s2 = [NSString stringWithFormat: @"Moose2"];
|
||||
|
||||
// Removing s1 then s2 works
|
||||
TestObject *t1 = AUTORELEASE([[TestObject alloc] init]);
|
||||
[t1 addItem: s1];
|
||||
[t1 addItem: s2];
|
||||
|
||||
PASS_RUNS(({ [t1 removeItem: s1]; [t1 removeItem: s2]; }),
|
||||
"array remove first t last")
|
||||
|
||||
// Removing s2 then s1 throws exception
|
||||
TestObject *t2 = AUTORELEASE([[TestObject alloc] init]);
|
||||
[t2 addItem: s1];
|
||||
[t2 addItem: s2];
|
||||
|
||||
PASS_RUNS(({ [t2 removeItem: s2]; [t2 removeItem: s1]; }),
|
||||
"array remove last to first")
|
||||
|
||||
LEAVE_POOL
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in a new issue