mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-31 08:41:03 +00:00
Memory usage improvements in attributed string.
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@8100 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
4c7e5c3dba
commit
a8ca001740
2 changed files with 203 additions and 29 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
2000-11-13 Richard Frith-Macdonald <rfm@gnu.org>
|
||||||
|
|
||||||
|
* Source/NSGAttributedString.m: partial rewrite to cache/unique
|
||||||
|
dictionaries and to coalesce adjacent regions of string with the
|
||||||
|
same attributes when setting attributes.
|
||||||
|
|
||||||
2000-11-12 Fred Kiefer <FredKiefer@gmx.de>
|
2000-11-12 Fred Kiefer <FredKiefer@gmx.de>
|
||||||
|
|
||||||
* Source/NSArray.m: Corrected [NSMutableArray arrayWithObject:] to
|
* Source/NSArray.m: Corrected [NSMutableArray arrayWithObject:] to
|
||||||
|
|
|
@ -50,11 +50,86 @@
|
||||||
#include <Foundation/NSRange.h>
|
#include <Foundation/NSRange.h>
|
||||||
#include <Foundation/NSDebug.h>
|
#include <Foundation/NSDebug.h>
|
||||||
#include <Foundation/NSArray.h>
|
#include <Foundation/NSArray.h>
|
||||||
|
#include <Foundation/NSLock.h>
|
||||||
|
#include <Foundation/NSThread.h>
|
||||||
|
#include <Foundation/NSNotification.h>
|
||||||
#include <Foundation/NSZone.h>
|
#include <Foundation/NSZone.h>
|
||||||
|
|
||||||
#define SANITY_CHECKS 0
|
#define SANITY_CHECKS 0
|
||||||
|
|
||||||
@interface GSAttrInfo : NSObject
|
#define GSI_MAP_RETAIN_KEY(X)
|
||||||
|
#define GSI_MAP_RELEASE_KEY(X)
|
||||||
|
#define GSI_MAP_RETAIN_VAL(X)
|
||||||
|
#define GSI_MAP_RELEASE_VAL(X)
|
||||||
|
#define GSI_MAP_EQUAL(X,Y) [(X).obj isEqualToDictionary: (Y).obj]
|
||||||
|
#define GSI_MAP_KTYPES GSUNION_OBJ
|
||||||
|
#define GSI_MAP_VTYPES GSUNION_INT
|
||||||
|
|
||||||
|
#include <base/GSIMap.h>
|
||||||
|
|
||||||
|
static NSLock *attrLock = nil;
|
||||||
|
static GSIMapTable_t attrMap;
|
||||||
|
static SEL lockSel;
|
||||||
|
static SEL unlockSel;
|
||||||
|
static IMP lockImp;
|
||||||
|
static IMP unlockImp;
|
||||||
|
|
||||||
|
#define ALOCK() if (attrLock != nil) (*lockImp)(attrLock, lockSel)
|
||||||
|
#define AUNLOCK() if (attrLock != nil) (*unlockImp)(attrLock, unlockSel)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a dictionary to the cache - if it was not already there, return
|
||||||
|
* the copy added to the cache, if it was, count it and return retained
|
||||||
|
* object that was there.
|
||||||
|
*/
|
||||||
|
static NSDictionary*
|
||||||
|
cacheAttributes(NSDictionary *attrs)
|
||||||
|
{
|
||||||
|
GSIMapNode node;
|
||||||
|
|
||||||
|
ALOCK();
|
||||||
|
node = GSIMapNodeForKey(&attrMap, (GSIMapKey)attrs);
|
||||||
|
if (node == 0)
|
||||||
|
{
|
||||||
|
attrs = [attrs copy];
|
||||||
|
GSIMapAddPair(&attrMap, (GSIMapKey)attrs, (GSIMapVal)(unsigned)1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node->value.uint++;
|
||||||
|
attrs = RETAIN(node->key.obj);
|
||||||
|
}
|
||||||
|
AUNLOCK();
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
unCacheAttributes(NSDictionary *attrs)
|
||||||
|
{
|
||||||
|
GSIMapBucket bucket;
|
||||||
|
|
||||||
|
ALOCK();
|
||||||
|
bucket = GSIMapBucketForKey(&attrMap, (GSIMapKey)attrs);
|
||||||
|
if (bucket != 0)
|
||||||
|
{
|
||||||
|
GSIMapNode node;
|
||||||
|
|
||||||
|
node = GSIMapNodeForKeyInBucket(bucket, (GSIMapKey)attrs);
|
||||||
|
if (node != 0)
|
||||||
|
{
|
||||||
|
if (--node->value.uint == 0)
|
||||||
|
{
|
||||||
|
GSIMapRemoveNodeFromMap(&attrMap, bucket, node);
|
||||||
|
GSIMapFreeNode(&attrMap, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AUNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@interface GSAttrInfo : NSObject <GCFinalization>
|
||||||
{
|
{
|
||||||
@public
|
@public
|
||||||
unsigned loc;
|
unsigned loc;
|
||||||
|
@ -67,18 +142,23 @@
|
||||||
|
|
||||||
@implementation GSAttrInfo
|
@implementation GSAttrInfo
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called to record attributes at a particular location - the given attributes
|
||||||
|
* dictionary must have been produced by 'cacheAttributes()' so that it is
|
||||||
|
* already copied/retained and this method doesn't need to do it.
|
||||||
|
*/
|
||||||
+ (GSAttrInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l;
|
+ (GSAttrInfo*) newWithZone: (NSZone*)z value: (NSDictionary*)a at: (unsigned)l;
|
||||||
{
|
{
|
||||||
GSAttrInfo *info = (GSAttrInfo*)NSAllocateObject(self, 0, z);
|
GSAttrInfo *info = (GSAttrInfo*)NSAllocateObject(self, 0, z);
|
||||||
|
|
||||||
info->loc = l;
|
info->loc = l;
|
||||||
info->attrs = [a copyWithZone: z];
|
info->attrs = a;
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) dealloc
|
- (void) dealloc
|
||||||
{
|
{
|
||||||
RELEASE(attrs);
|
[self gcFinalize];
|
||||||
NSDeallocateObject(self);
|
NSDeallocateObject(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +168,33 @@
|
||||||
loc, attrs];
|
loc, attrs];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) encodeWithCoder: (NSCoder*)aCoder
|
||||||
|
{
|
||||||
|
[aCoder encodeValueOfObjCType: @encode(unsigned) at: &loc];
|
||||||
|
[aCoder encodeValueOfObjCType: @encode(id) at: &attrs];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) gcFinalize
|
||||||
|
{
|
||||||
|
unCacheAttributes(attrs);
|
||||||
|
DESTROY(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) initWithCoder: (NSCoder*)aCoder
|
||||||
|
{
|
||||||
|
NSDictionary *a;
|
||||||
|
|
||||||
|
[aCoder decodeValueOfObjCType: @encode(unsigned) at: &loc];
|
||||||
|
a = [aCoder decodeObject];
|
||||||
|
attrs = cacheAttributes(a);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
|
||||||
|
{
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +229,8 @@ static void _setup()
|
||||||
{
|
{
|
||||||
NSMutableArray *a;
|
NSMutableArray *a;
|
||||||
|
|
||||||
|
GSIMapInitWithZoneAndCapacity(&attrMap, NSDefaultMallocZone(), 32);
|
||||||
|
|
||||||
infSel = @selector(newWithZone:value:at:);
|
infSel = @selector(newWithZone:value:at:);
|
||||||
addSel = @selector(addObject:);
|
addSel = @selector(addObject:);
|
||||||
cntSel = @selector(count);
|
cntSel = @selector(count);
|
||||||
|
@ -165,7 +274,8 @@ _setAttributesFrom(
|
||||||
|
|
||||||
attr = [attributedString attributesAtIndex: aRange.location
|
attr = [attributedString attributesAtIndex: aRange.location
|
||||||
effectiveRange: &range];
|
effectiveRange: &range];
|
||||||
info = [GSAttrInfo newWithZone: z value: attr at: 0];
|
attr = cacheAttributes(attr);
|
||||||
|
info = NEWINFO(z, attr, 0);
|
||||||
ADDOBJECT(info);
|
ADDOBJECT(info);
|
||||||
RELEASE(info);
|
RELEASE(info);
|
||||||
|
|
||||||
|
@ -173,7 +283,8 @@ _setAttributesFrom(
|
||||||
{
|
{
|
||||||
attr = [attributedString attributesAtIndex: loc
|
attr = [attributedString attributesAtIndex: loc
|
||||||
effectiveRange: &range];
|
effectiveRange: &range];
|
||||||
info = [GSAttrInfo newWithZone: z value: attr at: loc - aRange.location];
|
attr = cacheAttributes(attr);
|
||||||
|
info = NEWINFO(z, attr, loc - aRange.location);
|
||||||
ADDOBJECT(info);
|
ADDOBJECT(info);
|
||||||
RELEASE(info);
|
RELEASE(info);
|
||||||
}
|
}
|
||||||
|
@ -263,9 +374,34 @@ _attributesAtIndexEffectiveRange(
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are multi-threaded, we must guard access to the uniquing set.
|
||||||
|
*/
|
||||||
|
+ (void) _becomeThreaded: (id)notification
|
||||||
|
{
|
||||||
|
attrLock = [NSLock new];
|
||||||
|
lockSel = @selector(lock);
|
||||||
|
unlockSel = @selector(unlock);
|
||||||
|
lockImp = [attrLock methodForSelector: lockSel];
|
||||||
|
unlockImp = [attrLock methodForSelector: unlockSel];
|
||||||
|
}
|
||||||
|
|
||||||
+ (void) initialize
|
+ (void) initialize
|
||||||
{
|
{
|
||||||
_setup();
|
_setup();
|
||||||
|
|
||||||
|
if ([NSThread isMultiThreaded])
|
||||||
|
{
|
||||||
|
[self _becomeThreaded: nil];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter]
|
||||||
|
addObserver: self
|
||||||
|
selector: @selector(_becomeThreaded:)
|
||||||
|
name: NSWillBecomeMultiThreadedNotification
|
||||||
|
object: nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id) initWithString: (NSString*)aString
|
- (id) initWithString: (NSString*)aString
|
||||||
|
@ -285,6 +421,11 @@ _attributesAtIndexEffectiveRange(
|
||||||
{
|
{
|
||||||
GSAttrInfo *info;
|
GSAttrInfo *info;
|
||||||
|
|
||||||
|
if (attributes == nil)
|
||||||
|
{
|
||||||
|
attributes = [NSDictionary dictionary];
|
||||||
|
}
|
||||||
|
attributes = cacheAttributes(attributes);
|
||||||
info = NEWINFO(z, attributes, 0);
|
info = NEWINFO(z, attributes, 0);
|
||||||
ADDOBJECT(info);
|
ADDOBJECT(info);
|
||||||
RELEASE(info);
|
RELEASE(info);
|
||||||
|
@ -349,7 +490,7 @@ _attributesAtIndexEffectiveRange(
|
||||||
|
|
||||||
+ (void) initialize
|
+ (void) initialize
|
||||||
{
|
{
|
||||||
_setup();
|
[NSGAttributedString class]; // Ensure immutable class is initialised
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id) initWithString: (NSString*)aString
|
- (id) initWithString: (NSString*)aString
|
||||||
|
@ -370,6 +511,11 @@ SANITY();
|
||||||
{
|
{
|
||||||
GSAttrInfo *info;
|
GSAttrInfo *info;
|
||||||
|
|
||||||
|
if (attributes == nil)
|
||||||
|
{
|
||||||
|
attributes = [NSDictionary dictionary];
|
||||||
|
}
|
||||||
|
attributes = cacheAttributes(attributes);
|
||||||
info = NEWINFO(z, attributes, 0);
|
info = NEWINFO(z, attributes, 0);
|
||||||
ADDOBJECT(info);
|
ADDOBJECT(info);
|
||||||
RELEASE(info);
|
RELEASE(info);
|
||||||
|
@ -394,6 +540,17 @@ SANITY();
|
||||||
index, aRange, [_textChars length], _infoArray, &dummy);
|
index, aRange, [_textChars length], _infoArray, &dummy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Primitive method! Sets attributes and values for a given range of
|
||||||
|
* characters, replacing any previous attributes and values for that
|
||||||
|
* range.
|
||||||
|
*
|
||||||
|
* Sets the attributes for the characters in aRange to attributes.
|
||||||
|
* These new attributes replace any attributes previously associated
|
||||||
|
* with the characters in aRange. Raises an NSRangeException if any
|
||||||
|
* part of aRange lies beyond the end of the receiver's characters.
|
||||||
|
* See also: - addAtributes: range: , - removeAttributes: range:
|
||||||
|
*/
|
||||||
- (void) setAttributes: (NSDictionary*)attributes
|
- (void) setAttributes: (NSDictionary*)attributes
|
||||||
range: (NSRange)range
|
range: (NSRange)range
|
||||||
{
|
{
|
||||||
|
@ -413,6 +570,7 @@ SANITY();
|
||||||
{
|
{
|
||||||
attributes = [NSDictionary dictionary];
|
attributes = [NSDictionary dictionary];
|
||||||
}
|
}
|
||||||
|
attributes = cacheAttributes(attributes);
|
||||||
SANITY();
|
SANITY();
|
||||||
tmpLength = [_textChars length];
|
tmpLength = [_textChars length];
|
||||||
GS_RANGE_CHECK(range, tmpLength);
|
GS_RANGE_CHECK(range, tmpLength);
|
||||||
|
@ -426,7 +584,25 @@ SANITY();
|
||||||
*/
|
*/
|
||||||
attrs = _attributesAtIndexEffectiveRange(
|
attrs = _attributesAtIndexEffectiveRange(
|
||||||
afterRangeLoc, &effectiveRange, tmpLength, _infoArray, &arrayIndex);
|
afterRangeLoc, &effectiveRange, tmpLength, _infoArray, &arrayIndex);
|
||||||
if (effectiveRange.location > beginRangeLoc)
|
if (attrs == attributes)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The located range has the same attributes as us - so we can
|
||||||
|
* extend our range to include it.
|
||||||
|
*/
|
||||||
|
if (effectiveRange.location < beginRangeLoc)
|
||||||
|
{
|
||||||
|
range.length += beginRangeLoc - effectiveRange.location;
|
||||||
|
range.location = effectiveRange.location;
|
||||||
|
beginRangeLoc = range.location;
|
||||||
|
}
|
||||||
|
if (NSMaxRange(effectiveRange) > afterRangeLoc)
|
||||||
|
{
|
||||||
|
range.length = NSMaxRange(effectiveRange) - range.location;
|
||||||
|
afterRangeLoc = NSMaxRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (effectiveRange.location > beginRangeLoc)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The located range also starts at or after our range.
|
* The located range also starts at or after our range.
|
||||||
|
@ -435,13 +611,13 @@ SANITY();
|
||||||
info->loc = afterRangeLoc;
|
info->loc = afterRangeLoc;
|
||||||
arrayIndex--;
|
arrayIndex--;
|
||||||
}
|
}
|
||||||
else
|
else if (effectiveRange.location < beginRangeLoc)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The located range starts before our range.
|
* The located range starts before our range.
|
||||||
* Create a subrange to go from our end to the end of the old range.
|
* Create a subrange to go from our end to the end of the old range.
|
||||||
*/
|
*/
|
||||||
info = NEWINFO(z, attrs, afterRangeLoc);
|
info = NEWINFO(z, cacheAttributes(attrs), afterRangeLoc);
|
||||||
arrayIndex++;
|
arrayIndex++;
|
||||||
INSOBJECT(info, arrayIndex);
|
INSOBJECT(info, arrayIndex);
|
||||||
RELEASE(info);
|
RELEASE(info);
|
||||||
|
@ -465,11 +641,17 @@ SANITY();
|
||||||
arrayIndex--;
|
arrayIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use the location/attribute info in the current slot if possible,
|
||||||
|
* otherwise, add a new slot and use that.
|
||||||
|
*/
|
||||||
info = OBJECTAT(arrayIndex);
|
info = OBJECTAT(arrayIndex);
|
||||||
if (info->loc >= beginRangeLoc)
|
if (info->loc >= beginRangeLoc || info->attrs == attributes)
|
||||||
{
|
{
|
||||||
info->loc = beginRangeLoc;
|
info->loc = beginRangeLoc;
|
||||||
ASSIGNCOPY(info->attrs, attributes);
|
unCacheAttributes(info->attrs);
|
||||||
|
RELEASE(info->attrs);
|
||||||
|
info->attrs = attributes;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -480,19 +662,6 @@ SANITY();
|
||||||
}
|
}
|
||||||
|
|
||||||
SANITY();
|
SANITY();
|
||||||
/*
|
|
||||||
* Primitive method! Sets attributes and values for a given range of
|
|
||||||
* characters, replacing any previous attributes and values for that
|
|
||||||
* range.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sets the attributes for the characters in aRange to attributes.
|
|
||||||
* These new attributes replace any attributes previously associated
|
|
||||||
* with the characters in aRange. Raises an NSRangeException if any
|
|
||||||
* part of aRange lies beyond the end of the receiver's characters.
|
|
||||||
* See also: - addAtributes: range: , - removeAttributes: range:
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) replaceCharactersInRange: (NSRange)range
|
- (void) replaceCharactersInRange: (NSRange)range
|
||||||
|
@ -520,8 +689,7 @@ SANITY();
|
||||||
* simply appends the new string and attributes are inherited.
|
* simply appends the new string and attributes are inherited.
|
||||||
*/
|
*/
|
||||||
[_textChars appendString: aString];
|
[_textChars appendString: aString];
|
||||||
SANITY();
|
goto finish;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arraySize = (*cntImp)(_infoArray, cntSel);
|
arraySize = (*cntImp)(_infoArray, cntSel);
|
||||||
|
@ -532,8 +700,7 @@ SANITY();
|
||||||
* then the replacement characters will get them too.
|
* then the replacement characters will get them too.
|
||||||
*/
|
*/
|
||||||
[_textChars replaceCharactersInRange: range withString: aString];
|
[_textChars replaceCharactersInRange: range withString: aString];
|
||||||
SANITY();
|
goto finish;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -552,7 +719,7 @@ SANITY();
|
||||||
arrayIndex++;
|
arrayIndex++;
|
||||||
if (NSMaxRange(effectiveRange) > NSMaxRange(range))
|
if (NSMaxRange(effectiveRange) > NSMaxRange(range))
|
||||||
{
|
{
|
||||||
info = NEWINFO(z, attrs, NSMaxRange(range));
|
info = NEWINFO(z, cacheAttributes(attrs), NSMaxRange(range));
|
||||||
INSOBJECT(info, arrayIndex);
|
INSOBJECT(info, arrayIndex);
|
||||||
arraySize++;
|
arraySize++;
|
||||||
SANITY();
|
SANITY();
|
||||||
|
@ -613,6 +780,7 @@ SANITY();
|
||||||
}
|
}
|
||||||
SANITY();
|
SANITY();
|
||||||
[_textChars replaceCharactersInRange: range withString: aString];
|
[_textChars replaceCharactersInRange: range withString: aString];
|
||||||
|
finish:
|
||||||
SANITY();
|
SANITY();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue