다음 글은 https://www.codemag.com/Article/2111082/An-Introduction-to-.NET-MAUI의 글을 번역했습니다.

 

An Introduction to .NET MAUI

You’ve been using Xamarin for years. Steven shows how the .NET Multi-platform App UI (.NET MAUI) hasn't just kept up with everything, but how it compare...

www.codemag.com

 

Microsoft 환경에서 모바일 개발을 해본적이 있습니까? 그럼 아마도 Xamarin 을 들어봤을 겁니다. Xamarin 기술은 Xamarin 이라는 회사에 의해 2011년 구축되었으며 2016년 Microsoft 가 Xamarin 사를 인수한 이후부터 Microsoft 의 모바일개발 주력 제품이 되었습니다.

Xamarin 은 개발자가 VisualStudio 상에서 C# 코드를 사용하여 IOS, Android, UWP 용 Application 을 개발할 수 있도록 해줍니다. platform-specific 한 작업을 제외한 모든 작업을 shared codebase 에서 수행합니다. 즉, 여러분이 원하는 작업의 대부분을 단일 공유라이브러리 상에서 작성할 수 있습니다.

Xamarin.Forms 의 출현으로 shared codebase 위에 추가로 XAML 을 이용하여 User interface 를 정의할 수 있는 abstraction layer 가 제공되었습니다. 개발경험을 개선하기 위해 Microsoft 는 몇년동안 많은 도구들을 추가로 만들어 Xamarin 을 모바일 개발자를 위한 완벽한 제품으로 만들었습니다. 이런 노력의 자연스런 다음 단계로 2020 년 .NET Multi-platform App UI (.NET MAUI) 라는 형식을 소개하였습니다. 이번 Article 에서는 .NET MAUI 가 무엇인지 Xamarin.Forms 에 비교해서 무엇이 크게 변경이 되었는지에 관해 자세히 알아보겠습니다.

 

.NET MAUI 란 무엇인가?

.NET MAUI 는 현재 Xamarin.Forms 로 부터 진화했습니다. IOS, Android 와 같은 다양한 형태의 Workload 들이 이제 .NET 의 일부인 Single .NET 6 Base Class Library (BCL) 에 존재합니다. 그래서 여러분의 코드에서 사용되는 기본 platform 의 세부정보를 효과적으로 추상화합니다. 여러분의 App 이 IOS, Android, MacOS 에서 실행된다면 일관된 API 와 동작을 제공하기위해서 Common BCL 에 의존하며, Window 상에서 작동하는 App 이라면 이를 처리하는 .NET Runtime 은 CoreCLR 입니다.
이런 BCL 을 사용하면 다른 platform 에서 동일한 Sared code 를 실행할 수 있지만 User interface definition(정의)을 공유할 수는 없습니다. 그래서 UI 추상화에 따른 추가적인 Layer 의 필요성은 하나의 문제점으로 이를 .NET MAUI 가 해결할 것이며 다양한 추가적인 Desktop Scenario 로 나타납니다(branching out).
아키텍쳐관점에서 보면 여러분이 작성하는 대부분의 코드들은 Figure 1 에 보이는 다이어그램의 상위 두 layer 와 상호작용합니다. .NET MAUI layer 는 아래 Layer 들과의 통신을 처리합니다. 하지만 platform-specific 한 기능에 접근해야하는 경우 해당 layer 의 호출을 막지않습니다.

 

Figure 1 : .NET MAUI Architecture

.NET MAUI 로 바꾸는 것은 Xamarin.Forms 팀에게도 8 년이나 된 Toolkit 들을 처음부터 Rebuild 하고 그동안 주저했던 낮은 수준의 이슈들을 해결할 수있는 기회라고 할 수 있습니다. 성능과 확장을 위한 Redesigning 은 이런 노력의 필수적인 부분입니다. 전세계의 많은 회사들이 Xamarin 을 사용하고 있습니다. 그래서 현재의 Toolkit 을 빠르게 변경하는 것은 거의 불가능합니다. 예전에 Xamarin.Forms 를 이용하여 cross-platform UserInterface 를 Build 한 경험이 있다면, .NET MAUI 에 대해 공부할때 많은 유사점을 알수있습니다. 그래도 살펴볼만한 몇가지 차이점이 있습니다.

 

새로운 Handler Infrastructure

여러분이 이미 Xamarin.Forms 개발을 해본 경험이 있다면, renderer 라는 개념을 알고 있을 것입니다. renderer 는 각 platform 에 맞게 일관된 방식으로 화면에 특정 Control 을 렌더링하는 코드입니다. 개발자는 특정 platform 의 특정 타입의 control 을 지정할 수 있으며 built-in behavior 를 재정의 할 수 있는 custom renderer 를 생성할 수 있습니다. 예를 들어, Android 입력 field 의 아래밑줄을 제거하고 싶다면 모든 fielsd 에 적용되는 단일 사용자 지정 renderer(Single custom renderer) 를 작성하기만 하면 됩니다.
.NET MAUI 에서 renderer 의 개념은 더이상 사용하지 않지만, 현재 사용하는 renderer 를 .NET MAUI 로 가져오는 것은 호환성 API 를 통해 가능합니다. 앞으로 Handler 는 renderer 를 완전히 대체할 것입니다. 하지만 왜? 현재 Xamrin.Forms 구현에는 대안의 접근방식(alternative approach)으로 개발을 해야했던 몇가지 근본적인 architectural 한 문제(underlying architectural issues)가 있습니다.

renderer 와 control 은 Xamarin.Forms 내에서 아주 밀접하게 연결되어 (tightly coupled) 있어서 이상적이지 않습니다.

사용자 지정 renderer (custom renderer) 를 어셈블리수준에서 등록(register)한다는 말은 platform 이 모든 control 을 어셈블리 스캔하여 앱이 시작하는 동안 사용자지정 renderer 를 적용해야해야 하는지를 확인한다는 것을 의미합니다. 이런 동작은 상대적으로 말하면 느린 프로세스라고 할 수 있습니다. 게다가 Xamarin.Forms platform renderer 는 성능에 영향을 미치는 추가적인 view element 도 삽입합니다.

Xamarin.Forms 는 여러 platform (multiple different platform) 의 상단에 위치한 추상화 layer 입니다. 이 가상화로 인해 간혹 renderer 범위내에서 변경하는 방법으로 platform-specific 한 코드에 접근해는 것이 매우 어려울 수 있습니다. Private method 로 지정하면 여러분이 구현한 Code 가 차단(block) 될 수 있습니다. Xamarin.Forms 팀은 이 문제를 해결하기위해서 platform-specific API 와 같은 추가적인 구성을 구축했지만 이 API 사용법은 일반적으로 사용자에게 명확하지 않습니다.

사용자지정 renderer 를 생성은 그리 직관적이지 않습니다. 잘 알려져 있지 않은 base renderer type 을 상속해야하고 잘 알려져 있지 않은 method 를 재정의 해야합니다. 여러분이 만든 custom renderer 가 control 의 특정 instance 에만 적용되도록 하고 싶다면, custom type (e.g CustomButton 과 같은) 을 생성하고, renderer 를 지정해야하며 일반 Button 대신 해당 control 을 사용해야합니다. 이런 방식은 불필요한 code overhead 가 발생합니다.

이 모든 내용들이 개선해야할 좋은 이유처럼 들리지만 왜 지금 변경해야할 까요? platform 을 재구성(reshaping)할 수 있는 이런 기회와 함께 약간의 약간 골치아픈부분이었던 이런 근본적인 개념을 다시 생각(rethinking)할 수 있는 기회가 된 것입니다. renderer 하나만 놓고 봤을 때 성능, API 단순화 그리고 균질화 측면에서 이득이 큽니다.

 

기본 Infrastructure 재구성

기본 Infrastructure 재구성의 첫 단계는 control 간의 강한 연결을 제거하는 것입니다. .NET MAUI 팀은 control 들을 interface 뒤에 두고 각 component 는 interface 를 통해 상호작용하도록 구현하였습니다. 이런 방식은 IButton 과 같은 무언가를 다르게 구현하는 것을 쉽게합니다. 동시에 기본 Infrastructure 가 모든 구현사항들이 동일한 방식으로 처리하는지 확인합니다. Figure 2 는 이를 개념적 관점으로 보여줍니다.

 

Figure 2 : Control 구현에서 강한 연결(tight coupling)제거를 위한 추상화

reflection 을 이용한 assembly scan 을 방지하기 위해서 팀은 handler 등록하는 방식을 변경하기로 결정했습니다. Attribute 로 assembly level 에서 등록하는 대신 handler 는 platform 에 의해 명시적으로 등록합니다. 그리고 이제 startup 코드에서 모든 custom handler 를 등록할 수 있습니다. 이 Article 후반부에서 작업방법을 다루겠지만 그렇게하면 startup 시 발생하는 assembly scanning panalty 를 제거합니다.

native platform 깊숙히 숨겨진 것을 쉽게 접근할 수 있도록 만들기 위해 팀은 mapper dictionary 를 정의하는 접근 방식을 채택했습니다. mapper 는 native view object 에 곧바로 접근 할 수 있게 하는 특정 control 의 interface 에 정의된 속성(properties) (그리고 action) 가 있는 dictionary 입니다. 이 native view object 를 올바른 Type 으로 형변환(casting) 하면 여러분이 작성한 shared code 에서 platform-specific code 로 즉시 접근 할 수 있도록 합니다. 다음 sample 은 generic view 에 대해 mapper dictionary 를 호출하는 방법과 platform-specific code 를 이용하여 배경색을 설정하는 방법을 보여 줍니다. 또한 native view 에 접근하는 방법을 보여줍니다.

 

#if __ANDROID__

ViewHandler.ViewMapper[nameof(IView.BackgroundColor)] =
    (h, v) => (h.NativeView as AView).SetBackgroundColor(Color.Green);

var textView = label.Handler.NativeView;

#endif

 

이 sample 에서 배경색에 접근하기 위해서 generic ViewHandler 를 사용하는데 이는 각 view 가 background 속성(property)을 가지고 있기 때문입니다. 여러분이 필요한 상세수준에 따라 ButtonHandler 같은 더욱 specific한 handler 를 사용할 수도 있습니다. 그렇게하면 native button control 을 직접 노출하기 때문에 형변환할 필요가 없습니다. 이 새로운 mapper dictionary 덕분에 기존의 built-in platform-specific API 는 더이상 사용되지 않습니다. 다음은 어떻게 기존 custom renderer 를 handler 로 변경하여 현재 존재하는 overhead 를 어떻게 개선하는지 살펴보도록 하겠습니다.

 

Randerer 와 Handler 의 차이점

Xamarin.Forms renderer 의 구현은 기본적으로 native control 의 platform-specific 한 구현입니다. renderer 를 생성하려면 다음 단계를 수행합니다 : 

 

원하는 control 의 하위 class (subclass) 로 지정합니다. 이 것은 필수는 아니지만 따를만한 규칙입니다.
control 내에 필요한 public property 들을 생성합니다.
native control 생성을 담당하는 ViewRenderer 의 subclass 를 생성합니다.
control 을 customize 하기 위해 OnElementChanged 를 재정의(override) 합니다. 이 method 는control 이 화면에 생성될 때 호출됩니다.
특정 속성(property) 의 값이 변경되었을 때  대상으로 지정하려면 OnElementPropertyChanged 를 재정의합니다.
ExportRenderer assembly attribute 를 추가하여 Scan 가능하게 합니다.
여러분이 작성한 XAML 에 새로 만든 custom control 을 사용합니다.

 

.NET MAUI 를 사용하여 동일한 control 을 만드는 방법을 살펴봅시다. handler 를 생성하는 과정은 다음과 같습니다 :

 

native control 생성을 담당하는 ViewHandler class 의 subclass 를 생성합니다.
native control 을 rendering 하는 CreateNativeView method 를 재정의합니다.
property 변경에 응답(respond)할 mapper dictionary 를 생성합니다.
startup class 에 handler 를 등록(register)합니다.

 

둘 사이에 유사점이 있지만 .NET MAUI 구현이 훨씬 더 간결합니다. .NET MAUI 내부의 일부 변경으로 인해 custom renderer 를 사용할 때 함께 따라오던 많은 기술적인 사항들도정리되었습니다. handler infrastructure 의 architecture 를 Figure 3 에 요약했습니다. 이 sample 은 button 이 device 화면에 rendering 되기 위해 통과하는 layer 들을 보여줍니다.

 

Figure 3 : .NET MAUI handler Architecture

 

Handler 구현

custom handler 를 구현하기 위해서 control-specific 한 interface 부터 생성합니다. 앞에서 말했던 것 처럼 control 과 handler 사이에 느슨하게 결합(loosly coupled)하도록 할 겁니다. cross-platform control 에 대한 참조가 유지되는 것을 피하기 위해서 여러분이 생성한 control 을 위한 interface 가 필요합니다.

 

public interface IMyButton : IView
{
    public string Text { get; }
    public Color TextColor { get; }
    void Completed();
}

 

여러분이 만들 custom control 이 이 interface 를 구현하면 여러분이 만든 handler 에서 이 control 의 특정 type 을 지정할 수 있습니다.

 

public class MyButton : View, IMyButton
{
}

 

그 다음, platform-specific 한 동작(behavior) 을 원하는 각 platform 에서 위에서에 정의한 interface 를 대상으로 하는 handler 를 생성합니다. 이 sample 에서는 IOS 의 native button 구현인 Apple 의 UIButton control 을 대상(target) 으로 합니다. 

 

public partial class MyButtonHandler : ViewHandler<IMyButton, UIButton>
{
}

 

이 handler 가 ViewHandler 를 상속하고 있기때문에 CreateNativeView 를 구현해야합니다.

 

protected override UIButton CreateNativeView()
{
    return new UIButton();
}

 

기본 값을 재정의하고 객체가 생성되기전에 native control 과 상호작용하기위해 이 method를 사용하는 것이 가능합니다. 그렇게 하면 이전에 custom renderer 에서 수행했던 많은 작업들을 설정하는 것이 가능합니다. 다양한 시나리오를 처리하기위한 추가적인 method 들이 존재하지만 이 Article 에서는 다루지 않겠습니다.

 

Mapper 작업

 

이 Article 앞부분에서 언급한 Mapper 는 Xamarin.Forms 의 OnElementChanged method 를 대체하는 것으로 handler 의 속성변경을 처리하는 역할을 합니다. 그리고 변경이 발생했을 때 여러분이 작성한 custom code 와 연결할 수 있는 곳이기도 합니다. 다음은 여러분이 이전에 작성한 IMyButton 의 property mapper 입니다 :

 

public static PropertyMapper<IMyButton,
    MyButtonHandler> MyButtonMapper =
    new PropertyMapper<IMyButton, MyButtonHandler>
        (ViewHandler.ViewMapper)
{
    [nameof(ICustomEntry.Text)] = MapText,
    [nameof(ICustomEntry.TextColor)] = MapTextColor
};

 

dictionary 는 property 변경과 customize 한 behavior 를 처리하는데 사용할 수 있는 static method 에 property 를 mapping 합니다.

 

public static void MapText(MyButtonHandler handler, IMyButton button)
{
    handler.NativeView?.SetTitle(button.Text, UIControlState.Normal);
}

 

마지막으로 이 handler 가 여러분이 작성한 button 에 customize 한 behavior 를 제공할 수있도록 등록하는 작업이 남았습니다. 기억하시겠지만 .NET MAUI 는 이제 더이상 assembly scanning 을 하지 않습니다. 여러분이 startup 에 handler 를 수동으로 등록해야합니다. 다음 section 에서는 handler 를 등록하는 방법에 대해 다룹니다.

 

.NET Generic Host 채택 (Adopting)

여러분은 이미 ASP.NET Core 에서 제공되는 .NET Generic Host 에 대해서 알고 있을 겁니다. .NET Generic Host 는 App 을 구성하고 시작하는 명료한 방법을 제공합니다. 구성(configuration), 의존성 주입(dependency injection), logging 등과 같은 표준화된 방법들을 통해서 이루어집니다. object 는 일반적으로 모든것을 host 로 capsule화(encap) 하며, 일반적으로 Program class 의 Main method 에서 구성됩니다. 다른 방법으로 host 를 구성하기위한 진입점을 제공하기위해서 Startup class 를 제공할 수도 있습니다. ASP.NET Core 에서 out-of-the-box generic host 는 다음과 같습니다 : 

 

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
    public static IHostBuilder
        CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
}

 

.NET MAUI 또한 .NET Generic Host model 을 사용할 것이기 때문에 이제는 한 곳에서 여러분의 앱을 초기화하는 것이 가능합니다. 또한 폰트, 서비스 그리고 third-party library 들을 중앙(centralized location)에서 구성하는 기능을 제공합니다. CreateMauiApp method 를 포함하는 MauiProgram class 를 행성하여 이런 작업을 수행합니다. 여러분이 작성한 앱이 초기화될 때 모든 platform 은 자동으로 이 method 를 호출합니다.

 

using Microsoft.Maui;
using Microsoft.Maui.Hosting;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("ComicSans.ttf","ComicSans");
            });

        return builder.Build();
    }
}

 

MauiApp 을 Build 해서 Return 하는 것이 MauiProgram Class 가 실행해야하는 최소한의 작업입니다. UseMauiApp Method 에서 참조하고 있는 App 인 Appication class 는 Application 의  Root 객체입니다.  이 객체는 startup 이 완료되면 실행되는 window 를 정의합니다. App 은 실행되는 앱의 시작페이지를 정의하는 곳이기도 합니다.

 

using Microsoft.Maui;
using Microsoft.Maui.Controls;

public partial class App : Application
{
    public App()
    {
        InitializeComponent();
        MainPage = new MainPage();
    }
}

 

이 article 의 앞부분에서 handler 의 새로운 개념을 언급했습니다. 이 새로운 handler architecture 에 연결하려한다면 MauiProgram class 가 등록할 수 있는 장소입니다. ConfigureMauiHandler method 를 호출하고 handler collection 의 AddHandler method 를 호출하여 등록합니다.

 

using Microsoft.Maui;
using Microsoft.Maui.Hosting;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureMauiHandlers(handlers =>
        {
            handlers.AddHandler(typeof(MyEntry),typeof(MyEntryHandler));
        });

        return builder.Build();
    }
}

 

이 예제에서는 MyEntry 의 모든 Instance 에 MyEntryHandler 를 적용합니다. 그래서 이 handler 의 코드는 여러분이 작성한 모바일 앱의 MyEntry type 의 모든 object 에 대해 실행됩니다. 이 것은 여러분이 작성한 handler 로 완전히 새로운 control 을 target 으로 정하길 원할 때 적합한 해결방법입니다. 혹시 여러분이 원하는 모든 작업이 사용가능한(out-of-the-box) control 의 속성을 변경하는 것이라면, MauiProgram class 에서도 곧바로 작업할 수 있습니다. 아니면 control 이 실행되기 앞서 여러분이 실행되리라 생각되는 모든 곳에서 실행될 것입니다. (조금 이상함 ^^;) (원문 : If all you want to do is change a property on an out-of-the-box control, you can do this straight from the MauiProgram class as well, or really anywhere you know your code will run prior to the control being used.)

 

#if __ANDROID__

Microsoft.Maui.Handlers.ButtonHandler
    .ButtonMapper["MyCustomization"] = (handler, view) =>
        {
            handler.NativeView.SetBackgroundColor(Color.Green);
        };

#endif

 

이 예제는 Handler code 가 Android 플랫폼에서만 작동할 수 있도록 compiler 지시문을 사용합니다. 다른 platform 에서는 사용할 수 없는 API 를 사용하기 때문입니다. 만약 platform 별 code작업이 많다면 compiler 지시문으로 여러분의 코드를 지저분하게 하는 대신에 다른 multi-targeting 규칙(convention) 사용을 고려할 수도 있습니다. 이것은 본질적으로 여러분이 작성한  platform 별 code 로 구분하는 대신 접미사를 platform 이름으로 구분하는 platform 별 file 로 분리하는 것을 의미합니다. Project file 에서 조건문을 사용하여 특정 platform 용으로 compile 할 때 platform 별 file 들만 포함되도록 보장할 수 있습니다.

 

기존 Xamarin.Forms 사용자 지정(custom) Renderer 사용

만약 기존 Xamarin.forms App 을 .NET MAUI 로 migration 하려한다면,  여러분이 작성한 app 의 일부 기능을 처리하기 위해서 custom renderer 를 이미 작성했을 수도 있습니다. 이 custom renderer 들은 많은 수정 없이 .NET MAUI 에서 사용가능하지만 handler infrastructure 로 전환하는 것이 좋습니다. Xamarin.forms custome renderer 를 사용하려면 MauiProgram class 에 등록합니다.

 

var builder = MauiApp.CreateBuilder();

#if __ANDROID__

    builder.UseMauiApp<App>()
        .ConfigureMauiHandlers(handlers =>
        {
            handlers.AddCompatibilityRenderer(
                typeof(Microsoft.Maui.Controls.BoxView),
                typeof(MyBoxRenderer));
        });

#endif

return builder;

 

AddCompatibilityRenderer method 를 사용하여 custom renderer 를 .NET MAUI control 에 연결할 수 있습니다. platform 별로 작업할 필요가 있는데, multiple platform app 을 작성하고 있다면 각 platform 별로 renderer 를 추가해 줄 필요가 있습니다.

 

Single(단일) Project 로 Resource 이동

Xamarin.Forms 의 짜증나는 것들중 하나는 여러 project 사이에서 유사한 Resouce 를 여러번 복사해야하는 것입니다. 예를 들어서 만약 여러분의 앱에서 사용하고 싶은 특정 이미지를 가지고 있을 때, 여러분은 각각의 platform project 에 모두 포함시켜야하고 여러분 앱이 지원하고자 하는 모든 다양한 device 의 해상도에 맞게 제공하는 것이 좋습니다. font 나 app icon 도 비슷한 문제를 가지고 있습니다.

.NET MAUI 의 새 Single Project 기능은 모든 resource 들을 공유 head project 에 통합하며 지원하는 모든 platform 이 사용할 수 있습니다. .NET MAUI Build task 는 platform 별 artifact 로 compile 해 내려갈 때 resource 들이 올바른 위치에 있는지 확인합니다. Single project 접근 방법은 app manifest 수정과 NuGet package 수정같은 경험도 개선합니다. Figure 4 는 single project 가 VisualStudio 에서 어떻게 보일수 있는지 Mockup 을 보여줍니다. 동일한 Project 에 여러 공유 Logic 과 함께 공유 resource 를 포함합니다.

 

 

Figure 4 : &amp;nbsp;The new Single Project experience in Visual Studio

Figure 4 : Visual Studio 에서의 새 Single Project 경험

 

 

다른 쓸만한 Library 들은 무엇이 있나?

많은 공개적으로 유지관리되는 Library 들은 만든 사람이 .NET MAUI 에 이식해야합니다. Xamarin.Forms 형식이 없는 .NET 표준 Library 는 아무런 update 없이도 잘 작동할 것입니다. 다른 Library 들은 새로운 interface 와 type 들을 채택해야하고 .NET 호환 NuGet 으로 다시 Compile 해야합니다. 이들중 일부는 초기 alpha/beta version 의 library 로 출시를 함으로써 이미 이 process 를 시작했습니다. 만약 여러분이 과거에 Xamarin application 을 개발한 경험이 있다면 Xamarin Essentials 및/또는 Xamarin Community Toolkit 도 사용해 봤을 겁니다.

Essential 은 이제 .NET MAUI 의 일부분이 되었으며, Microsoft.Maui.Essential namespace 에 있습니다.

Xamarin.Forms 가 .NET MAUI 로 진화한 것처럼, Xamarin Community Toolkit 도 진화하고 있으며 .NET MAUI Community Toolkit 으로 알려질 것입니다. 지금처럼 완전히 open-source, community-supported library 로 되겠지만 두 Toolkit 간의 더욱 효과적인 code 공유와 engineering 노력을 결합할 수 있도록 Windows Community Toolkit 과 병합됩니다. Xamarin Community Toolkit 은 Xamarin.Forms 와 같은 공개일정에 따라 Service update 를 받습니다.

https://github.com/CommunityToolkit/Maui 에서 .NET MAUI Community Toolkit 정보를 확인하세요.

 

기존 App 을 .NET MAUI 로 전환

 

비록 Microsoft 가 기존에 개발된 app 을 .NET MAUI 로 이식하는 것을 현재는 권하지 않지만 .NET MAUI 가 release 되면 upgrade 경로를 제공하는 것이 언제나 우선시되었습니다. Xamarin.Forms 와 .NET MAUI 의 유사성때문에 migration process 는 간단할 수 있습니다. .NET Upgrade Assistant 는 .NET Framework 에서 .NET 5로 Upgrade 할 수있도록 도와주는 도구입니다. .NET Upgrade Assistant 의 상위에 있는 Extension 의 도움으로 Xamarin.Forms Project 를 .NET MAUI SDK Style 의 Project로 자동으로 migrate 하는 것이 가능하며 동시에 여러분이 작성한 코드의 일부 잘 알려진 namespace 를 변경하는 작업을 수행하는 것도 가능합니다. 그러면 .NET Upgrade Assistant 는 여러분의 코드를 자동적으로 upgrade 하고 변환하는데 필요한 단계를 제안합니다. 그리고 새로운 version 에 맞도록 특정 project property 와 attribute 를 mapping 하고 동시에 오래된 것들은 제거합니다. Figure 5 에서 보는 바와 같이 광범위한 logging 을 사용하여, 도구가 여러분의 project 를 upgrade 하기위해 취한 단계를 모두 확인할 수 있습니다. 이는 migration 동안 잠재적인 문제를 debugging 하는데 도움이 됩니다.

 

Figure 5 : &amp;nbsp;Informational output from the .NET Upgrade Assistant for .NET MAUI

Figure 5 : .NET MAUI 용 .NET Upgrade Assistant 의 정보 output

 

.NET MAUI 초기 동안에는, 몇가지 NuGet package 지원이 적절하지 않을 수 있습니다. .NET Upgrade Assistant 는 Analyzer 들과 작동하여 package 가 안전하게 제거되거나 다른 version으로 upgrade 할 수 있는지 확인합니다.

비록 여러분이 작성한 Project 를 100% upgrade 하지 못한다고 하더라도, 많은 양의 지루한 이름바꾸기와 반복해야하는 단계를 없애줍니다. 개발자로서, 여러분은 모든 종속성을 upgrade 해야하고 호환성 서비스와 renderer 들을 수동으로 등록해야합니다. Microsoft 는 가능한한 이런 노력들을 최소화하려고 노력하겠다고 했습니다. 정확한 process 에 대한 추가 문사는 release 되는 시점에 사용가능하게 될 것입니다.

https://dotnet.microsoft.com/platform/upgrade-assistant 에서 .NET Upgrade Assistant 에 대한 내용을 확인하십시오.

 

결론

 

과거에 Xamarin.Forms 로 작업했던 개발자들은 .NET MAUI 에서 친숙한 점들을 많이 발견할 수 있습니다. Infrastructure 의 아랫단 변경, 더 넓은 platform 범위(scope), 그리고 .NET 6 로의 전반적인 통합(overall unification) 또한 platform 을 처음 사용하는 사용자들에게는 매력적입니다. Single project feature 를 사용하여 더 많은 resource 와 code 를 shared library 로 중앙화하면 solution 관리를 엄청나게 단순화 합니다. handler 를 사용함으로써 추가적인 성능향상을 이룰수 있으며 노련한 Xamarin 개발자들에게 탐색할 항목을 제공합니다.

비록 .NET6 의 .NET MAUI 의 version이 매우 기대되지만, platform 의 첫번째 version 일 뿐입니다. 저는 개인적으로 추가기능이 곧 제공될 것으로 기대합니다. 그리고 전체 platform 이 open source 라는 점이 무엇보다 가장 좋습니다. 이는 여러분을 비롯한 .NET ecosystem 의 모든 사람들이 platform 을 개선하고 향상시키는데 기여할 수 있음을 의미합니다. 저는 미래가 어떻게 될지 너무나 궁금합니다.!

여러분이 .NET MAUI 를 직접 사용해보고 싶다면 다음 GitHub repository (https://dotnet.microsoft.com/platform/upgrade-assistant) 와 Microsoft Docs(https://docs.microsoft.com/dotnet/maui/) 에서 확인할 수 있습니다. 이 곳은 이미 시작하기(getting start) content 를 제공하고 있습니다.

 

위 글은 다음 글을 번역했습니다.

https://www.codemag.com/Article/2111082/An-Introduction-to-.NET-MAUI

 

An Introduction to .NET MAUI

You’ve been using Xamarin for years. Steven shows how the .NET Multi-platform App UI (.NET MAUI) hasn't just kept up with everything, but how it compare...

www.codemag.com

많은 부분 예상이 되는 오역이 있다면 알려주셨으면 합니다. ^^

 

행복한 고수되셔요...

 woojja ))*

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

반응형
LIST
Posted by woojja

댓글을 달아 주세요

import tensorflow as tf
node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0, tf.float32)
node3 = tf.add(node1, node2)
print("node1 : ", node1, "node2 : ", node2)
print("node3 : ", node3)
node1 :  tf.Tensor(3.0, shape=(), dtype=float32) node2 :  tf.Tensor(4.0, shape=(), dtype=float32)
node3 :  tf.Tensor(7.0, shape=(), dtype=float32)
#tfSession =  tf.session()
#print("tfSession.run(node1, node2) : ", tfSession.run(node1, node2))
#pring("tfSession.run(node3) : ", tfSession.run(node3))

tf.print("node1 : ", node1, ", node2 : ", node2)
tf.print("node3 : ", node3)
node1 :  3 , node2 :  4
node3 :  7
#a = tf.placeholder(tf.float32)
#b = tf.placeholder(tf.float32)
#add_node = a + b

@tf.function
def add_node(a, b):
    return a+b

a = tf.constant(4, tf.float32)
b = tf.constant(3, tf.float32)
print("add_node : ", add_node(a, b))
add_node : tf.Tensor(7.0, shape=(), dtype=float32)

# tf.add: 덧셈
print("tf.add : ", tf.add(2,3))

# tf.subtract: 뺄셈
print("tf.subtract : ", tf.subtract(2,3))

# tf.multiply: 곱셈
print("tf.multiply : ", tf.multiply(2,3))

# tf.divide: 나눗셈
print("tf.divide : ", tf.divide(2,3))

# tf.pow: n-제곱
print("tf.pow : ", tf.pow(2,3))

# tf.negative: 음수 부호
print("tf.negative : ", tf.negative(2))

# tf.abs: 절대값
print("tf.abs : ", tf.abs(-2))

# tf.sign: 부호
print("tf.sign : ", tf.sign(-2))

# tf.round: 반올림
print("tf.round : ", tf.round(1.2))

# tf.math.ceil: 올림
print("tf.ceil : ", tf.math.ceil(1.2))

# tf.floor: 내림
print("tf.floor : ", tf.floor(1.2))

# tf.math.square: 제곱
print("tf.math.square : ", tf.math.square(-2))

# tf.math.sqrt: 제곱근
A = tf.constant(4, tf.float32)
print("tf.math.sqrt : ", tf.math.sqrt(A))

# tf.maximum: 두 텐서의 각 원소에서 최댓값만 반환.
print("tf.maximum : ", tf.maximum(2, 3))

# tf.minimum: 두 텐서의 각 원소에서 최솟값만 반환.
print("tf.minimum : ", tf.minimum(2, 3))

# tf.cumsum: 누적합
x = tf.constant([2, 4, 6, 8])
print("tf.cumsum : ", tf.cumsum(x))

# tf.math.cumprod: 누적곱
x = tf.constant([2, 4, 6, 8])
print("tf.math.cumprod : ", tf.math.cumprod(x))
tf.add :  tf.Tensor(5, shape=(), dtype=int32)
tf.subtract :  tf.Tensor(-1, shape=(), dtype=int32)
tf.multiply :  tf.Tensor(6, shape=(), dtype=int32)
tf.divide :  tf.Tensor(0.6666666666666666, shape=(), dtype=float64)
tf.pow :  tf.Tensor(8, shape=(), dtype=int32)
tf.negative :  tf.Tensor(-2, shape=(), dtype=int32)
tf.abs :  tf.Tensor(2, shape=(), dtype=int32)
tf.sign :  tf.Tensor(-1, shape=(), dtype=int32)
tf.round :  tf.Tensor(1.0, shape=(), dtype=float32)
tf.ceil :  tf.Tensor(2.0, shape=(), dtype=float32)
tf.floor :  tf.Tensor(1.0, shape=(), dtype=float32)
tf.math.square :  tf.Tensor(4, shape=(), dtype=int32)
tf.math.sqrt :  tf.Tensor(2.0, shape=(), dtype=float32)
tf.maximum :  tf.Tensor(3, shape=(), dtype=int32)
tf.minimum :  tf.Tensor(2, shape=(), dtype=int32)
tf.cumsum :  tf.Tensor([ 2  6 12 20], shape=(4,), dtype=int32)
tf.math.cumprod :  tf.Tensor([  2   8  48 384], shape=(4,), dtype=int32)

 

 

be the happy Gosu.

woojja ))*

\\\\\\\\\\\\\\\\\\

 

PS) I wrote this in markdown, but.. ^^;

반응형
LIST

'ETC > Tensorflow' 카테고리의 다른 글

[Tensorflow] Session and PlaceHolder in Tensorflow 2.0  (0) 2021.11.08
Posted by woojja

댓글을 달아 주세요

1. Selection Sort

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <limits.h>

#define SIZE 1000

int a[SIZE];

int swap(int *a, int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main(void) {
	int n, min, index;
	scanf("%d", &n);

	for (int i = 0; i < n; i++) scanf("%d", &a[i]);

	for (int i = 0; i < n; i++) {
		min = INT_MAX;
		for (int j = i; j < n; j++) {
			if (min > a[j]) {
				min = a[j];
				index = j;
			}
		}
		swap(&a[i], &a[index]);
	}
	for (int i = 0; i < n; i++) {
		printf("%d", a[i]);
	}
	system("pause");
	return 0;
}

 

2. Insertion Sort

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <limits.h>

#define SIZE 1000

int a[SIZE];

int swap(int *a, int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main(void) {
	int n;
	scanf("%d", &n);

	for (int i = 0; i < n; i++) scanf("%d", &a[i]);

	for (int i = 0; i < n - 1; i++) {
		int j = i;
		while (j >= 0 && a[j] > a[j + 1]) {
			swap(&a[j], &a[j + 1]);
			j--;
		}
	}


	for (int i = 0; i < n; i++) {
		printf("%d", a[i]);
	}
	system("pause");
	return 0;
}

 

be the happy Gosu.

woojja ))*

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

반응형
LIST

'ETC > C, C++' 카테고리의 다른 글

[C, C++] Selection Sort, Insertion Sort  (0) 2021.11.04
[C, C++] Queue in Array, Linked List  (0) 2021.10.20
[C, C++] Stack in Array, Lined List  (0) 2021.10.20
[C, C++] Sorted Doubly Linked List  (0) 2021.10.20
Posted by woojja

댓글을 달아 주세요

python -V

Anaconda 에 Tensorflow 를 설치하는 방법입니다.

Jupyter notebook 을 사용중 Tensorflow 가 설치되지 않았다는 오류메세지가 나타난다면 Tensorflow 를 설치해야합니다.

일단 Anaconda 의 Prompt 를 실행시킵니다.

Select Anaconda Prompt.

python -V

를 입력하여 python 의 버전을 확인합니다.

그리고 일단

pip install --upgrade pip

를 입력하여 pip 를 upgrade 해줍니다.

## conda create -n tensorflow pip python={python 버전}

conda create -n tensorflow pip python=3.8

조금전에 확인한 python 버전과 함께 위 명령을 입력하여 Anaconda 에 Tensorflow 만의 가상의 사용공간을 생성해줍니다.

conda activate tensorflow

생성한 가상공간을 활성화 시킵니다.

 

이제 Tensorflow 를 설치합니다.

# CPU Version 으로 설치하려면
pip install --ignore-installed --upgrade tensorflow-cpu

# GPU Version 으로 설치하려면
pip install --ignore-installed --upgrade tensorflow-gpu


# CPU / GPU stable release version (CPU/GPU 통합버전)으로 설치하려면
pip install tensorflow

# 특정 version 으로 설치하려면
pip install tensorflow=-1.92

# unstable preview version (Preview version)으로 설치하려면
pip install tf-nightly

사용하는 network 환경에 방화벽이 설치되어 있는 경우 다음과 같은 오류가 발생한다.

connection error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

이런 경우에는

# 다음 명령문을 conda install 전에 입력한다.
conda config --set ssl_verify false
conda install pip tensorflow

또는 

pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org tensorflow

를 실행한다.

다음과 같은 오류가 발생한 경우라면

ERROR: Could not install packages due to an OSError: [WinError 5] 액세스가 거부되었습니다:

Anaconda prompt 를 "관리자 권한으로 실행" 으로 실행하여 진행한다.

설치 후 prompt 에서 tensorflow version 을 확인한다.

# python 실행
python

# Tensorflow version 확인
import tensorflow as tf
# _ _ 두번 입력주의
tf.__version__

# python 종료
exit()

 

행복한 고수되세요.

woojja ))*

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

반응형
LIST
Posted by woojja

댓글을 달아 주세요

1. Queue in Array

#include <stdio.h>
#include <stdlib.h>

#define SIZE 10000
#define INF		99999

int queue[SIZE];
int front = 0;
int rear = 0;

void push(int data) {
	if (rear >= SIZE - 1) {
		printf("Queue Overflow !!");
		return;
	}
	queue[rear++] = data;
}

int pop() {
	if (front == rear) {
		printf("Queue Underflow !!");
		return -INF;
	}

	return queue[front++];
}

void show() {
	printf("Front Of Queue \n");
	for (int i = front; i < rear; i++) {
		printf("%d \n", queue[i]);
	}
	printf("Rear Of Queue \n");
}

int main() {
	push(2);
	push(1);
	push(5);
	push(7);
	push(6);
	pop();
	push(8);
	pop();
	push(3);
	show();
	system("pause");
}

 

2. Queue in LinkedList

#include <stdio.h>
#include <stdlib.h>

#define INF		99999

typedef struct {
	int data;
	struct Node *next;
}Node;

typedef struct {
	struct Node *front;
	struct Node *rear;
	int count;
} Queue;

void push(Queue *queue, int data) {
	Node *node = (Node*)malloc(sizeof(Node));
	node->data = data;
	node->next = NULL;

	if (queue->count == 0) {
		queue->front = node;
	}
	else {		  
		Node *rearNode = (Node*)malloc(sizeof(Node));
		rearNode = queue->rear;
		rearNode->next = node;
	}			  
	queue->rear = node;	
	queue->count++;
}

int pop(Queue *queue) {
	if (queue->count == 0) {
		printf("Queue underflow !! \n");
		return -INF;
	}

	Node *node = (Node*)malloc(sizeof(Node));
	node = queue->front;
	int data = node->data;
	queue->front = node->next;
	free(node);

	queue->count--;
	return data;
}

void show(Queue *queue) {
	if (queue->count == 0) {
		printf("No data in Queue !! \n");
		return -INF;
	}

	Node* cur = queue->front;

	printf("Front Of Queue \n");
	while (cur != NULL) {
		printf("%d \n", cur->data);
		cur = cur->next;
	}
	printf("Rear Of Queue \n");
}

int main() {
	Queue queue;
	queue.count = 0;
	queue.front = NULL;
	queue.rear = NULL;

	push(&queue, 2);
	push(&queue, 1);
	push(&queue, 5);
	push(&queue, 7);
	push(&queue, 6);
	pop(&queue);
	push(&queue, 8);
	pop(&queue);
	push(&queue, 3);
	show(&queue);
	system("pause");
	return 0;
}

 

be the happy Gosu.

woojja ))*

반응형
LIST

'ETC > C, C++' 카테고리의 다른 글

[C, C++] Selection Sort, Insertion Sort  (0) 2021.11.04
[C, C++] Queue in Array, Linked List  (0) 2021.10.20
[C, C++] Stack in Array, Lined List  (0) 2021.10.20
[C, C++] Sorted Doubly Linked List  (0) 2021.10.20
Posted by woojja
TAG C, Queue

댓글을 달아 주세요

반응형