最近在项目中使用一个XML文件描述应用程序的界面,内容类似如下:
3. 扩展数据成员
OC中的每个对象,在运行时期都有一个对象关联表,我们可以操作该对象关联表来实现动态扩展一个对象的数据成员。使用下面的函数向表中添加或删除一个关联对象:
因为我们在属性声明中指定的参数是”copy”,所以最后一个参数应为:OBJC_ASSOCIATION_COPY。第二个参数为键值,可以用来获取关联对象。
但是请注意,上面的写法是极其错误的。
在OC中扩展一个类时,如果定义了一个与原生类同样的方法,那么原生类中的方法会被覆盖掉。上面的代码,在UIView的扩展中实现了dealloc方法,那么在UIView的对象被释放时,只会调用这个扩展方法,而原生UIView类的dealloc方法是无论如何也不会再被调到的,这会导致更加严重的内存问题。解决方法:
1)在扩展UIView时,不能定义dealloc函数。
2)先找到原生UIView的dealloc方法的函数指针并保存。
2)将dealloc替换为一个自定义的函数。
3)在自定义的函数中能过保存的函数指针调用dealloc。
<UIView
id="View_sendmoney"
frame="0,0,320,480">
<UITextField
id="account"
frame="10,10,300,35"
/>
<UITextField
id="amount"
frame="10,55,300,35"
/>
<UIButton
id="submit"
frame="10,100,300,40"
/>
</UIView>
2. 扩展属性
上面的内容经过layout inflate后会生成view树,其中每个view有一个id属性,这个属性在后续的视图处理中会用到。因此我们需要给UIView扩展一个属性:
@interface UIView (DOMExtensions)
@property(nonatomic, copy) NSString *viewId;
@end
3. 扩展数据成员
OC中的每个对象,在运行时期都有一个对象关联表,我们可以操作该对象关联表来实现动态扩展一个对象的数据成员。使用下面的函数向表中添加或删除一个关联对象:
void objc_setAssociatedObject(
id object,
void *key,
id value,
objc_AssociationPolicy policy);
注意第二个参数指定的key值是(void*)类型,表示其接收一个内存地址,不能传递一个临时的字符串给该参数。
第三个参数value是需要关联的对象,由于这个参数的类型是id类型,因此它只接收类型为NSObject或其子类的对象。如果关联一个整数或浮点型的数据,要先转换成NSNumber再进行关联。该参数为 nil 时,则从关联表中删除一个已存在的对象。
@implementation UIView (DOMExtensions)
static const NSInteger kViewIdKey = 1;
- (void)setViewId:(NSString *)aViewId {
objc_setAssociatedObject(self, &kViewIdKey, aViewId, OBJC_ASSOCIATION_COPY);
}
- (NSString *)viewId {
return (NSString *)objc_getAssociatedObject(self, &kViewIdKey);
}
@end
因为我们在属性声明中指定的参数是”copy”,所以最后一个参数应为:OBJC_ASSOCIATION_COPY。第二个参数为键值,可以用来获取关联对象。
4. 方法拦截
上述的代码中,为UIView的对象动态的关联了另外一个对象,关联对象在使用完成之后,需要从关联表中移除,否则会有内存泄露。移除关联对象最好的方法是在UIView的dealloc方法中。
@implementation UIView (DOMExtensions)
- (void)dealloc {
objc_removeAssociatedObjects(self);
[super dealloc];
}
@end
但是请注意,上面的写法是极其错误的。
在OC中扩展一个类时,如果定义了一个与原生类同样的方法,那么原生类中的方法会被覆盖掉。上面的代码,在UIView的扩展中实现了dealloc方法,那么在UIView的对象被释放时,只会调用这个扩展方法,而原生UIView类的dealloc方法是无论如何也不会再被调到的,这会导致更加严重的内存问题。解决方法:
1)在扩展UIView时,不能定义dealloc函数。
2)先找到原生UIView的dealloc方法的函数指针并保存。
2)将dealloc替换为一个自定义的函数。
3)在自定义的函数中能过保存的函数指针调用dealloc。
// 函数指针,指向原生UIView的dealloc方法。
static IMP UIView_dealloc_old;
// 替换后的dealloc函数,注意函数名必需以两个"$"符开始。
static void $$UIView_dealloc(UIView *self, SEL op) {
// 移除所有关联对象。
objc_removeAssociatedObjects(self);
// 调用dealloc方法。
UIView_dealloc_old(self, op);
}
- (void)setViewId:(NSString *)aViewId {
// 获取dealloc函数指针。
UIView_dealloc_old = class_getMethodImplementation([UIView class], @selector(dealloc));
// 替换dealloc方法。
class_replaceMethod([UIView class], @selector(dealloc), (IMP)$$Shape_dealloc, "v@:");
// 设置关联对象。
objc_setAssociatedObject(self, "viewId", aViewId, OBJC_ASSOCIATION_COPY);
}