浅谈编程思想

一、链式编程

OC中有一个使用非常广泛的框架——masonry。在masonry中,我们可以使用“点语法”实现方法调用。但是OC 的调用方法使用“[Class func]”。相比于OC中反人类的AutoLayout语法,masonry的链式语法使用的便利性,被广大iOS开发者所接受。

masonry语法:↓


  1. [view mas_makeConstraints:^(MASConstraintMaker *make) {
  2.    make.top.equalTo(superview.mas_top).with.offset(10);
  3.    make.left.equalTo(10);
  4.    make.bottom.right.equalTo(-10);
  5. }];

上面的示例代码中,链式编程思想被体现,并且极大的简化了自动布局代码。

今天我们由简到繁,逐步实现这样的链式语法设计。

首先创建一个Person类文件,该类文件在下面的示例中均被使用。


  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. @end

我们最终想要实现的方法调用格式为:


  1.    self.p = [[Person alloc] init];
  2.    self.p.eat(2);

1.block定义为属性

    block定义为属性时,常备用于反向传值(此处不再讨论)。现在我们仅仅给出属性定义语法!


  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /* block类型属性:(ARC使用strong,非ARC使用copy) **/
  4. @property (nonatomic, strong) void(^eat)();
  5. @end


2.block作为方法参数使用

    block作为代码参数使用,可以向方法传递一个可被执行的代码块。这也是很多框架中常用的一种设计方式。例如常用的AFN框架中,对网络请求结果的处理。相比于用协议实现回调功能,这样写可以使代码更加紧凑!

例如我们在Person中定义一个eat(吃)的方法:


  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /** block当参数(block代码块)*/
  4. - (void)eat:(void (^)())block;
  5. @end

此时作为Person的调用者,我们可以将 eat 的具体行为通过代码块的形式传递给该方法执行:


  1. /** block当做方法的参数使用 */
  2. - (void)demo2 {
  3.    self.p = [[Person alloc] init];
  4.    [self.p eat:^{
  5.        NSLog(@"我是怎样吃饭的");
  6.    }];
  7. }

到此并没有结束,我们还需要在Person文件中将声明的该方法实现。并调用执行参数block。否则我们的block代码块没有被调用当然不会被执行:


  1. #import "Person.h"
  2. @implementation Person
  3. // 实现声明方法并调用block
  4. - (void)eat:(void (^)())block {
  5.    block();
  6. }
  7. @end

然后用实例调用该 eat 方法,我们会在控制台看到结果:

浅谈编程思想

3.block作为方法返回值

    本文开头提到,使用“点语法”调用方法。既然block作为参数使用很广泛,那我们是不是可以用它作为返回值使用?答案是肯定的!

    我们看masonry的方法:在bolck内点开 make.top.equalTo 方法,我们看它的返回值:

浅谈编程思想

我们看到它的返回值是一个block类型,MASConstraint是返回值类型,我们暂且先不管(以 void 类型替换)。到此我们可以有一点想法了。

我们模仿masonry的该方法定义我们的自己Person类的 eat 方法:


  1. #import <Foundation/Foundation.h>
  2. @interface Person : NSObject
  3. /** block作为方法返回值使用 */
  4. - (void(^)(int))eat;
  5. @end

声明了该方法,当然还要实现它:


  1. #import "Person.h"
  2. @implementation Person
  3. - (void (^)(int))eat {
  4.    return ^(int n){
  5.        NSLog(@"吃了 %d 个馒头!!!", n);
  6.    };
  7. }
  8. @end

到这我们貌似搞定了,接下来试试到底行不行:


  1. - (void)demo3 {
  2.    // block当返回值
  3.    self.p = [[Person alloc] init];
  4.    self.p.eat(2);
  5. }

运行,控制台打印出来了我们期待的结果:

浅谈编程思想

写到这里,我们已经搞清楚了链式语法结构的实现原理。我们写了一个很简单的小栗子循序渐进,貌似很简单有没有。


4.链式语法结构的实现

    但是我们知道masonry中我们可以用点语法连续调用,比如:make.bottom.right.equalTo(-10);  可以连续的用点语法调用。我们不妨设计一个简单的demo ,使其实现类似masonry的语法。具体如何实现,可以学习masonry的设计。(最终我们想要的结果是可以连续调用的语法结构 add(value).add(value).add(value) 

打开masonry的约束方法:

浅谈编程思想

浅谈编程思想

mas_makeConstraints 方法被设计在 UIView的分类中,分析我们要实现的demo的需求,应该将计算方法声明在NSObject的分类中!参数 MASConstraintMaker 为方法调用者,借此分析我们创建NSObject的分类以及计算管理者Manager,并声明计算方法:


  1. #import <Foundation/Foundation.h>
  2. #import "Manager.h"
  3. @interface NSObject (Tools)
  4. /** 计算方法 */
  5. + (int)KY_makeCalculate:(void(^)(Manager *mgr))block;
  6. @end

参数Manager就是我们的计算方法管理者,我们在该类中声明加法运算,并根据之前点语法的实现原理,以block作为返回值:


  1. #import <Foundation/Foundation.h>
  2. @interface Manager : NSObject
  3. /** 加法计算 */
  4. - (void(^)(int))add;
  5. @end

接下来我们貌似就可以调用了:


  1. - (void)demo4 {
  2.    // 链式实现
  3.    int result = [NSObject KY_makeSum:^(Manager *mgr) {
  4.        mgr.add(5);
  5.    }];
  6.    NSLog(@"运算结果----> %d", result);
  7. }

但是,对于连续的 add(value) 方法调用,貌似我们并没有实现!其实只要将返回值void类型改为管理者本身就可以了,另外我们再声明一个属性将计算结果临时保存一下:


  1. #import <Foundation/Foundation.h>
  2. @interface Manager : NSObject
  3. /** 计算结果 */
  4. @property (nonatomic, assign) int result;
  5. - (Manager *(^)(int))add;
  6. @end

.m 文件中实现我们的 add 方法:


  1. #import "Manager.h"
  2. @implementation Manager
  3. - (Manager *(^)(int))add {
  4.    return ^(int value) {
  5.        _result += value;
  6.        return self;
  7.    };
  8. }
  9. @end

接下来我们再调用一下:


  1. // 链式实现
  2. - (void)demo4 {
  3.    // 链式实现
  4.    int result = [NSObject KY_makeSum:^(Manager *mgr) {
  5.        mgr.add(1).add(2).add(3).add(4).add(5);
  6.    }];
  7.    NSLog(@"运算结果----> %d", result);
  8. }

然后我们运行测试,看下结果:

浅谈编程思想

真的可以了,哈哈哈哈哈????!!!!

Swift 本身就是链式的语法结构,所以写起来就相对简单的多了,在此不再过多的复数了。


二、响应式编程

    响应式编程概念性的定义,很难懂!找到的定义是:响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

仅仅看定义的话真的很难理解,在网上找到一个感觉可以加深一下理解。例如,在命令式编程环境中,a=b+c表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。

    响应式编程, 开发中我们常见和常用的 Target、Delegate、KVO、通知、时钟、网络异步回调等,都属于响应式编程。(有事件触发时响应相关事件)。这些我们使用过的方法或许可以帮助我们理解其概念。

OC中有一个响应式编程框架 ReactiveCocoa(RAC)

其它的资料暂时还未找到。。。~~~~(>_<)~~~~


三、面向协议编程

    我们常听到的应该是OOP的概念,即面向对象的编程。OOP的核心思想是使用封装和继承,将一系列相关的东西放到一起。举个小例子帮助理解一下,看一段小代码:


  1. /// 动物类
  2. class Animal {
  3.    func eat() {
  4.        print("吃东西")
  5.    }
  6.    func run() {
  7.        print(" 2 条腿跑")
  8.    }
  9. }
  10. /// 继承自动物类的人
  11. class Dog: Animal {
  12.    override func run() {
  13.        print(" 4 条腿跑")
  14.    }
  15.    func bite() {
  16.        print("咬人")
  17.    }
  18. }
  19. let dog = Dog()
  20. person.eat()
  21. person.run()

我们定义了一个动物类Animal,然后抽象出它的leg(腿)属性,以及eat(吃)、run(跑)的方法。子类Dog继承Animal,并根据自己的特性重写了run(跑)的方法。而对于eat(吃)已经满足需求,不必重写。

Dog和Animal共享了一部分代码eat(吃),这部分代码被封装在父类中,所有继承自Animal的子类都可以使用。这也就是我们刚才提到的OOP的核心思想——使用封装和继承,将一系列相关的东西放到一起。

再看,Dog类中我们还写了一个方法bite(咬人),狗会咬人。然后我们还要写一个类Wolf(狼)继承Animal(动物),同样狼也会咬人。


  1. /// 继承自动物类的人
  2. class Wolf: Animal {
  3.    override func run() {
  4.        print("四条腿跑")
  5.    }
  6.    func bite() {
  7.        print("咬人")
  8.    }
  9. }

但是我们不得不重新写bite方法,因为我们无法使用Dog(狗)的bite方法。这时就会看到代码的重复性。同样,我们要在Dog(狗)中添加bark(叫)方法,而狼也会叫,此时就需要在Wolf中再次添加bark(叫)方法。如此下来,代码会显得越来越臃肿,并且扩展性和维护性会大大降低。而OC和Swift代码中不能够多继承!

此时我们就用到了协议的另一个功能,定义统一的方法bark(叫),然后由Dog和Wolf分别遵守该协议:


  1. protocol BarkProtocol {
  2.    func bark()
  3. }

这样我们就在协议中统一规定了Dog和Wolf共有的Bark(叫)方法,但是这样貌似也需要在子类中分别实现该方法,但是,这样写的好处无疑会比之前的方法好很多。至少它可以在需要的是否对定义的方法进行统一的修改,规范性提高不少。同样我们还可以这样给协议接口一个默认实现,这样就不用在子类中去分别实现了!


  1. import UIKit
  2. protocol BarkProtocol {
  3.    func bark()
  4. }
  5. extension BarkProtocol {
  6.    func bark() {
  7.        print("狼和狗的叫声")
  8.    }
  9. }

以上所述是一种面向协议的编程思想。通过面向协议的思想,我们的实现代码得到优化。

  • 协议定义

    • 提供实现的入口

    • 遵循协议的类型需要对其进行实现

  • 协议扩展

    • 为入口提供默认实现

    • 根据入口提供额外实现


之前写过的隔离地图的设计Demo也是一种面向协议的编程思想。

我们的小demo描述的都很简单,但在实际开发中,我们遇到的问题会比这复杂的多。这就需要对实际问题的具体分析,然后将这样的编程思想应用其中,提高我们的代码质量!开发中要培养良好的代码习惯,将这些我们看似简单的东西应用到项目开发中。不要一味地为了实现功能而写代码,更多的融入一些设计思想,会使我们的代码更舒适!