Connect with us

Программирование

50 вопросов и ответов для собеседования iOS-разработчиков: часть 1

iOS-разработчик Дурул Далканат собрал распространенные вопросы с собеседования iOS-разработчиков и, конечно, дал ответы на них.

Анна Гуляева

Опубликовано

/

     
     

1. Как настроить Live Rendering?

Атрибут @IBDesignable позволяет Interface Builder обновлять конкретные элементы.

2. Чем отличаются синхронная и асинхронная задача?

Синхронная: ждет, пока задача завершится. Асинхронная: завершает задачу в фоновом режиме и уведомляет вас о завершении.

3. Что такое b-деревья?

Это поисковые деревья, которые предоставляют упорядоченное хранилище ключевых значений с отличными характеристиками производительности. Каждый узел хранит отсортированный массив своих собственных элементов и другой массив для своих дочерних элементов.

4. Что такое объект NSError?

Существует три части объекта NSError: домен, код ошибки и словарь с пользовательской информацией. Домен — это строка, которая идентифицирует, к какой категории относится эта ошибка.

5. Что такое Enum?

Enum – это тип, который в основном содержит группу связанных значений.

6. Что такое ограничивающий параллелепипед?

Ограничивающий параллелепипед — это термин, используемый в геометрии; он означает наименьшую меру (площадь или объем), в пределах которой находится набор точек.

7. Почему мы не используем strong для enum в Objective-C?

Поскольку enum не являются объектами, мы не указываем здесь strong или weak.

8. Что такое @synthesize в Objective-C?

Synthesize генерирует методы getter и setter для вашего свойства.

9. Что такое @dynamic в Objective-C?

Мы используем dynamic для подклассов NSManagedObject. @dynamic сообщает компилятору, что геттер и сеттеры реализованы где-то в другом месте.

10. Почему мы используем synchronized?

Synchronized гарантирует, что только один поток может выполнять этот код в блоке в любой момент времени.

11. В чем разница strong, weak, read only и copy?

Атрибуты свойства strong, weak, assign определяют, как будет управляться память для этого свойства.

Strong означает, что в сгенерированном сеттере счетчик ссылок на присваиваемый объект будет увеличен и ссылка на него будет поддерживаться в течение жизни объекта.

Weak означает, что мы указываем на объект, но не увеличиваем счетчик ссылок. Он часто используется при создании родительских-дочерних отношений. Родитель имеет сильную ссылку на ребенка, но ребенок имеет только слабую ссылку на родителя.

Read only —  мы можем установить свойство изначально, но затем его нельзя будет изменить.

Copy означает, что мы копируем значение объекта при его создании. Также предотвращает изменение его значения.

Больше подробностей вы можете узнать здесь.

12. Что такое Dynamic Dispatch?

Dynamic Dispatch — это процесс выбора реализации полиморфной операции, которая является методом или функцией для вызова во время выполнения. Это происходит, когда мы хотим вызывать наши методы как метод объекта. Swift по умолчанию не выполняет Dynamic Dispatch.

13. Что такое покрытие кода?

Покрытие кода — это метрика, которая помогает нам измерять ценность наших юнит-тестов.

14. Что такое обработчик завершения?

Обработчики завершения очень удобны, когда наше приложение вызывает API, и нам нужно что-то сделать, когда эта задача будет выполнена: например, обновить пользовательский интерфейс, чтобы отобразить данные из вызова API. Обработчики завершения можно найти в API Apple, например, dataTaskWithRequest, и они могут быть очень полезными в вашем коде.

Обработчик завершения принимает код с тремя аргументами: (NSData?, NSURLResponse?, NSError?), который ничего не возвращает: void. Это означает завершение.

Обработчики завершения должны быть помечены @escaping, так как они выполняются после выполнения функции.

15. Как определить место юзабилити в дизайне?

Для этого нужно разбить процесс дизайна на четыре шага:

  • Думайте, как пользователь, а потом создавайте UX.
  • Помните, что пользователи — это люди, а не их демографические данные.
  • При продвижении приложения подумайте обо всех ситуациях, в которых оно будет полезно.
  • Продолжайте работать над удобством приложения даже после запуска.

Разница между UI и UX-дизайном

16. В чем разница между рамкой и границами (frame и bound)?

Границы в UIView — это прямоугольник, имеющий местоположение (x, y) и размер (ширина, высота) относительно собственной системы координат (0,0).

Рамка в UIView это прямоугольник, имеющий местоположение (x, y) и размер (высота, ширина) относительно элемента, в котором он содержится.

17. Что такое Responder Chain?

Responder Chain — это иерархия объектов, которые могут ответить на полученные события.

18. Что такое регулярные выражения?

Регулярные выражения — это специальные строки-шаблоны, которые описывают, как искать в строке.

19. Что такое перегрузка операторов?

Перегрузка операторов позволяет нам изменять взаимодействие существующих операторов с существующими типами.

20. Что такое TVMLKit?

TVMLKit — это связь между TVML, JavaScript и нативным tvOS-приложением.

21. Какие ограничения существуют у платформы tvOS?

Во-первых, tvOS не поддерживает браузеры, и поэтому вы не сможете использовать WebKit или другой веб-механизма рендеринга. Это означает, что ваше приложение совсем не сможет ссылаться на веб-браузер, включая веб-ссылки, OAuth или сайты социальных сетей.

Во-вторых, приложения tvOS не могут явно использовать локальное хранилище. При запуске продукта устройства поставляются с жестким диском либо на 32 ГБ, либо на 64 ГБ, но приложениям не разрешается напрямую сохранять файлы на устройство.

Бандл tvOS-приложения  не может превышать 4 ГБ.

22. Что такое функции?

Функции позволяют нам группировать серии утверждений, чтобы выпонить какое-либо задание. Как только функция будет создана, её можно использовать в коде снова и снова. Если вы находите повторяющиеся утверждения в коде, функция может стать средством от этого повторения.

Совет: хорошие функции принимают входные данные и возвращают выходные. Плохие функции устанавливают общие переменные и полагаются в работе на другие функции.

23. Что такое ABI?

ABI (двоичные интерфейсы приложения) важны, когда речь идет о приложениях, которые используют внешние библиотеки. Если программа создана для использования определенной библиотеки, и эта библиотека будет позже обновлена, то вам надо будет перекомпилировать это приложение (а с точки зрения конечного пользователя у вас вообще может не быть исходника). Если обновленная библиотека использует один и тот же ABI, то ваша программа не будет нуждаться в изменении.

24. Почему шаблон проектирования очень важен?

Шаблоны проектирования — это повторно используемые решения для распространенных проблем в создании приложений. Эти шаблоны созданы, чтобы помочь вам писать простой код, который можно будет использовать снова и снова. Самые распространенные шаблоны проектирования Cocoa:

  • порождающий — одиночка (Singleton);
  • структурные — декоратор (Decorator), адаптер (Adapter), фасад (Facade);
  • поведенческие — наблюдатель (Observer) и хранитель (Memento).

25. Что такое одиночка (Singleton)?

Такой шаблон проектирования гарантирует, что для данного класса существует только один экземпляр и что для этого экземпляра есть глобальная точка доступа. Обычно он использует ленивую загрузку для создания единственного экземпляра, когда это необходимо в первый раз.

Следующая статья →

Комментарии
Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Advertisement
Click to comment

You must be logged in to post a comment Login

Leave a Reply

Программирование

Применение Google Cloud Vision API в приложении для Android

Android-разработчик Леонардо Пирро рассказал об инструменте компьютерного зрения от Google и его применении в приложениях.

Анна Гуляева

Опубликовано

/

Искусственный интеллект и машинное обучение — это одни из самых популярных тем в бизнесе. Google, лидер области, разработала набор инструментов для разработчиков, которые позволят создать новый пользовательский опыт с безграничными возможностями. Сегодня мы исследуем Google Cloud Vision API и его применение в приложениях Android.

Google Cloud Vision API

Это интересный API, который позволяет разработчикам анализировать изображения и контекстуальные данные, используя самонаводящуюся модель машинного обучения в дальнейшей эволюции — все в простом REST API. Благодаря этому API, мы можем получать контекстуальную информацию о данном изображении, классифицировать изображения на категории и подкатегории, достигая глубокого уровня детализации информации.

Для примера возьмем это изображение:

Vision API удивителен, он может распознать основной субъект фото (животное), определить его вид (собака) и породу (бигль). Более того, вы можете получить дополнительные данные о траве и горах на фоне.

Давайте взглянем на все функции Google Cloud Vision API:

  • Обнаружение меток: обнаружение категорий внутри изображения (пример выше).
  • Обнаружение откровенного содержимого: обнаружение неприличного или жестокого содержимого в изображении.
  • Обнаружение популярных логотипов.
  • Обнаружение ориентиров: естественных и искусственных структур в изображении.
  • Оптическое распознавание символов: обнаружение и извлечение текста внутри изображения, API даже распознает язык текста
  • Обнаружение лица: обнаружение нескольких лиц внутри изображения, а также других атрибутов, таких как эмоциональное состояние или головные уборы.
  • Атрибуты изображения. Обнаружение общих атрибутов изображения, таких как доминирующие цвета.

В нашем примере мы используем две функции: обнаружение меток и оптическое распознавание символов. Давайте посмотрим, как интегрировать Vision API в приложение Android. Мы создадим пробный проект, который позволит пользователю выбирать изображение из галереи и получать о нем информацию.

Внедрение Google Cloud API

Чтобы использовать API, мы должны внедрить его в Google Cloud Developer Console. Вот как это сделать:

  1. Создайте проект в Google Cloud Console или используйте существующий.
  2. Включите в проекте Billing. Если это ваше первое использование Google Cloud Console, вы можете начать бесплатный пробный период использования. У вас могут попросить данные карты, но денег не спишут.
  3. Включите Google Cloud Vision API, используя эту ссылку.
  4. Откройте в боковом меню слева секцию Credentials.
  5. Выберите в меню OAuth Client ID: установите тип приложения Android, введите название приложения и отпечаток SHA1 (если у вас его нет или вы не знаете, как его сгенерировать, введите эту команду в терминале keytool -exportcert -keystore path-of-your-keystore -list -v). Затем введите имя пакета вашего приложения: оно должно совпадать с именем, указанным в файле build.gradle вашего приложения, в ключе applicationId. В моем случае —  com.lpirro.cloudvision.

Мы готовы начать, давайте приступим к кодингу.

Cloud Vision API в действии

Создайте новый проект в Android Studio и помните, что имя пакета должно совпадать с названием в проекте в Google Cloud Developer Console. Затем откройте build.gradle и добавьте зависимости Vision API.

Теперь откройте AndroidManifest.xml и добавьте необходимые разрешения для сетевых вызовов и получения информации об учетной записи, необходимой для запроса OAuth.

Сейчас мы можем создать активность, которая позволит нам выбирать изображение из галереи и вызывать сервисы Cloud Vision, чтобы получать информацию об изображении.

Файл макета нашей активности очень прост: у нас есть один ImageView, используемый для отображения выбранного изображения из галереи, два TextView для отображения результатов и одна Button, используемая для выбора изображения из галереи.

Вот файл с макетом нашей активности:

Теперь остановимся на Activity. В этом примере мы используем библиотеку Google API Client для Java, и так как мы используем OAuth request, нам нужно получить от Google токен аутентификации. Давайте определим класс, который позволит нам получить этот токен.

Примечание: для простоты мы будем использовать AsyncTask для сетевых операций, но если вы будете использовать этот API в реальном проекте, используйте библиотеку, например, Retrofit, возможно, вместе с RxJava.

Теперь у нас есть вся необходима информация, чтобы вызвать Cloud Vision API и получить результаты.

При помощи метода setType() мы определим тип функции, которую хотим использовать: в нашем случае это LABEL_DETECTION и TEXT_DETECTION. Формат изображения, переданного API, находится в Base64. Как только результаты будут получены, они передаются методу getDetectedText (), который будет форматировать строку и фильтровать информацию, после чего мы можем окончательно отобразить их в интерфейсе.

И искусственный интеллект, и машинное обучение быстро становятся основой для цифровых преобразований на ближайшие годы. С внедрением Cloud Vision API Google предлагает первоклассный инструмент для интеграции этих технологий в повседневный рабочий процесс как пользователей, так и разработчиков. Прямо сейчас, та же технология, что мы видели выше, уже является частью основных продуктов Google, таких как «Фотографии», используемых в качестве помощи для организации и классификации нашей коллекции воспоминаний. Благодаря общей доступности этих инструментов тысячи продуктов смогут интегрировать эту удивительную технологию.

Комментарии
Продолжить чтение

Новости

В Humble Book Bundle распродажа книг по функциональному программированию

Распродажа книг по мобильной разработке у Humble Book Bundle закончилась, зато началась продажа книг по функциональному программированию от O’Reilly.

Леонид Боголюбов

Опубликовано

/

За один доллар вы можете получить электронные учебники по Scala, Elixir, Erlang, Clojure и функциональному программированию.

Если вы заплатит 8 долларов или больше, то получите еще 5 книг – например, по функциональному JavaScript и Haskell.

Следующая отметка это 15 долларов и еще 5 книг, одна из которых обучает программированию на Rust, а остальные раскрывают подробности разработки на Clojure, Scala, Haskell.

Функциональное программирование — раздел дискретной математики и парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании).

Противопоставляется парадигме императивного программирования, которая описывает процесс вычислений как последовательное изменение состояний (в значении, подобном таковому в теории автоматов). При необходимости, в функциональном программировании вся совокупность последовательных состояний вычислительного процесса представляется явным образом, например, как список.

Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных и результатов других функций, и не предполагает явного хранения состояния программы. Соответственно, не предполагает оно и изменяемость этого состояния (в отличие от императивного, где одной из базовых концепций является переменная, хранящая своё значение и позволяющая менять его по мере выполнения алгоритма).

На практике отличие математической функции от понятия «функции» в императивном программировании заключается в том, что императивные функции могут опираться не только на аргументы, но и на состояние внешних по отношению к функции переменных, а также иметь побочные эффекты и менять состояние внешних переменных. Таким образом, в императивном программировании при вызове одной и той же функции с одинаковыми параметрами, но на разных этапах выполнения алгоритма, можно получить разные данные на выходе из-за влияния на функцию состояния переменных. А в функциональном языке при вызове функции с одними и теми же аргументами мы всегда получим одинаковый результат: выходные данные зависят только от входных. Это позволяет средам выполнения программ на функциональных языках кешировать результаты функций и вызывать их в порядке, не определяемом алгоритмом и распараллеливать их без каких-либо дополнительных действий со стороны программиста.

Комментарии
Продолжить чтение

Программирование

Динамическое изменение иконки приложения в iOS

Начиная с iOS 10.3 вы можете менять иконки приложения прямо из кода. В этом статья мы рассмотрим простой пример кода, который реализует эту возможность.

Анна Гуляева

Опубликовано

/

C iOS 10.3 Apple запустила для разработчиков возможность менять иконку приложения не только при заливке нового билда, но и при помощи кода. Хотя эта функция не такая гибкая, как в официальном приложении “Часы” с двигающимися стрелками, на основе неё можно создать отличную динамику. Давайте посмотрим, как это работает.

Теория

В документе Apple API стоит взглянуть на три вещи:

var supportsAlternateIcons: Bool { get }
var alternateIconName: String? { get }
func setAlternateIconName(String?, completionHandler: ((Error?) -> Void)? = nil)
  • supportsAlternateIcons — это свойство только для чтения, оно решает, может ли приложение менять иконку. Чтобы установить его как true, нам нужно добавить альтернативные иконки в файл info.plist.
  • alternateIconName — также свойство только для чтения, это название текущей иконки приложения. Оно обозначено как nil. если приложение отображает первоначальную иконку.
  • setAlternateIconName — функция, которая устанавливает отображаемую иконку. Если мы установим название на nil, приложение покажет первоначальную иконку.

Больше деталей — на официальной странице Apple API. Давайте зайдем в Xcode и начнем.

Подготовка

Сделаем три изображения с иконками: pichu.png, pikachu.png, raichu.png. Изображения бесполезно размещать в папке Assets, нужно поместить их в директорию проекта. Вот мой пример:

Затем нам нужно подготовить info.plist:

  1. Добавьте файлы иконок в Information Property List.
  2. Добавьте CFBundleAlternateIcons как словарь.
  3. Добавьте 3 вложенных словаря в CFBundleAlternateIcons: pichu, pikachu и raichu.
  4. Для каждого массива нужно определить два свойства: UIPrerenderedIcon и CFBundleIconFiles.

Больше деталей можно найти на странице Core Foundation Keys. Вот мой скриншот info.plist:

Пример кода

Предположим, у нас есть три кнопки. Нажмем на любую из них, чтобы приложение показывало соответствующую иконку.

// change app icon to "pichu"
@IBAction func pichuButtonDidTap(_ sender: UIButton) {
       changeIcon(to: "pichu")
}

// change app icon to "pikachu"
@IBAction func pikachuButtonDidTap(_ sender: UIButton) {
        changeIcon(to: "pikachu")
}

// change app icon to "raichu"
@IBAction func raichuButtonDidTap(_ sender: UIButton) {
        changeIcon(to: "raichu")
}

func changeIcon(to iconName: String) {
 // 1
        guard UIApplication.shared.supportsAlternateIcons else {
               return
 }

// 2
 UIApplication.shared.setAlternateIconName(iconName, completionHandler: { (error) in
 // 3
        if let error = error {
               print("App icon failed to change due to \(error.localizedDescription)")
        } else {
               print("App icon changed successfully")
        }
})
}

 


  1. Проверьте, поддерживает ли приложение альтернативные иконки.
  2. Измените иконку на конкретное изображение с соответствующим названием.
  3. После изменения иконки выведите сообщение с ошибкой или успешным завершением.

Запустите приложение и наслаждайтесь шоу.

Комментарии
Продолжить чтение

Обучение

«Где можно получить серфитекат?»: как мы тренировали всех школьников страны

Все школьники кодят — каждый год во всех школах страны проходит акция «Час кода». В этом году тренажер, который прошли 10 миллионов детей, разработали в Кодвардс в сотрудничестве с традиционными партнерами акции в России ZeptoLab, «Лабораторией Касперского» и Microsoft. Ниже — история Redmadrobot о том, как заинтересовать ребенка программированием, сделать интерфейс, понятный детям от 6 до 17 лет, и сервер, который выдержит нагрузки и атаки продвинутых восьмиклассников.

Redmadrobot

Опубликовано

/

Автор:

Одна из задач «Часа кода» — заинтересовать школьников и показать, что программирование и IT — не про ботаников и снеговиков в Excel. Тренажер должен быть одновременно увлекательным и похожим на рабочее пространство разработчиков. Поэтому основой тренажера стала игра, где дети могут быстро освоить базовые команды и увидеть результаты своей работы.

«Час кода» рассказывает, что программирование — это доступный инструмент и он не ограничивается только компьютерами, а используется в промышленности, медицине, энергетике. Самый простой способ показать, как это работает — дать детям возможность самим запрограммировать персонажа, – Галина Денина, руководитель проекта.

Традиционный партнер акции ZeptoLab предоставила персонажей своей игры C.A.T.S., в оригинальном сюжете которой котики собирают и бесконечно улучшают боевых роботов для победы в сражениях. Сражения нам не подходили — сражаться детей могут научить и на других уроках (физкультуре или трудах, например), а Мультиплеер долго и сложно разрабатывать, да и на полноценную работу с ним одного урока явно недостаточно — тут каждый должен программировать, а не размазывать в FIFA Гошана из параллельного.

Поэтому мы придумали новый сюжет и назначили главной героиней кошечку Элси, которая решила победить в Олимпиаде роботов. Главным преимуществом Элси в борьбе стало как раз участие ребенка — по легенде живой пилот, управляющий роботом, помогает быстрее обучить искусственный интеллект. Команде предстояло собрать необходимое оборудование, установить его на заводе и протестировать на полигоне. Для тренажера мы продумали 3 локации и 14 челленджей разного уровня сложности.

Четко в натуре ваще пацаны, – Антон, 9 лет (Дагестан).

Главной сложностью при проектировании интерфейса было то, что пользоваться им должно быть легко и интересно и первоклашкам и ученикам старших классов, при том что уровень подготовки даже в одном классе может отличаться. К тому же, в каких-то школах детям мог подсказывать учитель информатики, а где-то весь процесс они осваивали сами. Изначально дизайн для акции делался на основе решений, разработанных для актуальной на тот момент версии платформы Кодвардса, но в процессе решили опробовать новую, которую только готовили к внедрению в основной продукт. Вот так смело бета-тест апдейта мы возложили на школьников со всей страны. Ключевым отличием был переезд главных “управляющих” кнопок на игровое поле. Нам важно было удержать внимание детей и вовремя переключать их фокус с панели, где пишется код, на игровое поле. И не забыть, что мониторы в классах информатики могут быть очень разными. Сам код, написанный детьми, напоминает тот, который они, возможно, будут писать в будущем. Мы посмотрели, как выглядит интерфейс для написания кода у наших разработчиков iOS и Android, какие цвета используются в среде разработки, и перенесли в тренажер, сделав их чуть более детскими. Черный фон для кода также выбран неслучайно — так меньше нагрузка на глаза.

Поскольку дети привыкли пользоваться сенсорными гаджетами, они не всегда уверенно работают с клавиатурой и мышью. Мы решили немного упростить им задачу, и все команды вводятся с помощью мышки. Почувствовать себя максимально самостоятельными разработчиками детям помогают подсказки, которые мы оставили на каждом уровне: начиная с подсветки строки с ошибкой в коде до туториалов на ключевых позициях. Чтобы подсказки всегда были на видном месте, но при этом органично смотрелись в интерфейсе, Элси расположилась над редактором кода, где в виде чат-сообщения выдает нужную подсказку из библиотеки.

Важно, что мы не ведем пользователя через уровень, показывая, что ему делать. Мы объясняем значение каждого действия, чтобы он освоился и в дальнейшем осознанно его использовал. Так, первое, что ребенок видит на уровне, это подсказка: «Выбери, кто будет выполнять действие». Он нажимает на соответствующую кнопку, следом появляется новая подсказка: «А теперь выбери, что робот будет делать», «теперь запусти программу». По мере появления новых команд для управления объектами появляются и новые подсказки, – Александр Седнави, руководитель разработки.

Язык, на котором программируют дети в «Часе кода», содержит только объекты, методы и ключевые слова. Команды в редакторе кода набираются построчно по формуле «объект (кто/что должен делать?) — метод (что именно делать?) — аргумент (как делать?)».

Для каждого из слагаемых кода в интерфейсе есть соответствующая кнопка, связанная с другими составляющими формулы. То есть написать команду неправильно, например, указав 2 объекта или применить сразу 2 метода, нельзя. Такая модель помогает сразу заложить правильную модель поведения и избежать ошибок в будущем.

Сами мы писали бэкенд тренажера на Go и верстали фронт на JS, хотя изначально все работало на Python. Увидев, что серверу с трудом дается запланированное количество обращений в секунду, ребята за ночь переписали всё с Python на Go, и по итогам тестирования сервер держал в три раза больше запросов, чем было запланировано.

Вместе с партнерами из Microsoft и «Лаборатории Касперского» мы запустили систему, в которой входящая нагрузка распределялась между серверами равномерно, а в случае необходимости предусмотрели возможность подключения дополнительных мощностей.

Мы также предусмотрели защиту, чтобы даже самый продвинутый старшеклассник не смог взломать тренажер и сайт, а также защитили систему от DDoS-атак — подозрительные источники автоматически отключались.

За время акции через тренажер прошли 10 миллионов ребят со всей страны — и система отработала без сбоев.

Благодаря этому у человека возникает побольше ума, – Дарина (Тыва), 8 лет.

Перед запуском мы протестировали каждый из 14 уровней: сначала пользователи Кодвардса, а потом в ходе закрытого бета-тестирования тренажер до старта акции увидели несколько десятков детей в заданных возрастных категориях. После прохождения тренажера старшие школьники и родители младших заполнили анкету — и это также помогло нам устранить последние баги перед боем.

В итоге мы получили систему, которая отработала без сбоев весь период акции, и несколько сотен отзывов — некоторые из них есть в этом тексте. Конечно, научить разработке за час невозможно — и речь не только о детях. Но показать, что разработка — это интересно, в этом можно разобраться и подготовиться к будущему, в котором знание языков программирования важнее, чем английский.

Комментарии
Продолжить чтение

Постеры для разработчиков

Наша рассылка

Каждому подписавшемуся - "1 час на UI аудит": бесплатный ускоренный курс для разработчиков!

Нажимая на кнопку "Подписаться" вы даете согласие на обработку персональных данных.

Вакансии

Популярное

X
X

Спасибо!

Теперь редакторы в курсе.