55. The order of member variables in a class declaration and the order of the actual member variables.

In oop programming languages, every object is an instance of some class. In Objective-C, all objects are essentially an objc_Object structure, and the first member variable of each instance object is ISA, from which the object belongs. Each class describes a series of information about its instance object. This includes the memory size of the object, the list of member variables, the list of functions that the object can perform… And so on.

In the memory layout of an instance object of a class, the first member variable is ISA, and the member variables are sorted according to the inheritance system of the class to which the object belongs, in the following order: The member variables of the root class, the member variables of the parent class, and finally its own member variables, and the member variables in each class definition (including only the _ member variables of the same name generated by the compiler after the @property declaration) may not be in the same order as they were defined. The compiler optimizes the order of member variables in a class definition for memory alignment to minimize memory usage. (It also involves member variables and attributes in.h, member variables and attributes added in extension in.m, and the sorting order between them)

Verification code:

@interface SubObject: BaseObject {NSArray *cus_array; } @property (nonatomic, assign) int cus_int; @property (nonatomic, assign) double cus_dou; @property (nonatomic, assign) int cus_int2; @property (nonatomic, copy) NSString *cus_string; @end // Add breakpoint, Console print (LLDB) p *sub (SubObject) $2 = {BaseObject = {NSObject = {ISA = SubObject} baseString = nil _baseArray = nil}  cus_array = nil _cus_int = 0 _cus_int2 = 0 _cus_dou = 0 _cus_string = nil }Copy the code

You can see that NSObject isa comes first, then the BaseObject member variables, and finally the SubObject member variables, and then notice that _CUS_int2 comes before _CUS_dou, The Cus_dou property comes before the Cus_INT2 property when the class is defined. (At least 4 bytes less memory is wasted because no more padding is needed for double alignment.)


56. Why can’t you dynamically add a member variable to a class but can add a method?

The layout of a class’s member variables and the size of its instantiated objects are determined at compile time. Imagine if you allowed dynamic addition of member variables to a class in Objective-C, there was a problem: dynamically adding member variables to a base class would render all instances of the created subclasses unusable. When we say “instances of classes” (objects), we mean an area of memory where isa Pointers and all of its member variables are stored. So if you allow dynamic changes to the layout of a class’s fixed member variables, then objects that have already been created do not conform to the class’s definition and become invalid. Methods are defined in the class object or metaclass object, no matter how to add or delete methods, will not affect the memory layout of the object, the object has been created can still be used normally.


57. What are the 64 bits in ISA_BITFIELD?

#   define ISA_BITFIELD                                                      \
      // Indicates whether the isa contains only Class CLS Pointers or bits containing more information
      uintptr_t nonpointer        : 1;                                       \
      // Indicate whether the object has an associated object. If not, the object can be destroyed faster.
      // If so, the _object_remove_assocations function is called before destruction to release each associated object according to the association policy
      uintptr_t has_assoc         : 1;                                       \
      // flag whether the object's class has a custom C++ destructor. If not, the object can be destroyed faster.
      Object_cxxDestruct is called to execute the destructor of the class before the object is destroyed, if any
      uintptr_t has_cxx_dtor      : 1;                                       \
      Isa & ISA_MASK Gets the address of the class to which the instance object belongs
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      // It is used by the debugger to determine if the current object is a real object or if there is no space to initialize it
      uintptr_t magic             : 6;                                       \
      // Flag if the object has a weak reference. If not, the object can be destroyed faster.
      Weak_clear_no_lock is called before the object is destroyed to set the weak reference of the object to nil,
      // And call weak_entry_remove to remove the entry of the object from weak_table
      uintptr_t weakly_referenced : 1;                                       \
      // Marks whether the object is being destroyed
      uintptr_t deallocating      : 1;                                       \
      // Mark whether reference counts of instance objects are also stored in refcnts, some of which are stored in the EXTRA_RC when overflowing.
      uintptr_t has_sidetable_rc  : 1;                                       \
      // Save the value of reference count -1 for this object (save RC_HALF after overflow before overflow)
      uintptr_t extra_rc          : 19 // 2^ 8-1 = 255
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)
Copy the code

The 58.isa () function returns the address of the class to which objc_object belongs.

In one case, isa’s bitfield (IndexCLs) holds the index of a class object in the global class table. One is that isa is just a class pointer. One is that the isa’s bitfields (Shiftcls) hold the addresses of class objects.

// ISA() assumes this is NOT a tagged pointer object
// Call this function if it is not tagged Pointer
inline Class 
objc_object::ISA(a) 
{
    // If it is tagged pointer, execute the assertion directly
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    // The index of the class can be saved in isa
    
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        
        // Return the class in class table based on the index
        return classForIndex((unsigned)slot);
    }
    
    // If the non-optimized pointer returns bits in ISA directly, the value of (Class)isa.bits and (Class)isa.cls are the same because ISA is union.
    return (Class)isa.bits;
#else
    // Get the Class pointer from the shiftcls bit field. This is the most common way to get the Class of an instance object.
    return (Class)(isa.bits & ISA_MASK);
#endif
}
Copy the code

59. isWeaklyReferenced / sidetable_isWeaklyReferenced

Determines whether an object has a weak reference.

inline bool
objc_object::isWeaklyReferenced(a)
{
    // Perform an assertion if it is Tagged Pointer
    ASSERT(!isTaggedPointer());
    
    Weakly_referenced flag bit is returned if it is not a pointer
    if (isa.nonpointer) return isa.weakly_referenced;
    
    // Other cases call sidetable_isWeaklyReferenced
    // (when ISA is the objC_class pointer, the object's weak reference identifier bit is in SideTable's refcnts)
    else return sidetable_isWeaklyReferenced(a); }Copy the code

Isa is an indication of whether the object with the original class pointer has weak references in refcnts.

bool 
objc_object::sidetable_isWeaklyReferenced(a)
{
    bool result = false;
    
    // Get the SideTable of the object
    SideTable& table = SideTables(to)this];
    / / lock
    table.lock(a); RefcountMap::iterator it = table.refcnts.find(this);
    // Determine whether the current object exists in SideTable's refcnts
    if(it ! = table.refcnts.end()) {
        // If yes
        // it->second is reference counting with SIDE_TABLE_WEAKLY_REFERENCED for and operation
        // The first bit of the referential count is the identifier bit for weak references
        result = it->second & SIDE_TABLE_WEAKLY_REFERENCED;
    }
    
    / / unlock
    table.unlock(a);return result;
}
Copy the code

60. Unscramble Tagged Pointer.

In September 2013, Apple launched iPhone (iPhone 5S) equipped with 64-bit architecture processor on iOS platform for the first time. In order to save memory and improve operation efficiency, the concept of Tagged Pointer was proposed.

Tagged Pointer is a concept created by Apple to save memory and improve performance on 64-bit processors. Its essence is to put the data of some small objects directly into the memory space of the pointer, and then use the pointer directly as an object, directly eliminating the process of making space for objects in the heap.

This raises the question, “Is the object’s memory in the heap?” Yes. Here are my own guesses: By default, all of these objects are subclasses of NSObject, and when you look at the + (id)alloc function, you can see that the last thing that makes space is malloc, Malloc is a C runtime function, and the memory applied to it is managed by C runtime, using the memory management mode of heap. This function actually requests memory from the operating system and allocates it to the requester, while internally maintaining the allocation of the memory it requests to manage the memory it has.

The length of a pointer variable depends on the address bus. The length of pointer variables increases from 32 bits to 64 bits when you switch from a 32-bit to a 64-bit architecture. If other factors are not taken into account, the length of the address represented by the 64-bit Pointer can reach 2^64 bytes, that is, 2^34 TB. Considering the memory of the current device, if 8 bytes are used to store an address data, many bits are actually free, and Tagged Pointer is just to use these free space. (For example, on a real iPhone, create an NSObject object in the heap, print its address, and see that it takes up only 36 bits, leaving 28 bits with zero.)

To be clear, NSInteger/NSUInteger is the basic type long/int/unsigned long/unsigned int declared using a typedef. NSNumber, NSString, NSDate, and so on are all subclasses that inherit from NSObject.

In objc-Runtime-new. h, CF requires all objects to be at least 16 bytes. (Object internal member variables are mostly 8-byte aligned, or extended to 16 bytes if the object memory is less than 16 bytes after the last alignment.)

_OBJC_TAG_MASK indicates that the 64th bit of the Pointer variable in the order of the highest part of the string is Tagged; the first bit of the Pointer variable in the order of the lowest part of the string is Tagged.

Check whether it is Tagged Pointer on an iOS phone. Check whether the 64th bit of the Pointer is 1. Check whether the first bit of the Pointer is 1 on an X86_64 architecture Mac. (that is, determine the highest digit in iOS and the lowest digit in MAC)

By analyzing the printed result, it can be seen that all Tagged Pointer 64-bit memory usage is almost full (the highest bit is 1), and malloc_size returns 0. Compared to the last non-tagged Pointer, no space is allocated for the object. The first member variable of a normal Objective-C instance object is an ISA Pointer to the memory address of the class object. And when Tagged Pointer is of type NSNumber, the class function still prints __NSCFNumber. Apple does not design a separate class to represent Tagged Pointer. NSString prints NSTaggedPointerString, so this raises another question: how do you get TaggedPointer’s class?

Why can Tagged Pointer be identified by setting the highest or lowest bit? This is because the allocated memory is always an integer multiple of 2. In this way, the last bit of the allocated normal memory address cannot be 1. By marking the lowest bit as 1, it can be distinguished from other normal Pointers. So why is it possible to identify the highest bit as 1? (Currently, the memory of iOS devices is fixed, such as iPhone, iPad and iWatch are fixed, unlike MAC products, we can add memory chips by ourselves.) This is because of the 64-bit operating system, devices generally do not have that large memory. Therefore, memory addresses generally only have about 48 significant bits (64-bit iOS heap addresses only use 36 significant bits), that is to say, the highest 16 bits are all 0. Therefore, the highest bit can be marked as 1 to indicate Tagged Pointer. So now that 1 bit identifies Tagged Pointer, what else does it do? For example, the next three bits represent the index of the specified type in the Tagged Pointer table. The next 60 bits are used to store values and load data capacity. Used to store object data.

* Tagged Pointer the pointer object willclassAnd object data is stored in object Pointers, which don't actually point to anything. *Tagged pointerCurrently this representation is used:* * (LSB)(string low order priority,64Bit under MAC) *1 bit   set if tagged, clear if ordinary object pointer // A flag with a value of 1 is tagged pointer, and a common object pointer is 0
*  3 bits  tag index // Mark the type
* 60 bits  payload // Load data capacity, (store object data)
*
* (MSB)(64Bit iPhone) * tag index indicates which object belongs toclass. The payload format is determined by the objectclassDefinition. * * iftag indexIs zerob111(7), tagged pointerObjects use an "extended" representation that allows for more classes but smaller payloads:* (LSB)(string low order priority,64Bit under MAC) *1 bit   set if tagged, clear if ordinary object pointer // A flag with a value of 1 is tagged pointer, and a common object pointer is 0
*  3 bits  0b111 
*  8 bits  extended tag index // Extended tag index
* 52 bits  payload // Load data capacity, at this time only 52 bits
* (MSB)
Copy the code

Another extension, Tagged Pointer, specifies the maximum value that can be stored. If Tagged Pointer is of the NSNumber type, on an X86_64 Mac platform:

NSNumber *number = [[NSNumber alloc] initWithInteger: pow(2.55) - 2];;
NSLog(@"number %p %@ %zu", number, [number class], malloc_size(CFBridgingRetain(number)));
/ / print:
number 0x10063e330 __NSCFNumber 32

NSNumber *number = [[NSNumber alloc] initWithInteger: pow(2.55) - 3];;
NSLog(@"number %p %@ %zu", number, [number class], malloc_size(CFBridgingRetain(number)));
/ / print:
number 0x21a60cf72f053d4b __NSCFNumber 0
Copy the code

An NSString Tagged Pointer is stored on an X86_64 Mac platform. Each Pointer contains 8 bytes and 64 bits. The first bit is used to flag whether a Pointer is Tagged. The second to fourth bits are used to mark the type of a Pointer to Tagged Pointer, and the last four bits are used to mark the length of a value. Therefore, there are only 56 bits to store a value. NSTaggedPointerString is encoded in a different way:

  1. If the length is between 0 and 7, the string is stored in octet encoding.
  2. If the length is eight or nine, with six code stored string, use the code table eilotrm. ApdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P – B79AFKEWV_zGJ/HYX.
  3. If the length is 10 or 11, store the string in a five-digit encoding, using the encoding table eilotrm.apDNsic ufkMShjTRxgC4013.

0x2082082082082088; 0x208208208208 after decoding @” aaaaaAAA “(8 as) TaggedPointer value 0x2082082088 6 bits in a group, 0x08080808080808 after grouping, exactly 8 bytes, the length meets the requirement. Adopt coding table eilotrm. ApdnsIc ufkMShjTRxgC4013bDNvwyUL2O856P – B79AFKEWV_zGJ/HYX, is just a subscript 8.

0x1084210842108A = 0x1084210842108 after decoding @” aaaaAAaAAA “(10 as) TaggedPointer = 0x1084210842108A (0x1084210842108 after decoding the last 4 bits) 5 bits in a group, 0x0808080808080808 after grouping, just 10 bytes, the length meets the requirement. Use the code table eilotrm.apdnsIc ufkMShjTRxgC4013, subscript 8 is just a.

The + character is not seen in the encoding table. To test this with the + character, 7 + should be NSTaggedPointerString and 8 + should be a normal __NSCFString object.

For more information about string storage, see: string using Tagged Pointer.


61. When adding attributes to an existing class in a category.

Adding methods to an existing class using a Category is a familiar routine, but if you add @property to a class in a Category, the compiler immediately gives us the following warning:

Property 'categoryProperty' requires method 'categoryProperty' to be defined - use @dynamic or provide a method implementation in this category.
Property 'categoryProperty' requires method 'setCategoryProperty:' to be defined - use @dynamic or provide a method implementation in this category
Copy the code

Indicating that we need to manually add setters, gettr methods for properties, or use the @dynamic flag to tell the compiler that these methods are implemented at run time, That said, it definitely tells us that @property does not automatically generate underline instance variables and setter and getter access methods in the classification.

With @property, the compiler will automatically generate the underscore instance variable and the corresponding setter and getter methods. This mechanism can only be implemented in class definitions, because the memory layout of the instance variables of the class is fixed in the classification, and it is no longer possible to add new instance variables to the fixed memory layout using @property. So we need to use an association object and two methods, objc_getAssociatedObject and objc_setAssociatedObject, to simulate the three elements that make up the property.

Sample code:

#import "HMObject.h"

NS_ASSUME_NONNULL_BEGIN

@interface HMObject (category)

// Add an attribute to the category
@property (nonatomic, copy) NSString *categoryProperty;

@end

NS_ASSUME_NONNULL_END
Copy the code
#import "HMObject+category.h"
#import <objc/runtime.h> 

@implementation HMObject (category)

- (NSString *)categoryProperty {
    // the _cmd generation refers to the selector of the current method, that is, @selector(categoryProperty)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setCategoryProperty:(NSString *)categoryProperty {
    objc_setAssociatedObject(self,
                             @selector(categoryProperty),
                             categoryProperty,
                             OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end
Copy the code

At this point we can manually add an access method to the categoryProperty property using the Associated Object.


62. When adding attributes to a class definition.

When we add a property to a class using @Property in our class definition, if we don’t use @dynamic to identify the property, the compiler automatically generates an instance variable with an underscore and the property name and setter and getter methods for that property. We write the following code:

H is written as follows
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface HMObject : NSObject
@property (nonatomic, copy) NSString *cusProperty;
@end

NS_ASSUME_NONNULL_END

// do nothing in.m
#import "HMObject.h"
@implementation HMObject
// @dynamic cusProperty;

@end
Copy the code

The compiler automatically does three things for us:

  1. Add the instance variable _cusProperty
  2. Add setter method setCusProperty
  3. Add the getter method cusProperty

The following code implementation of hmobject. m:

#import "HMObject.h"

@implementation HMObject
// @dynamic cusProperty;
{
    NSString *_cusProperty;
}

- (void)setCusProperty:(NSString *)cusProperty {
    _cusProperty = cusProperty;
}

- (NSString *)cusProperty {
    return _cusProperty;
}

@end
Copy the code

To verify this, we comment out all the hmobject. m code except for the cusProperty property in hmobject. h. Then write the following code in the main function:

Class cls = NSClassFromString(@"HMObject");
NSLog(@"% @", cls); // ⬅️ make a breakpoint here
Copy the code

Start validation:

Here we can also use the Runtime class_copyPropertyList, class_copyMethodList, and class_copyIvarList functions to get HMObject, respectively The property list, method list, and member variable list are used to verify what the compiler has automatically generated for us, but here we take a simpler approach and just print from the console.

  1. Find the bits of CLS:
(lldb) x/5gx cls
0x1000022e8: 0x00000001000022c0 (isa) 0x00000001003ee140 (superclass)
0x1000022f8: 0x00000001003e84a0 0x0000001c00000000 (cache_t)
0x100002308: 0x0000000101850640 (bits)
Copy the code
  1. Cast the class_datA_bits_t pointer
(lldb) p (class_data_bits_t *)0x100002308
(class_data_bits_t *) $1 = 0x0000000100002308
Copy the code
  1. Obtain class_rw_t *
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000101850640
Copy the code
  1. Obtain class_ro_t *
(lldb) p $2->ro
(const class_ro_t *) $3 = 0x0000000100002128
Copy the code
  1. Print ro content
(lldb) p *$3
(const class_ro_t) $4 = {
  flags = 388
  instanceStart = 8
  instanceSize = 16
  reserved = 0
  ivarLayout = 0x0000000100000ee6 "\x01"
  name = 0x0000000100000edd "HMObject" / / the name of the class
  baseMethodList = 0x0000000100002170 // List of methods
  baseProtocols = 0x0000000000000000 // Follow the protocol is null
  ivars = 0x00000001000021c0 // Member variables
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000021e8 / / property
  _swiftMetadataInitializer_NEVER_USE = {}
}
Copy the code
  1. Print ivars
(lldb) p $4.ivars
(const ivar_list_t *const) $5 = 0x00000001000021c0
(lldb) p *$5
(const ivar_list_t) $6 = {
  entsize_list_tt<ivar_t.ivar_list_t.0> = {
    entsizeAndFlags = 32
    count = 1 // There is one member variable
    first = {
      offset = 0x00000001000022b8
      // See the member variable named _cusProperty
      name = 0x0000000100000ef6 "_cusProperty"
      type = 0x0000000100000f65 "@\"NSString\""
      alignment_raw = 3
      size = 8}}}Copy the code
  1. Print baseProperties
(lldb) p $4.baseProperties
(property_list_t *const) $7 = 0x00000001000021e8
(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t.property_list_t.0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "cusProperty", attributes = "T@\"NSString\",C,N,V_cusProperty")}}Copy the code

See only one attribute named cusProperty. The attributes of the attribute are: “T@”NSString”,C,N,V_cusProperty”

code meaning
T type
C copy
N nonatomic
V The instance variables

For more information, see the Objective-C Runtime Programming Guide.

  1. Print baseMethodList
(lldb) p $4.baseMethodList
(method_list_t *const) $9 = 0x0000000100002170
(lldb) p *$9
(method_list_t) $10 = {
  entsize_list_tt<method_t.method_list_t.3> = {
    entsizeAndFlags = 26
    count = 3 // There are three methods
    first = {
      // The first is the getter for cusProperty
      name = "cusProperty"
      types = 0x0000000100000f79 "@ @ 0:8 16"
      imp = 0x0000000100000c30 (KCObjcTest`-[HMObject cusProperty])
    }
  }
}
Copy the code

See the TypeEncoding of the method as follows:

Types = 0x0000000100000F79 “@16@0:8” From left to right are: @ denotes the return type of an OC object, 16 denotes the total length of all arguments, and thereafter @ denotes the type of the first argument, corresponding to the function call self, 0 denotes the beginning of the 0th bit, and the separator: Represents the second argument type, corresponding to SEL, and 8 means to start at bit 8, since the preceding argument self takes up 8 bytes. Now we start with custom parameters, because getter functions don’t have custom functions, so we just end up with self and SEL parameters. The corresponding function prototype is the objc_msgSend function:

void
objc_msgSend(void /* id self, SEL op, ... * / )
Copy the code
  1. Print the remaining two methods
(lldb) p $10.get(1)
(method_t) $11 = {
  name = "setCusProperty:"
  types = 0x0000000100000f81 "v24@0:8@16"
  imp = 0x0000000100000c60 (KCObjcTest`-[HMObject setCusProperty:])
}
(lldb) p $10.get(2)
(method_t) $12 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f71 "v16@0:8"
  imp = 0x0000000100000c00 (KCObjcTest`-[HMObject .cxx_destruct])
}
Copy the code

See a setter function for cusProperty and a destructor for C++.

For comparison, we comment out the cusProperty property in hmobject. h and then retrace the flow to print the following:

(lldb) x/5gx cls
0x100002240: 0x0000000100002218 0x00000001003ee140
0x100002250: 0x00000001003e84a0 0x0000001000000000
0x100002260: 0x00000001006696c0
(lldb) p (class_data_bits_t *)0x100002260
(class_data_bits_t *) $1 = 0x0000000100002260
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001006696c0
(lldb) p $2->ro
(const class_ro_t *) $3 = 0x0000000100002118
(lldb) p *$3
(const class_ro_t) $4 = {
  flags = 128
  instanceStart = 8
  instanceSize = 8
  reserved = 0
  ivarLayout = 0x0000000000000000
  name = 0x0000000100000f22 "HMObject"
  baseMethodList = 0x0000000000000000
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000000000000
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000000000000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) 
Copy the code

You can see that ivars, baseProperties, and baseMethodList are all 0x0000000000000000, meaning that the compiler does not generate properties, member variables, and functions for HMObject. At this point, the @property function is completely proven.

@property automatically generates instance variables and access methods that make up the syntactic sugar concept of properties, providing a convenient point syntax for accessing properties:

Self. Property is equivalent to [self property]; The self. The property = value; Equivalent to [self setProperty:value];

Used to C/C++ struct and struct Pointers fetching struct member variables using. And – >. Self is a pointer to a member variable. Self is a pointer to a member variable. Self is a pointer to a member variable. ? If you follow C/C++ rules, you should use self->_property.

OC midpoint syntax is used to facilitate access to properties. Inside a class, we can use _proerty, self->_propery, and self.property to access the same member variable. The difference is that with self.property, member variables are read by calling the setter and getter of the property, whereas the first two are read directly, so when we override the setter and getter of the property and do some custom operations internally, It is important to remember to use self.property to access attributes rather than member variables directly.


63. Associated Object principle.

We use objc_setAssociatedObject and objc_getAssociatedObject to simulate access methods for properties, respectively, and use associated objects to simulate instance variables. Runtime. h defines the following three function interfaces related to associated objects:

Objc_setAssociatedObject sets the associated value for a given source object using the given key and association policy.

/** * @param object The source object to associate with * @param Key The associated key * @param value The value associated with the key of the source object. Pass nil to clear an existing association. * @param policy Association policy * * @see objc_setAssociatedObject * @see objc_removeAssociatedObjects */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy);
Copy the code

Objc_getAssociatedObject returns the value associated with the given key of the source object.

/** * @param object * @param key The associated key * @return The value associated with The key \e key for \e object * @see objc_setAssociatedObject */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
Copy the code

Objc_removeAssociatedObjects removes all associations for a given object.

/** * means that this function removes all of the associated objects of the object at once. If we want to remove the specified associated object, * should use objc_setAssociatedObject to pass the value argument to nil. * * The main purpose of this feature is to make it easy for the object to return to its "original state," so associations should not be generally removed from this object, * because it also removes associations that other clients may have added to the object. * In general, you should use objc_setAssociatedObject with nil to clear specified associations. * * @see objc_setAssociatedObject * @see objc_getAssociatedObject */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object);
Copy the code

We use @selector(categoryProperty) as our key, and we can use static Pointers to static void * instead. However, it is highly recommended to pass in @selector(categoryProperty) as the key, because this method omits the code to declare the argument and does a good job of ensuring that the key is unique.

Policy stands for association policy:

/** * Policies associated with associated references. * /
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_ASSIGN = 0./**< Specifies a strong reference to the associated object. * The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1./**< Specifies that the associated object is copied. * The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3./**< Specifies a strong reference to the associated object. * The association is made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401./**< Specifies that the associated object is copied. * The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          
};
Copy the code

The comments make it clear that different policies correspond to different modifiers:

objc_AssociationPolicy The modifier
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC Nonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMIC Nonatomic, copy
OBJC_ASSOCIATION_RETAIN atomic, strong
OBJC_ASSOCIATION_COPY atomic, copy

Let’s look at some of the key data structures in the Associated Object mechanism.

Class ObjcAssociation is used to store association policies and association values.

class ObjcAssociation {
    // typedef unsigned long uintptr_t;
    uintptr_t _policy; // Associate the policy
    id _value; / / associated values
public:...// called in the SETTER to determine if _value needs to be held based on the associated policy _policy
    inline void acquireValue(a) {
        if (_value) {
            switch (_policy & 0xFF) {
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value); // retain, call the objc_retain function
                break;
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy)); // copy, call copy
                break; }}}// Called when SETTER: The acquireValue function pair above should be required to release the old _value
    inline void releaseHeldValue(a) {
        if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
            objc_release(_value); // release reduces the reference count}}// Called in the GETTER: Determine whether to perform a retain operation on ReturnedValue based on the association policy
    inline void retainReturnedValue(a) {
        if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
            objc_retain(_value); }}// Use in getters to determine if _value needs to be put into the automatic release pool
    inline id autoreleaseReturnedValue(a) {
        if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
            return objc_autorelease(_value);
        }
        return_value; }};Copy the code

Typedef DenseMap

ObjectAssociationMap; ObjectAssociationMap is a hash table whose key is const void * and value is object Society. (Const void * is the key we use to associate objects with)

Typedef DenseMap < DisguisedPtr < objc_object >, ObjectAssociationMap > AssociationsHashMap; AssociationsHashMap is a key is DisguisedPtr< objC_Object >, and value is the hash table of the ObjectAssociationMap. (DisguisedPtr

is the pointer address of our source object, disguised as an integer used)

The class definition of AssociationsManager is not complex. From the point of view of data structure, it is used as a key is DisguisedPtr< objC_Object > and value is the hash table of ObjectAssociationMap. The AssociationsHashMap contains a local static AssociationsHashMap that stores all Associated objects in the Associated Object mechanism.

class AssociationsManager {
    // Storage template class name
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    // Static variable _mapStoreage, used to store AssociationsHashMap data
    staticStorage _mapStorage; .// Return the internal saved AssociationsHashMap,
    AssociationsHashMap &get(a) {
        return _mapStorage.get();
    }
    ...
};
Copy the code

To sum up, the data structure used by the Associated Object can be summarized as follows:

  1. Get a global AssociationsHashMap using the Get function of the AssociationsManager.
  2. According to our source object DisguisedPtr

    get the ObjectAssociationMap from AssociationsHashMap.
  3. Get the Object Society from the ObjectAssociationMap based on the association key we specified (const void *key).
  4. The two member variables of ObjcAssociation hold our association policy _policy and association value _value, respectively.

ForbidsAssociatedObjects (indicates whether instance objects of a class are allowed to be Associated with objects)

// class does not allow associated objects on its instances
#define RW_FORBIDS_ASSOCIATED_OBJECTS       (1<<20)

bool forbidsAssociatedObjects(a) {
    return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
}
Copy the code

SetHasAssociatedObjects sets the uintptr_t has_assoc in the ISA of the object: 1; Bit to indicate that the object has an associated object that is to be cleaned up when dealloc is executed.

_object_set_associative_reference is the internal implementation of the objc_setAssociatedObject function, which is the complete process of adding Associated objects to the source Object.

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if(! object && ! value)return; // Nulls the object and its associated value if both nil return

    // Determine whether the class allows associated objects
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    // Disguise the object pointer to backup
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // Create an association based on the input (association policy and association value)
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    // Determine whether to retain/copy value according to the association policy before locking
    association.acquireValue(a); {// Create a temporary mananger variable
        // There is one more joint operation
        / / in its constructor AssociationsManagerLock. Lock (lock)
        AssociationsManager manager;
        // Get the global AssociationsHashMap
        AssociationsHashMap &associations(manager.get());

        if (value) {
            // Here DenseMap is a black box for us, just look at try_emplace
            
            // Try to insert 
      
       , ObjectAssociationMap> in the global AssociationsHashMap
      
            // The return value type is STD ::pair
      ,>
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            // If the new insert succeeds
            if (refs_result.second) {
                /* it's the first association we make */
                // The association was established for the first time
                // Set uintptr_t has_assoc to 1; Bit to indicate that the object has an associated object
                object->setHasAssociatedObjects(a); }/* establish or replace the association */
            // Rebuild or replace association
            auto &refs = refs_result.first->second;
            
            auto result = refs.try_emplace(key, std::move(association));
            if(! result.second) {/ / replace
                // If there is an old value before, swap the old member variable to association
                // Then execute release at the end of the function according to the corresponding associated policy
                association.swap(result.first->second); }}else {
            // If value is nil, the previous associated object is set to nil
            // Remove the specified associated object
            auto refs_it = associations.find(disguised);
            if(refs_it ! = associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if(it ! = refs.end()) {
                    association.swap(it->second);
                    // Clears the specified associated object
                    refs.erase(it);
                    // If the current AssociationsHashMap is empty, the global AssociationsHashMap is used
                    // To remove the object
                    if (refs.size() = =0) {
                        associations.erase(refs_it); }}}}// Destruct the mananger temporary variable
        // There is one more joint operation
        / / in its destructor AssociationsManagerLock. Unlock () to unlock
    }

    // release the old value (outside of the lock).
    // The association internal value has been replaced. // The association internal value has been replaced
    association.releaseHeldValue(a); }Copy the code

_object_get_associative_reference is the internal implementation of the objc_getAssociatedObject function, which reads the AssociatedObject specified by the source Object based on the key.

id
_object_get_associative_reference(id object, const void *key)
{
    // Local variables
    ObjcAssociation association{};

    {
        / / lock
        AssociationsManager manager;
        // Get the globally unique AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        
        // Get the corresponding ObjectAssociationMap from the global AssociationsHashMap
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if(i ! = associations.end()) {
            // If yes
            ObjectAssociationMap &refs = i->second;
            // Get the objcasSociety corresponding to the key from ObjectAssocationMap
            ObjectAssociationMap::iterator j = refs.find(key);
            if(j ! = refs.end()) {
                // If yes
                association = j->second;
                // Determine whether to retain _value based on the association policy
                association.retainReturnedValue();
            }
        }
        / / unlock
    }
    // Return _value and determine whether to add it to the automatic release pool based on the association policy
    return association.autoreleaseReturnedValue(a); }Copy the code

_object_remove_ASsocations Removes Associated Objects from all source Objects.

// Unlike setting/getting an associated reference, 
// this function is performance sensitive because
// of raw isa objects (such as OS Objects) that can't
// track whether they have associated objects.

// Unlike setting/getting, this function is performance-sensitive,
// Because the original ISA objects (such as OS objects) cannot track whether they have associated objects.
void
_object_remove_assocations(id object)
{
    // ObjectAssociationMap of the object
    ObjectAssociationMap refs{};

    {
        / / lock
        AssociationsManager manager;
        // Get the global AssociationsHashMap
        AssociationsHashMap &associations(manager.get());
        
        // Get the corresponding ObjectAssociationMap of the object, which contains all the (keys, objects Association).
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if(i ! = associations.end()) {
            // put the contents of I ->second into the refs object
            refs.swap(i->second);
            // remove the ObjectAssociationMap from the global AssociationsHashMap
            associations.erase(i);
        }
        
        / / unlock
    }

    // release everything (outside of the lock).
    // iterate over (key, object society) in the ObjectAssociationMap
    // Release _value of ObjcAssociation based on _policy
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}
Copy the code

An extension:

Can attributes be implemented in classification at all? The first thing to know is what a property is, and the concept of a property determines the answer to that question.

  • If attributes are understood as instance variables accessed through methods, the answer to this question is no, because classification cannot add additional instance variables to a class.
  • Classification can implement properties if they are simply a collection of access methods and containers that store values.

The implementation of a property in a classification simply implements an interface that looks like a property.

🎉🎉🎉 To be continued…