В дополнение к основным библиотекам базовых классов, входящим в состав Mono, Xamarin.iOS поставляется с привязками для различных API iOS, что позволяет разработчикам создавать нативные iOS-приложения с помощью Mono.
В основе Xamarin.iOS лежит механизм взаимодействия, который связывает C# с Objective-C, и привязки для API на базе iOS C, таких как CoreGraphics и OpenGL ES.
Низкоуровневая среда выполнения для взаимодействия с кодом Objective-C находится в MonoTouch.ObjCRuntime. Кроме того, предусмотрены привязки для Foundation, CoreFoundation и UIKit.
Принципы проектирования
В этом разделе подробно описаны некоторые принципы проектирования привязок для Xamarin.iOS (они также применимы к Xamarin.Mac, привязкам Mono для Objective-C на macOS):
- Следуйте рекомендациям по проектированию Framework
- Разрешите разработчикам создавать подклассы классов Objective-C:
- используйте производные от существующего класса
- вызывайте базовый конструктор для создания цепочки
- переопределяйте методы с помощью системы переопределения C#.
- подклассификация должна работать со стандартными конструкциями C#
- Не давайте разработчикам доступ к селекторам Objective-C
- Обеспечьте механизм для вызова произвольных библиотек Objective-C
- Сделайте обычные задачи Objective-C простыми, а сложные задачи Objective-C – возможными
- Выражайте свойства Objective-C как свойства C#
- Предоставляйте строго типизированный API:
- Повышайте безопасность типов
- Минимизируйте ошибки во время выполнения
- Получите IDE IntelliSense для возвращаемых типов
- Позволяйте использовать всплывающую документацию IDE
- Поощряйте изучение API в IDE:
Например, вместо того, чтобы раскрывать слабо типизированный массив, как это сделано здесь:
NSArray *getViews
Предоставьте строгий тип, например, так:
NSView [] Views { get; set; }
Использование строгих типов дает Visual Studio для Mac возможность автозаполнения при просмотре API, делает все операции System.Array доступными для возвращаемого значения и позволяет возвращаемому значению участвовать в LINQ.
- Нативные типы C#:
- NSString заменяется на string
- Превратите параметры int и uint, которые должны были быть перечислениями, в перечисления C# и C# с атрибутами [Flags].
- Вместо нейтральных по типу объектов NSArray, раскрывайте массивы как строго типизированные.
- Для событий и уведомлений предоставьте пользователям выбор между:
- строго типизированной версией по умолчанию
- слабо типизированной версией для расширенных случаев использования.
- Поддержка паттерна делегата Objective-C:
- система событий C#
- предоставление делегатов C# (лямбда-выражений, анонимные методы и System.Delegate) в API Objective-C в виде блоков
Сборки
Xamarin.iOS включает в себя множество сборок, которые составляют профиль Xamarin.iOS.
Основные пространства имен
ObjCRuntime
Пространство имен ObjCRuntime позволяет разработчикам соединить миры между C# и Objective-C. Это новое связывание, разработанное специально для iOS и основанное на опыте Cocoa# и Gtk#.
Foundation
Пространство имен Foundation предоставляет основные типы данных, разработанные для взаимодействия с фреймворком Objective-C Foundation, который является частью iOS, и является основой для объектно-ориентированного программирования на Objective-C.
Xamarin.iOS отражает в C# иерархию классов из Objective-C. Например, базовый класс Objective-C NSObject можно использовать из C# через Foundation.NSObject.
Хотя пространство имен Foundation предоставляет привязки для базовых типов Objective-C Foundation, в некоторых случаях мы отобразили базовые типы на типы .NET. Например:
- вместо того, чтобы иметь дело с NSString и NSArray, среда выполнения раскрывает их как строки C# и строго типизированные массивы во всем API.
- здесь представлены различные вспомогательные API, позволяющие разработчикам связывать сторонние API Objective-C, другие API iOS или API, которые в настоящее время не связаны с Xamarin.iOS.
NSObject
Тип NSObject является основой для всех привязок Objective-C. Типы Xamarin.iOS отражают два класса типов из API CocoaTouch iOS: типы C (обычно называемые типами CoreFoundation) и типы Objective-C (которые все происходят от класса NSObject).
Для каждого типа, который является зеркалом неуправляемого типа, можно получить нативный объект через свойство Handle.
Хотя Mono обеспечит сборку мусора для всех ваших объектов, Foundation.NSObject реализует интерфейс System.IDisposable. Вы можете освободить ресурсы любого данного NSObject, не дожидаясь, пока сработает сборщик мусора. Освобождение ресурсов важно при использовании тяжелых NSObjects, например, UIImages, которые могут содержать указатели на большие блоки данных.
Если ваш тип должен выполнять детерминированную финализацию, переопределите метод NSObject.Dispose(bool) Параметр Dispose - это «bool disposing», и, если он имеет значение true, это означает, что ваш метод Dispose вызывается, потому что пользователь явно вызвал Dispose () на объекте. Значение false означает, что ваш метод Dispose(bool disposing) вызывается из финализатора в потоке финализатора.
Категории
Начиная с Xamarin.iOS 8.10, можно создавать категории Objective-C из C#.
Это делается с помощью атрибута Category, указывая в качестве аргумента атрибута тип для расширения. В следующем примере расширяется NSString.
[Category (typeof (NSString))]
Каждый метод категории использует обычный механизм экспорта методов в Objective-C с помощью атрибута Export:
[Export ("today")] public static string Today () { return "Today"; }
Все управляемые методы расширения должны быть статическими, но можно создавать методы экземпляра Objective-C, используя стандартный синтаксис для методов расширения в C#:
[Export ("toUpper")] public static string ToUpper (this NSString self) { return self.ToString ().ToUpper (); }
А первым аргументом метода расширения будет экземпляр, на котором был вызван метод.
Полный пример:
[Category (typeof (NSString))] public static class MyStringCategory { [Export ("toUpper")] static string ToUpper (this NSString self) { return self.ToString ().ToUpper (); } }
В этом примере будет добавлен в класс NSString собственный метод экземпляра toUpper, который можно вызывать из Objective-C.
[Category (typeof (UIViewController))] public static class MyViewControllerCategory { [Export ("shouldAutoRotate")] static bool GlobalRotate () { return true; } }
Один из сценариев, где это полезно, является добавление метода к целому набору классов в вашей кодовой базе. Например, этот сценарий заставит все экземпляры UIViewController сообщать, что они могут вращаться:
[Category (typeof (UINavigationController))] class Rotation_IOS6 { [Export ("shouldAutorotate:")] static bool ShouldAutoRotate (this UINavigationController self) { return true; } }
PreserveAttribute
PreserveAttribute – это пользовательский атрибут, который используется для указания mtouch - инструменту развертывания Xamarin.iOS – сохранить тип или член типа на этапе, когда приложение обрабатывается для уменьшения его размера.
Каждый член, не связанный статически приложением, подлежит удалению. Следовательно, этот атрибут используется для пометки членов, на которые нет статических ссылок, но которые все еще нужны вашему приложению.
Например, если вы создаете типы динамически, вы можете захотеть сохранить конструктор по умолчанию ваших типов. Если вы используете XML-сериализацию, вам понадобится сохранить свойства ваших типов.
Вы можете применить этот атрибут к каждому члену типа или к самому типу. Если вы хотите сохранить весь тип, вы можете использовать синтаксис [Preserve (AllMembers = true)] для типа.
UIKit
Пространство имен UIKit содержит отображение один к одному всех компонентов пользовательского интерфейса, составляющих CocoaTouch, в виде классов C#. API был изменен, чтобы следовать соглашениям, используемым в языке C#. Для общих операций предусмотрены делегаты C#.
OpenGLES
Для OpenGLES мы распространяем модифицированную версию OpenTK API, объектно-ориентированную привязку к OpenGL, которая была изменена для использования типов и структур данных CoreGraphics, и раскрывает только ту функциональность, которая доступна на iOS.
- Функциональность OpenGLES 1.1 доступна через тип ES11.GL.
- Функциональность OpenGLES 2.0 доступна через тип ES20.GL.
- Функциональность OpenGLES 3.0 доступна через тип ES30.GL.
Разработка привязки
Xamarin.iOS – это не просто привязка к базовой платформе Objective-C. Он расширяет систему типов .NET и систему диспетчеризации для лучшего сочетания C# и Objective-C.
Подобно тому, как P/Invoke является полезным инструментом для вызова родных библиотек в Windows и Linux, или как поддержка IJW может использоваться для COM interop в Windows, Xamarin.iOS расширяет среду выполнения для поддержки привязки объектов C# к объектам Objective-C.
Обсуждение в следующих нескольких разделах не является необходимым для пользователей, создающих приложения Xamarin.iOS, но поможет разработчикам понять, как все делается, и поможет им при создании более сложных приложений.
Типы
Там, где это уместно, выявляются типы C# вместо низкоуровневых типов Foundation для C#. Это означает, что API использует тип C# "string" вместо NSString, а также строго типизированные массивы C# вместо раскрытия NSArray.
В целом, в дизайне Xamarin.iOS и Xamarin.Mac базовый объект NSArray не раскрывается. Вместо этого среда выполнения автоматически преобразует NSArray в сильно типизированные массивы некоторого класса NSObject. Таким образом, в Xamarin.iOS слабо типизированный метод, например, GetViews, не возвращает NSArray:
NSArray GetViews ();
Вместо этого привязка раскрывает строго типизированное возвращаемое значение, например, так:
UIView [] GetViews ();
Есть несколько методов, раскрытых в NSArray, для сложных случаев, когда вам понадобится применить NSArray напрямую, но их использование не рекомендуется в привязке API.
Кроме того, в классическом API вместо раскрытия CGRect, CGPoint и CGSize из CoreGraphics API мы заменили их на реализации System.Drawing RectangleF, PointF и SizeF, так как они помогут разработчикам сохранить существующий OpenGL код, использующий OpenTK. При использовании нового 64-битного унифицированного API следует использовать CoreGraphics API.
Наследование
Дизайн API Xamarin.iOS позволяет разработчикам расширять нативные типы Objective-C так же, как они расширяют типы C#, используя ключевое слово "override" для производного класса и цепочку до базовой реализации с помощью ключевого слова "base" C#.
Такой дизайн позволяет разработчикам не сталкиваться с селекторами Objective-C в процессе разработки, поскольку вся система Objective-C уже обернута внутри библиотек Xamarin.iOS.
Типы и постройка интерфейсов (Interface Builder)
При создании классов .NET, которые являются экземплярами типов, созданных с помощью Interface Builder, необходимо предоставить конструктор, принимающий один параметр IntPtr. Это необходимо для связывания экземпляра управляемого объекта с неуправляемым объектом. Код состоит из одной строки, как показано ниже:
public partial class void MyView : UIView { // This is the constructor that you need to add. public MyView (IntPtr handle) : base (handle) {} }
Делегаты
В каждом языке программирования Objective-C и C# имеют разное значение.
В Objective-C и документации, которую вы найдете в Интернете по CocoaTouch, делегат – это, как правило, экземпляр класса, который отвечает на набор методов. Это похоже на интерфейс C#, с той лишь разницей, что методы не всегда являются обязательными.
Эти делегаты играют важную роль в UIKit и других API CocoaTouch. Они используются для выполнения различных задач:
- Для предоставления уведомлений вашему коду (аналогично доставке событий в C# или Gtk+).
- Для реализации моделей для элементов управления визуализацией данных.
- Для управления поведением элемента управления.
Шаблон программирования был разработан для минимизации создания производных классов для изменения поведения элемента управления. Это решение схоже по духу с тем, что делали другие наборы инструментов GUI на протяжении многих лет: сигналы Gtk, слоты Qt, события Winforms, события WPF/Silverlight и так далее. Чтобы не иметь сотни интерфейсов (по одному на каждое действие) и не требовать от разработчиков реализации слишком большого количества методов, которые им не нужны, Objective-C поддерживает необязательные определения методов. Это отличается от интерфейсов C#, которые требуют реализации всех методов.
В классах Objective-C вы увидите, что классы, использующие этот шаблон программирования, открывают свойство, называемое делегатом, который должен реализовать обязательные части интерфейса и ноль, или более, необязательных частей.
В Xamarin.iOS предлагается три взаимоисключающих механизма привязки к этим делегатам:
- через события;
- строго типизированный через свойство Delegate;
- слабо типизированный через свойство WeakDelegate.
Например, рассмотрим класс UIWebView. Он диспетчеризирует экземпляр UIWebViewDelegate, который назначается свойству delegate.
Через события
Для многих типов Xamarin.iOS автоматически создаст соответствующий делегат, который будет перенаправлять вызовы UIWebViewDelegate на события C#. Для UIWebView:
- Метод webViewDidStartLoad сопоставляется с событием UIWebView.LoadStarted.
- Метод webViewDidFinishLoad отображается на событие UIWebView.LoadFinished.
- Метод webView:didFailLoadWithError сопоставлен с событием UIWebView.LoadError.
Например, эта простая программа записывает время начала и окончания загрузки веб-представления:
DateTime startTime, endTime; var web = new UIWebView (new CGRect (0, 0, 200, 200)); web.LoadStarted += (o, e) => startTime = DateTime.Now; web.LoadFinished += (o, e) => endTime = DateTime.Now;
Через свойства
События полезны, когда у события может быть более одного подписчика. Кроме того, события ограничены случаями, когда код не возвращает значение.
Для случаев, когда ожидается, что код вернет значение, мы выбрали свойства. Это означает, что в определенный момент времени в объекте может быть установлен только один метод.
Например, вы можете использовать этот механизм для отмены клавиатуры на экране в обработчике для UITextField:
void SetupTextField (UITextField tf) { tf.ShouldReturn = delegate (textfield) { textfield.ResignFirstResponder (); return true; } }
Свойство ShouldReturn текстового поля UITextField в данном случае принимает в качестве аргумента делегат, который возвращает значение bool и определяет, должно ли текстовое поле что-то делать с нажатой кнопкой Return. В нашем методе мы возвращаем вызывающему значение true, но при этом убираем клавиатуру с экрана (это происходит, когда текстовое поле вызывает ResignFirstResponder).
Строгая типизация через свойство Delegate
Если вы предпочитаете не использовать события, вы можете создать свой собственный подкласс UIWebViewDelegate и назначить его свойству UIWebView.Delegate. После назначения UIWebView.Delegate механизм диспетчеризации событий UIWebView больше не будет функционировать, а методы UIWebViewDelegate будут вызываться при возникновении соответствующих событий.
Например, этот простой тип записывает время, необходимое для загрузки веб-представления:
class Notifier : UIWebViewDelegate { DateTime startTime, endTime; public override LoadStarted (UIWebView webview) { startTime = DateTime.Now; } public override LoadingFinished (UIWebView webView) { endTime= DateTime.Now; } } Вышеуказанный код используется следующим образом: var web = new UIWebView (new CGRect (0, 0, 200, 200)); web.Delegate = new Notifier ();
Вышеуказанный код создаст UIWebViewer и поручит ему отправлять сообщения экземпляру Notifier, класса, который мы создали для ответа на сообщения.
Этот паттерн также используется для контроля поведения некоторых элементов управления, например, в случае с UIWebView свойство UIWebView.ShouldStartLoad позволяет экземпляру UIWebView контролировать, будет ли UIWebView загружать страницу или нет.
Шаблон также используется для предоставления данных по требованию для нескольких элементов управления. Например, элемент управления UITableView является мощным элементом управления с функцией рендеринга таблиц - и внешний вид, и содержимое управляются экземпляром источника данных UITableViewDataSource.
Слабая типизация через свойство WeakDelegate
В дополнение к строго типизированному свойству существует также слабо типизированный делегат, который позволяет разработчику при желании связывать вещи по-другому. Везде, где в привязке Xamarin.iOS раскрывается свойство Delegate, соответствующее свойство WeakDelegate также раскрывается.
При использовании WeakDelegate вы отвечаете за правильное оформление вашего класса, используя атрибут Export для указания селектора. Например:
class Notifier : NSObject { DateTime startTime, endTime; [Export ("webViewDidStartLoad:")] public void LoadStarted (UIWebView webview) { startTime = DateTime.Now; } [Export ("webViewDidFinishLoad:")] public void LoadingFinished (UIWebView webView) { endTime= DateTime.Now; } } [...] var web = new UIWebView (new CGRect (0, 0, 200, 200)); web.WeakDelegate = new Notifier ();
После назначения свойства WeakDelegate свойство Delegate не будет использоваться. Кроме того, если вы реализуете метод в наследуемом базовом классе, который вы хотите [Export], вы должны сделать его публичным методом.
Отображение паттерна делегата Objective-C на C#
Когда вы видите примеры Objective-C, которые выглядят следующим образом:
foo.delegate = [[SomethingDelegate] alloc] init]
Это дает указание языку создать и сконструировать экземпляр класса "SomethingDelegate" и присвоить значение свойству delegate переменной foo. Этот механизм поддерживается Xamarin.iOS и C#, синтаксис следующий:
foo.Delegate = new SomethingDelegate ();
В Xamarin.iOS мы предоставили сильно типизированные классы, которые соответствуют классам делегатов Objective-C. Чтобы использовать их, вы будете создавать подклассы и переопределять методы, определенные в реализации Xamarin.iOS.
Сопоставление делегатов с C#
UIKit в целом использует делегаты Objective-C в двух формах.
Первая форма предоставляет интерфейс к модели компонента. Например, в качестве механизма предоставления данных по запросу для представления, такого как хранилище данных для представления List. В этих случаях всегда следует создавать экземпляр соответствующего класса и присваивать переменную.
В следующем примере мы предоставляем UIPickerView реализацию для модели, использующей строки:
public class SampleTitleModel : UIPickerViewTitleModel { public override string TitleForRow (UIPickerView picker, nint row, nint component) { return String.Format ("At {0} {1}", row, component); } } [...] pickerView.Model = new MyPickerModel ();
Вторая форма - это предоставление уведомлений о событиях. В этих случаях, хотя мы по-прежнему раскрываем API в форме, описанной выше, мы также предоставляем события C#, которые должны быть проще в использовании для быстрых операций и интегрированы с анонимными делегатами и лямбда-выражениями в C#.
Например, вы можете подписаться на события UIAccelerometer:
UIAccelerometer.SharedAccelerometer.Acceleration += (sender, args) => { UIAcceleration acc = args.Acceleration; Console.WriteLine ("Time={0} at {1},{2},{3}", acc.Time, acc.X, acc.Y, acc.Z); }
Эти два варианта доступны там, где они имеют смысл, но как программист вы должны выбрать один или другой. Если вы создадите свой собственный экземпляр сильно типизированного responder/delegate и назначите его, события C# не будут работать. Если вы используете события C#, методы вашего класса responder/delegate никогда не будут вызваны.
Предыдущий пример с использованием UIWebView может быть написан с использованием лямбд C# 3.0 следующим образом:
var web = new UIWebView (new CGRect (0, 0, 200, 200)); web.LoadStarted += () => { startTime = DateTime.Now; } web.LoadFinished += () => { endTime = DateTime.Now; }
Реагирование на события
В коде Objective-C иногда обработчики событий для нескольких элементов управления и поставщики информации для нескольких элементов управления размещаются в одном классе. Это возможно, поскольку классы отвечают на сообщения, а пока классы отвечают на сообщения, можно связывать объекты вместе.
Как было описано ранее, Xamarin.iOS поддерживает как модель программирования на основе событий C#, так и паттерн делегата Objective-C, где можно создать новый класс, реализующий делегат и переопределяющий нужные методы.
Также можно поддерживать паттерн Objective-C, когда респонденты для нескольких различных операций размещаются в одном экземпляре класса. Однако для этого вам придется использовать низкоуровневые возможности привязки Xamarin.iOS.
Например, если вы хотите, чтобы ваш класс реагировал на сообщение UITextFieldDelegate.textFieldShouldClear: и UIWebViewDelegate.webViewDidStartLoad: в одном и том же экземпляре класса, вам придется использовать объявление атрибута [Export]:
public class MyCallbacks : NSObject { [Export ("textFieldShouldClear:"] public bool should_we_clear (UITextField tf) { return true; } [Export ("webViewDidStartLoad:")] public void OnWebViewStart (UIWebView view) { Console.WriteLine ("Loading started"); } }
Имена методов в C# не важны; все, что имеет значение, - это строки, переданные в атрибут [Export].
При использовании этого стиля программирования убедитесь, что параметры C# соответствуют реальным типам, которые будет передавать механизм выполнения.
Модели
В хранилищах UIKit или в респондентах, реализованных с помощью вспомогательных классов, они упоминаются в коде Objective-C как делегаты, и реализуются как протоколы.
Протоколы Objective-C похожи на интерфейсы, но они поддерживают необязательные методы – то есть не все методы должны быть реализованы, чтобы протокол работал.
Существует два способа реализации модели. Вы можете либо реализовать ее вручную, либо использовать существующие строго типизированные определения.
Ручной механизм необходим, когда вы пытаетесь реализовать класс, который не был связан с Xamarin.iOS. Это легко сделать:
- пометьте свой класс для регистрации в среде выполнения;
- примените атрибут [Export] с фактическим именем селектора к каждому методу, который вы хотите переопределить;
- создайте класс и передайте его.
Например, следующие реализуют только один из необязательных методов в определении протокола UIApplicationDelegate:
public class MyAppController : NSObject { [Export ("applicationDidFinishLaunching:")] public void FinishedLaunching (UIApplication app) { SetupWindow (); } }
Имя селектора Objective-C ("applicationDidFinishLaunching:") объявляется с помощью атрибута Export, а класс регистрируется с помощью атрибута [Register].
Xamarin.iOS предоставляет сильно типизированные объявления, готовые к использованию, которые не требуют ручного связывания. Для поддержки этой модели программирования среда выполнения Xamarin.iOS поддерживает атрибут [Model] в объявлении класса. Это сообщает среде выполнения, что она не должна подключать все методы класса, если только эти методы не реализованы явно.
Это означает, что в UIKit классы, представляющие протокол с необязательными методами, записываются следующим образом:
[Model] public class SomeViewModel : NSObject { [Export ("someMethod:")] public virtual int SomeMethod (TheView view) { throw new ModelNotImplementedException (); } ... }
Если вы хотите реализовать модель, которая реализует только некоторые методы, все, что вам нужно сделать, это переопределить методы, которые вас интересуют, и игнорировать остальные методы. Время выполнения подключит к миру Objective-C только перезаписанные методы, а не оригинальные методы.
Эквивалент предыдущего примера руководства выглядит следующим образом:
public class AppController : UIApplicationDelegate { public override void FinishedLaunching (UIApplication uia) { ... } }
Преимущества заключаются в том, что нет необходимости копаться в заголовочных файлах Objective-C, чтобы найти селектор, типы аргументов или отображение на C#, а также в том, что вы получаете intellisense из Visual Studio for Mac, наряду с сильными типами.
XIB Outlets и C#
Это описание того, как Outlets интегрируются с C#, и готовы для работы в Xamarin.iOS. При использовании Visual Studio for Mac сопоставление выполняется автоматически за кадром с использованием сгенерированного кода на лету.
Когда вы разрабатываете пользовательский интерфейс с помощью Interface Builder, вы создаете только внешний вид приложения и устанавливаете некоторые соединения по умолчанию. Если вы хотите программно получать информацию, изменять поведение элемента управления во время выполнения или модифицировать элемент управления во время выполнения, необходимо связать некоторые элементы управления с вашим управляемым кодом.
Для этого необходимо выполнить несколько шагов:
- добавьте объявление outlet к владельцу вашего файла;
- подключите свой элемент управления к владельцу файла;
- сохраните пользовательский интерфейс и соединения в файле XIB/NIB;
- загрузите файл NIB во время выполнения;
- получите доступ к переменной Outlet.
При использовании Xamarin.iOS вашему приложению потребуется создать класс, производный от UIViewController. Он реализуется следующим образом:
public class MyViewController : UIViewController { public MyViewController (string nibName, NSBundle bundle) : base (nibName, bundle) { // You can have as many arguments as you want, but you need to call // the base constructor with the provided nibName and bundle. } }
Затем, чтобы загрузить ваш ViewController из файла NIB, вы делаете следующее:
var controller = new MyViewController ("HelloWorld", NSBundle.MainBundle, this);
Это загружает пользовательский интерфейс из NIB. Теперь, чтобы получить доступ к розеткам, необходимо сообщить среде выполнения, что мы хотим получить к ним доступ. Для этого подкласс UIViewController должен объявить свойства и аннотировать их атрибутом [Connect]. Например:
[Connect] UITextField UserName { get { return (UITextField) GetNativeField ("UserName"); } set { SetNativeField ("UserName", value); } }
Реализация свойства фактически получает и сохраняет значение для реального нативного типа.
Вам не нужно беспокоиться об этом при использовании Visual Studio for Mac и InterfaceBuilder. Visual Studio for Mac автоматически зеркалирует все объявленные выходы с кодом в частичном классе, который компилируется как часть вашего проекта.
Селекторы
Основной концепцией программирования на Objective-C являются селекторы. Вы часто будете сталкиваться с API, которые требуют передачи селектора или ожидают, что ваш код будет реагировать на селектор.
Создавать новые селекторы в C# очень просто – вы просто создаете новый экземпляр класса ObjCRuntime.Selector и используете результат в любом месте API, где это требуется. Например:
var selector_add = new Selector ("add:plus:");
Чтобы метод C# ответил на вызов селектора, он должен наследоваться от типа NSObject, а метод C# должен быть украшен именем селектора с помощью атрибута [Export]. Например:
public class MyMath : NSObject { [Export ("add:plus:")] int Add (int first, int second) { return first + second; } }
Имена селекторов должны точно совпадать, включая все промежуточные и последующие двоеточия (":"), если они присутствуют.
Конструкторы NSObject
Большинство классов в Xamarin.iOS, производных от NSObject, открывают конструкторы, специфичные для функциональности объекта, но они также открывают различные конструкторы, которые не сразу очевидны.
Конструкторы используются следующим образом:
public Foo (IntPtr handle)
Этот конструктор используется для создания вашего класса, когда среде выполнения необходимо сопоставить ваш класс с неуправляемым классом. Это происходит, когда вы загружаете файл XIB/NIB. В этот момент среда выполнения Objective-C создаст объект в неуправляемом мире, а этот конструктор будет вызван для инициализации управляемой стороны.
Достаточно вызвать базовый конструктор с параметром handle, а в теле выполнить любую необходимую инициализацию.
public Foo ()
Это конструктор по умолчанию для класса, и в классах, предоставляемых Xamarin.iOS, он инициализирует класс Foundation.NSObject и все классы между ними, а в конце передает его в метод Objective-C init класса.
public Foo (NSObjectFlag x)
Этот конструктор используется для инициализации экземпляра, но предотвращает вызов метода Objective-C "init" в конце кода. Обычно вы используете его, когда вы уже зарегистрировались для инициализации (когда вы используете [Export] в конструкторе) или когда вы уже выполнили инициализацию другим способом.
public Foo (NSCoder coder)
Этот конструктор предусмотрен для случаев, когда объект инициализируется из экземпляра NSCoding.
Исключения
Дизайн Xamarin.iOS API не поднимает исключения Objective-C как исключения C#. Дизайн обеспечивает, чтобы в мир Objective-C не отправлялся мусор, а все исключения, которые должны быть созданы, создаются самим связыванием до того, как недопустимые данные будут переданы в мир Objective-C.
Уведомления
Как в iOS, так и в OS X разработчики могут подписаться на уведомления, которые транслируются базовой платформой. Это делается с помощью метода NSNotificationCenter.DefaultCenter.AddObserver. Метод AddObserver принимает два параметра: один – это уведомление, на которое вы хотите подписаться; другой - метод, который будет вызван при появлении уведомления.
В Xamarin.iOS и Xamarin.Mac ключи для различных уведомлений размещаются в классе, который вызывает уведомления. Например, уведомления, вызываемые UIMenuController, размещаются как статические свойства NSString в классах UIMenuController, которые заканчиваются именем "Notification".
Управление памятью
В Xamarin.iOS есть сборщик мусора, который позаботится об освобождении ресурсов, когда они больше не используются. В дополнение к сборщику мусора все объекты, производные от NSObject, реализуют интерфейс System.IDisposable.
NSObject и IDisposable
Использование интерфейса IDisposable – это удобный способ помочь разработчикам освободить объекты, которые могут содержать большие блоки памяти (например, UIImage может выглядеть просто как невинный указатель, но может указывать на изображение размером 2 мегабайта) и другие важные и ограниченные ресурсы (например, буфер декодирования видео).
NSObject реализует интерфейс IDisposable, а также паттерн .NET Dispose. Это позволяет разработчикам, подклассам NSObject, переопределять поведение Dispose и освобождать свои собственные ресурсы по требованию. Например, рассмотрим этот контроллер представления, который хранит вокруг себя кучу изображений:
class MenuViewController : UIViewController { UIImage breakfast, lunch, dinner; [...] public override void Dispose (bool disposing) { if (disposing){ if (breakfast != null) breakfast.Dispose (); breakfast = null; if (lunch != null) lunch.Dispose (); lunch = null; if (dinner != null) dinner.Dispose (); dinner = null; } base.Dispose (disposing) } }
Когда управляемый объект утилизируется, он перестает быть полезным. У вас все еще может быть ссылка на объект, но сам объект в этот момент становится недействительным. Некоторые API .NET гарантируют это, выбрасывая исключение ObjectDisposedException, если вы, например, пытаетесь получить доступ к любым методам утилизированного объекта:
var image = UIImage.FromFile ("demo.png"); image.Dispose (); image.XXX = false; // this at this point is an invalid operation
Даже если вы все еще можете получить доступ к переменной "image", она действительно является недопустимой ссылкой и больше не указывает на объект Objective-C, в котором хранилось изображение.
Но утилизация объекта в C# не означает, что объект обязательно будет уничтожен. Все, что вы делаете, это освобождаете ссылку, которую C# имел на объект. Возможно, что среда Cocoa сохранила ссылку для собственного использования. Например, если вы установите свойство Image в UIImageView на изображение, а затем утилизируете изображение, то базовый UIImageView принял свою собственную ссылку и будет сохранять ссылку на этот объект, пока не закончит его использовать.
Когда вызывать Dispose
Вызывайте Dispose, когда вам нужно, чтобы Mono избавился от вашего объекта. Возможный случай использования – когда Mono не знает, что ваш NSObject на самом деле хранит ссылку на важный ресурс, например, память или информационный пул. В таких случаях следует вызвать Dispose, чтобы немедленно освободить ссылку на память, вместо того чтобы ждать, пока Mono выполнит цикл сборки мусора.
Внутренне, когда Mono создает ссылки NSString из строк C#, он немедленно их утилизирует, чтобы уменьшить объем работы, которую должен выполнить сборщик мусора. Чем меньше объектов вокруг, тем быстрее работает GC.
Когда следует сохранять ссылки на объекты
Один из побочных эффектов автоматического управления памятью заключается в том, что GC будет избавляться от неиспользуемых объектов до тех пор, пока на них нет ссылок. Это иногда может иметь неожиданные побочные эффекты, например, если вы создадите локальную переменную для хранения контроллера представления верхнего уровня или окна верхнего уровня, а затем они исчезнут у вас за спиной.
Если вы не храните в статических переменных или переменных экземпляра ссылки на ваши объекты, Mono с радостью вызовет для них метод Dispose(), и они освободят ссылку на объект. Поскольку это может быть единственная оставшаяся ссылка, среда выполнения Objective-C уничтожит объект за вас.