A list,
Category is a language feature added after Objective-C 2.0. The main purpose of a category is to add methods to an existing class. You can use a category to add methods to an existing class without knowing the source code of the class.
- We can use
category
Separating the implementation of a class into several different files reduces the size of a single file. Different functions can be organized into different onescategory
Make the function simple. It is possible for multiple developers to work on a class by creating its owncategory
Can. You can load what you want on demandcategory
, such asSDWebImage
中UIImageView+WebCache
和UIButton+WebCache
, loading different according to different requirementscategory
. - We can still be there
category
Declare private methods.
Ii. Comparison between Extension and Category
extension
It’s determined at the compiler, it’s part of the class, at compile time and in the header file@interface
And in the implementation file@implementation
Form a complete class, which comes into being with the creation of the class and dies out with the extinction of the class.extension
Generally used to hide the private information of a class, you must have the source of the class to add to a classextension
. Cannot be added for system classesextension
.category
It’s determined at runtime,category
You can’t add instance variables,extension
It can be added.
The nature of categories
3.1 Basic use of Category
Let’s start with the basic use of the following categories:
// Person+Eat.h
#import "Person.h"
@interface Person (Eat) <NSCopying, NSCoding>
- (void)eatBread;
+ (void)eatFruit;
@property (nonatomic, assign) int count;
@end
// Person+Eat.m
#import "Person+Eat.h"
@implementation Person (Eat)
- (void)eatBread {
NSLog(@"eatBread");
}
+ (void)eatFruit {
NSLog(@"eatFruit");
}
@end
Copy the code
- Created a
Person
Classification, specifically to achieve the function of eating - This classification complies with two protocols, namely
NSCopying
和NSCoding
- Two methods are declared, one instance method and one class method
- To define a
count
attribute
3.2 Category at compile time
Let’s look at the clang compiler and see what the nature of this code is at compile time.
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc MyClass.m -o MyClass-arm64.cpp
Copy the code
After compiling, we can see that the essence of a category is the category_t structure. No matter how many categories we create, category_T will eventually be generated, and the methods, properties, and protocols in the category will be stored in this structure. That is, at compile time, members of a class are not merged with the class.
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
Copy the code
name
: Class namecls
Class:instanceMethods
:category
List of all instance methods added to the class inclassMethods
:category
A list of all class methods added to a class inprotocols
:category
A list of all protocols implemented ininstanceProperties
:category
All attributes added to the
fromcategory
You can add instance methods, you can add class methods, you can implement protocols, you can add properties.
Instance variables cannot be added
Let’s continue with the following compiled code:
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eatBread"."v16@0:8", (void *)_I_Person_Eat_eatBread}}
};
static struct/ * _method_list_t* / {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1].
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"eatFruit"."v16@0:8", (void *)_C_Person_Eat_eatFruit}}
};
static struct/ * _protocol_list_t* / {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[2].
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
2,
&_OBJC_PROTOCOL_NSCopying,
&_OBJC_PROTOCOL_NSCoding
};
static struct/ * _prop_list_t* / {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1].
} _OBJC_$_PROP_LIST_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"count"."Ti,N"}}};static struct _category_t _OBJC_The $_CATEGORY_Person_The $_Eat __attribute__ ((used.section(" __DATA, __objc_const"))) =
{
"Person".0.// &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Eat,
};
Copy the code
- So let’s take a look at that
_OBJC_$_CATEGORY_Person_$_Eat
The values in the struct variable are the correspondingcategory_t
The first member is the class name, because we declare instance methods, class methods, we follow the protocol, we define properties, so we have values for all of those in our structure variables. _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat
The struct represents the list of instance methods that containeatBread
Instance methods_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Eat
The structure containseatFruit
Class method_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Eat
The structure containsNSCoping
和NSCoding
agreement_OBJC_$_PROP_LIST_Person_$_Eat
The structure containscount
attribute
3.3 Category during runtime
After looking at compile-time categories, we move on to run-time categories
In the objc-Runtime-new.mm source code, we can finally figure out how to add the method list, property list, and protocol list of the category to the class.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/* * Only a few classes have more than 64 categories during launch. * This uses a little stack, and avoids malloc. * * Categories must be added in the proper order, which is back * to front. To do that with the chunking, we iterate cats_list * from front to back, build up the local buffers backwards, * and call attachLists on the chunks. attachLists prepends the * lists, so the final result is in the expected order. */
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data() - >extAllocIfNeeded(a);for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if() {if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle(a); }property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; }}if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
Copy the code
rwe->methods.attachLists(mlists, mcount);
rwe->protocols.attachLists(protolists, protocount);
rwe->properties.attachLists(proplists, propcount);
These three functions add a list of methods, attributes, and protocols from a category to a class.
Continue looking at the implementation of the attchLists function:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0])); }}Copy the code
- In this source code, we focus on two functions
memmove
和memcpy
. memmove
The move () function moves memory back and the list of methods backmemcpy
The memory copy () function is used to copy memorycategory
The list of methods in is copied to the location moved from the previous step.
If both the category and the original class have a method of the same name, but the methods in the category are placed first in the list of new methods, the methods are found in order at runtime. Once you find that method, you don’t look any further down, giving the illusion that the category overrides the method of the original class.
That’s why we always prefix methods in categories to avoid accidentally overwriting the class’s own methods.
- If multiple
category
The compiler determines which method the runtime ultimately calls,The last method participating in the compilation will be called first.
The +load method
Next, we’ll look at the +load method in classes and classes, starting with the following code:
// Person.h @interface Person : NSObject + (void)test; @end // Person.m @implementation Person + (void)load { NSLog(@"Person +load"); } + (void)test { NSLog(@"Person +test"); } @end // Person+Test1.m @implementation Person (Test1) + (void)load { NSLog(@"Person (Test1) +load"); } + (void)test { NSLog(@"Person (Test1) +test"); } @end // Person+Test2.m @implementation Person (Test2) + (void)load { NSLog(@"Person (Test2) +load"); } + (void)test { NSLog(@"Person (Test2) +test"); } @end /// int main(int argc, const char * argv[]) { @autoreleasepool { [Person test]; } return 0; } // Print the result: Person + Load Person (Test1) + Load Person (Test2) + Load Person (Test2) +testCopy the code
- And by printing the results,
+load
Method is called three times, andtest
The method will only be called once, and the result seems to be different from the above study. The previous study concluded that methods with the same name will only call the last compiled class of the class, which shows that+load
Methods andtest
The calling nature of methods is different. Why exactly? Only through the source code to explore
The call_load_methods function is used to load the +load method of the class:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
Copy the code
- So from this function, we can see this
do-while
The cycle, first of all, is throughcall_class_loads
Function to load the class+load
methods
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if(! cls)continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
Copy the code
- from
call_class_loads
In the function, you can find thatload_method
Function pointer foundload
Method and call directly
When the load method of the class is called, the load method of the call_category_loads class is called.
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if(! cat)continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n", cls->nameForLogging(), _category_getName(cat)); } (*load_method)(cls, @selector(load)); cats[i].cat = nil; }}// Compact detached list (order-preserving)
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
// Copy any new +load candidates from the new list to the detached list.
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
// Destroy the new list.
if (loadable_categories) free(loadable_categories);
// Reattach the (now augmented) detached list.
// But if there's nothing left to load, destroy the list.
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
if (PrintLoading) {
if(loadable_categories_used ! =0) {
_objc_inform("LOAD: %d categories still waiting for +load\n", loadable_categories_used); }}return new_categories_added;
}
Copy the code
- This function also passes
load_method
The function pointer calls the class directlyload
Methods.
Through the above source code analysis, we can draw the following conclusions:
load
The order in which methods are called, first of all, in the call classload
Method and has nothing to do with the order in which it was compiled, followed by the call classload
Method, classificationload
Methods are called in the order in which they were compiled- Explains why in the previous example, right
Person
Class and classificationload
Why is the method called 3 times, whiletest
Method is called only once. becauseload
Method makes a direct call through the function pointer to find the memory address of the function, while+test
Methods byisa
The pointer eventually finds a call to a list of class methods in a metaclass object, and the nature of the call is different.
Next look at calling the load method with inheritance, create the Student class that inherits from the Person class, and create two classes
// Student.m @implementation Student + (void)load { NSLog(@"Student +load"); } @end // Student+Test1.m @implementation Student (Test1) + (void)load { NSLog(@"Student (Test1) +load"); } @end // Student+Test1.m @implementation Student (Test2) + (void)load { NSLog(@"Student (Test2) +load"); Person +load Student +load Student (Test2) +load Person (Test2) +load Student (Test1) +load Person (Test1) +loadCopy the code
- The parent class is called first based on the printed result
load
Method, and then call the subclass’sload
Method, and then call the classload
methods
Prepare_load_methods was called before call_category_loads was called from the runtime source
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if(! cls)continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods"); } realizeClassWithoutSwift(cls, nil); ASSERT(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); }}Copy the code
The schedule_class_load function is called iteratively based on the list of classes
static void schedule_class_load(Class cls)
{
if(! cls)return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
Copy the code
- from
schedule_class_load
Function is recursive call, first look up the parent class call, ensure that the parent classload
Methods. Through the source code to confirm our previous print results. - The classification of
load
Instead of calling the parent class first, method calls are compiled first and called first
If you create a new class that has no inheritance from the Person class, the load methods are called in compile order, compile first.
+Initialize
The initialize and load methods are very confusing. Let’s change the Person example above and change the load method to initailize to see the printed result:
// Person.m @implementation Person + (void)initialize { NSLog(@"Person +initialize"); } @end // Person+Test1.m @implementation Person (Test1) + (void)initialize { NSLog(@"Person (Test1) +initialize"); } @end // Person+Test2.m @implementation Person (Test2) + (void)initialize { NSLog(@"Person (Test2) +initialize"); } @end // Student.m @implementation Student + (void)initialize { NSLog(@"Student +initialize"); } @end // main function int main(int argc, const char * argv[]) {@autoreleasepool {[Student alloc]; } return 0; Person (Test1) +initialize Student +initializeCopy the code
- in
main
The function we just calledStudent
的alloc
Method to find that there are two printed results, if we comment outmain
Function,Student
的alloc
Method, we’ll see that the console doesn’t print anything. Here we come to a conclusion:initilize
Method is called the first time a class receives a message. - The second phenomenon is that we only have
Student
Sends a message and finds its parent classPerson
的initilize
Methods are also called, and they are called in the classinitilize
Methods. This suggests thatinitilize
Methods are invoked through the messaging mechanism, that is, throughisa
Find the class object/metaclass object to call the method, because the class exists so only the methods in the class will be called.
Let’s explore the underlying problem with the source code:
Class class_initialize(Class cls, id obj)
{
runtimeLock.lock();
return initializeAndMaybeRelock(cls, obj, runtimeLock, false);
}
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
ASSERT(cls->isRealized());
if (cls->isInitialized()) {
if(! leaveLocked) lock.unlock();return cls;
}
// Find the non-meta class for cls, if it is not already one.
// The +initialize message is sent to the non-meta class object.
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// Realize the non-meta class if necessary.
if (nonmeta->isRealized()) {
// nonmeta is cls, which was already realized
// OR nonmeta is distinct, but is already realized
// - nothing else to do
lock.unlock();
} else {
nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
// runtimeLock is now unlocked
// fixme Swift can't relocate the class today,
// but someday it will:
cls = object_getClass(nonmeta);
}
// runtimeLock is now unlocked, for +initialize dispatch
ASSERT(nonmeta->isRealized());
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
Copy the code
class_initialize
The function calls thetainitializeAndMaybeRelock
Delta function, which is concerned withinitializeNonMetaClass
function
void initializeNonMetaClass(Class cls)
{ ASSERT(! cls->isMetaClass()); Class supercls;bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
...
#endif{ callInitialize(cls); . }void callInitialize(Class cls)
{((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
Copy the code
- From the function above, we find that
initilize
The parent class is called recursivelyinitilize
Function, called lastcallInitialize
Function, andcallInitialize
The function is called internallyobjc_msgSend
The function.
Through the source code, explained the initilize method is to call the parent class and then call the child class.
One final note: if the subclass does not implement the initilize method, the subclass initilize method will be called multiple times when it receives the message.
The reason is: When the initilize method of the parent class is called, but the subclass does not have the initilize method, the subclass will use the superclass pointer to search the initilize method in the parent class and find the existence of the parent class, then call the initilize method of the parent class, but it does not mean that the parent class is initialized twice.
Category and associated objects
Since there is no way to add instance variables to the category structure, we can use the Runtime’s associated object to do this.
If we just add properties to a category, by default, we only generate declarations of getter and setter methods, not implementations of getter and setter methods and member variables, so we want to use the defined properties to value and assign, It crashes because it can’t find a way to do it.
The function of value and assignment can be realized indirectly by associating objects:
// Person+Test.h
@interface Person (Test)
@property (nonatomic, copy) NSString *name;
@end
// Person+Test.m
@implementation Person (Test)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @selector(name));
}
@end
Copy the code
-
You start by defining a property in the category declaration file, manually implementing getter and setter methods in the implementation file, and using associated objects inside the getter and setter methods.
-
The objc_setAssociatedObject function takes four arguments
- The first argument is the object being associated
- The second argument is that you need to pass in an address,
const void *
Type, the purpose of which is to map the values set by this address - The third parameter is the value to set
- The fourth parameter is similar to the keyword that defines the attribute, and is mainly for memory management
-
The objc_getAssociatedObject function takes two arguments
- The first argument is the object being associated
- The second argument is passed in an address from which to get the value set earlier, so the sum is guaranteed
objc_setAssociatedObject
The second parameter remains the same.
But where do the associated objects live? Does it exist in the object’s memory? What happens to the associated object when the object is destroyed?
The _object_get_associative_reference and _object_set_associative_reference functions can be found in the objc-references. Mm file. We can see that the associated objects are managed through the AssociationsManager.
In AssociationsManager, AssociationsHashMap is used to store all associated objects. The AssociationsHashMap key is the pointer address of the associated object, and the corresponding value is an ObjectAssociationMap. And ObjectAssociationMap’s key is a pointer to const void * and value is objcasSociety, The objcassociety contains two members: _policy and _value. At this point, we understand the nature of the associated object.
If we want to delete the value of an associated object, all we need to do is pass the value of the corresponding key to nil in objc_setAssociatedObject, and an associated object value will be erased.
Another puzzle is what happens to the associated object when the object is destroyed?
In the object destruction source: objc-Runtime-new.mm file
Determines whether the object contains an associated object, and if so, removes the associated object. So when an object is destroyed, its associated object is also destroyed.
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor(a);bool assoc = obj->hasAssociatedObjects(a);// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating(a); }return obj;
}
Copy the code