Разработка API Xamarin.iOS
В дополнение к основным библиотекам базовых классов, которые являются частью 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):
Следуйте рекомендациям по разработке платформы.
Разрешите разработчикам использовать подклассы классов Objective-C:
- Используйте наследование от существующего класса.
- Вызывайте базовый конструктор для создания цепочки.
- Переопределяйте методы с помощью системы переопределения C#.
- Подклассы должны работать со стандартными конструкциями C#.
Не предоставляйте разработчикам доступ к селекторам Objective-C.
Предоставляйте механизм вызова произвольных библиотек Objective-C.
Упростите обычные задачи Objective-C и сделайте возможными сложные задачи Objective-C.
Предоставляйте свойства Objective-C как свойства C#.
Предоставляйте строго типизированный API:
- Повышайте безопасность типов.
- Сведите к минимуму ошибки среды выполнения.
- Получайте IntelliSense IDE для возвращаемых типов.
- Разрешайте всплывающую документацию по интегрированной среде разработки.
Поощряйте изучение API в интегрированной среде разработки:
Например, вместо предоставления слабо типизированного массива следующим образом:
NSArray *getViews
Предоставьте строгий тип следующим образом:
NSView [] Views { get; set; }
В случае использования строгих типов в Visual Studio для Mac предоставляется возможность автоматического завершения при просмотре API, все операции
System.Array
становятся доступными в возвращаемом значении, а возвращаемое значение может участвовать в LINQ.
Собственные типы C#:
Включите параметры
int
иuint
, которые должны быть перечислены в перечислениях 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 предоставляет базовые типы данных, предназначенные для взаимодействия с платформой Objective-C Foundation, которая является частью iOS и основой объектно-ориентированного программирования в Objective-C.
Xamarin.iOS отражается в иерархии C# классов из Objective-C. Например, базовый класс Objective-C NSObject можно использовать из C# через Foundation.NSObject.
Хотя пространство имен Foundation предоставляет привязки для базовых типов Objective-C Foundation, в некоторых случаях мы сопоставили базовые типы с типами .NET. Например:
Вместо того чтобы работать с NSString и NSArray, среда выполнения предоставляет их в виде элементов string и строго типизированных элементов array C# по всему API.
Здесь представлены различные вспомогательные API, позволяющие разработчикам привязывать сторонние API Objective-C, другие API iOS или API, которые сейчас не привязаны через Xamarin.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, не дожидаясь запуска сборщика мусора. Освобождать ресурсы явным образом важно при использовании больших элементов NSObject, например 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 ();
}
}
В этом примере будет добавлен собственный метод экземпляра toUpper в класс NSString, который можно вызвать из 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 мы распространяем измененную версию API OpenTK — объектно-ориентированную привязку к 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-взаимодействия в 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.
Кроме того, в Classic API вместо предоставления CGRect
, CGPoint
и CGSize
из API CoreGraphics мы заменили их на реализации System.Drawing
RectangleF
, PointF
и SizeF
, так как они помогут разработчикам сохранить имеющийся код OpenGL, использующий OpenTK. При использовании нового 64-разрядного Unified API следует использовать API CoreGraphics.
Наследование
Структура API Xamarin.iOS позволяет разработчикам расширять собственные типы Objective-C точно так же, как тип C#, используя ключевое слово "override" в производном классе, а также выполнять связывание с базовой реализацией с помощью ключевого слова C# "base".
Такая схема позволяет разработчикам избегать использования селекторов 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+).
- Для реализации моделей для элементов управления визуализации данных.
- Для поддержки поведения элемента управления.
Шаблон программирования предназначен для того, чтобы свести к минимуму создание производных классов для изменения поведения элемента управления. Это решение похоже на то, что другие наборы средств графического интерфейса на протяжении многих лет: сигналы gtk, слоты Qt, события Winforms, события WPF/Silverlight и т. д. Чтобы избежать использования сотен интерфейсов (по одному для каждого действия) или необходимости реализовывать слишком много ненужных методов, Objective-C поддерживает необязательные определения методов. Это отличается от интерфейсов C#, требующих реализации всех методов.
В классах Objective-C вы увидите, что классы, использующие этот шаблон программирования, предоставляют свойство, именуемое delegate
, которое требуется для реализации обязательных частей интерфейса и ни одной или нескольких дополнительных частей.
В Xamarin.iOS существует три взаимоисключающих механизма привязки к этим делегатам.
- Через события.
- Строго типизировано с помощью свойства
Delegate
. - Слабо типизировано с помощью свойства
WeakDelegate
.
Например, рассмотрим класс UIWebView. Он работает с экземпляром UIWebViewDelegate, который назначается свойству делегата.
Через события
Для многих типов 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
в этом случае принимает в качестве аргумента делегат, возвращающий логическое значение, и определяет, должен ли TextField выполнять действие с нажатой кнопкой возврата. В нашем методе мы возвращаем true вызывающему объекту, но также удаляем клавиатуру с экрана (это происходит, когда объект TextField вызывает 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
Помимо строго типизированного свойства существует также слабо типизированный делегат, позволяющий разработчику при необходимости привязывать элементы иначе.
Везде, где строго типизированное свойство Delegate
предоставляется в привязке Xamarin.iOS, также предоставляется соответствующее свойство 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 и присвоить значение свойству делегата в переменной foo. Этот механизм поддерживается Xamarin.iOS, и код C# имеет следующий синтаксис:
foo.Delegate = new SomethingDelegate ();
В Xamarin.iOS мы предоставили строго типизированные классы, соответствующие классам делегата Objective-C. Чтобы использовать их, нужно будет создать подклассы и переопределить методы, определенные в реализации Xamarin.iOS. Дополнительные сведения о том, как они работают, см. в разделе "Модели" ниже.
Сопоставление делегатов с C#
UIKit, как правило, использует делегаты Objective-C в двух формах.
Первая форма предоставляет интерфейс модели компонента. Например, в качестве механизма предоставления данных по запросу для представления (скажем, средство хранения данных для представления списка). В таких случаях следует всегда создавать экземпляр соответствующего класса и присваивать ему переменную.
В следующем примере мы предоставляем 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);
}
Доступны два варианта в соответствующих случаях, но выбрать можно только один из них. Если вы создаете собственный экземпляр строго типизированного ответчика или делегата и назначаете его, события C# не будут работать. При использовании событий C# методы в классе ответчика или делегата никогда не вызываются.
Предыдущий пример, в котором использовался 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 для Mac и строгие типы.
Точки XIB и C#
Это общее описание того, как можно интегрировать аутлеты с C#, для опытных пользователей Xamarin.iOS. При использовании Visual Studio для Mac сопоставление выполняется автоматически в фоновом режиме с помощью созданного кода.
При проектировании пользовательского интерфейса с помощью Interface Builder вы разрабатываете только внешний вид приложения и устанавливаете некоторые подключения по умолчанию. Если вы хотите получить информацию программным способом, изменить поведение элемента управления во время выполнения или элемент управления во время выполнения, необходимо привязать некоторые элементы управления к управляемому коду.
Для этого выполните следующие действия.
- Добавьте объявление аутлета к владельцу файла.
- Подключите элемент управления к владельцу файла.
- Сохраните пользовательский интерфейс и подключения в файл XIB/NIB.
- Загрузите файл NIB во время выполнения.
- Получите доступ к переменной аутлета.
Шаги с 1 по 3 описаны в документации Apple по созданию интерфейсов с помощью Interface Builder.
При использовании 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 для Mac и Interface Builder не нужно беспокоиться об этом. Visual Studio для Mac автоматически отражает все объявленные аутлеты с кодом в разделяемом классе, который компилируется как часть проекта.
Селекторы
Основной концепцией программирования на Objective-C являются селекторы. Часто приходится использовать API, требующие передачи селектора или ожидающие, что код будет отвечать на селектор.
Создавать новые селекторы в C# просто — вы создаете новый экземпляр класса ObjCRuntime.Selector
и используете результат в любом месте в API, которому он требуется. Например:
var selector_add = new Selector ("add:plus:");
Чтобы метод C# ответил на вызов селектора, он должен наследовать от типа NSObject
и быть дополнен именем селектора с помощью атрибута [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-Cinit
метода в классе.
public Foo (NSObjectFlag x)
Этот конструктор используется для инициализации экземпляра, но запрещает коду вызывать метод Objective-C "Init" в конце. Обычно он используется, если вы уже зарегистрировались для инициализации (при использовании [Export]
в конструкторе) или если вы уже выполнили инициализацию с помощью другого способа.
public Foo (NSCoder coder)
Этот конструктор предоставляется для случаев, когда объект инициализируется из экземпляра NSCoding.
Исключения
Структура API Xamarin.iOS не вызывает исключения Objective-C как исключения C#. Проект применяет, что в первую очередь не отправляется Objective-C мусор в мир и что любые исключения, которые должны быть созданы самой привязкой, прежде чем недопустимые данные когда-либо передаются Objective-C в мир.
Notifications
В iOS и OS X разработчики могут подписываться на уведомления, которые рассылаются базовой платформой. Это делается с помощью метода NSNotificationCenter.DefaultCenter.AddObserver
. Метод AddObserver
принимает два параметра. Один из них — уведомление, на которое вы хотите подписаться; другой — метод, который вызывается при возникновении уведомления.
В Xamarin.iOS и Xamarin.Mac ключи для различных уведомлений размещаются в классе, который запускает уведомления. Например, уведомления, созданные UIMenuController
, размещаются в виде свойств static NSString
в классах UIMenuController
, которые оканчиваются на слово "Notification".
Управление памятью
В Xamarin.iOS есть сборщик мусора, который занимается освобождением ресурсов, когда они больше не используются. Помимо сборщика мусора, все объекты, производные от NSObject
, реализуют интерфейс System.IDisposable
.
NSObject и IDisposable
Предоставление доступа к интерфейсу IDisposable
— это удобный способ помочь разработчикам освобождать объекты, которые могут занимать большие блоки памяти (например, UIImage
может выглядеть как безобидный указатель, но указывать на изображение размером 2 МБ) и другие важные и ограниченные ресурсы (например, буфер декодирования видео).
NSObject реализует интерфейс IDisposable, а также шаблон освобождения .NET. Это позволяет разработчикам использовать подклассы 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#, она немедленно удаляет их, чтобы уменьшить объем работы, которую должен выполнить сборщик мусора. Чем меньше объектов, тем быстрее будет работать сборщик мусора.
Когда следует сохранять ссылки на объекты
Одним из побочных эффектов автоматического управления памятью является то, что сборщик мусора будет избавляться от неиспользуемых объектов, если на них нет ссылок. Это иногда может приводить к неожиданным результатам. Например, если вы создаете локальную переменную для размещения контроллера представления верхнего уровня или окна верхнего уровня, а они затем исчезают без вашего ведома.
Если вы не сохраняете ссылку на объекты в статических переменных или переменных экземпляра, Mono просто вызовет для них метод Dispose () и освободит ссылку на объект. Так как это может быть единственная ссылка, среда выполнения Objective-C автоматически уничтожит объект.