Еще на конференции Build 2014 майкрософтовцы рассказали о новом типе проектов — Universal App. Естественно, на прошедшем DevCon 2014 я постарался разузнать побольше об этом типе проектов, и как он может помочь при разработке под iOS/Android.
Microsoft традиционно акцентирует внимание на том, что UniversalApp поможет повторно использовать кода между WinPhone и WinRT приложениями, не слишком афишируя то, что начиная с Xamarin 3.0 этот подход сработает и для iOS и Android приложений.
Давайте попробуем и посмотрим, что же у нас получится :)
Проблема
Задача использования общего кода на разных платформах появилась сразу, как только эти самые разные платформы появились :) Широкое распространение кроссплатформенных проектов произошло уже пару-тройку лет назад, почти одновременно с WinPhone/Xamarin iOS/Android.
Самым старым способом повторного использования кода между платформами всегда был File Linking. Это способ, когда вы создаете под каждую платформу отдельный проект. Файлы с общим кодом при этом просто включаются во все требуемые проекты, позволяя их таким образом повторно использовать. Выглядит это в итоге как-то так:
Обратите внимание на количество проектов (по 2 на каждую платформу, всего — 6) и абсолютно идентичную структуру проекта Core (который на каждой платформе включает один и тот же файл — MainViewModel).
Недостатки этого подхода в том, что IDE не догадывается, о повторном использовании и не считает, что проекты на разных платформах хоть как-то связаны. Из-за этого затрудняется рефакторинг — при переименовании классов/методов автоматические исправления происходят только в рамках одной платформы.
Также приходится не забывать о добавлении/удалении файлов во все «платформо-зависимые» проекты, либо использовать утилиты для синхронизации .csproj файлов (например, Project Linker, хотя от его работы у меня остались негативные впечатления, и мы в итоге от его использования отказались).
Решением этих проблем видится специальный тип проекта — Portable Class Library. Действительно, в PCL один проект на все платформы, всё отлично с рефакторингом и разработкой, но вот незадача — PCL включают только те возможности .NET Framework, которые поддерживаются всеми платформами. К сожалению, набор фичей, поддерживаемых всеми платформами ограничен (например, классические HttpWebRequest использовать не получится). И при портировании настольных приложений на мобильные платформы это может стать реальной проблемой.
Решение
Майкрософт решили совместить плюсы File Linking и Portable Class Library в новом типе проекта — Universal App. В этом случае у нас как обычно остается по одному платформо-специфичному проекту, плюс появляется общий проект, очень похожий на PCL:
Сравнивая с примером выше видно, что у нас осталось всего 4 проекта — по одному платформо-зависимому (содержит UI) и один — проект с общим кодом, в котором как и раньше находится MainViewModel (плюс — приятным бонусом — можно использовать и общие xaml-файлы между WinPhone и WinRT/WPF проектами).
От PCL же существенное отличие в том, что в нем доступны все платформо-зависимые вещи и, что самое замечательное, доступны директивы условной компиляции (#if).
«Под капотом» работа UniversalApp полностью аналогична самому старому методу — линковке файлов, отсюда и все её плюсы (#ifdef и использование любых возможностей, поддерживаемых каждой из платформ). Но реализована эта линковка полностью автоматически, и всю работу за нас делает MsBuild, так что можно забыть о ручной синхронизации проектов. К сожалению, подключать к одному Shared проекту другие Shared проекты невозможно, и для достаточно больших приложений это может стать существенным ограничением.
Ну и поскольку реализован механизм UniversalApp через линковку всех файлов к основному UI-проекту, то собрать SharedProject в виде отдельной .dll тоже не получится, что первое время приводит к определенному когнитивному диссонансу :)
Подытоживая плюсы и минусы PCL и UniversalApp (о старой линковке файлов можно, наверное, уже забыть :))
PCL:
+ легкость повторного использования
— доступны не все возможности .NET Framework (принцип «наибольшего общего делителя»).
UniversalApp:
+ все возможности .NET Framework
+ возможность включения платформо-зависимого кода (с использованием условной компиляции #if)
— невозможность повторного использования в виде .dll (только подключение как source code)
— невозможность построения «иерархий» Shared-проектов (только один Shared-проект в Solution)
В итоге для меня предпочтительным способом повторного использования кода остается Portable Class Library (особенно с учетом поддержки платформо-зависимых плагинов на базе PCL во фреймворке MvvmCross), за счет легкости переноса кода в виде dll между разными solution’ами. Однако игнорировать возможности Universal App однозначно не стоит, и Shared Project могут быть очень полезны для кода, тесно связанного с предметной областью конкретного проекта и его бизнес-логикой, если она завязана на платформо-специфичные сервисы. Это объясняется как минимум простотой и понятностью #if-def’ов в небольших дозах (усилия на реализацию аналогичных вещей через интерфейсы и инъекцию зависимостей подчас на порядок выше).
P.S. Для использования Universal App не забываем установить VS 2013 Update 2 (ну и, конечно, Xamarin 3.0)