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
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)∝ }}}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.