阅读 614

Masonry 源码学习整理

@(第三方库源码学习)

[TOC]

Masonry框架的类结构

学习一、Masonry采用了经典的组合设计模式(Composite Pattern)。

1、定义

将对象组合成树状结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象(Leaf)和组合对象(Composite)的使用具有一致性。 注意:这个组合模式不是“组合优于继承”的那种组合,是狭义的指代一种特定的场景(树状结构)

2、理解这个模式要知道三个设定:
  • Component协议:树中的组件(Leaf、Composite)都需要实现这个协议
  • Leaf组件:树结构中的一个没有子元素的组件
  • Composite组件:容器,与Leaf不同的是有子元素,用来存储Leaf和其他Composite
3、什么时候使用?
  • 1、想获得对象抽象的树形表示(部分——整体层次结构)像文件夹、公司组织架构等,需要处理分支和节点的树状结构场景
  • 2、想让客户端统一处理组合结构中的所有对象
4、在Cocoa Touch框架中,UIView被组织成一个组合结构。
5、优点:对不同的对象以相同的方式处理
6、swift实现Demo
import Foundation

// 一:Component协议:树中的组件(Leaf、Composite)都需要实现这个协议
protocol File {
    var name: String { get set }
    func showInfo()
}

// 二:Leaf:树结构中的一个没有子元素的组件
class TextFile: File {
    var name: String
    init(name: String) {
        self.name = name
    }
    func showInfo() {
        print("\(name) is TextFile")
    }
}

class ImageFile: File {
    var name: String
    init(name: String) {
        self.name = name
    }
    func showInfo() {
        print("\(name) is ImageFile")
    }
}

class VideoFile: File {
    var name: String
    init(name: String) {
        self.name = name
    }
    func showInfo() {
        print("\(name) is VideoFile")
    }
}

// 三:Composite:容器,与Leaf不同的是有子元素,用来存储Leaf和其他Composite
class Fold: File {
    var name: String
    private(set) var files: [File] = []
    init(name: String) {
        self.name = name
    }
    func showInfo() {
        print("\(name) is Fold")
        files.forEach { (file) in
            file.showInfo()
        }
    }
    func addFile(file: File)  {
        files.append(file)
    }
}

class Client {
    init() {
    }
    func test() {
        let fold1: Fold = Fold.init(name: "fold1")
        let fold2: Fold = Fold.init(name: "fold2")
        let text1: TextFile = TextFile.init(name: "text1")
        let text2: TextFile = TextFile.init(name: "text2")
        let image1: ImageFile = ImageFile.init(name: "image1")
        let image2: ImageFile = ImageFile.init(name: "image2")
        let video1: VideoFile = VideoFile.init(name: "video1")
        let video2: VideoFile = VideoFile.init(name: "video2")
        fold1.addFile(file: text1)
        fold2.addFile(file: text2)
        fold1.addFile(file: image1)
        fold2.addFile(file: image2)
        fold1.addFile(file: video1)
        fold2.addFile(file: video2)
        fold1.addFile(file: fold2)
        fold1.showInfo()
    }
}
复制代码
7、参考资料:

iOS_Design_Patterns_Swift

iOS应用开发中运用设计模式中的组合模式的实例解析

设计模式读书笔记-----组合模式

23个经典设计模式的Swift实现

【设计模式】19 – 组合模式 (Composite Pattern)

学习二、Masonry采用了经典的工厂设计模式(Factory Pattern)。

1、定义

工厂方法模式实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行"。 工厂方法要解决的问题对象的创建时机,它提供了一种扩展策略,很好地符合了开放封闭原则。与直接创建新的对象相比,使用工厂方法创建对象可算作一种最佳做法。工厂方法模式让客户程序可以要求由工厂方法创建的对象拥有一组共同的行为。所以往类层次结构中引入新的具体产品并不需要修改客户端代码,因为返回的任何具体对象的接口都跟客户端一直在用的从前的接口相同。

2、优点:
  • 1、将对象的创建和对象本身的业务处理分离可以降低系统的耦合度,使得两者的修改相对容易。
  • 2、扩展性高,屏蔽产品的具体实现,调用者只关心产品的接口
3、缺点:
  • 增加产品的时候,使得系统中的类个数大量增加,延长App的启动时间。
4、简单工厂模式、工厂模式、抽象工厂模式的区别
区别 简单工厂模式 工厂模式 抽象工厂模式
开放封闭程度
抽象产品的个数 一个 一个 多个
抽象工厂的个数 没有 一个 多个

工厂模式生产的是整车,抽象工厂模式生产的是零件组合成整车。

5、swift实现Demo

5.1、简单工厂模式

import UIKit

enum VenderType {
    case razer, logitech, steelSeries
}
/// 抽象产品类
protocol Mouse {
    func click()
}
/// 具体产品类:雷蛇鼠标
class RazerMouse: Mouse {
    func click() {
        print("Razer click")
    }
}
/// 具体产品类:罗技鼠标
class LogitechMouse: Mouse {
    func click() {
        print("Logitech click")
    }
}
/// 具体产品类:赛睿鼠标
class SteelSeriesMouse: Mouse {
    func click() {
        print("SteelSeries click")
    }
}

// 简单工厂模式
class SimpleFactoryClient {
    func create(type: VenderType) -> Mouse {
        switch type {
        case .razer:
            return self.razer()
        case .logitech:
            return self.logitech()
        case .steelSeries:
            return self.steelSeries()
        }
    }
    private func razer() -> Mouse {
        let mouse: RazerMouse = RazerMouse.init()
        return mouse
    }
    private func logitech() -> Mouse {
        let mouse: LogitechMouse = LogitechMouse.init()
        return mouse
    }
    private func steelSeries() -> Mouse {
        let mouse: SteelSeriesMouse = SteelSeriesMouse.init()
        return mouse
    }
}
复制代码

5.2、工厂模式

// 工厂模式
/// 抽象工厂类
protocol MouseProductable {
    func productMouse() -> Mouse
}
/// 具体工厂类:雷蛇工厂
class RazerFactory: MouseProductable {
    func productMouse() -> Mouse {
        let mouse: RazerMouse = RazerMouse.init()
        return mouse
    }
}
/// 具体工厂类:罗技工厂
class LogitechFactory: MouseProductable {
    func productMouse() -> Mouse {
        let mouse: LogitechMouse = LogitechMouse.init()
        return mouse
    }
}
/// 具体工厂类:赛睿工厂
class SteelSeriesFactory: MouseProductable {
    func productMouse() -> Mouse {
        let mouse: SteelSeriesMouse = SteelSeriesMouse.init()
        return mouse
    }
}
class FactoryClient {
    func create(type: VenderType) -> Mouse {
        switch type {
        case .razer:
            return self.razer()
        case .logitech:
            return self.logitech()
        case .steelSeries:
            return self.steelSeries()
        }
    }
    private func razer() -> Mouse {
        let factory: RazerFactory = RazerFactory.init()
        return factory.productMouse()
    }
    private func logitech() -> Mouse {
        let factory: LogitechFactory = LogitechFactory.init()
        return factory.productMouse()
    }
    private func steelSeries() -> Mouse {
        let factory: SteelSeriesFactory = SteelSeriesFactory.init()
        return factory.productMouse()
    }
}
复制代码

5.3、抽象工厂模式

// 抽象工厂模式
/// 抽象产品类
protocol Keyboard {
    func enter()
}
/// 具体产品类:雷蛇键盘
class RazerKeyboard: Keyboard {
    func enter() {
        print("RazerKeyboard enter")
    }
}
/// 具体产品类:罗技鼠标键盘
class LogitechKeyboard: Keyboard {
    func enter() {
        print("LogitechKeyboard enter")
    }
}
/// 具体产品类:赛睿鼠标键盘
class SteelSeriesKeyboard: Keyboard {
    func enter() {
        print("SteelSeriesKeyboard enter")
    }
}

/// 抽象工厂类
protocol KeyboardProductable {
    func productKeyBoard() -> Keyboard
}

/// 具体工厂类:雷蛇工厂(生产鼠标和键盘)
class RazerFactory: MouseProductable, KeyboardProductable {
    func productKeyBoard() -> Keyboard {
        let keyboard: RazerKeyboard = RazerKeyboard.init()
        return keyboard
    }

    func productMouse() -> Mouse {
        let mouse: RazerMouse = RazerMouse.init()
        return mouse
    }
}
/// 具体工厂类:罗技工厂(生产鼠标和键盘)
class LogitechFactory: MouseProductable, KeyboardProductable {
    func productKeyBoard() -> Keyboard {
        let keyboard: LogitechKeyboard = LogitechKeyboard.init()
        return keyboard
    }

    func productMouse() -> Mouse {
        let mouse: LogitechMouse = LogitechMouse.init()
        return mouse
    }
}
/// 具体工厂类:赛睿工厂(生产鼠标和键盘)
class SteelSeriesFactory: MouseProductable, KeyboardProductable {
    func productKeyBoard() -> Keyboard {
        let keyboard: SteelSeriesKeyboard = SteelSeriesKeyboard.init()
        return keyboard
    }
    func productMouse() -> Mouse {
        let mouse: SteelSeriesMouse = SteelSeriesMouse.init()
        return mouse
    }
}

class FactoryClient {
    /// 生产鼠标
    ///
    /// - Parameter type: 厂商类型
    /// - Returns: 鼠标
    func createMouse(type: VenderType) -> Mouse {
        switch type {
        case .razer:
            return self.razerMouse()
        case .logitech:
            return self.logitechMouse()
        case .steelSeries:
            return self.steelSeriesMouse()
        }
    }
    private func razerMouse() -> Mouse {
        let factory: RazerFactory = RazerFactory.init()
        return factory.productMouse()
    }
    private func logitechMouse() -> Mouse {
        let factory: LogitechFactory = LogitechFactory.init()
        return factory.productMouse()
    }
    private func steelSeriesMouse() -> Mouse {
        let factory: SteelSeriesFactory = SteelSeriesFactory.init()
        return factory.productMouse()
    }

    /// 生产键盘
    ///
    /// - Parameter type: 厂商类型
    /// - Returns: 键盘
    func createKeyBoard(type: VenderType) -> Keyboard {
        switch type {
        case .razer:
            return self.razerKeyboard()
        case .logitech:
            return self.logitechKeyboard()
        case .steelSeries:
            return self.steelSeriesKeyboard()
        }
    }
    private func razerKeyboard() -> Keyboard {
        let factory: RazerFactory = RazerFactory.init()
        return factory.productKeyBoard()
    }
    private func logitechKeyboard() -> Keyboard {
        let factory: LogitechFactory = LogitechFactory.init()
        return factory.productKeyBoard()
    }
    private func steelSeriesKeyboard() -> Keyboard {
        let factory: SteelSeriesFactory = SteelSeriesFactory.init()
        return factory.productKeyBoard()
    }
}
复制代码

从上面的代码可以看出,抽象工厂模式的扩展性最好。

6、工厂模式在CocoaTouch中的应用
[NSNumber numberWithBool:YES];
[NSNumber numberWithInteger:1];
[NSNumber numberWithInt:1];
复制代码
7、参考资料:

Swift-工厂方法(Factory Method) 抽象工厂模式

学习三、链式语法

实现的核心:重写Block属性的Get方法,在Block里返回对象本身

#import "ChainProgramVC.h"

@class ChainAnimal;
typedef void(^GeneralBlockProperty)(int count);
typedef ChainAnimal* (^ChainBlockProperty)(int count);

@interface ChainAnimal : NSObject
@property (nonatomic, strong) GeneralBlockProperty 	eat1;
@property (nonatomic, strong) ChainBlockProperty 	eat2;
@end
@implementation ChainAnimal
/**
 函数返回一个block,block返回void
 */
-(GeneralBlockProperty)eat1 {
    return ^(int count) {
        NSLog(@"%s count = %d", __func__, count);
    };
}
/**
 函数返回一个block,block返回ChainAnimal对象
 */
- (ChainBlockProperty)eat2 {
    return ^(int count){
        NSLog(@"%s count = %d", __func__, count);
        return self;
    };
}
@end

@interface ChainProgramVC ()
@property (nonatomic, strong) ChainAnimal *dog;
@end
@implementation ChainProgramVC
- (ChainAnimal *)dog {
    if (!_dog) {
        _dog = [[ChainAnimal alloc] init];
    }
    return _dog;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [super viewDidLoad];
    self.dog.eat1(1);
    self.dog.eat2(2).eat2(3).eat2(4).eat1(5);
}
@end
复制代码

学习四、接口简洁

把复杂留给自己,把简单留给别人

学习五、抽象方法小技巧

#define MASMethodNotImplemented() \
    @throw [NSException exceptionWithName:NSInternalInconsistencyException \
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
                                 userInfo:nil]

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}
复制代码

自己实现类似需求的时候,可以采用这个技巧阻止直接使用抽象方法。

实践:实现一个自定义转场动画的基类
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BaseAnimatedTransiton : NSObject<UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) NSTimeInterval p_transitionDuration;
+(instancetype)initWithTransitionDuration:(NSTimeInterval)transitionDuration;
-(instancetype)initWithTransitionDuration:(NSTimeInterval)transitionDuration NS_DESIGNATED_INITIALIZER;
@end

#pragma mark - (Abstract)
@interface BaseAnimatedTransiton (Abstract)
// 子类实现,父类NSException
-(void)animate:(nonnull id<UIViewControllerContextTransitioning>)transitionContext;
@end

NS_ASSUME_NONNULL_END
复制代码
#import "BaseAnimatedTransiton.h"

@implementation BaseAnimatedTransiton
+(instancetype)initWithTransitionDuration:(NSTimeInterval)transitionDuration {
    BaseAnimatedTransiton* obj = [[BaseAnimatedTransiton alloc] init];
    obj.p_transitionDuration = transitionDuration;
    return obj;
}
-(instancetype)initWithTransitionDuration:(NSTimeInterval)transitionDuration {
    if (self = [super init]) {
        self.p_transitionDuration = transitionDuration;
    }
    return self;
}
-(instancetype)init {
    return [self initWithTransitionDuration:0.25];
}
-(void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
    [self animate:transitionContext];
}
-(NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext {
    return self.p_transitionDuration;
}
-(void)animate:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
    [self throwException:_cmd];
}
/**
 在Masonry的源码中使用的是宏(感觉宏不是很直观)

 @param aSelector 方法名字
 */
-(void)throwException:(SEL)aSelector {
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(aSelector)]
                                 userInfo:nil];
}
@end
复制代码

学习六、包装任何值类型为一个对象

我们添加约束的时候使用equalTo传入的参数只能是id类型的,而mas_equalTo可以任何类型的数据。

[view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.size.mas_equalTo(CGSizeMake(100, 100));
    make.center.equalTo(self.view);
    // 下面这句效果与上面的效果一样
    //make.center.mas_equalTo(self.view);
}];
复制代码
#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
复制代码
/**
 *  Given a scalar or struct value, wraps it in NSValue
 *  Based on EXPObjectify: https://github.com/specta/expecta
 */
static inline id _MASBoxValue(const char *type, ...) {
    va_list v;
    va_start(v, type);
    id obj = nil;
    if (strcmp(type, @encode(id)) == 0) {
        id actual = va_arg(v, id);
        obj = actual;
    } else if (strcmp(type, @encode(CGPoint)) == 0) {
        CGPoint actual = (CGPoint)va_arg(v, CGPoint);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(CGSize)) == 0) {
        CGSize actual = (CGSize)va_arg(v, CGSize);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(double)) == 0) {
        double actual = (double)va_arg(v, double);
        obj = [NSNumber numberWithDouble:actual];
    } else if (strcmp(type, @encode(float)) == 0) {
        float actual = (float)va_arg(v, double);
        obj = [NSNumber numberWithFloat:actual];
    } else if (strcmp(type, @encode(int)) == 0) {
        int actual = (int)va_arg(v, int);
        obj = [NSNumber numberWithInt:actual];
    } else if (strcmp(type, @encode(long)) == 0) {
        long actual = (long)va_arg(v, long);
        obj = [NSNumber numberWithLong:actual];
    } else if (strcmp(type, @encode(long long)) == 0) {
        long long actual = (long long)va_arg(v, long long);
        obj = [NSNumber numberWithLongLong:actual];
    } else if (strcmp(type, @encode(short)) == 0) {
        short actual = (short)va_arg(v, int);
        obj = [NSNumber numberWithShort:actual];
    } else if (strcmp(type, @encode(char)) == 0) {
        char actual = (char)va_arg(v, int);
        obj = [NSNumber numberWithChar:actual];
    } else if (strcmp(type, @encode(bool)) == 0) {
        bool actual = (bool)va_arg(v, int);
        obj = [NSNumber numberWithBool:actual];
    } else if (strcmp(type, @encode(unsigned char)) == 0) {
        unsigned char actual = (unsigned char)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedChar:actual];
    } else if (strcmp(type, @encode(unsigned int)) == 0) {
        unsigned int actual = (unsigned int)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedInt:actual];
    } else if (strcmp(type, @encode(unsigned long)) == 0) {
        unsigned long actual = (unsigned long)va_arg(v, unsigned long);
        obj = [NSNumber numberWithUnsignedLong:actual];
    } else if (strcmp(type, @encode(unsigned long long)) == 0) {
        unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
        obj = [NSNumber numberWithUnsignedLongLong:actual];
    } else if (strcmp(type, @encode(unsigned short)) == 0) {
        unsigned short actual = (unsigned short)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedShort:actual];
    }
    va_end(v);
    return obj;
}

#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
复制代码

其中@encode()是一个编译时特性,其可以将传入的类型转换为标准的OC类型字符串

学习七、Block避免循环应用

Masonry中,Block持有View所在的ViewController,但是ViewController并没有持有Blcok,因此不会导致循环引用。

[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.centerY.equalTo(self.otherView.mas_centerY);
}];
复制代码

源码:仅仅是block(constrainMaker),没有被self持有

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

参考资料

读 SnapKit 和 Masonry 自动布局框架源码

iOS开发之Masonry框架源码解析

Masonry 源码解读

Masonry源码解析

关注下面的标签,发现更多相似文章
评论