@property in Category

原稿链接
转发申明出处

前言

我们是足以在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);
    }
}

毫无疑问,它会崩溃的。难点出在哪?如何修改?一步一步来。

到底可不可以添加属性到Category

咱俩可以在苹果官方文档中找到以下表明:

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,大家得以在接口文件中宣称属性,不过编译器不会自动合成实例变量(Ivars)和存取方法。
咱俩应该本人已毕存取方法。

大家的代码到底错在什么样地点?

won’t be able to keep track of a value for that property

咱俩的代码return self.fcHeight;业已黔驴技穷无天了。

什么修改代码让其健康获取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

合法文档的辨证,我们在Category中的属性是不能保留其值的,可是咱们得以自定义存取方法,fcHeight归来的是view作者的万丈,
setter方法也不将值赋值给fcHeight,而间接的给view.frame,这样在main.m中的fcView.fcHeight = 20.;只是依靠了fcHeight的存取方法给view自身的赋值。

透过以上,我们作证了那句古话

不能在Category中添加实例变量

学而不思则罔,思而不学则殆

假如挖到runtime,任何事物都以吃透的。
稍稍博客看了两遍看不懂,那就多看四次,博客讲的只是一个方面,下边有为数不少浩大知识点,这一面映射出来的富有点并未须求全部领会,发现标题标基本点,带着难题考虑,总会学到很多文化的。

少谈些主义,多钻研些难题。 ———— 胡希疆

objc全部类和对象都以c结构体,category当然也如出一辙,下边是runtime中Category的结构:

struct _category_t {
    const char *name; // 类名
    struct _class_t *cls; //
    const struct _method_list_t *instance_methods; // 实例方法 -
    const struct _method_list_t *class_methods; // 类方法 +
    const struct _protocol_list_t *protocols; // 
    const struct _prop_list_t *properties; //
};

properties以此category全部的property,那也是category里面能够定义属性的原委,不过这几个property不见面成实例变量和存取方法。

1个普普通通的性质(fcTestProperty),经过编译器编译过后,会加上以下:

  1. _ivar_list_t中添加了_fcTestProperty变量
  2. _method_list_t中添加了fcTestPropertysetFcTestProperty七个法子
  3. _prop_list_t中添加了fcTestProperty这个property

一个Category中的属性(fcTestProperty),经过编译器编译后,只会在_prop_list_t中增加fcTestProperty其一性子。

如何在Category中添加实例变量呢?

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];

        // 存储新的
        [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);
}

...

她是通过runtime中的关联对象达成的,关于Associated Objects可以学习NSHipster的鬼怪的贸易这一篇。

末段大家的代码调整为:

/** 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_setAssociatedObjectobjc_getAssociatedObject艺术绑定的实例变量与八个屡见不鲜的实例变量完全是两遍事。

参考链接