前言
- 在開發(fā)過程中饶套,避免不了會使用公共變量垒探,記錄共享對象狀態(tài)、數據最簡單的方式就是創(chuàng)建創(chuàng)建公共變量蛤克;
- 當業(yè)務邏輯變多构挤,還采用這種思想就會變得危險,代碼邏輯變得不清晰筋现,慢慢就有一種代碼壞味道矾飞。
- 具體總結如下:
1、過多邏輯分支豹绪,不夠清晰申眼,公共變量不利于系統(tǒng)維護和項目拓展括尸;
2、安全性收到威脅钓辆,過多地方共享變量肴焊,變量的寫入和讀取在多線程下是危險的功戚;
3啸臀、業(yè)務邏輯交叉過多時,很難保證數據-邏輯的一致性豌注;
如何解決呢灯萍?
- 出現問題旦棉,解決問題药薯,Objective-C針對上述問題救斑,提供了一個解決方案:即使用關聯對象(Associated Object)脸候;
- 我們可以把關聯對象想象成一個Objective-C對象(如字典),這個對象通過給定的key連接到類的一個實例上鄙煤;
- 不過由于使用的是C接口茶袒,所以key是一個
void指針(const void *)
薪寓。我們還需要指定一個內存管理策略,以告訴Runtime如何管理這個對象的內存锥腻。
這個內存管理的策略可以由以下值指定:
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. */
- 當宿主對象被釋放時瘦黑,會根據指定的內存管理策略來處理關聯對象;
- 如果指定的策略是OBJC_ASSOCIATION_ASSIGN奇唤,則宿主釋放時咬扇,關聯對象不會被釋放甲葬;
- 而如果指定的是Retain或者是Copy,則宿主釋放時懈贺,關聯對象會被釋放经窖。
- 我們甚至可以選擇是否是自動Retain/Copy。當我們需要在多個線程中處理訪問關聯對象的多線程代碼時梭灿,這就非常有用了画侣,實現線程和邏輯綁定。
具體解決:
- 1堡妒、我們將一個對象連接到其它對象所需要做的就是下面兩行代碼:
static char anObjectKey;
objc_setAssociatedObject(self, &anObjectKey, anObject, OBJC_ASSOCIATION_RETAIN)
- 2配乱、使用下面一行代碼獲取綁定的對象:
id anObject = objc_getAssociatedObject(self, &anObjectKey);
在這種情況下,Self對象將獲取一個新的關聯的對象anObject,且內存管理策略是自動Retain關聯對象的诵,當Self對象釋放時,會自動Release關聯對象;
另外佑钾,如果我們使用同一個key來關聯另外一個對象時西疤,也會自動釋放之前關聯的對象。這種情況下休溶,先前的關聯對象會被妥善地處理掉代赁,并且新的對象會使用它的內存;
3兽掰、移除關聯對象:
objc_removeAssociatedObjects(anObject);
或者使用objc_setAssociatedObject函數將key指定的關聯對象設置為nil;
舉個栗子
- 在開發(fā)工程中芭碍,給UIView添加單擊手勢是非常常見的需求。假定孽尽,現在我們就要動態(tài)地將一個Tap手勢操作連接到任何UIView中窖壕,并且根據需要指定點擊后的實際操作;
- 這時候我們就可以將一個手勢對象及操作的Block對象關聯到我們的UIView對象中杉女。這項任務分為一下兩部分瞻讽。
- 首先,如果需要熏挎,我們要創(chuàng)建一個手勢識別對象并將它及Block作為關聯對象速勇。具體實現如下:
- (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);
}
- 這段代碼檢測了手勢識別的關聯對象。如果沒有坎拐,則創(chuàng)建并建立關聯關系烦磁。同時,將傳入的塊對象連接到指定的key上哼勇。注意Block對象的關聯內存管理策略-Copy;
- 然后都伪,處理單擊事件,具體實現如下:
- (void) JL_handleActionForTapGesture:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateRecognized)
{
void(^action)(void) = objc_getAssociatedObject(self, kJLActionHandlerTapBlockKey);
if (action)
{
action();
}
}
}
- 我們需要檢測手勢識別對象的狀態(tài)猴蹂,因為我們只需要在點擊手勢被識別出來時才執(zhí)行操作院溺。
- 通過上面可以看到,關聯對象實現起來也不是很復雜磅轻,而且還可以動態(tài)的增強類現有的功能。
優(yōu)化完善
- 但是逐虚,還是有一點不太完美聋溜,代碼過于松散,按照上述的方式去應用到項目中叭爱,會寫不少重復代碼撮躁,我們需要封裝一下,并不暴露#import <objc/runtime.h>引用买雾,具體實現如下:
- 重新定義一套表征內存策略的枚舉:
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
};
- 聲明方法:
/**
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;
Key把曼,在使用的時候只需要傳入NSString類的參數就可以了,不需要const void * _Nonnull key
杨帽,接口方法變得更優(yōu)雅簡潔一些。
- 用封裝的方法重寫上述方法:
//定義綁定對象的Key
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();
}
}
}