2019-10-15

A property is a public accessor for a class’s member variables. Properties are closely related to methods, with getters and setters corresponding to read-write properties.

First, attributes overview

Properties are mostly used as accessors for member variables, providing an interface for external access to member variables. When you use @property to declare an attribute, you need to specify the attributes of the attribute, including:

  • Read/write feature (readwrite/readonly);
  • Atomicity (atomic/nonatomic);
  • Memory management features (assign/strong/weak/copy);
  • Nullable (nullable/nonnull);

Note: The first value in the above parentheses is the default property of the property, but nullability is a special matter. You can set the default nullability of the property to nonNULL by enclosing the NS_ASSUME_NONNULL_BEGIN/NS_ASSUME_NONNULL_END macro around the property declaration statement.

In addition to the above features, you can explicitly specify getters and setters. The properties of a property specify the behavior characteristics of the property as an accessor. Declaring a property simply means declaring an accessor, which in this case has no implementation of getters and setters. There are two ways in code to want an accessor to associate a particular member variable: 1. Implement getters and setters for properties. But the essence of both is the same, that is, the getters and setters of properties are implemented according to their properties.

Note: @dynamic decorates a property, indicating that getters and setters for the property are not synthesized. Either implement the getter/setter in the current class or subclass implementation, or synthesize the property at sign synthesize in the subclass implementation.

2. Data structure

Property_array_t = list_ARRAY_tt So the property list in the class holds an array of property_LIST_T, which is also a two-dimensional array. Property_list_t inherits from the entsize_LIST_TT sequential table container, and the element type is property_t. The related data structures are as follows, most of which are mentioned in the introduction to member variables and method lists, so I won’t go into them again. According to the data structure related to the attribute, the attribute information stored in the class is only the attribute name and characteristic information. ,>

Attributes are stored in a class much like a method list. The basePropertyList of type property_list_t in class_ro_t holds only the basic properties defined when the class is defined. These properties are determined at compile time; Properties of type PROPERty_ARRAY_T in class_rw_T hold the complete list of properties of the class, including the basic properties of the class, as well as the properties defined in the class’s classification as determined by the runtime and the properties added dynamically at runtime.

class property_array_t : 
    public list_array_tt<property_t, property_list_t> 
{
    typedef list_array_tt<property_t, property_list_t> Super;

 public:
    property_array_t duplicate() {
        returnSuper::duplicate<property_array_t>(); }}; struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> { }; struct property_t { const char *name; const char *attributes; };Copy the code

Third, the implementation principle of adding attributes

To add a property call class_addProperty(…) Function, note that there is no operation associated with the method list in the source code, and assume that the operation of the property associated with the method list is hidden in the implementation of @synthesize, @Dynamic, and attributes resolution for the property. Property addition is basically the same as method addition. It is the beginning of the outer bit array container added to class_rw_t’s complete property list properties, and therefore satisfies the priority relationship: properties dynamically added at run time > properties defined by the class’s classification > basic properties defined at class definition.

BOOL class_addProperty(Class CLS, const char *name, const objc_property_attribute_t *attrs, unsigned int n) {return_class_addProperty(cls, name, attrs, n, NO); Static bool _class_addProperty(Class CLS, const char *name, const objc_property_attribute_t *attrs, const char *name, const objc_property_attribute_t *attrs, unsigned int count, bool replace) {if(! cls)return NO;
    if(! name)returnNO; Property_t *prop = class_getProperty(CLS, name);if(prop && ! Replace) {// Exists and does not specify the replace attributereturn NO;
    } 
    else if(prop) {// replace property rwlock_writer_t lock(runtimeLock); try_free(prop->attributes); prop->attributes = copyPropertyAttributeString(attrs, count);return YES;
    }
    else {
        rwlock_writer_t lock(runtimeLock);
        
        assert(cls->isRealized());
        
        property_list_t *proplist = (property_list_t *)
            malloc(sizeof(*proplist));
        proplist->count = 1;
        proplist->entsizeAndFlags = sizeof(proplist->first);
        proplist->first.name = strdup(name);
        proplist->first.attributes = copyPropertyAttributeString(attrs, count);
        
        cls->data()->properties.attachLists(&proplist, 1);
        
        return YES;
    }
}

objc_property_t class_getProperty(Class cls, const char *name)
{
    if(! cls || ! name)return nil;

    mutex_locker_t lock(runtimeLock);

    checkIsKnownClass(cls);
    
    assert(cls->isRealized());

    for(; cls; cls = cls->superclass) {for (auto& prop : cls->data()->properties) {
            if (0 == strcmp(name, prop.name)) {
                return(objc_property_t)&prop; }}}return nil;
}
Copy the code

Four, access attribute implementation principle

The code for accessing property values is concentrated in the objc-Accessors.mm source file.

4.1 Obtaining an Object Attribute Value

Call objc_getProperty_gc (…). To get the value of an object’s property, you only access the member variable space corresponding to the property in a certain way. If the property is atomic, then spinlock_t is added to both ends of the code that gets the value of the property. This is the difference between atomic and nonatomic.

Note: In fact, the dynamic addition of properties described in Section 3 is of little use to application developers (and certainly to Runtime itself) for the following reasons: 1. There is no Runtime API that specifies attributes associated with member variables; 2. 2. You can simulate attributes by defining functions associated with objects. At this time, the dynamically added attributes become a chicken rib and are optional.

#if SUPPORT_GC
id objc_getProperty_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    return *(id*) ((char*)self + offset);
}
#else
id 
objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) 
{
    return objc_getProperty_non_gc(self, _cmd, offset, atomic);
}
#endif

id objc_getProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if(! atomic)return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}
Copy the code

Getting the property value does nothing to the copy type, that is, the getter for copy returns the property pointing to the object itself. The getter for copy does not contain the copy operation **. You can verify that the break point runs at the tag with the following code. Looking at testObj’s memory, bytes 8-16 hold the actual NSArray address that testObj’s ARR attribute points to. The printed testobj. arr address is found to be consistent with bytes 8-16 of testObj’s memory.

@interface TestPropCopy: NSObject @property(copy, nonatomic) NSArray* arr; @end @implementation TestPropCopy +(void)testPropCopy{
    NSArray* arr = [NSArray arrayWithObject:@"app"];
    TestPropCopy* testObj = [[self alloc] init];
    testObj.arr = arr;
    
    NSLog(@"TestObj: % @".testObj);
    NSLog(@"Arr: % @", arr);
    NSLog(@"TestObj. Arr: % @".testObj.arr); } @end;} @endCopy the code

Note: spinlock_t is the os_LOCK_handoff_s lock, which is supposed to be a mutex. Note that spinlock_t is not OSSpinLock. OSSpinLock has been deprecated due to known performance issues.

4.2 Modifying object Attribute Values

Call objc_setProperty (…). Setting the value of an object’s property is also a way to access the member variable space corresponding to the property. Similarly, if the property is atomic, the lock unlock code spinLOCK_t is added at both ends of the code that sets the value of the property. If the property is copy, then the object referred to by the parameter passed to the setter is copied into the corresponding member variable space.

#if SUPPORT_GC
void objc_setProperty_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) {
    if (shouldCopy) {
        newValue = (shouldCopy == MUTABLE_COPY ? [newValue mutableCopyWithZone:nil] : [newValue copyWithZone:nil]);
    }
    objc_assign_ivar(newValue, self, offset);
}
#else
void 
objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, 
                 BOOL atomic, signed char shouldCopy) 
{
    objc_setProperty_non_gc(self, _cmd, offset, newValue, atomic, shouldCopy);
}
#endifvoid objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { bool copy = (shouldCopy && shouldCopy ! = MUTABLE_COPY); bool mutableCopy = (shouldCopy == MUTABLE_COPY); reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); } static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {if (offset == 0) {
        object_setClass(self, newValue);
        return; } id oldValue; id *slot = (id*) ((char*)self + offset); // If the property is copy, copy the object pointed to by newVal into the corresponding member variable spaceif (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return; newValue = objc_retain(newValue); } // Atomicityif(! atomic) { oldValue = *slot; *slot = newValue; }else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
Copy the code

Note: The implementation of accessing property values is triggered by calling the SEL of the corresponding method of the getter and setter of the property directly, and the details of the association between the property and the method are not published in the source code.

4.3 Discuss the implementation of attribute association member variables

In objective-C code, the property associated with a member variable is implemented by @synthesize, and Runtime doesn’t expose that piece of code. This section looks at how at sign synthesize works.

When a class defines a property and does not manually define getter and setter methods for the property, the corresponding prop and setProp methods are added to the class’s method list. And object_getProperty (…). The argument list contains self and _cmd, which is similar to the format of the message. At @synthesize, Runtime generates the getter methods SEL, IMP, and setter methods SEL, IMP based on the attributes of the property, and adds them to the class method list. The IMP pseudo-code for getters and setters is as follows, where the # sign is surrounded by compile-time determinable parameters;

id propGetter(id self, SEL _cmd) {
    char* ivarName = synthizeName ? : ( '_' + #propertyName#)

    Class selfClass = object_getClass(self)
    Ivar ivar = getIvar(Class cls, ivarName)
    uint_32 ivarOffset = ivar_getOffset(ivar)
    
    objc_getProperty(self, _cmd, 
                     #propertyNameAttr#, 
                     ivar,
                     #propertyAtomicAttr#)
} 

void propSetter(id self, SEL _cmd, id newVal) {
    char* ivarName = #synthesizeName# ? : ( '_' + #propertyName#)

    Class selfClass = object_getClass(self)
    Ivar ivar = getIvar(Class cls, ivarName)
    uint_32 ivarOffset = ivar_getOffset(ivar)

    objc_setProperty(self, _cmd, 
                     #propertyNameAttr#, 
                     newVal
                     ivar,
                     #propertyAtomicAttr#,
                     #propertyShouldCopyAttr#)
}
Copy the code

However, there is an obvious performance drawback to the above process, which requires calling getIvar(…) every time a member variable is accessed. While getIvar (…). Is to walk through the entire list of member variables of the class, looking up member variables by their name, which is obviously not what the actual implementation should do. Therefore, the above code only simulates the implementation process of the property, which will be covered in a separate article.

Five, the summary

  • The dynamic nature of the properties that Runtime provides doesn’t make much sense for application development. The Runtime property associates member variables in the implementation code of the @synthesize, and member variables can’t be added dynamically, so even providing them doesn’t make much sense;

  • When a property is added dynamically, it does not include the operation of adding getter and setter methods to the property, so it must be implemented manually.

  • The next article introduces classification.