Masonry 原始碼學習整理

在路上重名了啊發表於2019-03-04

@(第三方庫原始碼學習)

[TOC]

Masonry框架的類結構

Masonry 原始碼學習整理
Masonry 原始碼學習整理

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

1、定義

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

2、理解這個模式要知道三個設定:
  • Component協議:樹中的元件(Leaf、Composite)都需要實現這個協議
  • Leaf元件:樹結構中的一個沒有子元素的元件
  • Composite元件:容器,與Leaf不同的是有子元素,用來儲存Leaf和其他Composite
    Masonry 原始碼學習整理
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、簡單工廠模式、工廠模式、抽象工廠模式的區別
區別 簡單工廠模式 工廠模式 抽象工廠模式
開放封閉程度
抽象產品的個數 一個 一個 多個
抽象工廠的個數 沒有 一個 多個

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

Masonry 原始碼學習整理
Masonry 原始碼學習整理
Masonry 原始碼學習整理
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原始碼解析

相關文章