Dart高效時間格式化、迭代器工具類庫

開始使用

當前最新版本爲: 1.1.7

在 “pubspec.yaml” 文件中加入

dependencies:
  quicklibs: ^1.1.7

github

https://github.com/CimZzz/quicklibs

Usage

  • 迭代器: 用提供閉包來實現循環每一步控制邏輯

  • Time: 提供一系列關於時間的操作,如時間格式化,字符串轉時間等方法

  • 轉換方法: 提供一系列關於轉換具體類型的操作

  • Scope: 提供在限定的作用域內,有狀態無狀態消息之間的交互。

迭代器

閉包命名

typedef EachBeginCallback<T> = T Function();
typedef EachCallback<T> = Function(T elem);
typedef EachJudgeCallback<T> = bool Function(T obj);
typedef EachChangeCallback<T> = T Function(T obj);
typedef EachOverCallback = dynamic Function(dynamic obj);
typedef EachOverAsCallback<T> = T Function(dynamic obj);
  • EachBeginCallback 爲一次循環回調閉包,只在循環開始時執行一次(可選)
  • EachCallback 爲循環體回調閉包,每次循環均會執行(可選)
  • EachJudgeCallback 爲判斷循環終止回調閉包,當返回 false 時循環終止
  • EachChangeCallback 爲末尾循環體回調閉包,每次循環體最後執行
  • EachOverCallback 處理循環結果閉包回調,一般在循環執行完成後調用
  • EachOverAsCallback 處理循環結果閉包回調,一般在循環執行完成後調用,在處理的同時會將結果進行轉型

以 for 循環爲例(僅僅爲參考樣例)

for(var i = 0 ; i < 10 ; i ++) {
	
};

將回調帶入後

for(EachBeginCallback; EachJudgeCallback; EachChangeCallback) {
    final result = EachCallback;
    if(result != null)
    	return EachOverCallback(result);
}

迭代構造器 EachBuilder<T>

提供一系列構造迭代器的方法

EachBuilder begin(EachBeginCallback<T> beginCallback); //構建一次循環回調閉包

EachBuilder change(EachChangeCallback<T> changeCallback); //構建末尾循環體回調閉包

EachBuilder judge(EachJudgeCallback<T> judgeCallback); //構建判斷循環終止回調閉包

EachBuilder call(EachCallback<T> callback); //構建循環體回調閉包

EachBuilder configAll({
    EachBeginCallback<T> beginCallback,
    EachChangeCallback<T> changeCallback,
    EachJudgeCallback<T> judgeCallback,
    EachCallback<T> callback
}); // 一次性配置全部回調(空閉包會被忽略)

void loopOnly(); // 只執行循環不考慮結果

EachResult loop(); // 執行循環返回結果 EachResult

E loopForResult<E>(); // 執行循環直接獲取最終結果

List<E> loopForList<E>(); // 執行循環直接獲取最終指定類型列表

loopOnly 例子如下:

loop1() {
	final builder = EachBuilder<int>();
	builder.begin(() => 0);
	builder.judge((position) => position < 5)
		.change((position) => position + 1)
		.call((position) => print(position))
		.loopOnly();
}

執行結果爲:

0
1
2
3
4

如果循環需要返回值,可以通過 loop 函數來獲取循環結果,
至於返回值,可以通過 EachCallback 回調閉包返回,這裏分爲兩種情況:

  1. 返回類型爲 EachResult 類型,強制中斷循環返回 EachResult
  2. 返回類型爲 非EachResult 類型,將值存入臨時的 List 中(不保證下標位置關係),
    在循環結束後返回一個包裝 List 對象的 EachResult 對象

loop 例子如下:

loop2() {
	final list = EachBuilder<int>()
			.begin(() => 0)
			.judge((position) => position < 5)
			.change((position) => position + 1)
			.call((position) => position)
			.loop()
			.end();
	
	print(list);
}

執行結果爲:

[0, 1, 2, 3, 4]

或者使用 loopForResult 來實現同樣的功能

loop3() {
	final list = EachBuilder<int>()
		.begin(() => 0)
		.judge((position) => position < 5)
		.change((position) => position + 1)
		.call((position) => position)
		.loopForResult();
	
	print(list);
}

同樣,loopForList 也能實現同樣功能,並且會返回一個明確類型的列表而不是 dynamic 類型的列表

loop12() {
	final list = EachBuilder<int>()
		.begin(() => 0)
		.judge((position) => position < 5)
		.change((position) => position + 1)
		.call((position) => position)
		.loopForList<int>();
	
	print(list);
}

迭代結果 EachResult

如果使用迭代構造器的 loop 方法,則會在執行結束後返回 EachResult 作爲循環結果

我們可以操作這個 EachResult 對象來處理返回結果

提供一些處理迭代結果的方法:

EachResult then(EachOverCallback overCallback); // 追加處理結果回調

T as<T>(EachOverAsCallback<T> overCallback); // 執行一次處理結果回調後返回有具體類型的結果

dynamic end(); // 返回結果

注意: 當 EachResult 返回結果後,無法通過任何方式追加處理結果回調

下面這個例子,通過循環產生一個整數數組,然後通過結果處理返回數組之和:

loop4() {
	final value = EachBuilder<int>()
		.begin(() => 0)
		.judge((position) => position < 5)
		.change((position) => position + 1)
		.call((position) => position)
		.loop() // 返回 EachResult
		.then((list) {
			var sum = 0;
			list.forEach((num) {
				sum += num;
			});
			return sum;
		})
		.end();
	print(value);
}

執行結果如下:

10

這個處理結果是單鏈表結構,意味着可以鏈接多個處理回調。當收到返回結果爲 EachResult 類型時,則不在執行下去忽略其餘處理回調,如:

loop5() {
	final value = EachBuilder<int>()
		.begin(() => 0)
		.judge((position) => position < 5)
		.change((position) => position + 1)
		.call((position) => position)
		.loop() // 返回 EachResult
		.then((list) {
		var sum = 0;
		list.forEach((num) {
			sum += num;
		});
		return sum;
		})
		.then((sum){
			return sum * 10;
		})
		.then((sum) {
			return sum + 50;
		})
		.then((sum){
			return EachResult(sum - 1);
		})
		.then((sum) {
			return sum * 10000;
		})
		.end();
	print(value);
}

執行結果爲:

149

如果可以明確最後一個處理回調,建議使用 as 方法

需要注意的是此方法不能返回 EachResult 類型對象

loop6() {
	final value = EachBuilder<int>()
		.begin(() => 0)
		.judge((position) => position < 5)
		.change((position) => position + 1)
		.call((position) => position)
		.loop() // 返回 EachResult
		.then((list) {
            var sum = 0;
            list.forEach((num) {
                sum += num;
            });
            return sum;
        })
        .then((sum){
            return sum * 10;
        })
        .then((sum) {
            return sum + 50;
        })
        .as((sum){
            return sum - 1;
        });
	print("value type: ${value.runtimeType}, value: $value");
}

執行結果爲:

value type: int, value: 149

簡化整數迭代器

常規循環通過構造器方式直接創建太過繁瑣複雜,因爲我們並不需要那麼強的兼容性,
我們這裏封裝了一種常用的整數迭代器,具體還是使用迭代構造器來實現的,絕大部分迭代邏輯無需自己實現


/// 快捷生成整數循環迭代器的方法,返回最終結果
/// 通過 [intEachBuilder] 生成整數循環構造器,通過返回的 EachBuilder<int> 獲得返回值
dynamic intEach({
		int start = 0,
		int end = 0,
		int total = 0,
		EachCallback<int> callback,
		EachChangeCallback<int> changeCallback
	});

/// 快捷生成整數循環迭代器的方法,返回最終 List 結果
/// 通過 [intEachBuilder] 生成整數循環構造器,通過返回的 EachBuilder<int> 獲得返回 List 值
List<E> intEachList<E>({
	int start = 0,
	int end = 0,
	int total = 0,
	EachCallback<int> callback,
	EachChangeCallback<int> changeCallback
	});

/// 快捷生成整數循環迭代器的方法,返回 EachBuilder<int>
EachBuilder<int> intEachBuilder({
		int start = 0,
		int end = 0,
		int total = 0,
		EachCallback<int> callback,
		EachChangeCallback<int> changeCallback
	});
  • EachCallback 循環回調
  • start 起始下標(可選)
  • end 終止下標(可選)
  • total 表示循環總數(可選)
  • EachChangeCallback 循環執行末尾回調,用於提供自增/自減方法變化下標

以下實例均以 “intEach” 方法爲例

舉個例子,一個最簡單的循環

loop7() {
	var i = 0;
	intEach(
		callback: (position) {
		//do something
		i += position;
	}, total: 100);
	
	print(i);
}

上述程序結果爲 4950,等同於 ∑99。

同樣也可以寫作

loop8() {
	var i = 0;
	intEach(
		callback: (position) {
		//do something
		i += position;
	}, start: 0, end: 100);
	
	print(i);
}

結果同樣爲 4950

上述兩個例子展示了執行整數迭代循環的兩種方式,下面總結一下全部的方式

  1. 提供 total 參數與 start 參數,表示從 start 對應的下標開始,循環 total 次
  2. 提供 start 參數與 end 參數,表示從 start 對應下標開始,到 end 下標終止(遵循 “左閉右開” 原則)
changeCallback

整數迭代器會跟傳遞的參數自動識別迭代的方向(正/負),提供默認的 自增/自減 閉包。

當然,如果普通的自增自減無法滿足需求(比如以指數增長),可以通過 changeCallback 來決定增長趨勢

loop9() {
	intEach(
		callback: (position) {
		//do something
		print("curPosition: $position");
	}, total: 100, changeCallback: (position) => position == 0 ? 1 : position * 3);
}

上述程序執行結果如下:

curPosition: 0
curPosition: 1
curPosition: 3
curPosition: 9
curPosition: 27
curPosition: 81

注意: changeCallback 增長方向要與實際方向一致,否則循環不會執行

簡化列表迭代器

有些時候原生的 list-for-each 方式並不能滿足我們的需求,
我們提供了簡化的列表迭代器

/// 快捷生成列表循環迭代器的方法,返回最終結果
/// 通過 [listEachBuilder] 生成整數循環構造器,通過返回的 EachBuilder<int> 獲得返回值
dynamic listEach<T>(List<T> list,{
	EachCallback<T> callback
});

/// 快捷生成列表循環迭代器的方法,返回 EachBuilder<int>
EachBuilder<int> listEachBuilder<T>(List<T> list,{
	EachCallback<T> callback
});

下面演示一個簡單的列表遍歷

loop13() {
	listEach([1, 2, 3, 4, 5],
	callback: (item) {
		print(item);
	});
}

執行結果如下:

1
2
3
4
5

我們可以利用 EachBuilder 的特性,來完成列表一些特殊操作,如下

loop14() {
	var list = ["1", "2", "3", "4", "5"];
	var newList = listEachBuilder(
		list,
		callback: (item) {
			return int.parse(item);
		}
	).loopForList<int>();
	
	print("list type: ${list.runtimeType}, $list");
	print("newList type: ${newList.runtimeType}, $newList");
}

執行結果如下:

list type: List<String>, [1, 2, 3, 4, 5]
newList type: List<int>, [1, 2, 3, 4, 5]

注意: 前提是在 callback 回調中返回對應的類型,否則會自動篩選列表,將滿足指定類型的元素重新組成一個新的列表

中斷循環

對於大部分閉包循環來說,中斷循環始終是一個煩人的問題。不過通過這個迭代器,只要在迭代回調中返回由 EachResult 包裝的值,
可以很輕易的實現中斷閉包循環,而這個值還會當做迭代的最終結果返回

注意: 如果返回值不使用 EachResult 包裝,則會記錄到一個內部的臨時列表中,在循環正常結束後將會返回該列表。如果沒有返回值則
不會觸發任何邏輯

如下方一個整數迭代器:


loop10() {
	var i = 0;
	var j = 
	intEach(
		callback: (position) {
		if(position > 50)
			return EachResult(i);
		i += position;
	}, total: 100);
	
	print("i: $i, j: $j");
}

執行結果爲:

i: 1275, j: 1275

快速通過迭代生成列表

在每次迭代返回一個整數值,當循環結束後可以獲得由返回值組成的一個列表

如下方一個整數迭代器:

loop11() {
	var list = 
	intEach(
		callback: (position) {
		return position * 10;
	}, total: 10);
	
	print(list);
}

執行結果爲:

[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

Time

提供了一系列關於操作時間的方法,通過 Time 類來訪問其中方法

閉包命名

typedef TimeFormatCallback = void Function(DateTime, int, StringBuffer);
typedef TimeParseCallback = void Function(_TimeParseBuilder, String);
typedef TimeFormatter = String Function(DateTime);
typedef TimeParser = int Function(String);

缺省的時間佔位符

  • y: 表示年份
  • M: 表示月份
  • d: 表示天
  • H: 表示小時(全時制)
  • m: 表示分鐘
  • s: 表示秒

時間格式化

將 DateTime 按照指定格式轉化成字符串

static String format(
     DateTime dateTime,
     String formatStr,
     {List<TimePlaceholder> otherPlaceholder}
)
  • dateTime DateTime 實例
  • formatStr 格式化字符串,如 “yyyy-MM-dd HH:mm:ss”
  • otherPlaceholder 其他自定義時間佔位符(可選)

示例如下

void example1() {
	print(Time.format(DateTime.now(), "yyyy-MM-dd HH:mm:ss"));
}

執行結果:

2019-06-04 14:56:14

時間格式化方法對象

將格式化方法包裝成閉包對象返回,每次調用只需傳遞 DateTime 作爲參數即可

將格式化中一些對象進行復用,優化了執行效率

static TimeFormatter generateFormatMethod(
    String formatStr,
    {List<TimePlaceholder> otherPlaceholder}
)
  • formatStr 格式化字符串,如 “yyyy-MM-dd HH:mm:ss”
  • otherPlaceholder 其他自定義時間佔位符(可選)

返回 TimeFormatter 類型的閉包對象

示例如下

void example2() {
	final method = Time.generateFormatMethod("yyyy-MM-dd HH:mm:ss");
	print(method(DateTime.now()));
}

執行結果:

2019-06-04 14:56:14

時間解析方法

將字符串按照指定格式轉化爲 時間戳(注意不是 DateTime 實例)

注意: 如果源字符串中包含非佔位符,用 “*” 來代替

static int parse(
    String sourceStr, 
    String formatStr, 
    {
    	List<TimePlaceholder> otherPlaceholder, 
    	bool isSafe = false, 
    	Duration timeZoneOffset
    }
)
  • sourceStr 源字符串,如 “2019-06-04 15:05:25”
  • formatStr 格式化字符串,如 “yyyy*MM*dd*HH*mm*ss”
  • otherPlaceholder 其他自定義時間佔位符(可選)
  • isSafe 如果爲 true,解析發生異常時返回 null,否則會拋出異常(可選)
  • timeZoneOffset 時區偏移量,默認爲本地時區(可選)

示例如下:

void example3() {
	print(Time.parse("2019-06-04 15:05:25", "yyyy*MM*dd*HH*mm*ss"));
}

執行結果:

1559631925000 // 本人在東八區,北京時間

時間解析方法對象

將解析方法包裝成閉包對象返回,每次調用只需傳遞源字符串作爲參數即可

將解析化中一些對象進行復用,優化了執行效率

static TimeParser generateParseMethod(
    String formatStr, 
    {
    	List<TimePlaceholder> otherPlaceholder, 
    	bool isSafe = false, 
    	Duration timeZoneOffset
    }
)
  • formatStr 格式化字符串,如 “yyyy*MM*dd*HH*mm*ss”
  • otherPlaceholder 其他自定義時間佔位符(可選)
  • isSafe 如果爲 true,解析發生異常時返回 null,否則會拋出異常(可選)
  • timeZoneOffset 時區偏移量,默認爲本地時區(可選)

返回 TimeParser 類型的閉包對象

示例如下:

void example4() {
	final method = Time.generateParseMethod("yyyy*MM*dd*HH*mm*ss");
	print(method("2019-06-04 15:05:25"));
}

執行結果:

1559631925000

測量執行時間

在開發測試中,會需要測量某段代碼具體的執行時間,來選擇最優的算法

提供了一個用來滿足這個需求的方法

static Duration measure(void run())
  • run 表示執行方法函數閉包

示例如下:

void example5() {
	final duration = Time.measure(() {
		print("hello world");
	});
	
	print(duration);
}

執行結果:

hello world
0:00:00.000369

利用這個方法,我們可以來比較一下時間解析方法的效率

void example6() {
	final loopCount = 10000;
	
	final duration1 = Time.measure(() {
	intEach(
		callback: (position) {
			DateTime.parse("2019-06-04 15:05:25");
		}, total: loopCount);
	});
	
	final duration2 = Time.measure(() {
	intEach(
		callback: (position) {
			Time.parse("2019-06-04 15:05:25", "yyyy*MM*dd*HH*mm*ss");
		}, total: loopCount);
	});
	
	
	final duration3 = Time.measure(() {
		final method = Time.generateParseMethod("yyyy*MM*dd*HH*mm*ss");
	intEach(
		callback: (position) {
			method("2019-06-04 15:05:25");
		}, total: loopCount);
	});
	
	
	print("dart 原生Api解析 $loopCount 次耗時: $duration1");
	print("Time 直接解析 $loopCount 次耗時: $duration2");
	print("Time 生成解析方法解析 $loopCount 次耗時: $duration3");
}

執行結果:

dart 原生Api解析 10000 次耗時: 0:00:00.233731
Time 直接解析 10000 次耗時: 0:00:00.089006
Time 生成解析方法解析 10000 次耗時: 0:00:00.012564

從結果可見,生成解析方法比原生方法大約快 20 倍左右

轉換方法

轉化動態對象爲指定類型列表

/// 如果 needPicker 爲 false 時,只有 obj 是 List<T> 類型纔會返回具體值,否則一律返回 null
/// 如果 needPicker 爲 true 時,分多種情況拾取指定類型的對象
/// 1. obj 是 Iterable 的子類,遍歷迭代器,將所有指定類型的對象放入新的列表中,返回新的列表
/// 2. obj 是指定類型對象,則將對象包裝到一個新的列表中,返回新的列表
List<T> convertTypeList<T>(dynamic obj, {bool needPicker = false});

示例如下:

void convert1() {
	final list = [1, "2", 5];
	print(convertTypeList<String>(list, needPicker: false));
}

void convert2() {
	final list = [1, "2", 5];
	print(convertTypeList<String>(list, needPicker: true));
}

執行結果爲:

null
[2]

轉換對象爲指定類型

/// 轉換對象爲指定類型
/// 如果對象爲 null 或者對象類型不匹配則返回 null
T castTo<T>(dynamic obj)

實例如下:

/// 轉換對象
void convert3() {
	dynamic number = 123;
	print(castTo<String>(number));
	print(castTo<int>(number));
}

執行結果爲:

null
123

Scope

抽象了一個擁有狀態的作用域:

  1. Activated (啓用狀態)
  2. Deactivated (禁用狀態)
  3. Destroy (銷燬狀態)

閉包命名

typedef ScopeMessageCallback = Future Function(dynamic obj);
typedef ScopeActiveDelayMessageCallback = void Function(Map<dynamic, dynamic>);
typedef ScopeBroadcastReceiver = Function(dynamic obj);
typedef ScopeProxyAsyncRunnable<T> = Future<T> Function();
typedef ScopeProxySyncRunnable<T> = Future<T> Function();

主要使用用例

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RvBBJHTe-1574221115045)(mdsrc/scope-relation.jpeg)]

可使用接口描述

/// 激活指定 Scope
static activate(Scope scope);
/// 關閉指定 Scope
static deactivate(Scope scope);
/// 銷燬指定 Scope
static destroy(Scope scope);
/// 將指定 Scope 作爲自己子 Scope
T fork<T extends Scope>(T scope);
/// 將調用的 Scope 與上級 Scope 斷開
void dropSelf();
/// 將調用的 Scope 與全部子 Scope 斷開
void dropChildren();
/// 註冊消息接收器
/// 默認在關閉狀態也可以接收
void registerMessageCallback(dynamic key, ScopeMessageCallback callback);
/// 註冊消息接收器
/// 可以指定在何種狀態下可以接收
void registerStatusMessageCallback(dynamic key, ScopeStatus allowRunStatus, ScopeMessageCallback callback);
/// 註銷消息接收器
void unregisterMessageCallback(dynamic key);
/// 向下分發一次性消息,在消息第一次被接收後停止分發
/// 該方法可以返回處理後的結果
Future dispatchOneTimeMessage(dynamic key, dynamic data, {bool allowTraceBack = false, bool onlyTrackBack = false}) async;
/// 向下分發消息,會觸發相同 key 值下全部的接收器
Future dispatchMessage(dynamic key, dynamic data) async;
/// 向上分發消息
/// 由該 Scope 向父級 Scope 傳遞消息
Future dispatchParentMessage(dynamic key, dynamic data, { int traceCount = 1}) async;
/// 註冊活動延遲消息回調
/// 只可註冊一次
void registerActiveDelayCallback(ScopeActiveDelayMessageCallback callback);
/// 發送活動延遲消息
void postActiveDelayMessage(dynamic key, dynamic data);
/// 重置活動延遲消息相關資源
void resetActiveDelay();
/// 發送廣播給對應 key 下注冊的全部廣播接收器
static void broadcast(dynamic key, dynamic data) async;
/// 註冊廣播接收器
void registerBroadcast(dynamic key, ScopeBroadcastReceiver receiver);
/// 註銷廣播接收器
/// 如果指定廣播接收器的話,則只註銷指定的廣播接收器,否則會將指定 key 值下全部的廣播接收器全部註銷
void unregisterBroadcast(dynamic key, {ScopeBroadcastReceiver receiver});
/// 代理執行 Future
/// 當 Scope 狀態爲銷燬狀態時不會指定並返回 null
Future<T> proxyAsync<T>(ScopeProxyAsyncRunnable<T> runnable) async;
/// 代理同步執行回調
/// 當 Scope 狀態爲銷燬狀態時返回 null
T proxySync<T>(ScopeProxySyncRunnable<T> runnable);
/// 設置存儲數據
/// 可以設置 `syncParent = true`,同時會將數據同步到父 Scope 中;若想同步全部父 Scope,
/// 設置 `untilNotExistParent = true` 會一直向上同步,直到到達頂級 Scope.
T setStoredData<T>(dynamic key, T data, { bool syncParent = false,  bool untilNotExistParent = false } );
/// 只設置父 Scope 存儲數據,不影響自身
T setParentStoredData<T>(dynamic key, T data, { bool syncParent = false,  bool untilNotExistParent = false });
/// 獲取存儲的數據
/// 如果沒有找到對應數據,可以設置 `fromParentIfNotExist = true` 從父 Scope 中
/// 獲取對應 Key 下的數據,如果存在父 Scope 的話;如果父 Scope 仍然不存在數據,可以設置
/// `fromParentUntilNotExist = true`,如此會一直向上查找,直到找數據或已到達頂級 Scope.
/// * `untilNotExistParent` 只在 `fromParentIfNotExist = true` 下才生效.
/// 但是如果自身對應 Key 下存在數據,但是類型不匹配的話,會直接返回 `null`.
T getStoredData<T>(dynamic key, { bool fromParentIfNotExist = false, bool untilNotExistParent = false });
/// 重置存儲所用的全部數據
/// * 只會重置自身存儲的數據,不影響父 Scope 中的數據
void resetStoredData();

詳細見使用樣例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章