포디 Podi
또렷한 기억보다 글이 낫다
포디 Podi
  • 분류 전체보기 (83)
    • 책 (14)
      • 기록 (14)
      • 한줄 (0)
    • IT (62)
      • 기록 (61)
    • 일상 (5)
      • 음식 (0)
      • 기록 (5)
    • 게임 (0)
      • 몬스터헌터 (0)

블로그 메뉴

  • 홈으로
  • 방명록
  • 태그들
  • 깃허브
  • 유튜브
  • 인스타

공지사항

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
포디 Podi
IT/기록

Flutter I/O Extended Korea 2022에 다녀오다!

Flutter I/O Extended Korea 2022에 다녀오다!
IT/기록

Flutter I/O Extended Korea 2022에 다녀오다!

2022. 6. 28. 01:29
728x90

https://festa.io/events/2361

 

Flutter I/O Extended Korea 2022 | Festa!

Festa에서 당신이 찾는 이벤트를 만나보세요.

festa.io

 

Flutter I/O Extended Korea 2022 

6월 26일(일) 6개의 세션으로 이루어진 이번 행사!!

코로나 이후로 오프라인 행사는 거의 가지 않았는데

오랜만에 오프라인 행사에 참여해봤다! 👍

 

이 스티커 외에 이것저것 받긴 했지만!

정말 젤 귀여웠던 DART SIDE 스티커 ㅎㅎ 

https://dev.to/bsutton/the-dart-side-blog-42g1

 

https://www.youtube.com/watch?v=w_ezWG1yKQQ&t=2s 

이번 행사는 Flutter 3.0을 비롯한 다양한 내용의 세션이 있다고 합니다!

그러면 각 세션별로 알게 된 내용과 느낀 점을 기록해보겠습니다

 

 플러터 리눅스 데스크톱 + ROS2 

Flutter 3.0 버전에 리눅스 앱 지원

첫 번째 세션은 리눅스 데스크톱!

Flutter 3.0 버전 이전에는 Windows 데스크톱만 지원했으나 3.0 이후로는 MacOS/Linux도 지원하게 되었습니다! 

리눅스 데스크톱을 만들기 위해 우분투 만든 회사인 캐노니컬과 협업했다고 합니다!

그래서 플러터 리눅스 데스크톱의 종합적인 테마와 위젯은 우분투 스타일을 따랐다고 합니다

 

Flutter architectural

https://docs.flutter.dev/resources/architectural-overview

전체 아키텍처와 윈도우 아키텍처

기본 운영체제에서 Flutter 응용 프로그램은 다른 기본 응용 프로그램과 동일한 방식으로 패키징 된다

플랫폼별 Embedder는 entrypoint을 제공합니다 

 

Flutter는 멀티 플랫폼이다 

플러터의 미션은 대중적이고 좋은 퀄리티의 높은 생산성을 가진 UI 툴킷을 만드는 것이 플러터의 목표와 미션이라고 합니다

그리고 플러터는 멀티 플랫폼을 지원합니다

https://github.com/flutter/flutter

다트 언어를 통해 모바일, 임베디드, 데스크톱, 웹까지 모두 다 싱글 코드로 돌릴 수 있는 장점이 있습니다

 

 

각 플랫폼에 머티리얼, 쿠퍼티노 위젯을 동일하게 그려낼 수 있었을까요?

다트라는 언어로 구현된 Flutter 프레임 워크로 엔진 내부에 Skia를 통해 빌드되면서 GPU에 올려 디스플레이해주는 방식입니다!

즉, 플러터에서 Skia에 렌더링 하는 명령을 주면 OpenGL이나 ANGLE 등이 vertext를 만들어서 GPU에 버퍼를 올려 디스플레이해주는 방식이라고 합니다! 

Skia는 다양한 하드웨어 및 소프트웨어 플랫폼에서 작동하는 공통 API를 제공하는 오픈 소스 2D 그래픽 라이브러리입니다.

그래서 플러터의 UI 렌더링은 Skia를 사용하기 때문에 멀티 플랫폼에 동일한 화면으로 렌더링이 가능한 것입니다

 

데스크톱 플랫폼별 드로잉 할 때 엔진

  • iOS/MacOS의 UI 엔진은 Metal Or OpenGL
  • Windows는 ANGLE

 

ROS2+Flutter

모든 로봇에는 ROS가 사용되고 있다

ROS는 Robot Operating System으로 하나의 미들웨어로 보면 된다

 

http://docs.ros.org/

ROS는 ROS1과 ROS2로 나눠진다

2021년부터 ROS distro는 매년 세계 거북이 날(5월 23일)의 새로운 버전이 출시된다 

알파벳 순서대로 거북이 이름이 붙어진다

 

ros1/ros2 architecture

플러터 행사이기 때문에 ROS에 딥하게는 하지 않는다고 했다!

ROS1->ROS2차이는 DDS다! 정도로 알면 된다고 했다

ROS2에 DDS가 들어오면서 많은 확장성이 열렸다고 했다 

ROS를 쓰는 이유는 메시지가 규격화되어있는데 어떤 플랫폼이든 동일한 메시지를 써서 송수신을 할 수 있다는 점이다!

로봇 개발자들은 개인 것을 만들려고 하는데 플랫폼화 시켰다고 보면 된다고 한다

 

그럼 왜 ROS+Flutter 인가?

우분투 운영체제에서 ROS기반 애플리케이션은 QT를 가지고 만들고 ROS 전용 rqt라는 것도 존재하지만

2020년 3월 26일부터 QT는 라이선스 비용을 내야 한다!!

 

 

그래서 Flutter3.0부터 리눅스 데스크톱 지원 + 멀티 플랫폼인 점!

Dart Socker으로 웹 소캣 통신도 된다고 한다!

마지막으로 Flutter 오픈소스인 점에서 도전해볼 만하다고 했다!

 

온도 읽기

Ros Temperature message -> Node B 온도 읽기 -> RosBridge -> Flutter App로 해서 구현!

웹 소캣은 https://pub.dev/packages/web_socket_channel 를 사용했다고 한다 

 

 

Dart 2.17 변경점 Enum 실 프로젝트 적용기

https://medium.com/dartlang/dart-2-17-b216bfc80c5d

생산성 향상 언어 기능

Dart 2.17는 최고의 생산성 및 플랫폼 이식성이라는 핵심 테마를 기반으로 

새로운 생산성 향상 언어 기능을 제공했다고 합니다

Enhanced Enum with Mebers

https://medium.com/dartlang/dart-2-17-b216bfc80c5d

Flutter2.0부터는 extension 기능을 제공했고 이 기능을 통해 Enum에서 구현했었다

하지만 Dart 2.17부터는 Enum안에 직접 멤버 변수를 선언이 업데이트되면서, Enum안에 데이터를 직접 할당할 수 있게 되었다 

 

Super Initializer

클래스 상속 계층이 있는 경우 일부 생성자 매개 변수를 super 클래스의 생성자에게 전달해야 했다

그러려면 각 매개 변수를 생성자에 나열하고, 해당 매개 변수를 사용하여 상속 변수를 수동으로 전달하게 되었다

그러다 보니 소스의 양이 많아지게 되었다 

하지만 Dart 2.17부터는 super 매개 변수로 한방에 해결하게 되었다

 

Named Parameters Everywhere

매개변수는 항상 정해진 순서대로 해야 했고, 선택 매개변수는 항상 마지막으로 할당해야 했다

하지만 Dart 2.17 이후로는 선택 매개변수 위치를 마음대로 할당할 수 있게 되면서 코드를 직관적으로 볼 수 있게 되었다

 

생산성 도구 기능 강화

Flutter Lints 2.0

Flutter Lints : 구글이 추천하는 룰셋을 제공해줌!

매개 변수에 대한 null 검사의 오용을 방지

하위 속성에 대한 일관된 스타일 보장하기 위한 린트 포함!

  • Dart 패키지의 경우:
    dart pub upgrade —-major-versions lints
  • Flutter 패키지의 경우:
    flutter pub upgrade —-major-versions flutter_lints

Dart 코드에 대해 10개의 새로운 린트 를 선택

Flutter 코드를 위해 특별히 2개의 새로운 린트를 선택

  • null_check_on_nullable_type_parameter
  • depend_on_referenced_packages
    (패키지 버전 명시)

Secure Sockets

SecureSockets는 일반적으로 TLS 및 SSL로 보호되는 TCP 소켓을 활성화할 때 사용된다

Dart 2.17 이전에는 보안 데이터 트래픽을 검사할 방법이 없어 이러한 소켓을 디버깅하는 것이 까다로웠다

하지만 Dart 2.17부터는 KeyLog callback를 통한 Logging 기능 추가되었다!
- KSS Key Log Format으로 서버와 새 TLS 키가 교환될 때 호출

https://api.dart.dev/stable/2.17.0/dart-io/SecureSocket/connect.html

Dart Docs

가장 많이 본 상위 200페이지 주요 핵심 라이브러리 샘플 추가!

 

플랫폼 통합 및 지원 확대

  1. Dart FFI
    - 네이티브 코드 호출로 flutter 지원되는 FFI 플러그인을 쉽게 만들 수 있다
    - ABI를 지원한다 (CPU에 맞게 Long type 지원)
    - NativeFinalizer 클래스 추가
      -> Finalizable 마커를 통한 Dart와 Navtive 코드 사이의 메모리 관리 효율화
    - Windows / MacOS 응용프로그램 CodeSigning 지원
  2. RISC-V(리스크 파이브) 아키텍처 테스트 지원
    - 프로세서를 위한 새롭고 혁신적인 명령어 세트로 앞으로 멀티 플랫폼 개발에 힘!

 

Enum 실 프로젝트 적용

카테고리, EndPoint, Status Code, Status Screen

 

멀티 모듈을 활용한 플러터 클린 아키텍처

클린 아키텍처란?

  • Robert C Martin이 제안한 아키텍처
  • 클린 아키텍처는 50년 넘게 써보니 좋더라 
    -> Best Practice

왜 클린 아키텍처를 도입하면 좋은가?

  • 50년 전의 프로그래머를 오늘날 PC에 앉혀둔다면 24시간 이내에 코드를 작성할 수 있어야 한다
  • 프로그래머의 구성 요소를 정렬하고 조립하는 방법에 대한 규칙은 보편적이며 시간이 흐름에 따라 변하지 않았다
  • 제대로 된 소프트웨어를 만들면 소수의 인력으로 프로그램을 지속시킬 수 있다

보편적 규칙이 존재함, 클린 아키텍처를 통해 이 규칙을 학습

소프트웨어 아키텍처란?

  • 소프트웨어 구성요소의 관계를 표현하는 구조다

좋은 아키텍처란?

  • 필요한 시스템을 만들고 유지 보수하는데 투입되는 인력을 최소화하는 아키텍처

좋은 아키텍처가 지원해야 하는 것

  • 유스케이스 (ex: 상품관리, 장바구니)
  • 운영 (ex: 초당 100,000의 고객 처리)
  • 개발 (ex: 팀별 독립적으로 개발 가능한 컴포넌트)
  • 배포 (ex: 빌드 후 즉시 배포)

좋은 아키텍처를 만들기 위해서는?

  • 개발 효율화! 개발 비용을 높이는 요소를 제거

개발 비용을 높이는 요소는?

  • 지속적인 변경 요청
  • 소프트웨어 구조의 복잡도는 시간이 지날수록 증가
  • 복잡도가 증가할수록 변경 비용은 증가

좋은 아키텍처 목표!

좋은 아키텍처의 목표는 변경에 잘 대응할 수 있어야 하는데 시간 관점 대응과 범위 관점 대응으로 볼 수 있다고 했다

 

시간 관점 대응

  • 늦게 결정해도 되는 요소를 뒤로 미룸
    -> 플러그인 아키텍처

범위 관점 대응

  • 변경 시 영향 범위 최소화
    -> 경계선 긋기

플러그인 아키텍처

플러그인 아키텍처를 통해 늦게 결정해도 되는 요소를 미루는 것

추상적인 것은 일찍 결정해야 하고 구체적인 것은 늦게 결정해도 되는 것이다

 

추상적

비즈니스 규칙 (what)에 해당한다

ex) 저장 -> 주문 내역 저장

 

구체적

도구 (how)에 해당한다

ex) 데이터 베이스 -> mysql
      화면 -> TextField를 활용해 주문량을 입력

 

클린 아키텍처 

일찍 결정되어야 하는 것들 (비즈니스 규칙), 인터페이스 어댑터, 프레임워크&드라이버는 바깥쪽

바깥쪽은 안쪽에 있는 의존성이 있는데(모듈을 알고 있음)

안쪽(일찍 결정되어야 하는 것들)은 바깥쪽(프레임워크 등)에 의존성이 없어야 한다

 

 

클린 아키텍처에 잘못된 해석은 UI/Domain/Data 세 개로 나눈다

안드로이드 권장 레이어 아키텍처

하지만 이게 곧 클린 아키텍처는 아니다

둘은 서로 다르다!

 

클린 아키텍처 모듈 배치와 플러그인 아키텍처

플러그인 아키텍처에서 도메인은 플러터의 영향을 받지 말아야 하므로 테스트 커버리지 100%를 가져갈 수 있다

 

경계선 긋기

위의 플러그인 아키텍처에서 인프라 영역에 UI(Mobile, Web) / Data(Remote, Local) / Device(Android, iOS)를 경계를 그어버리기 

  • 경계는 소프트웨어 요소를 서로 분리
  • 경계 반대편의 요소를 알 수 없다

-> 한쪽에서 일어나는 변경이 경계 밖으로 영향을 주지 않는다

 

경계선 긋는 방법은 여러 가지가 있다 

  • 소스 수준 분리: 모든 컴포넌트가 동일한 주소 공간에서 실행, 함수 호출로 통신.(모놀리틱)
  • 바이너리 수준 분리: jar, dll, shared libray. 동일한 주소 공간에서 실행 + 함수 호출로 통신. 다른 프로세스에서 실행 + IPC 독립적인 배포 가능
  • 실행 수준 분리: 데이터 구조에만 의존. 네트워크로 통신.(MSA)

 

소스 수준 분리

앱과 / 도메인으로 나눔

앱에서 도메인으로는 의존성 방향이 있어야 하고 도메인에서 앱으로는 의존성 방향이 없어야 한다

같은 패키지에 있으면 하지만 아키텍처 오염이 발생할 수 있다

(ex: 앱에서 도메인을 불러오게 되어버림)

 

막으려면 강력한 정책, 코드 리뷰, 정적 검사 -> 사후적 대처

이때 경계를 확실하게 나눈다면?!

 

아키텍처 강건성 향상!

즉 모듈을 활용하여 클린 아키텍처 선 긋기!

앱에서는 도메인을 의존성 추가가 해야 한다

 

모듈 만들기

모듈을 만드는 방법은 프로젝트 유형 생성할 때 Package (재사용 강화를 위한 서브 컴포넌트 개발)을 선택해야 한다

하지만 안드로이드 플러터 프로젝트 유형에서 Package를 선택하면 Flutter 패키지로 생성이 된다

 

도메인은 플러터 프레임워크의 영향을 받지 말아야 한다!

그렇기 때문에 Dart 모듈로 생성해줘야 한다

command에서 직접 생성해준다

dart create -t package domain

dart create -t package domain

 

Application
├── domain
│   └── lib
│      └── src (외부 프로젝트에서 접근 불가)
│         └── data, presentation, usecase folder
│      └── domain.dart (외부 프로젝트에서 접근 가능)
└── lib
    └── main.dart
  • 공개할 파일들들을 domain.dart에 모두 export 해야 한다. 
  • 캡슐화는 잘 되어 있으나 멀티 모듈로 사용할 때는 불편하다.

그래서 캡슐화를 완화해서 멀티 모듈로 사용할 때 편리하게 패키지 구조를 변경해야 한다

Application
├── domain
│   └── lib
│      └── data (외부 프로젝트에서 접근 가능)
│      └── presentation (외부 프로젝트에서 접근 가능)
│      └── usecase (외부 프로젝트에서 접근 가능)
│      └── domain.dart (외부 프로젝트에서 접근 가능)
└── lib
    └── main.dart

 

정리

플러그인 아키텍처

중요한 결정은 나중에 한다

  • 유스케이스 지원을 위한 내부 도메인과 상세 구현을 위한 외부 인프라를 분리한다
  • 내부 도메인을 먼저 개발하고 테스트할 수 있다.
  • 외부 인프라는 나중으로 미루거나 쉽게 변경이 가능하다

 

경계선 긋기

  • 소스코드 수준에서 경계선 긋기
  • 플러터 패키지를 활용하면 침범하기 어려운 경계선을 그을 수 있다
  • 내부 도메인은 순수 dart 패키지로 독립성을 지킬 수 있다

 

플러터로 게임 개발하기

https://flame-engine.org/

 

Flame

2D game engine made on top of Flutter

flame-engine.org

Flame를 사용하면 플러터로 2D 게임 엔진을 만들 수 있다

게임 엔진을 쓰면 계산하고, 그리고, 쉬고 등 여러 관리를 Game Loop를 통해 좀 더 쉽게 할 수 있다!

 

Flutter는 모든 것이 위젯이고 Flame은 모든 것이 컴포넌트다

https://docs.flame-engine.org/1.2.0/flame/components.html

Component Lifecycle

onLoad - 그리기 전 세팅

update - 계산하고

render - 그린다

 

플러터 애니메이션 적용기

애니메이션의 필수 요소 3대장은 Animation controller, Tween, Curve다

https://docs.flutter.dev/development/ui/animations/tutorial

https://www.tutorialspoint.com/mixins-in-dart-programming

필수 요소 3대장에 대한 설명과 

컵 흔들기 Hero로 이용한 화면 전환 등 다양한 애니메이션 예제를 보여주셨다!

 

[소스]

https://github.com/larsien/UIChallenge_event_ios_mobile_app_animation_by_ronasit

 

GitHub - larsien/UIChallenge_event_ios_mobile_app_animation_by_ronasit

Contribute to larsien/UIChallenge_event_ios_mobile_app_animation_by_ronasit development by creating an account on GitHub.

github.com

 

 

Flutter & Low code backend

BaaS (Backend as a Service)

Flutter는 Native Compile, Cross Platform, Declarative UI, Null Safety, Animation을 지원한다

애플리케이션 개발에는 몇 가지 예외를 제외하면 Backend가 필수적이다

그래서 파이어 베이스나 BaaS는 백엔드 서비스에서 굉장히 자주 사용하는 것들을 모아 반복적인 기능을 서비스로 제공하게 됩니다!

(Authentication, Database, Storage, Messaging, Hositng)

BaaS는 Firebase와 AWS Amplify 등이 있다

 

Flutter 3.0&Firebase 

파이어 베이스가 플러터를 공식 지원하게 되었다

이전에는 각각 dependency를 설정하고 버전도 맞추고... 할게 많았는데

Flutter 3.0이 업데이트되면서 Firebase CLI를 사용하면 쉽게 설치할 수 있고 FlutterFire 문서의 파이어 베이스 공식 편입되었다

https://firebase.google.com/docs/projects/learn-more?hl=ko

현재 파이어 베이스는 Web, Android, iOS를 지원하고 있다

 

플러터에서 파이어 베이스 적용

1. Firebase CLI 설치

npm install -g firebase-tools

or

dart pub global active flutterfire_cli

2. Firebase를 사용하도록 앱 구성

flutterfire configure

3. 앱에서 Firebase 초기화

flutter pub add firebase_core
flutterfire configure

https://pub.dev/packages/firebase_core

프로젝트에서 초기화하는 방법은 위의 문서를 통해 진행한다

 

4. Firebase 플러그인 추가

flutter pub add PLGIN_NAME
flutterfire configure
flutter run

 

파이어 베이스에서 사용 가능한 서비스는 https://firebase.google.com/docs/flutter/setup?platform=ios 에서 확인이 가능하다

 

BaaS 문제

마이그레이션 문제

파이어 베이스에서 무료 요금을 넘어가면 ec2보다 비싸다

그래서 파이어 베이스에서 클라우드 혹은 자체 서버로 이전할 때 Data와 Storage는 import 할 수 있지만 파이어 베이스에서 제공해주던 서비스는 처음부터 다시 만들어야 한다

즉 파이어 베이스를 사용하면 사용할수록 서비스에 종속되는 문제가 발생한다

 

신뢰성

실제로 작년 파이어 베이스 서비스가 중단된 적이 있다고 한다

이처럼 파이어 베이스에 문제가 생긴다면? 직접 해결할 수 없고 복구하기까지 기다려야 한다

 

느낀 점

나는 플러터를 실무에서 사용한 경험이 없다.

나에게 있어 플러터는 내가 만들고 싶은 앱을 만들기 위한 프레임워크이자

스터디원들과 함께 학습하는 프레임워크이다

 

그래서 이번 행사에서 다양한 세션을 보면서 많은 인사이트들 얻었다

 

리눅스 데스크톱과 게임 세션을 보면서 "우와 플러터로 이런 것도 할 수 있구나! 신기 신기하다!"를 느꼈다

내가 생각한 플러터 프레임워크는 멀티 플랫폼을 제공하기 때문에 단순하게 각 플랫폼별 앱으로만 생각했었다

ROS+Flutter를 하거나 Flame으로 웹으로 게임할 수 있고 조이스틱 컴포넌트를 추가하면 모바일 앱 게임도 만들 수 있다는 점에서이다

정말 게임도 만들 수 있다니 너무 신기했다 👾

 

Dart에서 Enum을 실무에서 다양하게 쓰는 것을 보면서 "실무에서 이렇게 활용하는구나!"와 앞으로 프로젝트를 하면 나도 저렇게 적용해봐야지 하고 느꼈고, 플러터 애니메이션 효과에서는 와! 프로젝트에서 저런 식으로 활용할 수 있겠구나!! 하고 알게 되었다

멀티 모듈 클린 아키텍처에서는 클린 아키텍처에 정의에서 대해 다시 한번 생각을 정리할 수 있었고 프로젝트에서 저렇게 써봐야지 했다

 

파이어베이스 세션에서 최근 트렌드 No-Code, Low-Code와 어떤 식으로 상호작용할지 기대한다고 한다

이야기한 것처럼 기승전 CRUD다

앞으로 코드를 적게 하고 비즈니스 신경 쓰는! 그런 개발 트렌드가 되겠구나 생각했다

 

현재는 모래시계 타이머만 올라갔지만!

스터디원들과 함께 프로젝트를 진행하거나 아니면 개인 앱을 만들 때 쏙쏙 활용하고 싶다  👍

 

그리고!! 플러터 마스코트 캐릭터 너무 귀엽다

다트+플러터 너무 좋아욧 💙

 

 

 

 

'IT > 기록' 카테고리의 다른 글

Spring Boot DevTools 사용  (0) 2022.07.04
[Error] Set the spring.mongodb.embedded.version property or define your own MongodConfig bean to use embedded MongoDB  (0) 2022.06.29
[Flutter] 마스코트 귀여운 새! 이름은 무엇인가?  (0) 2022.06.27
[Ktor] Ktor는 뭘까?  (0) 2022.06.13
[알고리즘] 검색 알고리즘 기초 개념 잡아가기  (0) 2022.06.12
  • Flutter I/O Extended Korea 2022 
  •  플러터 리눅스 데스크톱 + ROS2 
  • Flutter 3.0 버전에 리눅스 앱 지원
  • Flutter architectural
  • ROS2+Flutter
  • Dart 2.17 변경점 Enum 실 프로젝트 적용기
  • 생산성 향상 언어 기능
  • 생산성 도구 기능 강화
  • 플랫폼 통합 및 지원 확대
  • Enum 실 프로젝트 적용
  • 멀티 모듈을 활용한 플러터 클린 아키텍처
  • 클린 아키텍처란?
  • 소프트웨어 아키텍처란?
  • 좋은 아키텍처란?
  • 좋은 아키텍처 목표!
  • 정리
  • 플러터로 게임 개발하기
  • 플러터 애니메이션 적용기
  • Flutter & Low code backend
  • BaaS (Backend as a Service)
  • Flutter 3.0&Firebase 
  • 플러터에서 파이어 베이스 적용
  • BaaS 문제
  • 느낀 점
'IT/기록' 카테고리의 다른 글
  • Spring Boot DevTools 사용
  • [Error] Set the spring.mongodb.embedded.version property or define your own MongodConfig bean to use embedded MongoDB
  • [Flutter] 마스코트 귀여운 새! 이름은 무엇인가?
  • [Ktor] Ktor는 뭘까?
포디 Podi
포디 Podi
기록은 복리다

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.