iOS开发 - 链式编程思想

因为有 Masory 以及 Snapkit 这些知名开源库的存在,相信很多 iOS 开发者对链式编程都不会太陌生,先来看下面这句代码:

[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.bottom.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(250, 100, 250, 100));
    }];

这句代码就属于链式编程,而且 Masonry 框架本身也是通过链式语法对 NSLayoutConstraints 进行的封装。对 Masonry 感兴趣的童鞋可以读读这篇文章 iOS开发之源码解析 - Masonry

链式编程思想:

所谓链式编程就是通过点(.)将多个操作链接在一起成为一句代码,使代码更加紧凑,也提高了代码的可读性(如上面那句代码)。

链式编程特点:

  • 方法的返回值是 block,block 中必须有一个返回值,通常返回它本身,也可以是处理后的数据或对象。
  • 返回值中的 block 具备两个功能,第一:可以作为类的属性被'点'出来。第二:可以当作函数直接调用。
  • 通常会通过调用一个函数来给属性赋值,在函数内部封装赋值的语句,也可以加入一些判断逻辑等。

链式编程练习

我这里写了一个 UILabel 的扩展类用来练习链式编程,叫做 UILabel+NNCategory。利用这个分类创建 UILabel,并为其设置frame,text,font,textColor,backgroundColor等属性,代码如下:

    [UILabel addToView:self.view createLabel:^(UILabel *label) {
        label.nn_frame(50, 200, 300, 100).nn_text(@"NNTreasure").nn_fontSize(50).nn_textColor([UIColor redColor]).nn_backgroundColorRGB(224, 224, 224, 1).nn_textAlignment(NSTextAlignmentCenter);
    }];

细心观察上面的代码,我们可以猜出 nn_frame()nn_text()nn_fontSize() 这些都是属性,不过为什么它们后面都带有括号呢?这是因为这些属性都是 block 类型。那么为什么它们可以点出来呢?即为什么可以 label.nn_frame().nn_text().nn_fontSize() 这么用?简单来说,就是因为这些 block 类型的属性都带有返回值

下面是分类 UILabel+NNCategory 的详细实现过程

  • 在 UILabel+NNCategory.h 中声明一些属性
@property (nonatomic, copy, readonly) UILabel *(^nn_text)(NSString *);
@property (nonatomic, copy, readonly) UILabel *(^nn_frame)(CGFloat, CGFloat, CGFloat, CGFloat);
@property (nonatomic, copy, readonly) UILabel *(^nn_attributedText)(NSAttributedString *);
@property (nonatomic, copy, readonly) UILabel *(^nn_textAlignment)(NSTextAlignment);
@property (nonatomic, copy, readonly) UILabel *(^nn_textColor)(UIColor *);
@property (nonatomic, copy, readonly) UILabel *(^nn_textColorRGB)(CGFloat, CGFloat, CGFloat, CGFloat);
@property (nonatomic, copy, readonly) UILabel *(^nn_backgroundColor)(UIColor *);
@property (nonatomic, copy, readonly) UILabel *(^nn_backgroundColorRGB)(CGFloat, CGFloat, CGFloat, CGFloat);
@property (nonatomic, copy, readonly) UILabel *(^nn_highlightTextColor)(UIColor *);
@property (nonatomic, copy, readonly) UILabel *(^nn_highlight)(BOOL);
@property (nonatomic, copy, readonly) UILabel *(^nn_enable)(BOOL);
@property (nonatomic, copy, readonly) UILabel *(^nn_font)(UIFont *);
@property (nonatomic, copy, readonly) UILabel *(^nn_fontSize)(NSInteger);
@property (nonatomic, copy, readonly) UILabel *(^nn_shadowColor)(UIColor *);
@property (nonatomic, copy, readonly) UILabel *(^nn_shadowOffset)(CGSize);
@property (nonatomic, copy, readonly) UILabel *(^nn_lineBreakMode)(NSLineBreakMode);
@property (nonatomic, copy, readonly) UILabel *(^nn_numberOfLine)(NSInteger);
@property (nonatomic, copy, readonly) UILabel *(^nn_adjustsFontSizeToFitWidth)(BOOL);
@property (nonatomic, copy, readonly) UILabel *(^nn_baselineAdjust)(UIBaselineAdjustment);
@property (nonatomic, copy, readonly) UILabel *(^nn_drawText)(CGRect);

我们在 UILabel 分类里面声明了这些属性,所以接下来只要是 UILabel 类型都可以点出来这些属性。这里也可以看出这些属性都是 block 类型。我们抽出一句代码简单说明一下:@property (nonatomic, copy, readonly) UILabel *(^nn_text)(NSString *);,这句代码声明了一个 block(^) 原型,名字叫做 nn_text,包含了一个 NSString 类型的参数,返回值是 UILabel 类型。

  • 在 UILabel+NNCategory.m 中实现
- (UILabel *(^)(NSString *))nn_text {
    return ^(NSString *text) {
        self.text = text;
        return self;
    };
}

- (UILabel *(^)(CGFloat, CGFloat, CGFloat, CGFloat))nn_frame {
    return ^(CGFloat X, CGFloat Y, CGFloat W, CGFloat H) {
        self.frame = CGRectMake(X, Y, W, H);
        return self;
    };
}

- (UILabel *(^)(NSAttributedString *))nn_attributedText {
    return ^(NSAttributedString *attributedText) {
        self.attributedText = attributedText;
        return self;
    };
}

- (UILabel *(^)(NSTextAlignment))nn_textAlignment {
    return ^(NSTextAlignment textAlignment) {
        self.textAlignment = textAlignment;
        return self;
    };
}

- (UILabel *(^)(UIColor *))nn_textColor {
    return ^(UIColor *textColor) {
        self.textColor = textColor;
        return self;
    };
}

- (UILabel *(^)(CGFloat, CGFloat, CGFloat, CGFloat))nn_textColorRGB {
    return ^(CGFloat r, CGFloat g, CGFloat b, CGFloat a){
        self.textColor = [UIColor colorWithRed:r / 255.0 green:g / 255.0 blue:g / 255.0 alpha:a];
        return self;
    };
}

- (UILabel *(^)(UIColor *))nn_backgroundColor {
    return ^(UIColor *backgroundColor) {
        self.backgroundColor = backgroundColor;
        return self;
    };
}

- (UILabel *(^)(CGFloat, CGFloat, CGFloat, CGFloat))nn_backgroundColorRGB {
    return ^(CGFloat r, CGFloat g, CGFloat b, CGFloat a) {
        self.backgroundColor = [UIColor colorWithRed:r / 255.0 green:g / 255.0 blue:g / 255.0 alpha:a];
        return self;
    };
}

- (UILabel *(^)(UIColor *))nn_highlightTextColor {
    return ^(UIColor *color) {
        self.highlightedTextColor = color;
        return self;
    };
}

- (UILabel *(^)(BOOL))nn_highlight {
    return ^(BOOL isHighlighted) {
        self.highlighted = isHighlighted;
        return self;
    };
}

- (UILabel *(^)(BOOL))nn_enable {
    return ^(BOOL isEnabled) {
        self.enabled = isEnabled;
        return self;
    };
}

- (UILabel *(^)(UIFont *))nn_font {
    return ^(UIFont *font) {
        self.font = font;
        return self;
    };
}

- (UILabel *(^)(NSInteger))nn_fontSize {
    return ^(NSInteger size) {
        self.font = [UIFont systemFontOfSize:size];
        return self;
    };
}

- (UILabel *(^)(UIColor *))nn_shadowColor {
    return ^(UIColor *shadowColor) {
        self.shadowColor = shadowColor;
        return self;
    };
}

- (UILabel *(^)(CGSize))nn_shadowOffset {
    return ^(CGSize size) {
        self.shadowOffset = size;
        return self;
    };
}

- (UILabel *(^)(NSLineBreakMode))nn_lineBreakMode {
    return ^(NSLineBreakMode mode) {
        self.lineBreakMode = mode;
        return self;
    };
}

- (UILabel *(^)(NSInteger))nn_numberOfLine {
    return ^(NSInteger number) {
        self.numberOfLines = number;
        return self;
    };
}

- (UILabel *(^)(BOOL))nn_adjustsFontSizeToFitWidth {
    return ^(BOOL b) {
        self.adjustsFontSizeToFitWidth = b;
        return self;
    };
}

- (UILabel *(^)(UIBaselineAdjustment))nn_baselineAdjust {
    return ^(UIBaselineAdjustment adjustment) {
        self.baselineAdjustment = adjustment;
        return self;
    };
}

- (UILabel *(^)(CGRect))nn_drawText {
    return ^(CGRect rect) {
        [self drawTextInRect:rect];
        return self;
    };
}

UILabel+NNCategory.m 中的这些是 UILabel+NNCategory.h 文件中属性的 getter 方法。我们抽出一个简单说明一下

- (UILabel *(^)(NSString *))nn_text {
    // 返回临时变量的 block
    return ^(NSString *text) {
        // block 执行的一些功能
        self.text = text;
        // block 执行完毕的返回值
        return self;
    };
}

这个属性的类型是 block,具体是 UILabel *(^nn_text)(NSString *) 类型,需要说明的是,我们用 block 并不是为了返回 block 对象本身,而是为了在 block 内部执行一些操作,所以我们在属性的 getter 方法中首先返回一个临时的 block 对象,主要是为了在 block 内部返回 UILabel 类型的对象。具体请看上面代码块中的一些注释。

这时我们还需要定义一个方法方便外界调用,这个方法主要有两个功能,第一是用来创建 UILabel ,第二是为其“点”出各种属性。

  • 在 UILabel+NNCategory.h 中声明一个方法
+ (instancetype)addToView:(UIView *)superView createLabel:(void(^)(UILabel *label))block;

这里是模仿 Masonry,我们通常用 Masonry 加约束时首先会调用这个方法:- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

我们用 Masonry 加约束时调用这个方法,是因为 Masonry 在这个方法内部早已做好一些所需要的操作。所以接下来我们也要在 UILabel+NNCategory.m 中做些事情。

  • 在 UILabel+NNCategory.m 中实现这个方法
+ (instancetype)addToView:(UIView *)superView createLabel:(void(^)(UILabel *label))block {
    // 创建 UILabel
    UILabel *label = [[UILabel alloc] init];
    // 把 UILabel 添加到传过来的 superView 上
    [superView addSubview:label];
    // 通过 block 进行回调
    if (block) block(label);
    return label;
}

上面这个方法首先初始化一个 UILabel 对象,接着把 UILabel 添加到传过来的 superView 上,然后对 block 进行回调,执行 block,把外界对 UILabel 设置的属性添加上去,最后返回 UILabel 对象。


这就是一些简单的链式编程思维,外界调用的时候只需如下代码即可:

    [UILabel addToView:self.view createLabel:^(UILabel *label) {
        label.nn_frame(50, 200, 300, 100).nn_text(@"NNTreasure").nn_fontSize(50).nn_textColor([UIColor redColor]).nn_backgroundColorRGB(224, 224, 224, 1).nn_textAlignment(NSTextAlignmentCenter);
    }];

效果图如下:

链式编程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容