ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Flutter Macro
    카테고리 없음 2024. 5. 20. 21:22

    매크로

    5월 15일 구글 I/O 2024에서 플러터 3.22가 공개 되었네요.

    미디엄에도 글이 올라왔습니다. Landing Flutter 3.22 and Dart 3.4 at Google I/O 2024

    웹어셈블리도 공식적으로 지원하게 되었고, 매크로 기능을 소개하였습니다.

    웹어셈블리는 관심있게 보고 있던 주제였습니다.

     

    최근에 회사에서 러스트 스터디를 진행했는데 스터디 중 웹어셈블리 언어로 러스트가 가능하다는 걸 알았기 때문이죠..!

    특히 러스트의 소유권에 대한 개념은 참 흥미롭고 재밌었던 것 같습니다.

    이거에 대한 주제도 따로 한번 써봐야겠네요..

     

    다시 돌아와서..

    오늘 볼 주제는 매크로 입니다.

     

    Swift에서도 최근 매크로가 도입되었고 조금 공부해봤는데 이번 기회에 플러터에도 매크로가 개발중이라는 사실을 알게 되었네요..!

    이 부분 같이 문서 보면서 공부해 보겠습니다!


    Swift

    저는 Swift로 iOS를 개발하다가 Dart언어로 플러터로 개발해보면서 제일 익숙하지 않았던게 JSON에 대한 디코딩 인코딩 기능입니다. (build_runner,, freezed,,)

     

    Swift에서는 Codable 프로토콜을 사용하여 간단하게 인코딩과 디코딩을 할 수있죠.

    Swift에서 만약 모델 타입을 디코딩 하고 싶다면 아래와 같이 하면 됩니다.

    // 모델 타입
    // Codable은 Encodable과 Decodable을 합친 typealias
    struct ToDo: Codable {
        let userId: Int
        let id: Int
        let title: String
        let completed: Bool
    }
    
    
    // 모델 타입에 대한 인코딩 & 디코딩
    let json = """
        {
           "userId": 1,
           "id": 1,
           "title": "delectus aut autem",
           "completed": false
         }
    """
    
        do {
             // JSON 디코딩
            let decoded = try JSONDecoder().decode(ToDo.self, from: json.data(using: .utf8)!)
    
            print(decoded.id)
            print(decoded.completed)
            print(decoded.title)
    
            // 인코딩
            let encoded = try JSONEncoder().encode(decoded)
            let jsonString = String(data: encoded, encoding: .utf8)
    
            print(jsonString)
    
        } catch {
            print("error")
        }

    이렇게 Codable Protocol을 채택한 것만으로 해당 타입은 손쉽게 인코딩과 디코딩을 할 수 있습니다.

    플러터에서는 어떨까요?

    Flutter

    // 디코딩
    const jsonString = {
      "name": "John Smith",
      "email": "john@example.com"
    }
    
    final user = jsonDecode(jsonString) as Map<String, dynamic>;
    
    print('Howdy, ${user['name']}!');
    print('We sent the verification link to ${user['email']}.');
    

     

    라이브러리를 사용하면 이렇게 Map 타입으로 캐스팅 한 뒤 사용하게 됩니다.
    이러면 해당 value에 대한 type이 다이나믹이기 때문에 어떤 타입인지 모호하고 컴파일 시점에 유추하기 힘든면이 있습니다.
    만약 Key를 잘못 넣었을 때 상황도 생각하기 싫네요.. ㅠ

     

    그래서 보통의 경우에는 json_serializable 또는 freezed 라이브러리를 사용하여 모델 타입에 대한 코드를 생성하게 됩니다.

    @JsonSerializable()
    class Todo {
      final int userId;
      final int id;
      final String title;
      final String description;
    
      Todo({
        required this.userId,
        required this.id,
        required this.title,
        required this.description,
      });
    
      // 디코딩
      factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
    
      // 인코딩
      Map<String, dynamic> toJson() => _$TodoToJson(this);
    
    ------------------------------------------------------------------
      /// fromJson 메서드와 toJson에 대한 메서드 코드를 아래와 같이 자동생성해 줍니다.
      /// todo.g.dart 생성 파일
    
    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'todo.dart';
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    Todo _$TodoFromJson(Map<String, dynamic> json) => Todo(
          userId: (json['userId'] as num).toInt(),
          id: (json['id'] as num).toInt(),
          title: json['title'] as String,
          description: json['description'] as String,
        );
    
    Map<String, dynamic> _$TodoToJson(Todo instance) => <String, dynamic>{
          'userId': instance.userId,
          'id': instance.id,
          'title': instance.title,
          'description': instance.description,
        };
    }

     

    라이브러리를 사용하게 되면 모델타입에 대한 인코딩 & 디코딩 해주는 코드를 만들어주게 됩니다. 보통은 이렇게 현업에서 사용 했던 것 같아요.

    그럼 매크로는 코드제너레이터와 어떤 차이가 있을까요?

    매크로

    매크로에 대한 정의는

    https://github.com/dart-lang/language/blob/main/working/macros/motivation.md 이곳에 정의되어있습니다.


    Metaprogramming라고 설명하고 있는데요.
    매크로를 사용한다면 JSON 뿐만 아니라 수많은 Boilerplate code를 줄일 수 있습니다.

    단순 반복되는 코드를 매크로를 사용하여 줄일 수 있는 것이죠.
    다트팀은 개발자가 지루한 작업을 컴파일 타임에 대신 할 수 있는 것을 매크로라고 정의 한 것 같습니다.

     

    매크로와 코드 제네레이터 방식은 언뜻보면 의미는 비슷합니다만 자세히 보면 생성시점이 다릅니다.

    • 코드 제네레이터는 위에서 설명했듯이 컴파일 이전에 파일을 생성 합니다.
    • 매크로는 컴파일 타임에 코드를 생성하게 됩니다.

    매크로를 가장 쉽고 편하게 이해할 수 있는 라이브러리가 있습니다.!

    바로 Json 패키지 인데요. 해당 라이브러리는 다트팀에서 만든 라이브러리로 JSON 인코딩 & 디코딩을 쉽게 할 수 있도록 만들어진 라이브러리 입니다!

     

    다만 해당 패키지는 stable에 릴리즈 된게 아니라 dart 3.5.0 이상이여만 사용해 볼 수 있습니다.
    Macro 문서를 보시고 설치하시면 되요! (플러터 master 채널이나 dev 채널로 바꾸셔야 되요)

     

    설치를 완료 후 터미널에 flutter --version 을 하게 되면 dart 버전을 체크 후 3.5.0이면 됩니다.

     

    자 이제 매크로를 사용해 보겠습니다.
    먼저 Json타입을 파싱할 모델 타입을 정의합니다.

     

    네. 이게 끝입니다. 정의 부분에 fromJson 이라던지 toJson 메서드도 없습니다.

    freezed나 json_serializable을 사용했다면 사전에 해야 될 작업도 많았지만 @JsonCodable() 어노테이션만 붙이면 정말 간단히 끝나게 됩니다.

     

    위의 모델타입에 보면 Go to Augmentation 이라는 키워드가 보이고 해당 키워드를 클릭하게 되면

     

    컴파일러가 class를 따로 만들어 fromJson 과 toJson의 메서드를 만들어 준게 보입니다.

     

     

    저희는 이렇게 간편히 모델타입을 파싱 하면 됩니다..!

    정말 간편해 보이지 않나요? ㅎㅎ

    저는 프로젝트가 커질수록 코드제너레이트 하는 부분도 귀찮고, 점점 더 오래걸리는게 불편했거든요 ㅠ

     

    그리고 지금은 단순 예로 json을 사용했지만 매크로가 정식으로 사용가능하다면 파싱뿐만 아니라,

    다른 중복되는 코드도 굉장히 줄 일 수 있을것 같습니다.

     

    https://github.com/dart-lang/language/blob/main/working/macros/feature-specification.md#introspection

    해당 문서를 보시면 다트에서 매크로가 어떻게 클래스를 생성하는지 자세히 나와있습니다.

     

    이상으로 다트 매크로에 대해 알아보았습니다!! 

    모두 적게 일하고 돈 많이 버세요!!!!

Designed by Tistory.