スマレジのテックファーム(SES 部門)で Web 系エンジニアとして働いている やまて(@r_yamate) と申します。
2023 年 4 月からは、スマレジの関連アプリの開発業務を担当しています。触ったことのなかった Flutter での開発を担当することになり、日々奮闘中です。開発しているアプリはモニター利用も始まり、開発の醍醐味を味わえていて楽しいです。
はじめに
今回は、 Flutter を用いて UUID(Universally Unique Identifier)を生成する手順についてまとめます。 併せて UUID がどういったものか、uuid パッケージのコードを読んで UUID をどのように生成しているのかを確認します。
目次
環境
私が試した Flutter の環境は以下のとおりです。
- MacBook Pro(Intel)
- macOS Monterey 12.6.5
- Flutter 3.7.1
- Dart 2.19.1
- uuid 4.1.0
step1. 依存パッケージの追加
pubspec.yaml への依存パッケージ追加
プロジェクトの pubspec.yaml ファイルに以下のように uuid
パッケージを追加します。
dependencies: uuid: ^4.1.0
uuid
パッケージは、その名の通り UUID を生成してくれるパッケージです。
その後、ターミナルで flutter pub get
コマンドを実行して依存パッケージを取得します。
step2. コードの実装
パッケージのインポート
まず、 uuid
パッケージをインポートします。
import 'package:uuid/uuid.dart';
UUID 生成の関数を作成
次に、以下のような UUID 生成の関数を作成します。
/// UUIDを生成する。 /// /// 生成されたUUIDとして文字列を返す。 String generateUuid() { var uuid = Uuid(); return uuid.v4(); }
実行結果
生成される UUID は、以下のような形式になります。
'550e8400-e29b-41d4-a716-446655440000'
ところで… UUID とは
UUID(Universally Unique Identifier)の定義
UUID は、 Universally Unique Identifier の略で、直訳すると「全世界で一意な識別子」です。
UUID は、RFC 4122 に基づいて定義されています。
https://www.ietf.org/rfc/rfc4122.txt
UUID は「128 ビットの長さで、場所と時間を超えて一意性を保証することができる」という記載が RFC 4122 の概要にあります。
UUID の形式
UUID は 128 ビットの数値ですが、次の形式を持つ 16 進数の文字列として表現されます。
550e8400-e29b-41d4-a716-446655440000
なお、 RFC 4122 で定義されている UUID には現在 1 〜 5 のバージョンがあり、バージョンによって、生成方法が異なります。
例えば バージョン 1 については、以下の部分にハイフンで分けられています。
550e8400
: 8文字 - タイムロー (time-low) の値。 UUID の時間の低 32 ビットを示しています。e29b
: 4文字 - タイムミッド (time-mid) の値。 UUID の時間の中 16 ビットを示しています。41d4
: 4文字 - タイムハイとバージョン (time-high-and-version) の値。 UUID の時間の高12ビットとバージョンを示しています。a716
: 4文字 - クロックシーケンスハイとリザーブド (clock-seq-high-and-reserved) と クロックシーケンスロー (clock-seq-low) の値。この2つの情報が組み合わされています。446655440000
: 12文字 - ノード (node) の値。ノードは、つまり MAC アドレスを示しています。
UUID のバージョン
バージョンは、 UUID がどのように生成されるかを示しており、現在は以下の 5 つです。
- バージョン 1 :現在のタイムスタンプと MAC アドレスに基づいて生成
TTTTTTTT-TTTT-1TTT-sSSS-AAAAAAAAAAAA
- バージョン 2:DCE(分散計算環境)のセキュリティに関連
- バージョン 3:名前ベースの UUID 。 MD5 ハッシュを使用
- バージョン 4:ランダムまたは擬似ランダム数に基づいて生成
RRRRRRRR-RRRR-4RRR-rRRR-RRRRRRRRRRRR
- バージョン 5:名前ベースの UUID 。 SHA-1 ハッシュを使用
システム開発でよく使われる UUID はバージョン 4
システム開発で一般的によく使われる UUID は、 バージョン 4 です。バージョン 4 の UUID がよく使われる理由は、指定されたビットを除いて完全にランダムな値で構成されているためです。
私は「現在のタイムスタンプや端末などから生成するから UUID は一意なんだろうな」とバージョン 1 のイメージで想像していたのですが、バージョン 4 は現在のタイムスタンプなど関係なく、完全にランダムな値で生成されるとのことでした。
参考リンク
パッケージのコードリーディング
uuid
パッケージで、 UUID をどのように生成しているのかを確認します。
Uuid
クラスの v4
メソッドを実行したときにどのように動作しているか、パッケージのコードを読んで確認します。
全体的なざっくりとしたプロセスとしては、初めにランダムな 16 バイトのデータが生成され、次にそのデータが UUID の仕様に従って修正されて、最終的にハイフンを含む文字列形式に変換されるというプロセスが行われます。
確認:lib/uuid.dart
class Uuid { // 〜省略〜 String v4( {@Deprecated('use config instead. Removal in 5.0.0') Map<String, dynamic>? options, V4Options? config}) { if (options != null && options.isNotEmpty) { var rng = options["rng"]; if (options["rng"] != null && options["rng"] is! RNG) { rng = LegacyRNG( options["rng"], options["namedArgs"], options["positionalArgs"]); } config = V4Options(options["random"], rng); } return UuidV4(goptions: goptions).generate(options: config); }
Uuid
クラスのv4
メソッドは、引数としてoptions
とconfig
を受け取ることができます。options
が指定されている場合、古い RNG 方式をサポートするための変換処理を行っています。 RNG は Random Number Generator の略で、ランダムな数値を生成する方式ということです。- 実際の UUID 生成処理を行う
UuidV4
クラスのgenerate
メソッドを呼び出して、結果の UUID 文字列を返しています。
※ 余談ですが、 Uuid
クラスのメソッドに v6
, v7
, v8
もあり、v5
までじゃないの?となりましたが、RFC 4122 では v5
まで定義されていて、新しいバージョンが提案されているようです。
確認:lib/v4.dart
import 'data.dart'; import 'parsing.dart'; import 'rng.dart'; class UuidV4 { final GlobalOptions? goptions; const UuidV4({this.goptions}); String generate({V4Options? options}) { // Use the built-in RNG or a custom provided RNG List<int> rng = options?.rng?.generate() ?? goptions?.rng?.generate() ?? MathRNG().generate(); // Use provided values over RNG List<int> rnds = options?.random ?? rng; // per 4.4, set bits for version and clockSeq high and reserved rnds[6] = (rnds[6] & 0x0f) | 0x40; rnds[8] = (rnds[8] & 0x3f) | 0x80; return UuidParsing.unparse(rnds); } }
UuidV4
クラスのgenerate
メソッドは、バージョン 4 の UUID を生成する役割を持っています。- このメソッドは、既定の
MathRNG
を使用するか、または提供されたカスタム RNG を使用して、 16 バイトのランダムな数値のリストを生成します。 - 次に、このリストにいくつかのビット操作を適用して、 RFC 4122 の要件に従って UUID を生成します。
確認:lib/rng.dart
abstract class RNG { const RNG(); Uint8List generate() { final uint8list = generateInternal(); if (uint8list.length != 16) { throw Exception( 'The length of the Uint8list returned by the custom RNG must be 16.'); } else { return uint8list; } } Uint8List generateInternal(); }
RNG
は Random Number Generator のインターフェースを定義する抽象クラスです。- このクラスの目的は、
UUID
クラスがランダムな数値を生成する際に使用する RNG のインターフェースを提供することです。 generate
メソッドは、具体的な RNG の実装によるgenerateInternal
メソッドを呼び出して、 Uint8List 型のランダムな数値のリストを取得します。さらに、このgenerate
メソッドは取得したリストの長さが 16 であることを確認します。もし 16 でない場合、例外をスローします。generateInternal
メソッドは抽象メソッドとして定義されており、具体的な RNG の実装でオーバーライドされます。
class MathRNG extends RNG { static final _random = Random(); final int seed; const MathRNG({this.seed = -1}); @override Uint8List generateInternal() { final b = Uint8List(16); final rand = (seed == -1) ? _random : Random(seed); for (var i = 0; i < 16; i++) { b[i] = rand.nextInt(256); } return b; } }
MathRNG
クラスは、RNG
クラスを継承しています。_random
は静的なRandom
インスタンスを保持しており、このクラス全体で共有される乱数生成器として利用されます。seed
は、乱数生成の初期値として利用することができる整数です。デフォルトは-1
です。MathRNG
のコンストラクタでは、オプションでseed
を受け取ることができます。指定しない場合、デフォルトの-1
が使用されます。generateInternal
メソッドは、 16 バイトのUint8List
を生成して返します。seed
の値が-1
である場合、静的な_random
インスタンスを利用して乱数を生成します。それ以外の場合、新しいRandom
インスタンスをseed
で初期化して利用します。- 16 バイトのリスト内の各バイトに対して、 0 から 255 の範囲のランダムな整数を割り当てます。
- 最後に、生成された
Uint8List
を返します。
確認:lib/parsing.dart
class UuidParsing { /// Easy number -> hex conversion static final List<String> _byteToHex = List<String>.generate(256, (i) { return i.toRadixString(16).padLeft(2, '0'); }); // 〜省略〜 static String unparse(List<int> buffer, {int offset = 0}) { if (buffer.length - offset < 16) { throw RangeError('buffer too small: need 16: length=${buffer.length}' '${offset != 0 ? ', offset=$offset' : ''}'); } var i = offset; return '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}' '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}'; } }
unparse
メソッドは、バイト配列を UUID の文字列形式に変換するメソッドです。このメソッドは、入力として与えられたバイト配列を 16 進数の文字列に変換して、 UUID の形式に従った文字列を返します。_byteToHex
のi.toRadixString(16).padLeft(2, '0')
という式は、整数i
を 16 進数の文字列に変換し、結果の文字列の長さが 2 未満の場合に左側を'0'
で埋める処理を行っています。
おわりに
今回は、 Flutter を用いて UUID(Universally Unique Identifier)を生成する手順と、 併せて UUID がどういったものか、uuid パッケージで UUID をどのように生成しているのかについてまとめました。
ありがとうございました。
アイキャッチ画像を用意するときにChatGPTの画像生成を初めて使ってみた。よく分からんけど、めっちゃカッコいい!よく分からんから、アイキャッチ画像には不採用。#DALLE3 https://t.co/M8f3EYQvrD pic.twitter.com/vk7Hmkj8m1
— やまて|Web系エンジニア2年目 (@r_yamate) 2023年10月15日
便利そうなので画像生成も使えるようになりたい。