mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-30 08:21:25 +00:00
Merge branch 'master' of github.com:gnustep/libs-base
This commit is contained in:
commit
1e51260895
20 changed files with 420 additions and 102 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -98,7 +98,6 @@ compile_commands.json
|
|||
# Documentation
|
||||
Documentation/Base*
|
||||
Documentation/General
|
||||
Documentation/manual
|
||||
Documentation/ReleaseNotes
|
||||
Documentation/ANNOUNCE
|
||||
Documentation/*.pdf
|
||||
|
@ -108,6 +107,7 @@ Documentation/*.aux
|
|||
Documentation/*.toc
|
||||
Documentation/INSTALL
|
||||
Documentation/NEWS
|
||||
Documentation/manual/manual.*
|
||||
**/dependencies
|
||||
Source/Base.gsdoc
|
||||
Source/BaseAdditions.gsdoc
|
||||
Source/BaseAdditions.gsdoc
|
||||
|
|
|
@ -152,10 +152,9 @@ the NSApplicationMain function in a gui application).
|
|||
|
||||
GNUstep's NSInvocations and Distributed Objects code involves detailed
|
||||
manipulation of the stack and function calls using a library that
|
||||
implements a Foreign-Function Interface (FFI), such as the
|
||||
libffi library. Use of libffi is automatically enabled if the libffi
|
||||
library is found (and the same with ffcall, although libffi takes
|
||||
precedence), unless specifically disabled with @code{--disable-do}.
|
||||
implements a Foreign-Function Interface (FFI); the libffi library.
|
||||
Use of libffi is automatically enabled if the libffi library is found ,
|
||||
unless specifically disabled with @code{--disable-do}.
|
||||
|
||||
@node Compilation, , Configuration, Top
|
||||
@section Compilation
|
||||
|
|
|
@ -451,6 +451,24 @@ of statistics collection is only incurred when it is active. To access the
|
|||
statistics, use the set of @code{GSDebugAllocation...()} functions defined in
|
||||
@code{NSDebug.h}.
|
||||
|
||||
In addition to basic statistics (but at higher performance cose), the
|
||||
@code{GSDebugAllocation...()} functions provide detailed records of when and
|
||||
where objects are allocated/deallocated. This can be useful when debugging
|
||||
for memory leaks.
|
||||
|
||||
Finally, for pinpoint accuracy, the -trackOwnership method can be called on
|
||||
an individual object to turn on tracking of the lifetime of that object. In
|
||||
this case a stack trace is printed logging every ownership event (retain,
|
||||
release, or dealloc) and a log is printed at process exit if the object
|
||||
has not been deallocated. The same method may be called on a class to
|
||||
track every object of that class. This method is declared in
|
||||
@code{NSObject+GNUstepBase.h}. Tracking the life of an individual object is
|
||||
particularly useful if a leak checker (eg when your program was built using
|
||||
@code{(make asan=yes)} or run under valgrind) has reported a leak and the
|
||||
cause of the leak is hard to find: the leak checker will have told you the
|
||||
stack trace where the leaked memory was allocated, so you can change your
|
||||
code to start tracking immediately after that and see exacly what happened
|
||||
to the object ownership after its creation.
|
||||
|
||||
@section Assertions
|
||||
@cindex assertions
|
||||
|
|
|
@ -130,7 +130,7 @@ top-level directory of the package. A non-GNUstep Objective-C file may be
|
|||
compiled by adding @code{-lobjc on} at the command line.
|
||||
|
||||
|
||||
@subsection Debug and Profile Information
|
||||
@subsection Debug, Profile and Sanitization
|
||||
|
||||
|
||||
By default the Makefile Package does not flag the compiler to generate debugging
|
||||
|
@ -143,10 +143,15 @@ therefore necessary to override the optimization flag when running Make if both
|
|||
debugging information and optimization is required. Use the variable OPTFLAG to
|
||||
override the optimization flag.
|
||||
|
||||
By default the Makefile Package does not instruct the compiler to create profiling
|
||||
information that is generated by typing:
|
||||
By default the Makefile Package does not instruct the compiler to create
|
||||
profiling information that is generated by typing:
|
||||
|
||||
@code{make profile=yes}
|
||||
|
||||
By default the Makefile Package does not instruct the compiler to create
|
||||
address and leak sanitization information. This is turned on by typing:
|
||||
|
||||
@code{make asan=yes}
|
||||
@sp 1
|
||||
|
||||
@subsection Static, Shared and DLLs
|
||||
|
|
|
@ -124,7 +124,7 @@ in the same area of memory, or allocate in chunks - perhaps for performance
|
|||
reasons, you may create a Zone from where you would allocate those objects by
|
||||
using the @code{NSCreateZone} function. This will minimise the paging
|
||||
required by your application when accessing those objects frequently.
|
||||
In all normal yuse however, you should confine yourself to the default zone.
|
||||
In all normal use however, you should confine yourself to the default zone.
|
||||
|
||||
Low level memory allocation is performed by the @code{NSAllocateObject()}
|
||||
function. This is rarely used but available when you require more advanced
|
||||
|
@ -139,6 +139,10 @@ will probably not need to worry about Zones at all; unless performance is
|
|||
critical, you can just use the methods without zone arguments, that take the
|
||||
default zone.
|
||||
|
||||
With the ObjC-2 (NG) setup, the use of zones is obsoleted: the runtime
|
||||
library performs the actual allocation of objects and ignores the zone
|
||||
information.
|
||||
|
||||
|
||||
@subsection Memory Deallocation
|
||||
@cindex memory deallocation
|
||||
|
@ -159,6 +163,9 @@ As with @code{alloc}, the underlying implementation utilizes a function
|
|||
(@code{NSDeallocateObject()}) that can be used by your code if you know what
|
||||
you are doing.
|
||||
|
||||
With the ObjC-2 (NG) setup, the use of zones is obsoleted: the runtime
|
||||
library performs the freeing of memory used by objects.
|
||||
|
||||
|
||||
@section Memory Management
|
||||
@cindex memory management
|
||||
|
@ -180,6 +187,13 @@ pools which provide a degree of automated memory management. This gives
|
|||
a good degree of control over memory management, but requires some care
|
||||
in following simple rules. It's pretty efficient.
|
||||
|
||||
@item Automated Reference Counts (ARC)@*
|
||||
Only available when using the ObjC-2 (NG) environment rather than classic
|
||||
Objective-C. In this case the compiler generates code to use the retain
|
||||
count and autorelease pools. The use of ARC can be turned on/off for
|
||||
individual files.
|
||||
|
||||
|
||||
@end itemize
|
||||
|
||||
The recommended approach is to use some standard macros defined in
|
||||
|
@ -226,6 +240,12 @@ object gets deallocated.
|
|||
[c release]; // Calls 'release' ... (retain count 0) then 'dealloc'
|
||||
@end example
|
||||
|
||||
Retain count is best understood using the concept of ownership. When we
|
||||
retain an object we own it and are responsible for releasing it again.
|
||||
When nobody owns an object (its retain count is zero) it is deallocated.
|
||||
The retain count of an object is the number of places which own the object
|
||||
and have therefore undertaken to release it when they have finished with it.
|
||||
|
||||
One way of thinking about the initial retain count of 1 on the object is that
|
||||
a call to @code{alloc} (or @code{copy}) implicitly calls @code{retain} as
|
||||
well. There are a couple of default conventions about how @code{retain} and
|
||||
|
@ -256,7 +276,7 @@ Thus, a typical usage pattern is:
|
|||
Retain and release must also be used for instance variables that are objects:
|
||||
|
||||
@example
|
||||
- (void)setFoo:(FooClass *newFoo)
|
||||
- (void) setFoo: (FooClass *newFoo)
|
||||
@{
|
||||
// first, assert reference to newFoo
|
||||
[newFoo retain];
|
||||
|
@ -268,11 +288,15 @@ Retain and release must also be used for instance variables that are objects:
|
|||
@}
|
||||
@end example
|
||||
|
||||
To write portable code (which will work with both the classic retain counting
|
||||
mechanism and with ARC) you should use the macros RETAIN(expr) and
|
||||
RELEASE(expr) along with the DESTROY(lvalue) and ASSIGN(lvalue, expr) macros.
|
||||
|
||||
Because of this retain/release management, it is safest to use accessor
|
||||
methods to set variables even within a class:
|
||||
|
||||
@example
|
||||
- (void)resetFoo
|
||||
- (void) resetFoo
|
||||
@{
|
||||
FooClass *foo = [[FooClass alloc] init];
|
||||
[self setFoo: foo];
|
||||
|
@ -301,17 +325,17 @@ general you need to be careful in these cases that retains and releases match.
|
|||
One important case where the retain/release system has difficulties is when
|
||||
an object needs to be transferred or handed off to another. You don't want
|
||||
to retain the transferred object in the transferring code, but neither do you
|
||||
want the object to be destroyed before the handoff can take place. The
|
||||
want the object to be destroyed before the hand-off can take place. The
|
||||
OpenStep/GNUstep solution to this is the @i{autorelease pool}. An
|
||||
autorelease pool is a special mechanism that will retain objects it is given
|
||||
for a limited time -- always enough for a transfer to take place. This
|
||||
mechanism is accessed by calling @code{autorelease} on an object instead of
|
||||
@code{release}. @code{Autorelease} first adds the object to the active
|
||||
autorelease pool, which retains it, then sends a @code{release} to the
|
||||
object. At some point later on, the pool will send the object a second
|
||||
@code{release} message, but by this time the object will presumably either
|
||||
have been retained by some other code, or is no longer needed and can thus be
|
||||
deallocated. For example:
|
||||
autorelease pool, which retains it, then sends a @code{release} to the object.
|
||||
At some point later on (when the pool is destroyed), the pool will send the
|
||||
object a second @code{release} message, but by this time the object will
|
||||
presumably either have been retained by some other code, or is no longer
|
||||
needed and can thus be deallocated. For example:
|
||||
|
||||
@example
|
||||
- (NSString *) getStatus
|
||||
|
@ -346,6 +370,9 @@ stored and used later on however, it should be retained:
|
|||
[currentStatus retain];
|
||||
@end example
|
||||
|
||||
To write portable code (for both classic retain counting and ARC) you should
|
||||
use the AUTORELEASE(expr) macro.
|
||||
|
||||
@b{Convenience Constructors}
|
||||
|
||||
A special case of object transfer occurs when a @i{convenience} constructor is
|
||||
|
@ -372,7 +399,7 @@ retain it if you want to hold onto it for a while.
|
|||
|
||||
An autorelease pool is created automatically if you are using the GNUstep
|
||||
GUI classes, however if you are just using the GNUstep Base classes for a
|
||||
nongraphical application, you must create and release autorelease pools
|
||||
non-graphical application, you must create and release autorelease pools
|
||||
yourself:
|
||||
|
||||
@example
|
||||
|
@ -387,7 +414,7 @@ pool itself:
|
|||
[pool release];
|
||||
@end example
|
||||
|
||||
To achieve finer control over autorelease behavior you may also create
|
||||
To achieve finer control over autorelease behaviour you may also create
|
||||
additional pools and release them in a nested manner. Calls to
|
||||
@code{autorelease} will always use the most recently created pool.
|
||||
|
||||
|
@ -395,6 +422,11 @@ Finally, note that @code{autorelease} calls are significantly slower than
|
|||
plain @code{release}. Therefore you should only use them when they are
|
||||
necessary.
|
||||
|
||||
The best way to manage autorelease pools is using macros which will work
|
||||
both for the classic system or when using ARC. The ENTER_POOL macro
|
||||
begins a block in which a new pool handles autoreleases and the LEAVE_POOL
|
||||
macro ends that block and destroys the autorelease pool.
|
||||
|
||||
|
||||
@subsubsection Avoiding Retain Cycles
|
||||
|
||||
|
@ -408,6 +440,21 @@ careful with your designs. If you notice a situation where a retain cycle
|
|||
could arise, remove at least one of the links in the chain, but not in such a
|
||||
way that references to deallocated objects might be mistakenly used.
|
||||
|
||||
To help solve the problem of retain cycles you can use weak references
|
||||
to break a cycle. The runtime library provides functions to handle weak
|
||||
references so that you can safely check to see whether the reference is
|
||||
to an object that still exists or not. To manage that the objc_storeWeak()
|
||||
function is used whenever assigning a value to the variable (instead of
|
||||
retaining the value), and the objc_loadWeak() function is used to retrieve
|
||||
the value from the variable ... the retrieved value will be nil if the
|
||||
object has been deallocated. With the ObjC-2 (Next Generation) environment
|
||||
you can use the keyword `weak' to tell the compiler to automatically insert
|
||||
calls to those runtime functions whenever a value is written to or read from
|
||||
the variable.
|
||||
NB. weak references are relatively inefficient since each time objc_loadWeak()
|
||||
is called it both retains and autorelease the referenced value so that it
|
||||
will continue to exist for long enough for your code to work with it.
|
||||
|
||||
|
||||
@subsubsection Summary
|
||||
|
||||
|
@ -416,18 +463,18 @@ The following summarizes the retain/release-related methods:
|
|||
@multitable @columnfractions 0.25 0.75
|
||||
@item Method @tab Description
|
||||
@item @code{-retain}
|
||||
@tab increases the reference count of an object by 1
|
||||
@tab increases the retain count of an object by 1
|
||||
@item @code{-release}
|
||||
@tab decreases the reference count of an object by 1
|
||||
@tab decreases the retain count of an object by 1
|
||||
@item @code{-autorelease}
|
||||
@tab decreases the reference count of an object by 1 at some stage in the future
|
||||
@tab decreases the retain count of an object by 1 at some stage in the future
|
||||
@item @code{+alloc} and @code{+allocWithZone:}
|
||||
@tab allocates memory for an object, and returns it with retain count of 1
|
||||
@item @code{-copy}, @code{-mutableCopy}, @code{copyWithZone:} and @code{-mutableCopyWithZone:}
|
||||
@tab makes a copy of an object, and returns it with retain count of 1
|
||||
@item @code{-init} and any method whose name begins with @code{init}
|
||||
@tab initialises the receiver, returning the retain count unchanged.
|
||||
@code{-init} has had no effect on the reference count.
|
||||
@code{-init} has had no effect on the retain count.
|
||||
@item @code{-new} and any method whose name begins with @code{new}
|
||||
@tab allocates memory for an object, initialises it, and returns the result.
|
||||
@item @code{-dealloc}
|
||||
|
@ -473,6 +520,57 @@ ownership rules (how you should use the returned values) remain the same.
|
|||
Special examples: delegate, target
|
||||
@end ignore
|
||||
|
||||
@subsubsection Leak Checking
|
||||
|
||||
Looking at the following code:
|
||||
|
||||
@example
|
||||
#import "Client.h"
|
||||
|
||||
@@implementation Client
|
||||
- (void) executeCallSequence
|
||||
@{
|
||||
NSString *str = [NSString stringWithFormat: @@"one little string: %d\n", 100];
|
||||
const char *strCharPtr = [str cString];
|
||||
@}
|
||||
@@end
|
||||
|
||||
int main(int argv, char** argc)
|
||||
@{
|
||||
Client *client = [[Client alloc] init];
|
||||
|
||||
[[NSAutoreleasePool alloc] init];
|
||||
[client executeCallSequence];
|
||||
|
||||
return 0;
|
||||
@}
|
||||
@end example
|
||||
|
||||
So, what do we expect this to do if we build the program with leak checking ('make asan=yes') or run it with a separate leak checker such as valgrind?
|
||||
|
||||
Firstly this code creates a Client instance, owned by the main function. This is because +alloc returns an instance owned by the caller, and -init consumes its receiver and returns an instance owned by the caller, so the alloc/init sequence produces an instance owned by the main function.
|
||||
|
||||
Next it creates/enters an autorelease pool, owned by the main function.
|
||||
|
||||
Next it executes the method '-[Client executeCallSequence]' which:
|
||||
|
||||
Creates an NSString which is NOT owned by the method.
|
||||
|
||||
The +stringWithFormat: method creates a new instance and adds it to the current autorelease pool before returning it.
|
||||
|
||||
Creates a C string, which is NOT owned by the method.
|
||||
|
||||
A non-object return value can't be retained or released, but it conforms to the convention that the memory is not owned by the caller, so the caller need not free it. The -cString method is free to manage that however it likes (for instance it might return a pointer to some internal memory which exists until the NSString object is deallocated), but typically what's returned is a pointer to memory inside some other object which has been autoreleased.
|
||||
|
||||
Finally, the 'return' command means that the program exits with a status of zero.
|
||||
|
||||
|
||||
A simple look at the basic retain count and autorelease rules would say that all the memory is leaked (because the program contains no call to release anything), but there's a bit of behind the scenes magic: when a thread exits it releases all the autorelease pools created in it which were not already released. That's not to say that the failure to release the autorelease pool was not a bug (the code should have released it), just that there is a fail-safe behaviour to protect multithreaded programs from this particular programmer error.
|
||||
|
||||
So when you consider that, you can see that the autorelease pool is deallocated so the memory of the pool is actually freed, and the memory of the NSString and C-String inside it are therefore also freed.
|
||||
|
||||
This leaves us with the memory of the Client object being leaked. However, the idea that any unfreed memory is a leak is too simplistic (leak checkers would be useless if they reported so much) so the leak checker only reports some unfreed memory ... stuff that can't be reached from various standard routes. The main case is that anything pointed to by global or static variables is not considered leaked, but also anything pointed to by a variable in the main() function is not considered leaked. This is why the Client instance would not normally be reported by a leak checker.
|
||||
|
||||
|
||||
@subsection ObjC-2 and Automated Reference Counting
|
||||
@cindex ObjC-2 , automated reference counting
|
||||
|
@ -496,13 +594,13 @@ manual reference counting required when ARC is not available.
|
|||
@tab @code{[foo autorelease];}
|
||||
|
||||
@item @code{ASSIGN(foo, bar);}
|
||||
@tab @code{[bar retain]; [foo release]; foo = bar;}
|
||||
@tab @code{id tmp = [bar retain]; [foo release]; foo = tmp;}
|
||||
|
||||
@item @code{ASSIGNCOPY(foo, bar);}
|
||||
@tab @code{[foo release]; foo = [bar copy];}
|
||||
@tab @code{id tmp = [bar copy]; [foo release]; foo = tmp;}
|
||||
|
||||
@item @code{ASSIGNMUTABLECOPY(foo, bar);}
|
||||
@tab @code{[foo release]; foo = [bar mutableCopy];}
|
||||
@tab @code{id tmp = [bar mutableCopy]; [foo release]; foo = tmp;}
|
||||
|
||||
@item @code{DESTROY(foo);}
|
||||
@tab @code{[foo release]; foo = nil;}
|
||||
|
|
|
@ -40,12 +40,6 @@ All files in the @file{Documentation}, @file{Examples},
|
|||
@file{Tools}, @file{config}, and @file{macosx} directories are covered
|
||||
under the GPL.
|
||||
|
||||
With GNUstep-Base, we strongly recommend the use of the ffcall
|
||||
libraries, which provides stack frame handling for NSInvocation and
|
||||
NSConnection. "Ffcall is under GNU GPL. As a special exception, if used
|
||||
in GNUstep or in derivate works of GNUstep, the included parts of ffcall
|
||||
are under GNU LGPL" (Text in quotes provided by the author of ffcall).
|
||||
|
||||
@section How can you help?
|
||||
|
||||
@itemize @bullet
|
||||
|
|
|
@ -29,8 +29,12 @@
|
|||
|
||||
#define CF_DEFINES_CG_TYPES
|
||||
|
||||
#if defined(__has_attribute) && __has_attribute(objc_boxable)
|
||||
# define CF_BOXABLE __attribute__((objc_boxable))
|
||||
#if defined __has_attribute
|
||||
# if __has_attribute(objc_boxable)
|
||||
# define CF_BOXABLE __attribute__((objc_boxable))
|
||||
# else
|
||||
# define CF_BOXABLE
|
||||
# endif
|
||||
#else
|
||||
# define CF_BOXABLE
|
||||
#endif
|
||||
|
|
|
@ -97,12 +97,3 @@
|
|||
}
|
||||
@end
|
||||
|
||||
/**
|
||||
* objc_enumerationMutation() is called whenever a collection mutates in the
|
||||
* middle of fast enumeration.
|
||||
*/
|
||||
void objc_enumerationMutation(id obj)
|
||||
{
|
||||
[NSException raise: NSGenericException
|
||||
format: @"Collection %@ was mutated while being enumerated", obj];
|
||||
}
|
||||
|
|
|
@ -44,13 +44,15 @@
|
|||
|
||||
/* This Key Value Observing Implementation is tied to libobjc2 */
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSSet.h>
|
||||
#import <Foundation/NSKeyValueObserving.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import "Foundation/NSObject.h"
|
||||
#import "Foundation/NSString.h"
|
||||
#import "Foundation/NSDictionary.h"
|
||||
#import "Foundation/NSArray.h"
|
||||
#import "Foundation/NSSet.h"
|
||||
#import "Foundation/NSKeyValueObserving.h"
|
||||
#import "Foundation/NSException.h"
|
||||
|
||||
#import "GSPrivate.h"
|
||||
|
||||
#if defined(__OBJC2__)
|
||||
|
||||
|
@ -114,7 +116,7 @@
|
|||
|
||||
// From NSKVOSwizzling
|
||||
void
|
||||
_NSKVOEnsureKeyWillNotify(id object, NSString *key);
|
||||
_NSKVOEnsureKeyWillNotify(id object, NSString *key) GS_ATTRIB_PRIVATE;
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -123,6 +125,7 @@ _NSKVOEnsureKeyWillNotify(id object, NSString *key);
|
|||
*/
|
||||
@interface
|
||||
NSObject (NSKeyValueObservingPrivate)
|
||||
- (Class)_underlyingClass;
|
||||
- (void)_notifyObserversOfChangeForKey:(NSString *)key
|
||||
oldValue:(id)oldValue
|
||||
newValue:(id)newValue;
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
typedef void (^DispatchChangeBlock)(_NSKVOKeyObserver *);
|
||||
|
||||
NSString *
|
||||
static NSString *
|
||||
_NSKVCSplitKeypath(NSString *keyPath, NSString *__autoreleasing *pRemainder)
|
||||
{
|
||||
NSRange result = [keyPath rangeOfString:@"."];
|
||||
|
@ -333,7 +333,11 @@ _addNestedObserversAndOptionallyDependents(_NSKVOKeyObserver *keyObserver,
|
|||
// Aggregate all keys whose values will affect us.
|
||||
if (dependents)
|
||||
{
|
||||
Class cls = [object class];
|
||||
// Make sure to retrieve the underlying class of the observee.
|
||||
// This is just [object class] for an NSObject derived class.
|
||||
// When observing an object through a proxy, we instead use KVC
|
||||
// to optain the underlying class.
|
||||
Class cls = [object _underlyingClass];
|
||||
NSSet *valueInfluencingKeys = [cls keyPathsForValuesAffectingValueForKey: key];
|
||||
if (valueInfluencingKeys.count > 0)
|
||||
{
|
||||
|
@ -1123,6 +1127,11 @@ static const NSString *_NSKeyValueChangeOldSetValue
|
|||
@implementation
|
||||
NSObject (NSKeyValueObservingPrivate)
|
||||
|
||||
- (Class)_underlyingClass
|
||||
{
|
||||
return [self class];
|
||||
}
|
||||
|
||||
- (void)_notifyObserversOfChangeForKey: (NSString *)key
|
||||
oldValue: (id)oldValue
|
||||
newValue: (id)newValue
|
||||
|
@ -1254,3 +1263,19 @@ NSSet (NSKeyValueObserving)
|
|||
@end
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region KVO forwarding - NSProxy category
|
||||
|
||||
@implementation
|
||||
NSProxy (NSKeyValueObserving)
|
||||
|
||||
- (Class)_underlyingClass
|
||||
{
|
||||
// Retrieve the underlying class via KVC
|
||||
// Note that we assume that the class is KVC-compliant, when KVO is used
|
||||
return [(NSObject *)self valueForKey: @"class"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma endregion
|
||||
|
|
|
@ -476,7 +476,7 @@ GENERATE_NOTIFYING_SET_IMPL(notifyingSetImplPointer, void *);
|
|||
break; \
|
||||
}
|
||||
|
||||
SEL
|
||||
static SEL
|
||||
KVCSetterForPropertyName(NSObject *self, const char *key)
|
||||
{
|
||||
SEL sel = nil;
|
||||
|
@ -649,7 +649,7 @@ _NSKVOEnsureUnorderedCollectionWillNotify(id object, NSString *key,
|
|||
}
|
||||
}
|
||||
|
||||
char *
|
||||
static char *
|
||||
mutableBufferFromString(NSString *string)
|
||||
{
|
||||
NSUInteger lengthInBytes = [string length] + 1;
|
||||
|
@ -666,6 +666,8 @@ void
|
|||
_NSKVOEnsureKeyWillNotify(id object, NSString *key)
|
||||
{
|
||||
char *rawKey;
|
||||
Class cls;
|
||||
Class underlyingCls;
|
||||
|
||||
// Since we cannot replace the isa of tagged pointer objects, we can't swizzle
|
||||
// them.
|
||||
|
@ -674,8 +676,17 @@ _NSKVOEnsureKeyWillNotify(id object, NSString *key)
|
|||
return;
|
||||
}
|
||||
|
||||
cls = [object class];
|
||||
underlyingCls = [object _underlyingClass];
|
||||
// If cls differs from underlyingCls, object is actually a proxy.
|
||||
// Retrieve the underlying object with KVC.
|
||||
if (cls != underlyingCls)
|
||||
{
|
||||
object = [object valueForKey: @"self"];
|
||||
}
|
||||
|
||||
// A class is allowed to decline automatic swizzling for any/all of its keys.
|
||||
if (![[object class] automaticallyNotifiesObserversForKey: key])
|
||||
if (![underlyingCls automaticallyNotifiesObserversForKey: key])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
*/
|
||||
|
||||
#import "Foundation/NSString.h"
|
||||
#import "GSPrivate.h"
|
||||
|
||||
id
|
||||
valueForKeyWithCaching(id obj, NSString *aKey);
|
||||
valueForKeyWithCaching(id obj, NSString *aKey) GS_ATTRIB_PRIVATE;
|
||||
|
|
|
@ -426,7 +426,7 @@ _getBoxedBlockForMethod(NSString *key, Method method, SEL sel, uint64_t version)
|
|||
// resolveInstanceMethod:].
|
||||
//
|
||||
// objc_slot2 has the same struct layout as objc_method.
|
||||
Method _Nullable _class_getMethodRecursive(Class aClass, SEL aSelector,
|
||||
static Method _Nullable _class_getMethodRecursive(Class aClass, SEL aSelector,
|
||||
uint64_t *version)
|
||||
{
|
||||
struct objc_slot2 *slot;
|
||||
|
|
|
@ -83,6 +83,18 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
/* objc_enumerationMutation() is called whenever a collection mutates in the
|
||||
* middle of fast enumeration. We need to have this defined and linked into
|
||||
* any code that uses fast enumeration, so we define it in NSObject.h
|
||||
* This symbol is exported to take precedence over the weak symbol provided
|
||||
* by the runtime library.
|
||||
*/
|
||||
GS_EXPORT void objc_enumerationMutation(id obj)
|
||||
{
|
||||
[NSException raise: NSGenericException
|
||||
format: @"Collection %@ was mutated while being enumerated", obj];
|
||||
}
|
||||
|
||||
/* platforms which do not support weak */
|
||||
#if defined (__WIN32)
|
||||
#define WEAK_ATTRIBUTE
|
||||
|
|
|
@ -44,6 +44,7 @@ static Class concreteClass = Nil;
|
|||
void **_contents;
|
||||
unsigned _capacity;
|
||||
unsigned _grow_factor;
|
||||
unsigned long _version;
|
||||
}
|
||||
@end
|
||||
|
||||
|
@ -203,7 +204,7 @@ static Class concreteClass = Nil;
|
|||
{
|
||||
NSInteger count;
|
||||
|
||||
state->mutationsPtr = (unsigned long *)&state->mutationsPtr;
|
||||
state->mutationsPtr = state->mutationsPtr;
|
||||
count = MIN(len, [self count] - state->state);
|
||||
if (count > 0)
|
||||
{
|
||||
|
@ -312,6 +313,8 @@ static Class concreteClass = Nil;
|
|||
NSUInteger insert = 0;
|
||||
NSUInteger i;
|
||||
|
||||
_version++;
|
||||
|
||||
/* We can't use memmove here for __weak pointers, because that would omit the
|
||||
* required read barriers. We could use objc_memmoveCollectable() for strong
|
||||
* pointers, but we may as well use the same code path for everything
|
||||
|
@ -329,6 +332,7 @@ static Class concreteClass = Nil;
|
|||
}
|
||||
}
|
||||
_count = insert;
|
||||
_version++;
|
||||
}
|
||||
|
||||
- (id) copyWithZone: (NSZone*)zone
|
||||
|
@ -355,6 +359,16 @@ static Class concreteClass = Nil;
|
|||
return _count;
|
||||
}
|
||||
|
||||
- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState*)state
|
||||
objects: (__unsafe_unretained id[])stackbuf
|
||||
count: (NSUInteger)len
|
||||
{
|
||||
state->mutationsPtr = &_version;
|
||||
return [super countByEnumeratingWithState: state
|
||||
objects: stackbuf
|
||||
count: len];
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
int i;
|
||||
|
@ -529,6 +543,7 @@ static Class concreteClass = Nil;
|
|||
|
||||
- (void) removePointerAtIndex: (NSUInteger)index
|
||||
{
|
||||
_version++;
|
||||
if (index >= _count)
|
||||
{
|
||||
[self _raiseRangeExceptionWithIndex: index from: _cmd];
|
||||
|
@ -539,15 +554,18 @@ static Class concreteClass = Nil;
|
|||
pointerFunctionsMove(&_pf, &_contents[index-1], &_contents[index]);
|
||||
}
|
||||
_contents[--_count] = NULL;
|
||||
_version++;
|
||||
}
|
||||
|
||||
- (void) replacePointerAtIndex: (NSUInteger)index withPointer: (void*)item
|
||||
{
|
||||
_version++;
|
||||
if (index >= _count)
|
||||
{
|
||||
[self _raiseRangeExceptionWithIndex: index from: _cmd];
|
||||
}
|
||||
pointerFunctionsReplace(&_pf, &_contents[index], item);
|
||||
_version++;
|
||||
}
|
||||
|
||||
|
||||
|
@ -555,6 +573,7 @@ static Class concreteClass = Nil;
|
|||
|
||||
- (void) setCount: (NSUInteger)count
|
||||
{
|
||||
_version++;
|
||||
if (count > _count)
|
||||
{
|
||||
#if ZEROING
|
||||
|
@ -629,6 +648,7 @@ static Class concreteClass = Nil;
|
|||
pointerFunctionsRelinquish(&_pf, &_contents[_count]);
|
||||
}
|
||||
}
|
||||
_version++;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -303,7 +303,7 @@ progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
|
|||
*
|
||||
* libcurl does not unfold HTTP "folded headers" (deprecated since RFC 7230).
|
||||
*/
|
||||
size_t
|
||||
static size_t
|
||||
header_callback(char *ptr, size_t size, size_t nitems, void *userdata)
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
|
@ -689,7 +689,7 @@ header_callback(char *ptr, size_t size, size_t nitems, void *userdata)
|
|||
} /* header_callback */
|
||||
|
||||
/* CURLOPT_READFUNCTION: read callback for data uploads */
|
||||
size_t
|
||||
static size_t
|
||||
read_callback(char *buffer, size_t size, size_t nitems, void *userdata)
|
||||
{
|
||||
NSURLSession *session;
|
||||
|
|
|
@ -31,9 +31,6 @@
|
|||
#import "GSInternal.h"
|
||||
GS_PRIVATE_INTERNAL(NSXMLElement)
|
||||
|
||||
extern void cleanup_namespaces(xmlNodePtr node, xmlNsPtr ns);
|
||||
extern void ensure_oldNs(xmlNodePtr node);
|
||||
|
||||
@implementation NSXMLElement
|
||||
|
||||
- (void) dealloc
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#define _INCLUDED_NSXMLPRIVATE_H
|
||||
|
||||
#import "common.h"
|
||||
#import "GSPrivate.h"
|
||||
|
||||
#ifdef HAVE_LIBXML
|
||||
|
||||
|
@ -65,6 +66,9 @@
|
|||
*/
|
||||
#define XMLSTRING(X) ((const unsigned char*)[X UTF8String])
|
||||
|
||||
void cleanup_namespaces(xmlNodePtr node, xmlNsPtr ns) GS_ATTRIB_PRIVATE;
|
||||
BOOL ensure_oldNs(xmlNodePtr node) GS_ATTRIB_PRIVATE;
|
||||
|
||||
inline static unsigned char *XMLStringCopy(NSString *source)
|
||||
{
|
||||
char *xmlstr;
|
||||
|
|
|
@ -1,37 +1,68 @@
|
|||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSSet.h>
|
||||
#import <Foundation/NSOrderedSet.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ObjectTesting.h"
|
||||
#import "../../../Source/GSFastEnumeration.h"
|
||||
|
||||
static SEL add;
|
||||
static SEL set;
|
||||
static SEL key;
|
||||
|
||||
@implementation NSPointerArray (TestHelpers)
|
||||
- (void) addObject: (id)anObject
|
||||
{
|
||||
[self addPointer: anObject];
|
||||
}
|
||||
- (void) removeObject: (id)anObject
|
||||
{
|
||||
int i = [self count];
|
||||
|
||||
while (i-- > 0)
|
||||
{
|
||||
if ([self pointerAtIndex: i] == (void*)anObject)
|
||||
{
|
||||
[self removePointerAtIndex: i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
void fast_enumeration_mutation_add(id mutableCollection)
|
||||
{
|
||||
NSUInteger i = 0;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger c = [mutableCollection count]/2;
|
||||
|
||||
FOR_IN(id, o, mutableCollection)
|
||||
if (i == [mutableCollection count]/2) {
|
||||
if ([mutableCollection isKindOfClass: [NSMutableDictionary class]]) {
|
||||
[mutableCollection setObject: @"boom" forKey: @"boom"];
|
||||
} else {
|
||||
[mutableCollection addObject: @"boom"];
|
||||
if (i == c)
|
||||
{
|
||||
if ([mutableCollection respondsToSelector: set])
|
||||
{
|
||||
[mutableCollection setObject: @"boom" forKey: @"boom"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[mutableCollection addObject: @"boom"];
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
END_FOR_IN(mutableCollection)
|
||||
}
|
||||
|
||||
void fast_enumeration_mutation_remove(id mutableCollection)
|
||||
{
|
||||
NSUInteger i = 0;
|
||||
NSUInteger i = 0;
|
||||
NSUInteger c = [mutableCollection count]/2;
|
||||
|
||||
FOR_IN(id, o, mutableCollection)
|
||||
if (i == [mutableCollection count]/2) {
|
||||
if ([mutableCollection isKindOfClass: [NSMutableDictionary class]]) {
|
||||
[mutableCollection removeObjectForKey: o];
|
||||
} else {
|
||||
[mutableCollection removeObject: o];
|
||||
if (i == c)
|
||||
{
|
||||
if ([mutableCollection respondsToSelector: key])
|
||||
{
|
||||
[mutableCollection removeObjectForKey: o];
|
||||
}
|
||||
else
|
||||
{
|
||||
[mutableCollection removeObject: o];
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
END_FOR_IN(mutableCollection)
|
||||
}
|
||||
|
@ -50,7 +81,20 @@ void test_fast_enumeration(id collection, NSArray *objects)
|
|||
}
|
||||
PASS_EQUAL(returnedObjects, objects, "fast enumeration returns all objects")
|
||||
|
||||
id mutableCollection = [collection mutableCopy];
|
||||
id mutableCollection;
|
||||
if ([collection respondsToSelector: @selector(mutableCopyWithZone:)])
|
||||
{
|
||||
mutableCollection = AUTORELEASE([collection mutableCopy]);
|
||||
}
|
||||
else if ([collection respondsToSelector: add]
|
||||
|| [collection respondsToSelector: set])
|
||||
{
|
||||
mutableCollection = collection; // It has a method to mutate it
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // No mutable version
|
||||
}
|
||||
PASS_EXCEPTION(
|
||||
fast_enumeration_mutation_add(mutableCollection),
|
||||
NSGenericException,
|
||||
|
@ -59,18 +103,22 @@ void test_fast_enumeration(id collection, NSArray *objects)
|
|||
fast_enumeration_mutation_remove(mutableCollection),
|
||||
NSGenericException,
|
||||
"Fast enumeration mutation remove properly calls @\"NSGenericException\"")
|
||||
[mutableCollection release];
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
||||
|
||||
NSMutableArray *objects = [NSMutableArray array];
|
||||
int i;
|
||||
for (i = 0; i < 10000; i++) {
|
||||
[objects addObject: [NSString stringWithFormat: @"%.4d", i]];
|
||||
}
|
||||
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
||||
NSMutableArray *objects = [NSMutableArray array];
|
||||
int i;
|
||||
|
||||
add = @selector(addObject:);
|
||||
set = @selector(setObject:forKey:);
|
||||
key = @selector(removeObjectForKey:);
|
||||
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
[objects addObject: [NSString stringWithFormat: @"%.4d", i]];
|
||||
}
|
||||
|
||||
START_SET("NSArray")
|
||||
id array = [NSArray arrayWithArray: objects];
|
||||
|
@ -92,6 +140,30 @@ int main()
|
|||
test_fast_enumeration(dict, objects);
|
||||
END_SET("NSDictionary")
|
||||
|
||||
START_SET("NSMapTable")
|
||||
id map = [NSMapTable strongToStrongObjectsMapTable];
|
||||
FOR_IN(id, o, objects)
|
||||
[map setObject: o forKey: o];
|
||||
END_FOR_IN(objects)
|
||||
test_fast_enumeration(map, objects);
|
||||
END_SET("NSMapTable")
|
||||
|
||||
START_SET("NSHashTable")
|
||||
id table = [NSHashTable weakObjectsHashTable];
|
||||
FOR_IN(id, o, objects)
|
||||
[table addObject: o];
|
||||
END_FOR_IN(objects)
|
||||
test_fast_enumeration(table, objects);
|
||||
END_SET("NSHashTable")
|
||||
|
||||
START_SET("NSPointerArray")
|
||||
id array = [NSPointerArray weakObjectsPointerArray];
|
||||
FOR_IN(id, o, objects)
|
||||
[array addPointer: o];
|
||||
END_FOR_IN(objects)
|
||||
test_fast_enumeration(array, objects);
|
||||
END_SET("NSPointerArray")
|
||||
|
||||
[arp release]; arp = nil;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -96,9 +96,41 @@
|
|||
|
||||
@end
|
||||
|
||||
@interface Wrapper : NSObject
|
||||
{
|
||||
TProxy *_proxy;
|
||||
}
|
||||
|
||||
- (instancetype) initWithProxy: (TProxy *) proxy;
|
||||
|
||||
- (TProxy *) proxy;
|
||||
|
||||
@end
|
||||
|
||||
@implementation Wrapper
|
||||
|
||||
- (instancetype) initWithProxy: (TProxy *) proxy
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (TProxy *) proxy
|
||||
{
|
||||
return _proxy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface Observer: NSObject
|
||||
{
|
||||
int count;
|
||||
NSArray *keys;
|
||||
}
|
||||
|
||||
- (void)runTest;
|
||||
|
@ -107,10 +139,13 @@
|
|||
|
||||
@implementation Observer
|
||||
|
||||
- (void)runTest
|
||||
- (void)simpleKeypathTest
|
||||
{
|
||||
Observee *obj = [[Observee alloc] init];
|
||||
TProxy *proxy = [[TProxy alloc] initWithProxiedObject:obj];
|
||||
|
||||
keys = [NSArray arrayWithObjects: @"derivedName", @"name", nil];
|
||||
count = 0;
|
||||
|
||||
[(Observee *)proxy addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
|
||||
[(Observee *)proxy addObserver:self forKeyPath:@"derivedName" options:NSKeyValueObservingOptionNew context:NULL];
|
||||
|
@ -128,21 +163,50 @@
|
|||
[obj release];
|
||||
}
|
||||
|
||||
- (void)nestedKeypathTest
|
||||
{
|
||||
Observee *obj = [[Observee alloc] init];
|
||||
TProxy *proxy = [[TProxy alloc] initWithProxiedObject:obj];
|
||||
Wrapper *w = [[Wrapper alloc] initWithProxy: proxy];
|
||||
|
||||
keys = [NSArray arrayWithObjects: @"proxy.derivedName", @"proxy.name", nil];
|
||||
count = 0;
|
||||
|
||||
[w addObserver:self forKeyPath:@"proxy.name" options:NSKeyValueObservingOptionNew context:NULL];
|
||||
[w addObserver:self forKeyPath:@"proxy.derivedName" options:NSKeyValueObservingOptionNew context:NULL];
|
||||
|
||||
[((Observee *)proxy) setName: @"MOO"];
|
||||
PASS(count == 2, "Got two change notifications");
|
||||
|
||||
[obj setName: @"BAH"];
|
||||
PASS(count == 4, "Got two change notifications");
|
||||
|
||||
[w removeObserver:self forKeyPath:@"proxy.name" context:NULL];
|
||||
[w removeObserver:self forKeyPath:@"proxy.derivedName" context:NULL];
|
||||
|
||||
[w release];
|
||||
[proxy release];
|
||||
[obj release];
|
||||
|
||||
count = 0;
|
||||
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
count += 1;
|
||||
switch (count) {
|
||||
case 1:
|
||||
PASS_EQUAL(keyPath, @"derivedName", "change notification for dependent key 'derivedName' is emitted first");
|
||||
PASS_EQUAL(keyPath, [keys objectAtIndex: 0], "change notification for dependent key 'derivedName' is emitted first");
|
||||
break;
|
||||
case 2:
|
||||
PASS_EQUAL(keyPath, @"name", "'name' change notification for proxy is second");
|
||||
PASS_EQUAL(keyPath, [keys objectAtIndex: 1], "'name' change notification for proxy is second");
|
||||
break;
|
||||
case 3:
|
||||
PASS_EQUAL(keyPath, @"derivedName", "'derivedName' change notification for object is third");
|
||||
PASS_EQUAL(keyPath, [keys objectAtIndex: 0], "'derivedName' change notification for object is third");
|
||||
break;
|
||||
case 4:
|
||||
PASS_EQUAL(keyPath, @"name", "'name' change notification for object is fourth");
|
||||
PASS_EQUAL(keyPath, [keys objectAtIndex: 1], "'name' change notification for object is fourth");
|
||||
break;
|
||||
default:
|
||||
PASS(0, "unexpected -[Observer observeValueForKeyPath:ofObject:change:context:] callback");
|
||||
|
@ -154,15 +218,15 @@
|
|||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
NSAutoreleasePool *arp = [NSAutoreleasePool new];
|
||||
START_SET("KVO Proxy Tests")
|
||||
Observer *obs = [Observer new];
|
||||
|
||||
testHopeful = YES;
|
||||
[obs runTest];
|
||||
[obs simpleKeypathTest];
|
||||
[obs nestedKeypathTest];
|
||||
testHopeful = NO;
|
||||
|
||||
[obs release];
|
||||
|
||||
DESTROY(arp);
|
||||
END_SET("KVO Proxy Tests")
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue