原文鏈接
轉(zhuǎn)載注明出處
前言
我們是可以在Category
中添加屬性的忙厌,就像以下代碼
/** FaiChouView.h */
#import <UIKit/UIKit.h>
@interface FaiChouView : UIView
@end
@interface FaiChouView (fcHeight)
@property CGFloat fcHeight;
@end
/** FaiChouView.m */
#import "FaiChouView.h"
@implementation FaiChouView
@end
@implementation FaiChouView (fcHeight)
- (CGFloat)fcHeight {
return self.fcHeight;
}
- (void)setFcHeight:(CGFloat)fcHeight {
self.fcHeight = fcHeight;
}
@end
/** main.m */
#import "FaiChouView.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
FaiChouView *fcView = [[FaiChouView alloc] init];
fcView.fcHeight = 20.;
NSLog(@"%f", fcView.fcHeight);
}
}
毫無(wú)疑問(wèn)逢净,它會(huì)崩潰的。問(wèn)題出在哪爹土?如何修改踩身?一步一步來(lái)胀茵。
到底可不可以添加屬性到Category
挟阻?
我們可以在蘋(píng)果官方文檔中找到以下說(shuō)明:
Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class.
文檔中明確指出It’s valid syntax to include a property declaration in a category interface
附鸽,我們可以在接口文件中聲明屬性脱拼,但是編譯器不會(huì)自動(dòng)合成實(shí)例變量(Ivars)和存取方法坷备。
我們應(yīng)該自己實(shí)現(xiàn)存取方法。
我們的代碼到底錯(cuò)在什么地方击你?
won’t be able to keep track of a value for that property
我們的代碼return self.fcHeight;
已經(jīng)無(wú)法無(wú)天了谎柄。
如何修改代碼讓其正常獲取view
的高度?
@implementation FaiChouView (fcHeight)
- (CGFloat)fcHeight {
// return self.fcHeight;
return self.frame.size.height;
}
- (void)setFcHeight:(CGFloat)fcHeight {
// self.fcHeight = fcHeight;
CGRect newframe = self.frame;
newframe.size.height = fcHeight;
self.frame = newframe;
}
@end
官方文檔的說(shuō)明鸿摇,我們?cè)?code>Category中的屬性是不能夠保存其值的劈猿,但是我們可以自定義存取方法拙吉,fcHeight
返回的是view
本身的高度, setter
方法也不將值賦值給fcHeight
揪荣,而間接的給view.frame
,這樣在main.m
中的fcView.fcHeight = 20.;
只是借助了fcHeight
的存取方法給view
本身的賦值佛舱。
通過(guò)以上,我們驗(yàn)證了那句古話
不能在
Category
中添加實(shí)例變量
學(xué)而不思則罔请祖,思而不學(xué)則殆
只要挖到runtime
,任何東西都是一目了然的肆捕。
有些博客看了一遍看不懂,那就多看幾遍慎陵,博客講的只是一個(gè)方面,上面有許多許多知識(shí)點(diǎn)蒙幻,這一面映射出來(lái)的所有點(diǎn)沒(méi)有必要全部掌握,發(fā)現(xiàn)問(wèn)題的關(guān)鍵邮破,帶著問(wèn)題考慮仆救,總會(huì)學(xué)到很多知識(shí)的。
少談些主義彤蔽,多研究些問(wèn)題。 ———— 胡適
objc所有類(lèi)和對(duì)象都是c結(jié)構(gòu)體顿痪,category當(dāng)然也一樣,下面是runtime
中Category的結(jié)構(gòu):
struct _category_t {
const char *name; // 類(lèi)名
struct _class_t *cls; //
const struct _method_list_t *instance_methods; // 實(shí)例方法 -
const struct _method_list_t *class_methods; // 類(lèi)方法 +
const struct _protocol_list_t *protocols; //
const struct _prop_list_t *properties; //
};
properties
這個(gè)category所有的property征懈,這也是category里面可以定義屬性的原因,不過(guò)這個(gè)property不會(huì)合成實(shí)例變量和存取方法卖哎。
一個(gè)普通的屬性(fcTestProperty
)删性,經(jīng)過(guò)編譯器編譯過(guò)后,會(huì)添加以下:
- 在
_ivar_list_t
中添加了_fcTestProperty變量
- 在
_method_list_t
中添加了fcTestProperty
和setFcTestProperty
兩個(gè)方法 - 在
_prop_list_t
中添加了fcTestProperty
這個(gè)property
一個(gè)Category
中的屬性(fcTestProperty
),經(jīng)過(guò)編譯器編譯后蹬挺,只會(huì)在_prop_list_t
中增加fcTestProperty
這個(gè)屬性。
如何在Category
中添加實(shí)例變量呢巴帮?
在MJRefresh
中我們可以找到以下代碼(摘要):
/** UIScrollView+MJRefresh.h */
#import <UIKit/UIKit.h>
#import "MJRefreshConst.h"
@class MJRefreshHeader, MJRefreshFooter;
@interface UIScrollView (MJRefresh)
/** 下拉刷新控件 */
@property (strong, nonatomic) MJRefreshHeader *mj_header;
...
@end
/** UIScrollView+MJRefresh.m */
#pragma mark - header
static const char MJRefreshHeaderKey = '\0';
- (void)setMj_header:(MJRefreshHeader *)mj_header
{
if (mj_header != self.mj_header) {
// 刪除舊的,添加新的
[self.mj_header removeFromSuperview];
[self insertSubview:mj_header atIndex:0];
// 存儲(chǔ)新的
[self willChangeValueForKey:@"mj_header"]; // KVO
objc_setAssociatedObject(self, &MJRefreshHeaderKey,
mj_header, OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"mj_header"]; // KVO
}
}
- (MJRefreshHeader *)mj_header
{
return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
}
...
他是通過(guò)runtime中的關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的发乔,關(guān)于Associated Objects
可以學(xué)習(xí)NSHipster的魔鬼的交易這一篇。
最后我們的代碼調(diào)整為:
/** FaiChouView.m */
#import "FaiChouView.h"
#import <objc/runtime.h>
@implementation FaiChouView
@end
@implementation FaiChouView (fcHeight)
@dynamic fcHeight;
- (CGFloat)fcHeight {
// return self.fcHeight;
// return self.frame.size.height;
return [objc_getAssociatedObject(self, @selector(fcHeight)) floatValue];
}
- (void)setFcHeight:(CGFloat)fcHeightNew {
// self.fcHeight = fcHeight;
// CGRect newframe = self.frame;
// newframe.size.height = fcHeight;
// self.frame = newframe;
NSNumber *fcHeightFloatNumber = @(fcHeightNew);
objc_setAssociatedObject(self, @selector(fcHeight), fcHeightFloatNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
用
objc_setAssociatedObject
和objc_getAssociatedObject
方法綁定的實(shí)例變量與一個(gè)普通的實(shí)例變量完全是兩碼事起愈。