preface
- In the development process, it is inevitable to use public variables. The simplest way to record the state and data of shared objects is to create public variables;
- When there are more business logic, it will become dangerous to adopt this idea, and the code logic will become unclear. Slowly, there will be a bad smell of code.
- The details are summarized as follows:
1,Too many logical branches, not clear enough, and public variables are not conducive to system maintenance and project expansion; 2,Security is threatened, too many variables are shared, and the writing and reading of variables are dangerous under multithreading; 3,When the business logic crosses too much, it is difficult to guarantee the data-Logical consistency;
How to solve it?
- Objective-C provides a solution to the above problems: use associated objects;
- We can think of the associated object as an Objective-C object (such as a dictionary), which is connected to an instance of the class through a given key;
- However, since the C interface is used, the key is a void pointer (const void *). We also need to specify a memory management policy to tell the Runtime how to manage the memory of this object.
This memory management policy can be specified by the following values:
OBJC_ASSOCIATION_ASSIGN /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC/**< Specifies a strong reference to the associated object. * The association is not made atomically. */ OBJC_ASSOCIATION_COPY_NONATOMIC /**< Specifies that the associated object is copied.* The association is not made atomically. */ OBJC_ASSOCIATION_RETAIN /**< Specifies a strong reference to the associated object. * The association is made atomically. */ OBJC_ASSOCIATION_COPY /**< Specifies that the associated object is copied.* The association is made atomically. */
- When the host object is released, the associated object will be processed according to the specified memory management policy;
- If the specified policy is OBJC_ASSOCIATION_ASSIGN, the associated object will not be released when the host is released;
- If Retain or Copy is specified, the associated object will be released when the host is released.
- We can even choose whether to automatically Retain/Copy. This is very useful when we need to process multithreaded code accessing associated objects in multiple threads to implement thread and logical binding.
Specific solutions:
- 1. All we need to do to connect an object to other objects is the following two lines of code:
static char anObjectKey; objc_setAssociatedObject(self, &anObjectKey, anObject, OBJC_ASSOCIATION_RETAIN)
- 2. Use the following line of code to get the bound object:
id anObject = objc_getAssociatedObject(self, &anObjectKey);
-
In this case, the Self object will get a new associated object anObject, and the memory management policy is to automatically Retain the associated object. When the Self object is released, it will automatically Release the associated object;
-
In addition, if we use the same key to associate another object, the previously associated object will also be automatically released. In this case, the previous associated object will be properly disposed, and the new object will use its memory;
-
3. Remove associated objects:
objc_removeAssociatedObjects(anObject);
Or use objc_ The setassociatedobject function sets the association object specified by key to nil;
Take a chestnut
- In development engineering, adding click gestures to UIView is a very common requirement. Suppose that now we want to dynamically connect a Tap gesture operation to any UIView, and specify the actual operation after clicking as needed;
- At this time, we can associate a gesture object and the operated Block object with our UIView object. The task is divided into the following two parts.
- First, if necessary, we need to create a gesture recognition object and associate it with Block. The specific implementation is as follows:
- (void)setTapActionWithBlock:(void (^)(void))block { UITapGestureRecognizer *tapGR = objc_getAssociatedObject(self, &kJLActionHandlerTapGestureKey); if (!tapGR) { tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(JL_handleActionForTapGesture:)]; [self addGestureRecognizer: tapGR]; objc_setAssociatedObject(self, & kJLActionHandlerTapGestureKey, tapGR, OBJC_ASSOCIATION_RETAIN); } objc_setAssociatedObject(self, & kJLActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY); }
- This code detects the associated object of gesture recognition. If not, an association is created and established. At the same time, connect the incoming Block object to the specified key. Note the memory management policy associated with the Block object - Copy;
- Then, handle the click event. The specific implementation is as follows:
- (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateRecognized) { void(^action)(void) = objc_getAssociatedObject(self, kJLActionHandlerTapBlockKey); if (action) { action(); } } }
- We need to detect the state of the gesture recognition object, because we only need to perform the operation when the click gesture is recognized.
- As can be seen from the above, the implementation of associated objects is not very complex, and the existing functions of classes can be dynamically enhanced.
Optimization and perfection
- However, it is still a little imperfect. The code is too loose. If it is applied to the project in the above way, a lot of duplicate code will be written. We need to encapsulate it without exposing * * #import < objc / runtime h> * * reference. The specific implementation is as follows:
- Redefine a set of enumerations that represent memory policies:
typedef NS_ENUM(NSInteger, JLAssociationPolicy) { /** OBJC_ASSOCIATION_ASSIGN < Specifies a weak reference to the associated object> */ JLAssociationPolicyAssign = 1, /** OBJC_ASSOCIATION_RETAIN_NONATOMIC <Specifies a strong reference to the associated object. * The association is not made atomically> */ JLAssociationPolicyRetainNonatomic = 2, /** OBJC_ASSOCIATION_COPY_NONATOMIC < Specifies that the associated object is copied. * The association is not made atomically.> */ JLAssociationPolicyCopyNonatomic = 3, /** OBJC_ASSOCIATION_RETAIN < Specifies a strong reference to the associated object. * The association is made atomically.> */ JLAssociationPolicyRetain = 4, /** OBJC_ASSOCIATION_COPY < Specifies that the associated object is copied. * The association is made atomically.> */ JLAssociationPolicyCopy = 5 };
- Declaration method:
/** Set AssociatedObject @param object Be Associated Object @param key associted Key @param value associated value or object @param policy policy */+ (void)JL_setAssociatedObject:(id _Nonnull)object key:(NSString *_Nullable)key value:(id _Nullable)value policy:(JLAssociationPolicy)policy;/** Get AssociatedObject @param object Be Associated Object @param key associted Key @return associated value or object */+ (id _Nullable)JL_getAssociatedObject:(id _Nonnull)object key:(NSString *_Nullable)key;/** Remove AssociatedObject @param object associated value or object */+ (void)JL_removeAsociatedObject:(id _Nonnull)object;
When using Key, you only need to pass in the parameters of NSString class, not const void *_ Nonnull Key, the interface method becomes more elegant and concise.
- Rewrite the above method by encapsulating:
//Defines the Key of the binding object static NSString *const kJLActionHandlerTapGestureKey = @"JLActionHandlerTapGestureKey"; static NSString *const kJLActionHandlerTapBlockKey = @"JLActionHandlerTapBlocKey"; - (void)setTapActionWithBlock:(void (^)(void))block { UITapGestureRecognizer *tapGR = [JLAssociatedObjectUtils JL_getAssociatedObject:self key:kJLActionHandlerTapGestureKey]; if (!tapGR) { tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(JL_handleActionForTapGesture:)]; [self addGestureRecognizer: tapGR]; [JLAssociatedObjectUtils JL_setAssociatedObject:self key:kJLActionHandlerTapGestureKey value:tapGR policy:JLAssociationPolicyRetain]; } [JLAssociatedObjectUtils JL_setAssociatedObject:self key:kJLActionHandlerTapBlockKey value:tapGR policy:JLAssociationPolicyCopy]; } - (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateRecognized) { void(^action)(void) = [JLAssociatedObjectUtils JL_getAssociatedObject:self key:kJLActionHandlerTapBlockKey]; if (action) { action(); } } }
Sweep the following two-dimensional code, welcome to my personal WeChat official account: ape view dynamics (ID:iOSDevSkills), you can leave messages on WeChat official account, more exciting technical articles, and look forward to your joining! Discuss and grow together! Attack the City lion together!