The introduction

@property should be one of the most asked technical points in the interview process, which can not only investigate a person’s foundation, but also dig out a person’s grasp of technical details. This paper focuses on a comprehensive and detailed introduction of @property which technical points are worth our attention.

  • Code specification

When declaring @property, note the Spaces between keywords and characters.

@property (nonatomic, copy) NSString *name;

  • nature

The essence of @property is ivar + getter + setter;

  • Common keywords

Getter =getterName 2. setter=setterName // Read permission 1. readonly 2. readwrite // Memory management 1. strong 2. assign 3. copy 4. Retained 6. Unsafe_unretained // atom 1Copy the code

Let’s take a look at each key word one by one:

Accessor method

  1. getter=getterName
  2. setter=setterName:

GetterName specifies the name of the property object. If you do not use the getter to specify getterName, the system defaults to propertyName. Generally speaking, getterName is used only when the Bool value corresponding to isPropertyName is required for the indicated property. PropertyName is usually used directly. Setter = setterName: is used to specify set properties used by the setter method, setting the attribute value is used when setterName: method, setterName here is a method name, and therefore to end with “:”, concrete examples are as follows:

// specify getter access named 'isHappy' @property (nonatomic, assign, getter=isHappy) BOOL happy; // Use the default getter/setter method @property (nonatomic, assign) NSInteger age; // specify setter method name 'setNickName:' @property (nonatomic, copy, setter=setNickName:) NSString *name;Copy the code

Read and write access

  1. readwrite
  2. readonly
  • Readwrite: automatically generates the corresponding filegettersetterMethod, read/write permission,readwriteIs the default option for the compiler.
  • Readonly: generates data onlygetter, no need to generatesetter, that is, it can only be read and cannot be modified.

Memory management

  1. Strong // Strong reference, reference count +1
  2. Assign // Assign is a pointer assignment. It does not operate on reference counting. Objects are not automatically set to nil after being destroyed
  3. Copy // Copy a new object with a reference count of 1
  4. Weak // Weak reference, no reference counting operation, object destruction automatically set to nil
  5. Retain // strong reference, object reference count +1
  6. Unsafe_unretained // Weak references. No reference counting operation is performed, and the object is not automatically set to nil upon destruction
  • strong

Strong reference, that is, the reference count of a modifier object is +1. It is used to modify object types, mutable collections, and mutable string types. When the object reference count reaches zero, that is, it is not held by any object, and the object is no longer displayed in the list, the object is freed from memory.

  • assign

Object does not retain, that is, does not change the object reference count. It is usually used to modify basic data types (NSInteger, CGFloat, Bool, NSTimeInterval, etc.) that are stored on the stack and automatically reclaimed by the system.

Assign can also be used to modify NSObject objects. Because assign does not change the reference count of the modifier, the pointer to the object is not automatically emptied when the modifier’s reference count is zero and the object is destroyed. The address to which the object pointer points has already been destroyed. Accessing this property will result in a wild pointer error :EXC_BAD_ACCESS. Therefore, assign is often used to modify the base data type.

  • copy

When a setter method that decorates an object is called, a new object with a reference count of 1 is created. That is, a copy of the object is made in memory, with two Pointers to different memory addresses. Commonly used to modify immutable variables of strings (NSString) and collection classes (NSArray, NSDictionary), Block is also modified with copy.

For copy, here involves deep copy and shallow copy, here is a brief introduction, there will be a follow-up article to discuss this problem:

Shallow copy: a copy of a pointer to the same address. The reference count of the object is +1.

Deep copy: a copy of the content. It creates a new memory space and copies the content again.

  • Copy and mutableCopy of a non-collection object

Immutable objects: Copy is a shallow copy and mutableCopy is a deep copy.

Mutable objects: Copy is a deep copy, and mutableCopy is also a deep copy.

  • Copy and mutableCopy of a collection class object

Immutable objects: Copy is a deep copy and mutableCopy is a deep copy.

Mutable object: copy is a deep copy, and mutable object’s mutableCopy is also a deep copy.

Note: When assigning a copy modifier, copy produces an immutable object. So when an object is a mutable object, remember not to use copy to modify it. If the copy modifier is used in this case, when a method specific to the mutable object is called using the copied object, the system will Crash because the corresponding method cannot be found.

  • weak

The reference count of the modifier is not increased. When the modifier is destroyed, the object pointer is set to nil to prevent wild Pointers. Weak is also used to modify the delegate to avoid circular references. In addition, weak can only be used to modify object types, and is a new modifier introduced under ARC. MRC is equivalent to using assign.

The underlying implementation of weak

The weak implementation is based on the Hash array of SideTables maintained by the Runtime, which stores a SideTable data structure:

struct SideTable { spinlock_t slock; Objc-os (' using spinlock_t = mutex_tt<LOCKDEBUG>; `) RefcountMap refcnts; Weak_table_t Weak_table // Stores the hash table of the weak reference pointer of the object //... };Copy the code
  • Weak function to achieve core weak_table_t data structure:
/** * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */ struct weak_table_t { weak_entry_t *weak_entries; // Hash array size_t num_entries for weak objects; // Uintptr_t mask; // Uintptr_t max_hash_displacement; // Hash element maximum offset};Copy the code
  • Weak_entry_t is also a hash structure:
#define WEAK_INLINE_COUNT 4 #define REFERRERS_OUT_OF_LINE 2 struct weak_entry_t { DisguisedPtr<objc_object> referent; Union {// struct {weak_referrer_t *referrers; Uintptr_out_of_line_ness: 2; uintptr_t num_refs : PTR_MINUS_2; uintptr_t mask; uintptr_t max_hash_displacement; }; // A fixed length array with a maximum of 4. Apple considers that the number of Pointers for half weak references will not exceed this number. Struct {// out_of_line_ness field is low bits of inline_referrers[1] Weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }; Bool out_of_line() {return (out_of_line_ness == REFERRERS_OUT_OF_LINE); } weak_entry_t& operator=(const weak_entry_t& other) { memcpy(this, &other, sizeof(other)); return *this; } weak_entry_t(objc_object *newReferent, objc_object **newReferrer) : referent(newReferent) { inline_referrers[0] = newReferrer; for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; }}};Copy the code

Here we will focus on the switch between weak_entry_t fixed-length array and dynamic array. Firstly, the contents of the original fixed-length array will be transferred to the dynamic array, and then new elements will be inserted into the dynamic array.

If the number of elements in the dynamic array is greater than or equal to 3/4 of the total space, the total space of the dynamic array will be expanded by 2

    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
Copy the code

Each time a dynamic array is expanded, the contents of the original array are inserted back into the new array.

When the reference count of the object is 0, the _objc_rootDealloc method is called to release the object, and the rootDealloc method is called in the _objc_rootDealloc method. If the object is weakly referenced, Object_dispose is then called inside objc_destructInstance, which calls obj->clearDeallocating(); Obtain the address array of all weak Pointers according to the address of the object, traverse the number set to find the corresponding value, set its value to nil, then remove entry from the weak table, and finally delete the record with the address of the abandoned object as the key value from the reference counting table.

Note: many details of the low-level implementation of weak are omitted here. The detailed implementation will be introduced in a separate document later.

  • Retain is a common modifier under MRC:

Retain is no longer used in ARC. Strong is used instead. Retain, like strong, is used to modify object types. It strongly references objects. The reference count of the modified object is +1 and no new memory is allocated to the object.

  • Unsafe_unretained as weak:

Unsafe_unretained does not require +1 for the reference count of an object. It can only be used to modify the type of the object. The pointer of the modified object cannot be cleared automatically after being destroyed, but the pointer still points to the destroyed object.


atomic

Atomic atomicity: Getter /setter methods are locked automatically.

Nonatomic: The system does not lock automatically generated getters/setters.

Set the property function reallySetProperty(…) The atomicity and non-atomicity of is implemented as follows:

if (! atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); }Copy the code

Get property function objc_getProperty(… The internal implementation of

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

Therefore, locking operations on property objects is limited to getter/setter operations on the object. If it is anything other than getter/setter operations, the locking is meaningless. Therefore, atomic atomicity only guarantees thread-safe getters and setters of objects, but does not guarantee thread-safe operations on objects in multiple threads. If one thread is doing getter/setter operations and another thread is releasing operations, crash may occur. Thread-safety in this scenario is something that needs to be handled by the developers themselves.


Process knowledge

  • Add the attribute @property to the Category

Adding @property to a Category only generates declarations of setter/getter methods, and there is no code implementation. This is because the memory layout of the Category object is already established at runtime, and adding instance variables at this point would break the memory layout of the object, which would be disastrous. Therefore, Category cannot add instance variables.

So how do you implement instance variable functionality for a Category? Two ways are briefly listed, which will be introduced separately in the following article without detailed explanation:

  1. Use temporary global variables instead of member variables and in the propertysetter/getterFunction to store the value operation;
  2. throughRuntimeAdd associated object implementation member variables, and in the property ofsetter/getterThere are two key calls to the save value operation in the function:
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, Objc_getAssociatedObject (id _Nonnull Object, Const void * _Nonnull key) // Call to get the associated object valueCopy the code
  • Protocol adds the attribute @property

Add @property to protocol, which is the setter/getter method that declares the property. When implementing protocol, no corresponding member variable is generated. There are two ways to implement this property: dynamic implementation and automatic implementation.

  1. Dynamic implementation, need to be added in the implementation classprotocolCorresponding to the declaration attributesetter/getterMethod and declare a private member variable for the access operation.Remark:The implementation class does not implement the correspondingsetter/getterMethod in the callprotocolProperties of thesetter/getterThe method can not be found because the methodCrash;
  2. Automatic implementation, can be through@synthesize propertyName;Tells the compiler to automatically add the correspondingsetter/getterMethod, and generate corresponding member variables;
  • @ synthesize role

The @synthesize tells the compiler to automatically create setter/getter methods for properties, generate member variables, and alias properties. Such as:

@synthesize name = nickName;

  • @ the dynamic role

At sign dynamic tells the compiler that you don’t need to automatically create a setter/getter for that property, so you need to manually implement that setter/getter, otherwise, when you use that setter/getter for that property, They Crash because they can’t find a way.

  • nullSome related keywords
  1. Nullable: The value can be null
  2. Nonnull: cannot be considered empty
  3. Null_unspecified: Indicates the unknown type
  4. Null_resettable: Get cannot be null, but set can be null
  5. __nullable: Can be Null or nil
  6. __nonnull: cannot be considered empty
  • Swift: unowned and weak:
  1. Under the Swift,weakUse the same as objective-CweakUse the same.
  2. unownedAn unowned reference marks an object that retains a reference to the freed object even if its original reference has been freed, which it is notOptionalTheta is not going to be set to thetanil. When we try to access something like thisunownedAn error occurs in the program while referencing. whileweakAfter the referenced object is released, marked asweakIs automatically set tonil

Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.

Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.

NOTE:

If the captured reference will never become nil, it should always be captured as an unowned reference, rather than a weak reference.

According to the official Apple documentation, we should use unowned if captured references never become nil, otherwise weak.


conclusion

There are many technical points related to @property extension, such as: copy related NSCopying protocol, weak underlying detailed implementation principle, how to ensure the multi-thread security of the object. There are also many technical points related to Runtime and Runloop, which will be covered in future articles.

Knowledge points are a whole set of systems working together, each link is closely linked, and finally become what we see now. This article and future articles will try to narrow the scope of a single article, in order to make the topic closer.

References:

The Objective-C Programming Language


About The Technical Group

The iOS technology group is mainly used to learn and share the technology used in daily development, and keep learning and making progress together. The article repository is here: github.com/minhechen/i… Wechat official account: iOS Technology group, welcome to contact and exchange, thank you for reading.