Masonry1.0.2 源碼解析

来源:http://www.cnblogs.com/fishbay/archive/2017/08/21/7402482.html
-Advertisement-
Play Games

在瞭解`Masonry`框架之前,有必要先瞭解一下**自動佈局**的概念。在`iOS6`之前,`UI`佈局的方式是通過`frame`屬性和`Autoresizing`來完成的,而在`iOS6`之後,蘋果公司推出了`AutoLayout`的佈局方式,它是一種基於約束性的、描述性的佈局系統,尤其是蘋果的... ...


在瞭解Masonry框架之前,有必要先瞭解一下自動佈局的概念。在iOS6之前,UI佈局的方式是通過frame屬性和Autoresizing來完成的,而在iOS6之後,蘋果公司推出了AutoLayout的佈局方式,它是一種基於約束性的、描述性的佈局系統,尤其是蘋果的手機屏幕尺寸變多之後,AutoLayout的應用也越來越廣泛。

但是,手寫AutoLayout佈局代碼是十分繁瑣的工作(不熟悉的話,可以找資料體驗一下,保證讓你爽到想哭,^_^);鑒於此,蘋果又開發了VFL的佈局方式,雖然簡化了許多,但是依然需要手寫很多代碼;如果,你希望不要手寫代碼,那麼可以用xib來佈局UI,可以圖形化添加約束,只是xib的方式不太適合多人協作開發。綜合以上的各種問題,Masonry出現了,這是一款輕量級的佈局框架,採用閉包、鏈式編程的技術,通過封裝系統的NSLayoutConstraints,最大程度地簡化了UI佈局工作。

本文主要分析一下Masonry的源碼結構、佈局方式和實現原理等等。

框架結構

Masonry框架的源碼其實並不複雜,利用自己的描述語言,採用優雅的鏈式語法,使得自動佈局方法簡潔明瞭,並且同時支持iOSMacOS兩個系統。Masonry框架的核心就是MASConstraintMaker類,它是一個工廠類,根據約束的類型會創建不同的約束對象;單個約束會創建MASViewConstraint對象,而多個約束則會創建MAXCompositeConstraint對象,然後再把約束統一添加到視圖上面。

圖1

佈局方式

Masonry的佈局方式比較靈活,有mas_makeConstraints(創建佈局)、mas_updateConstraints(更新佈局)、mas_remakeConstraints(重新創建佈局)三種:

1.mas_makeConstraints:

給視圖(視圖本身或父視圖)添加新的約束

// 給view1添加約束,frame和superView一樣
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview);
}];

2.mas_updateConstraints:

更新視圖的約束,從視圖中查找相同的約束,如果找到,就更新,會設置makerupdateExistingYES

// 更新view1的上邊距離superView為60,寬度為100
[view1 mas_updateConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(@60);
    make.width.equalTo(@100);
}];

3.mas_remakeConstraints:

給視圖添加約束,如果視圖之前已經添加了約束,則會刪除之前的約束,會設置makerremoveExistingYES

// 重新設置view1的約束為:頂部距離父視圖為300,左邊距離父視圖為100,寬為100,高為50
[view1 mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview).offset(300);
    make.left.equalTo(superview).offset(100);
    make.width.equalTo(@100);
    make.height.equalTo(@50);
}];

Masonry的佈局相對關係也有三種:.equalTo(==)、.lessThanOrEqualTo(<=)、.greaterThanOrEqualTo(>=)。

Masonry的佈局關係的參數也有三種:

1. @100 --> 表示指定具體值

2. view --> 表示參考視圖的相同約束屬性,如view1的left參考view2的left等

3. view.mas_left --> 表示參考視圖的特定約束屬性,如view1的left參考view2的right等

實現原理

Masonry是利用閉包和鏈式編程的技術實現簡化操作的,所以需要對閉包和鏈式編程有一定的基礎。下麵會根據案例來具體分析一下Masonry的實現細節,代碼實現的功能是設置view1frameCGRectMake(100, 100, 100, 100);其中,mas_equalTo(...)是巨集,會被替換成equalTo(MASBoxValue((...))),功能是把基本類型包裝成對象類型:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.equalTo(@100);
    make.size.mas_equalTo(CGSizeMake(50, 50));
}];

1.創建maker

首先調用mas_makeConstraints:,這是一個UIView的分類方法,參數是一個設置約束的block,會把調用視圖作為參數創業一個maker

// View+MASAdditions.h
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    // 創建maker,並保存調用該方法的視圖view
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

2.生成約束

接下來,開始利用maker產生約束,即調用block(constraintMaker)

2.1 設置坐標x

make.left

調用過程:

// MASConstraintMaker.h
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        // 傳入的參數是nil,所以此處代碼不會執行
        ...
    }
    if (!constraint) {
        // 設置newConstraint的代理為maker
        newConstraint.delegate = self;
        // 把約束加入到數組中
        [self.constraints addObject:newConstraint];
    }
    // 返回MASViewConstraint類型的約束對象
    return newConstraint;
}

其中,上述代碼根據maker保存的view和傳入的約束屬性layoutAttribute創建了一個MASViewAttribute對象,然後根據viewAttribute對象創建了一個MASViewConstraint約束對象,代碼如下:

// MASViewAttribute.h
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if (!self) return nil;
    
    // 保存視圖view
    _view = view;
    _item = item;
    // 保存約束屬性:NSLayoutAttributeLeft
    _layoutAttribute = layoutAttribute;
    
    return self;
}

// MASViewConstraint.h
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
    self = [super init];
    if (!self) return nil;
    
    // 保存第一個屬性(封裝了視圖view和約束屬性NSLayoutAttributeLeft)
    _firstViewAttribute = firstViewAttribute;
    self.layoutPriority = MASLayoutPriorityRequired;
    self.layoutMultiplier = 1;
    
    return self;
}

2.2 設置坐標y

make.left.top

由於make.left返回的是MASViewConstraint對象,所以調用的top應該是MASViewConstraint類中的方法(該方法繼承自父類MASConstraint),調用過程如下:

// MASConstraint.h
- (MASConstraint *)top {
    // self是MASViewConstraint對象
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

// MASViewConstraint.h
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    // self.delegate是maker對象
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

// MASConstraintMaker.h
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        // 由於參數constraint不為nil,所以進入此處執行
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        // 創建約束集合對象,並把先前的約束對象和本次新創建的約束對象保存到數組中
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        // 設置約束集合對象的代理為maker
        compositeConstraint.delegate = self;
        // 用約束集合對象替換maker中已經保存的約束對象,因為我們同一個maker設置了2個以上的約束
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        // 返回MASCompositeConstraint約束集合對象
        return compositeConstraint;
    }
    if (!constraint) {
        ...
    }
    return newConstraint;
}

如果一個maker添加多個約束後,就會創建MASCompositeConstraint對象,創建約束集合的過程如下:

- (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if (!self) return nil;

    // 保存約束數組
    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        // 設置數組中所有的約束對象的代理為MASCompositeConstraint對象
        constraint.delegate = self;
    }

    return self;
}

在創建了MASCompositeConstraint對象後,就會更新maker中的約束數組,在最後添加約束的時候,就會是全部的約束對象,代碼如下:

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
    NSUInteger index = [self.constraints indexOfObject:constraint];
    NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
    [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}

2.3 設置x、y的值

make.left.top.equalTo(@100)

make.left.top返回的對象是MASCompositeConstraint類型,調用過程如下:

// MASConstraint.h
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

// MASCompositeConstraint.h
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            // 遍曆數組,把每個MASViewConstraint對象都調用該方法
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

// MASViewConstraint.h
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            // 由於attribute是@100的包裝類型,不是數組,此處代碼不會執行
            ...
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            // 設置約束類別為NSLayoutRelationEqual
            self.layoutRelation = relation;
            // 設置第二個屬性
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
    _layoutRelation = layoutRelation;
    // 表明已經有了約束關係
    self.hasLayoutRelation = YES;
}

下麵分析一下設置第二個屬性secondViewAttribute的過程,因為Masonry重寫了setter方法,過程如下:

// MASViewConstraint.h
- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        // secondViewAttribute是@100類型
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        // secondViewAttribute是視圖UIView類型
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        // secondViewAttribute是view.mas_left類型
        _secondViewAttribute = secondViewAttribute;
    } else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
    }
}

// MASConstraint.h   @100類型
- (void)setLayoutConstantWithValue:(NSValue *)value {
    // 根據value的不同類型,設置不同的屬性值
    if ([value isKindOfClass:NSNumber.class]) {
        self.offset = [(NSNumber *)value doubleValue];
    } else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
        CGPoint point;
        [value getValue:&point];
        self.centerOffset = point;
    } else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
        CGSize size;
        [value getValue:&size];
        self.sizeOffset = size;
    } else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets insets;
        [value getValue:&insets];
        self.insets = insets;
    } else {
        NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
    }
}

由於@100NSNumber類型,所以執行self.offset來設置偏移量,代碼如下:

// MASViewConstraint.h
- (void)setOffset:(CGFloat)offset {
    // 設置layoutConstant屬性值,在最後添加屬性時作為方法參數傳入
    self.layoutConstant = offset;
}

- (void)setLayoutConstant:(CGFloat)layoutConstant {
    _layoutConstant = layoutConstant;

#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
    ...
#else
    self.layoutConstraint.constant = layoutConstant;
#endif
}

這裡有網友疑惑,因為self.layoutConstraint在上面的方法中一直是nil,設置它的constant屬性是沒有意義的,不知道這麼寫有何意義?其實,我也有同樣的疑問!!!

2.4 設置size

另外,make.size的實現過程和上面的分析類似,有興趣的可以自行參考,看一看具體的實現過程,在此不做分析。

3.安裝約束

下麵分析一下約束的安裝過程

[constraintMaker install]

調用過程如下:

// MASConstraintMaker.h
- (NSArray *)install {
    if (self.removeExisting) {
        // 是remake,所以要先刪除已經視圖中存在的約束
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        // 添加約束
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}

// MASViewConstraint.h
- (void)uninstall {
    if ([self supportsActiveProperty]) {
        // 如果 self.layoutConstraint 響應了 isActive 方法並且不為空,會激活這條約束並添加到 mas_installedConstraints 數組中,最後返回
        self.layoutConstraint.active = NO;
        [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
        return;
    }
    
    [self.installedView removeConstraint:self.layoutConstraint];
    self.layoutConstraint = nil;
    self.installedView = nil;
    
    [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}

下麵分析一下install的過程:

- (void)install {
    // 如果已經安裝過約束,直接返回
    if (self.hasBeenInstalled) {
        return;
    }
    
    // 如果 self.layoutConstraint 響應了 isActive 方法並且不為空,會激活這條約束並添加到 mas_installedConstraints 數組中,最後返回
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    
    // 取出約束的兩個視圖及約束屬性
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    // alignment attributes must have a secondViewAttribute
    // therefore we assume that is refering to superview
    // eg make.left.equalTo(@10)
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        // 如果第一個屬性不是size屬性,並且第二個屬性為nil,就把第二個視圖設置為view的父視圖,約束屬性設置為view的約束屬性
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    
    // 創建約束對象
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    
    if (self.secondViewAttribute.view) {
        // 如果第二個屬性視圖存在,就取第一個視圖和第二個視圖的最小父視圖
        MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        NSAssert(closestCommonSuperview,
                 @"couldn't find a common superview for %@ and %@",
                 self.firstViewAttribute.view, self.secondViewAttribute.view);
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        // 如果第一個屬性是設置size的,就把第一個視圖賦值給installedView
        self.installedView = self.firstViewAttribute.view;
    } else {
        // 否則就取第一個視圖的父視圖賦值給installedView
        self.installedView = self.firstViewAttribute.view.superview;
    }


    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        // 如果是更新屬性,就根據layoutConstraint查看視圖中是否存在該約束
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

求兩個視圖的最小父視圖的代碼如下:

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = nil;

    MAS_VIEW *secondViewSuperview = view;
    while (!closestCommonSuperview && secondViewSuperview) {
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) {
            if (secondViewSuperview == firstViewSuperview) {
                // 如果first和second的視圖一樣,就設置closestCommonSuperview,並返回
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    return closestCommonSuperview;
}

其實,上述代碼是先判斷firstsecond的視圖是否一樣,如果一樣,直接返回;如果不一樣,就判斷fisrt的父視圖和second是否一樣,如果一樣,就返回;不一樣,繼續判斷first的父視圖和second的父視圖是否一樣,如果一樣,就返回;不一樣,重覆迭代。


結束語

Masonry的源碼分析完結,如果文中有不足之處,希望指出,互相學習。

參考資料

Masonry

Masonry 源碼解析

RAC之masonry源碼深度解析

Masonry 源碼進階

學習AutoLayout(VFL)

iOS開發-自動佈局篇:史上最牛的自動佈局教學!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ListView實現簡單列表 效果圖: 啥也沒乾的ListView張這樣: fry.Activity01 /ListView/res/layout/activity01.xml ...
  • ViewPager實現引導頁 廢話就不多說了,直接上效果圖和代碼 fry.Activity01 fry.ImagePagerAdapter /viewPager/res/layout/activity01.xml 1、多個控制項(這裡指小圓點)可以用List來一起訪問 2、可用動態的控制項設置tag來區 ...
  • (c)hele 一、基本情況 (一)開發工具改版 在我的上篇博客里,工程構建還是Eclipse模式,但是這篇博客寫出來時工程已經改為Android Studio模式,主要是Gradle工具的應用,特別是第三方庫的引用,不用將庫放進libs文件夾下,只需要進行Gradle依賴配置,十分方便。 (二)內 ...
  • Windows下的Android遠程桌面助手(Android Remote Displayer and Controller),增加了輸入法的快速切換功能,支持通過Google拼音輸入法在PC端快速輸入中文,支持通過ARDC輸入法複製粘貼文本到Android端。 ...
  • 歡迎各位同學加入: React-Native群:397885169 大前端群:544587175 大神超多,熱情無私幫助解決各種問題。 最近項目需求需要用到輪播圖,所以寫兩Demo練練手,不過效果不太理想,希望大牛予以指正。 不多說,先上圖。 這種輪播很常見,但是一個問題是,總感覺有點卡的感覺,最氣 ...
  • 說明: ^.*[\u4e00-\u9fa5].*$ 是否包含中文^[\u4E00-\u9FA5]+$ 是否全中文 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range repla ...
  • 轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/76165252 本文出自 "【趙彥軍的博客】" 一:什麼是路由? 說簡單點就是映射頁面跳轉關係的,當然它也包含跳轉相關的一切功能。 二:為什麼需要路由 Android系統已經給我們提供 ...
  • 轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/75578109 本文出自 "【趙彥軍的博客】" 起因 最近在項目中遇到需要在界面上顯示一個本地的 GIF 圖。按照慣例我直接用了 Glide 框架來實現。 Glide 地址: http ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...