Masonry 源碼學習整理

@(第三方庫源碼學習)html

[TOC]git

Masonry框架的類結構 github

學習1、Masonry採用了經典的組合設計模式(Composite Pattern)。

一、定義

將對象組合成樹狀結構以表示「部分-總體」的層次結構。組合模式使得用戶對單個對象(Leaf)和組合對象(Composite)的使用具備一致性。 注意:這個組合模式不是「組合優於繼承」的那種組合,是狹義的指代一種特定的場景(樹狀結構)swift

二、理解這個模式要知道三個設定:
  • Component協議:樹中的組件(Leaf、Composite)都須要實現這個協議
  • Leaf組件:樹結構中的一個沒有子元素的組件
  • Composite組件:容器,與Leaf不一樣的是有子元素,用來存儲Leaf和其餘Composite
三、何時使用?
  • 一、想得到對象抽象的樹形表示(部分——總體層次結構)像文件夾、公司組織架構等,須要處理分支和節點的樹狀結構場景
  • 二、想讓客戶端統一處理組合結構中的全部對象
四、在Cocoa Touch框架中,UIView被組織成一個組合結構。
五、優勢:對不一樣的對象以相同的方式處理
六、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()
    }
}
複製代碼
七、參考資料:

iOS_Design_Patterns_Swift設計模式

iOS應用開發中運用設計模式中的組合模式的實例解析bash

設計模式讀書筆記-----組合模式架構

23個經典設計模式的Swift實現app

【設計模式】19 – 組合模式 (Composite Pattern)框架

學習2、Masonry採用了經典的工廠設計模式(Factory Pattern)。

一、定義

工廠方法模式實質是「定義一個建立對象的接口,但讓實現這個接口的類來決定實例化哪一個類。工廠方法讓類的實例化推遲到子類中進行"。 工廠方法要解決的問題對象的建立時機,它提供了一種擴展策略,很好地符合了開放封閉原則。與直接建立新的對象相比,使用工廠方法建立對象可算做一種最佳作法。工廠方法模式讓客戶程序能夠要求由工廠方法建立的對象擁有一組共同的行爲。因此往類層次結構中引入新的具體產品並不須要修改客戶端代碼,由於返回的任何具體對象的接口都跟客戶端一直在用的從前的接口相同。ide

二、優勢:
  • 一、將對象的建立和對象自己的業務處理分離能夠下降系統的耦合度,使得二者的修改相對容易。
  • 二、擴展性高,屏蔽產品的具體實現,調用者只關心產品的接口
三、缺點:
  • 增長產品的時候,使得系統中的類個數大量增長,延長App的啓動時間。
四、簡單工廠模式、工廠模式、抽象工廠模式的區別
區別 簡單工廠模式 工廠模式 抽象工廠模式
開放封閉程度
抽象產品的個數 一個 一個 多個
抽象工廠的個數 沒有 一個 多個

工廠模式生產的是整車,抽象工廠模式生產的是零件組合成整車。

五、swift實現Demo

5.一、簡單工廠模式

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.二、工廠模式

// 工廠模式
/// 抽象工廠類
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.三、抽象工廠模式

// 抽象工廠模式
/// 抽象產品類
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()
    }
}
複製代碼

從上面的代碼能夠看出,抽象工廠模式的擴展性最好。

六、工廠模式在CocoaTouch中的應用
[NSNumber numberWithBool:YES];
[NSNumber numberWithInteger:1];
[NSNumber numberWithInt:1];
複製代碼
七、參考資料:

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

學習3、鏈式語法

實現的核心:重寫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
複製代碼

學習4、接口簡潔

把複雜留給本身,把簡單留給別人

學習5、抽象方法小技巧

#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
複製代碼

學習6、包裝任何值類型爲一個對象

咱們添加約束的時候使用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類型字符串

學習7、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源碼解析

相關文章
相關標籤/搜索