Библиотеки привязки Objective-C
При работе с Xamarin.iOS или Xamarin.Mac могут возникнуть случаи, когда требуется использовать стороннюю Objective-C библиотеку. В этих ситуациях можно использовать проекты привязки Xamarin для создания привязки C# к собственным Objective-C библиотекам. В проекте используются те же средства, которые мы используем для привлечения API iOS и Mac в C#.
В этом документе описывается, как привязать API, если вы привязываете Objective-C только API C, следует использовать стандартный механизм .NET для этого платформы P/Invoke. Сведения о том, как статически связать библиотеку C, доступны на странице связывания собственных библиотек.
Ознакомьтесь со справочным руководством по типам привязки компаньона. Кроме того, если вы хотите узнать больше о том, что происходит под капотом, ознакомьтесь со страницей обзора привязки.
Привязки можно создавать для библиотек iOS и Mac. На этой странице описывается, как работать с привязкой iOS, однако привязки Mac очень похожи.
Пример кода для iOS
Для экспериментов с привязками можно использовать пример проекта привязки iOS.
Начало работы
Самый простой способ создать привязку — создать проект привязки Xamarin.iOS. Это можно сделать из Visual Studio для Mac, выбрав тип проекта, библиотеку привязок библиотеки >iOS>:
Созданный проект содержит небольшой шаблон, который можно изменить, он содержит два файла: ApiDefinition.cs
и StructsAndEnums.cs
.
В ApiDefinition.cs
этом месте вы определите контракт API, это файл, описывающий, как базовый Objective-C API проецируется в C#. Синтаксис и содержимое этого файла являются основным разделом обсуждения этого документа, а его содержимое ограничено интерфейсами C# и объявлениями делегатов C#. Файл StructsAndEnums.cs
— это файл, в котором будут вводиться все определения, необходимые интерфейсам и делегатам. Сюда входят значения перечисления и структуры, которые может использовать код.
Привязка API
Чтобы выполнить комплексную привязку, необходимо понять Objective-C определение API и ознакомиться с рекомендациями по проектированию платформа .NET Framework.
Для привязки библиотеки обычно начинается с файла определения API. Файл определения API — это просто исходный файл C#, содержащий интерфейсы C#, которые были аннотированы с несколькими атрибутами, которые помогают управлять привязкой. Этот файл определяет, какой контракт между C# и Objective-C является.
Например, это тривиальный файл API для библиотеки:
using Foundation;
namespace Cocos2D {
[BaseType (typeof (NSObject))]
interface Camera {
[Static, Export ("getZEye")]
nfloat ZEye { get; }
[Export ("restore")]
void Restore ();
[Export ("locate")]
void Locate ();
[Export ("setEyeX:eyeY:eyeZ:")]
void SetEyeXYZ (nfloat x, nfloat y, nfloat z);
[Export ("setMode:")]
void SetMode (CameraMode mode);
}
}
В приведенном выше примере определяется класс Cocos2D.Camera
, который является производным от NSObject
базового типа (из этого типа Foundation.NSObject
) и который определяет статическое свойство (ZEye
), два метода, которые не принимают аргументов и метод, принимаюющий три аргумента.
Подробное обсуждение формата файла API и атрибутов, которые можно использовать, рассматриваются в разделе определения API ниже.
Чтобы создать полную привязку, обычно вы будете иметь дело с четырьмя компонентами:
- Файл определения API (
ApiDefinition.cs
в шаблоне). - Необязательно: все перечисления, типы, структуры, необходимые файлу определения API (
StructsAndEnums.cs
в шаблоне). - Необязательный: дополнительные источники, которые могут расширить созданную привязку или предоставить более понятный API C# (любые файлы C#, добавляемые в проект).
- Собственная библиотека, которую вы привязывают.
На этой диаграмме показана связь между файлами:
Файл определения API будет содержать только пространства имен и определения интерфейса (с любыми элементами, которые может содержать интерфейс), и не должен содержать классы, перечисления, делегаты или структуры. Файл определения API — это просто контракт, который будет использоваться для создания API.
Любой дополнительный код, который требуется, например перечисления или вспомогательные классы, должен размещаться в отдельном файле, в примере выше "CameraMode" является значением перечисления, которое не существует в CS-файле и должно размещаться в отдельном файле, например StructsAndEnums.cs
:
public enum CameraMode {
FlyOver, Back, Follow
}
Файл APIDefinition.cs
сочетается с классом StructsAndEnum
и используется для создания основной привязки библиотеки. Вы можете использовать полученную библиотеку как есть, но, как правило, вы хотите настроить полученную библиотеку, чтобы добавить некоторые функции C# для преимуществ пользователей. Некоторые примеры включают реализацию ToString()
метода, предоставление индексаторов C#, добавление неявных преобразований в некоторые собственные типы или предоставление строго типизированных версий некоторых методов. Эти улучшения хранятся в дополнительных файлах C#. Просто добавьте файлы C# в проект, и они будут включены в этот процесс сборки.
В этом примере показано, как реализовать код в Extra.cs
файле. Обратите внимание, что вы будете использовать частичные классы, как эти расширения частичных классов, созданных из сочетания привязки и основной ApiDefinition.cs
StructsAndEnums.cs
привязки:
public partial class Camera {
// Provide a ToString method
public override string ToString ()
{
return String.Format ("ZEye: {0}", ZEye);
}
}
Создание библиотеки приведет к созданию собственной привязки.
Чтобы завершить эту привязку, необходимо добавить собственную библиотеку в проект. Это можно сделать, добавив собственную библиотеку в проект, перетащив и удаляя собственную библиотеку из Finder в проект в обозревателе решений, или щелкнув проект правой кнопкой мыши и выбрав "Добавить>файлы ", чтобы выбрать собственную библиотеку. Собственные библиотеки по соглашению начинаются с слова lib и заканчиваются расширением .a. При этом Visual Studio для Mac добавит два файла: файл .a и автоматически заполненный файл C#, содержащий сведения о том, что содержит собственная библиотека:
Содержимое файла содержит сведения о том, как эту библиотеку можно использовать и указать интегрированной libMagicChord.linkwith.cs
среде разработки упаковать этот двоичный файл в результирующий DLL-файл:
using System;
using ObjCRuntime;
[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]
Полные сведения об использовании [LinkWith]
атрибут задокументирован в справочном руководстве по типам привязки.
Теперь при сборке проекта вы в конечном итоге будете иметь MagicChords.dll
файл, содержащий привязку и собственную библиотеку. Этот проект или результирующая библиотека DLL можно распространять другим разработчикам для собственного использования.
Иногда может потребоваться несколько значений перечисления, определений делегатов или других типов. Не помещайте их в файл определений API, так как это просто контракт
Файл определения API
Файл определения API состоит из ряда интерфейсов. Интерфейсы в определении API будут преобразованы в объявление класса, и они должны быть украшены [BaseType]
атрибутом, чтобы указать базовый класс для класса.
Возможно, вам интересно, почему мы не использовали классы вместо интерфейсов для определения контракта. Мы выбрали интерфейсы, так как это позволило нам записывать контракт для метода, не предоставляя текст метода в файле определения API или предоставляя текст, который должен был вызывать исключение или возвращать понятное значение.
Но так как мы используем интерфейс в качестве скелета для создания класса, мы должны были прибегнуть к декорированию различных частей контракта с атрибутами для управления привязкой.
Методы привязки
Простейшая привязка, что можно сделать, заключается в привязке метода. Просто объявите метод в интерфейсе с соглашениями об именовании C#и украшайте метод с помощью соглашения об именовании C#. Атрибут [Export]
. Атрибут [Export]
— это то, что связывает имя C# с Objective-C именем в среде выполнения Xamarin.iOS. Параметр объекта [Export]
атрибут — это имя селектора Objective-C . Некоторые примеры:
// A method, that takes no arguments
[Export ("refresh")]
void Refresh ();
// A method that takes two arguments and return the result
[Export ("add:and:")]
nint Add (nint a, nint b);
// A method that takes a string
[Export ("draw:atColumn:andRow:")]
void Draw (string text, nint column, nint row);
В приведенных выше примерах показано, как можно привязать методы экземпляра. Чтобы привязать статические методы, необходимо использовать [Static]
атрибут, как показано ниже.
// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();
Это необходимо, так как контракт является частью интерфейса, и интерфейсы не имеют понятия статических объявлений и объявлений экземпляров, поэтому необходимо еще раз прибегнуть к атрибутам. Если вы хотите скрыть определенный метод из привязки, можно украсить метод атрибутом [Internal]
.
Команда btouch-native
введет проверки ссылочных параметров, которые не будут иметь значение NULL. Если вы хотите разрешить значения NULL для определенного параметра, используйте [NullAllowed]
атрибут для параметра, как показано ниже:
[Export ("setText:")]
string SetText ([NullAllowed] string text);
При экспорте ссылочного типа с [Export]
ключевым словом можно также указать семантику выделения. Это необходимо для обеспечения утечки данных.
Свойства привязки
Как и методы, Objective-C свойства привязаны с помощью [Export]
атрибут и сопоставление непосредственно с свойствами C#. Как и методы, свойства можно декорировать с помощью [Static]
и [Internal]
Атрибуты.
При использовании атрибута [Export]
в свойстве под крышкой btouch-native фактически привязывается два метода: метод getter и метод задания. Имя, которое вы предоставляете для экспорта, является базовым именем , и средство задания вычисляется путем добавления слова "set", превращая первую букву базового имени в верхний регистр и делая селектор принимать аргумент. Это означает, что [Export ("label")]
применение к свойству фактически привязывает методы label и setLabel: Objective-C .
Objective-C Иногда свойства не соответствуют описанному выше шаблону, и имя вручную перезаписывается. В этих случаях можно управлять способом создания привязки с помощью [Bind]
атрибут в методе getter или setter, например:
[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }
Затем привязывается isMenuVisible и setMenuVisible:. При необходимости свойство можно привязать с помощью следующего синтаксиса:
[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
[Export ("name")]
string Name();
[Export("setName:")]
void SetName(string name);
}
Где метод получения и задания явно определяются как в name
приведенных setName
выше привязках.
Помимо поддержки статических свойств с помощью [Static]
, можно декорировать статические свойства [IsThreadStatic]
потока, например:
[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }
Как и методы, позволяющие помечать некоторые параметры с [NullAllowed]
помощью , можно применить [NullAllowed]
свойство, указывающее, что null является допустимым значением для свойства, например:
[Export ("text"), NullAllowed]
string Text { get; set; }
Параметр [NullAllowed]
также можно указать непосредственно в методе задания:
[Export ("text")]
string Text { get; [NullAllowed] set; }
Предостережения настраиваемых элементов управления привязки
При настройке привязки для пользовательского элемента управления следует учитывать следующие предостережения:
- Свойства привязки должны быть статическими . При определении привязки свойств
[Static]
атрибут должен использоваться. - Имена свойств должны совпадать точно . Имя, используемое для привязки свойства, должно точно соответствовать имени свойства в пользовательском элементе управления.
- Типы свойств должны совпадать точно . Тип переменной, используемый для привязки свойства, должен точно соответствовать типу свойства в пользовательском элементе управления.
- Точки останова и метод получения или задания — точки останова, помещенные в методы получения или задания свойства, никогда не будут попадания.
- Следите за обратными вызовами. Вам потребуется использовать обратные вызовы наблюдения, чтобы получать уведомления об изменениях значений свойств пользовательских элементов управления.
Сбой при наблюдении за любым из перечисленных выше предостережениях может привести к автоматическому сбою привязки во время выполнения.
Objective-C изменяемый шаблон и свойства
Objective-C платформы используют идиом, где некоторые классы неизменяемы с изменяемым подклассом. Например NSString
, это неизменяемая версия, а NSMutableString
подкласс, который разрешает мутацию.
В этих классах обычно видно, что неизменяемый базовый класс содержит свойства с методом получения, но не используется метод задания. И для изменяемой версии, чтобы ввести метод задания. Так как это на самом деле невозможно с C#, нам пришлось сопоставить эту идиому с идиомой, которая будет работать с C#.
Способ, который сопоставляется с C#, заключается в добавлении метода получения и задания в базовом классе, но примечание к методу задания с помощью метода Атрибут [NotImplemented]
.
Затем в изменяемом подклассе используется [Override]
атрибут для свойства, чтобы убедиться, что свойство фактически переопределяет поведение родительского элемента.
Пример:
[BaseType (typeof (NSObject))]
interface MyTree {
string Name { get; [NotImplemented] set; }
}
[BaseType (typeof (MyTree))]
interface MyMutableTree {
[Override]
string Name { get; set; }
}
Конструкторы привязки
Средство автоматически создает четыре конструктора в классе для заданного классаFoo
. Он btouch-native
создает:
Foo ()
: конструктор по умолчанию (сопоставляется с Objective-Cконструктором init)Foo (NSCoder)
: конструктор, используемый во время десериализации файлов NIB (сопоставляется с Objective-Cконструктором initWithCoder:).Foo (IntPtr handle)
: конструктор для создания на основе дескрипторов вызывается средой выполнения, когда среде выполнения необходимо предоставить управляемый объект из неуправляемого объекта.Foo (NSEmptyFlag)
: используется производными классами для предотвращения двойной инициализации.
Для определяемых конструкторов необходимо объявить с помощью следующей сигнатуры в определении интерфейса: они должны возвращать IntPtr
значение, а имя метода должно быть конструктором. Например, чтобы привязать initWithFrame:
конструктор, это то, что вы будете использовать:
[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);
Протоколы привязки
Как описано в документе проектирования API, в разделе ,посвященном моделям и протоколам, Xamarin.iOS сопоставляет Objective-C протоколы с классами, помеченными с помощью Атрибут [Model]
. Обычно это используется при реализации Objective-C классов делегатов.
Большая разница между обычным привязанным классом и классом делегата заключается в том, что класс делегата может иметь один или несколько необязательных методов.
Например, рассмотрим UIKit
класс UIAccelerometerDelegate
. Это то, как оно привязано в Xamarin.iOS:
[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
[Export ("accelerometer:didAccelerate:")]
void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}
Так как это необязательный метод определения, для UIAccelerometerDelegate
чего ничего не нужно делать. Но если в протоколе был обязательный метод, необходимо добавить [Abstract]
атрибут метода. Это приведет к том, что пользователь реализации фактически предоставит текст метода.
Как правило, протоколы используются в классах, реагирующих на сообщения. Обычно это делается Objective-C путем назначения свойству "делегат" экземпляр объекта, реагирующего на методы в протоколе.
Соглашение в Xamarin.iOS заключается в поддержке Objective-C как слабо связанного стиля, где любой экземпляр NSObject
может быть назначен делегату, так и для предоставления строго типизированной версии. По этой причине обычно мы предоставляем как Delegate
свойство, строго типизированное, так и WeakDelegate
свободно типизированное. Обычно мы привязываем слабо типизированную версию и [Export]
используем [Wrap]
атрибут для предоставления строго типизированной версии.
Ниже показано, как мы привязали UIAccelerometer
класс:
[BaseType (typeof (NSObject))]
interface UIAccelerometer {
[Static] [Export ("sharedAccelerometer")]
UIAccelerometer SharedAccelerometer { get; }
[Export ("updateInterval")]
double UpdateInterval { get; set; }
[Wrap ("WeakDelegate")]
UIAccelerometerDelegate Delegate { get; set; }
[Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
NSObject WeakDelegate { get; set; }
}
Новые возможности в MonoTouch 7.0
Начиная с MonoTouch 7.0 была включена новая и улучшенная функция привязки протокола. Эта новая поддержка упрощает использование Objective-C идиом для внедрения одного или нескольких протоколов в данном классе.
Для каждого определения MyProtocol
протокола в Objective-Cнастоящее время IMyProtocol
существует интерфейс, который перечисляет все необходимые методы из протокола, а также класс расширения, предоставляющий все необязательные методы. В сочетании с новой поддержкой в редакторе Xamarin Studio разработчики могут реализовывать методы протокола без использования отдельных подклассов предыдущих абстрактных классов моделей.
Любое определение, содержащее [Protocol]
атрибут, фактически создает три вспомогательных класса, которые значительно улучшают способ использования протоколов:
// Full method implementation, contains all methods
class MyProtocol : IMyProtocol {
public void Say (string msg);
public void Listen (string msg);
}
// Interface that contains only the required methods
interface IMyProtocol: INativeObject, IDisposable {
[Export ("say:")]
void Say (string msg);
}
// Extension methods
static class IMyProtocol_Extensions {
public static void Optional (this IMyProtocol this, string msg);
}
}
Реализация класса предоставляет полный абстрактный класс, который можно переопределить отдельные методы и получить полную безопасность типов. Но из-за того, что C# не поддерживает несколько наследование, существуют сценарии, в которых может потребоваться другой базовый класс, но вы по-прежнему хотите реализовать интерфейс, то есть где
Создается определение интерфейса. Это интерфейс, имеющий все необходимые методы из протокола. Это позволяет разработчикам, которые хотят реализовать протокол только для реализации интерфейса. Среда выполнения автоматически регистрирует тип в качестве принятия протокола.
Обратите внимание, что интерфейс перечисляет только необходимые методы и предоставляет необязательные методы. Это означает, что классы, которые принимают протокол, получат полную проверку типа для необходимых методов, но придется прибегнуть к слабому вводу (вручную с помощью [Export]
атрибутов и сопоставления сигнатуры) для необязательных методов протокола.
Чтобы упростить использование API, использующего протоколы, средство привязки также создаст класс методов расширений, который предоставляет все необязательные методы. Это означает, что до тех пор, пока вы используете API, вы сможете обрабатывать протоколы как все методы.
Если вы хотите использовать определения протокола в API, необходимо написать скелет пустых интерфейсов в определении API. Если вы хотите использовать MyProtocol в API, вам потребуется сделать следующее:
[BaseType (typeof (NSObject))]
[Model, Protocol]
interface MyProtocol {
// Use [Abstract] when the method is defined in the @required section
// of the protocol definition in Objective-C
[Abstract]
[Export ("say:")]
void Say (string msg);
[Export ("listen")]
void Listen ();
}
interface IMyProtocol {}
[BaseType (typeof(NSObject))]
interface MyTool {
[Export ("getProtocol")]
IMyProtocol GetProtocol ();
}
Это необходимо, так как во время IMyProtocol
привязки не будет существовать, поэтому необходимо предоставить пустой интерфейс.
Внедрение интерфейсов, созданных протоколом
При реализации одного из интерфейсов, созданных для протоколов, как показано ниже.
class MyDelegate : NSObject, IUITableViewDelegate {
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
Реализация необходимых методов интерфейса экспортируется с соответствующим именем, поэтому это эквивалентно следующему:
class MyDelegate : NSObject, IUITableViewDelegate {
[Export ("getRowHeight:")]
nint IUITableViewDelegate.GetRowHeight (nint row) {
return 1;
}
}
Это будет работать для всех обязательных элементов протокола, но существует особый случай с необязательными селекторами, которые следует учитывать. Необязательные члены протокола обрабатываются одинаково при использовании базового класса:
public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
но при использовании интерфейса протокола необходимо добавить [экспорт]. Интегрированная среда разработки добавит ее через автозавершение при добавлении, начиная с переопределения.
public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
Существует небольшое различие в поведении между двумя в среде выполнения.
- Пользователи базового класса (NSUrlSessionDownloadDelegate) предоставляют все обязательные и необязательные селекторы, возвращая разумные значения по умолчанию.
- Пользователи интерфейса (НАПРИМЕР, INSUrlSessionDownloadDelegate) отвечают только на указанные селекторы.
Некоторые редкие классы могут вести себя по-разному здесь. В почти всех случаях, однако, это безопасно использовать либо.
Расширения классов привязки
В Objective-C этом случае можно расширить классы новыми методами, аналогичными методам расширения C#. При наличии одного из этих методов можно использовать [BaseType]
атрибут, который помечает метод как получатель Objective-C сообщения.
Например, в Xamarin.iOS мы привязали методы расширения, определенные NSString
при UIKit
импорте в качестве методов в NSStringDrawingExtensions
следующем примере:
[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
[Export ("drawAtPoint:withFont:")]
CGSize DrawString (CGPoint point, UIFont font);
}
Списки аргументов привязки Objective-C
Objective-C поддерживает аргументы variadic. Например:
- (void) appendWorkers:(XWorker *) firstWorker, ...
NS_REQUIRES_NIL_TERMINATION ;
Чтобы вызвать этот метод из C#, необходимо создать подпись следующим образом:
[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)
Этот метод объявляется внутренним, скрывая приведенный выше API от пользователей, но предоставляя его библиотеке. Затем можно написать такой метод:
public void AppendWorkers(params Worker[] workers)
{
if (workers is null)
throw new ArgumentNullException ("workers");
var pNativeArr = Marshal.AllocHGlobal(workers.Length * IntPtr.Size);
for (int i = 1; i < workers.Length; ++i)
Marshal.WriteIntPtr (pNativeArr, (i - 1) * IntPtr.Size, workers[i].Handle);
// Null termination
Marshal.WriteIntPtr (pNativeArr, (workers.Length - 1) * IntPtr.Size, IntPtr.Zero);
// the signature for this method has gone from (IntPtr, IntPtr) to (Worker, IntPtr)
WorkerManager.AppendWorkers(workers[0], pNativeArr);
Marshal.FreeHGlobal(pNativeArr);
}
Поля привязки
Иногда требуется получить доступ к общедоступным полям, объявленным в библиотеке.
Обычно эти поля содержат значения строк или целых чисел, на которые необходимо ссылаться. Они обычно используются в качестве строки, представляющей определенное уведомление и как ключи в словарях.
Чтобы привязать поле, добавьте свойство в файл определения интерфейса и украсите свойство атрибутом [Field]
. Этот атрибут принимает один параметр: имя C символа для поиска. Например:
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
Если вы хотите упаковать различные поля в статический класс, который не является производным NSObject
, можно использовать [Static]
Атрибут класса, как показано ниже:
[Static]
interface LonelyClass {
[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }
}
В приведенном выше примере создается объект LonelyClass
, который не является производным от NSObject
и будет содержать привязку к NSSomeEventNotification
NSString
предоставляемым в качестве NSString
объекта.
Атрибут [Field]
можно применить к следующим типам данных:
NSString
ссылки (только свойства только для чтения)NSArray
ссылки (только свойства только для чтения)- 32-разрядные ints (
System.Int32
) - 64-разрядные ints (
System.Int64
) - 32-разрядные с плавающей запятой (
System.Single
) - 64-разрядные с плавающей запятой (
System.Double
) System.Drawing.SizeF
CGSize
Помимо имени собственного поля можно указать имя библиотеки, в которой находится поле, передав имя библиотеки:
[Static]
interface LonelyClass {
[Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
NSString SomeSharedLibrarySymbol { get; }
}
Если вы связываете статически, нет библиотеки для привязки, поэтому необходимо использовать __Internal
имя:
[Static]
interface LonelyClass {
[Field ("MyFieldFromALibrary", "__Internal")]
NSString MyFieldFromALibrary { get; }
}
Перечисления привязки
Вы можете добавить enum
непосредственно в файлы привязки, чтобы упростить их использование внутри определений API без использования другого исходного файла (который необходимо скомпилировать как в привязках, так и в окончательном проекте).
Пример:
[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}
interface MyType {
[Export ("initWithEnum:")]
IntPtr Constructor (MyEnum value);
}
Также можно создать собственные перечисления для замены NSString
констант. В этом случае генератор автоматически создаст методы для преобразования значений перечисления и констант NSString.
Пример:
enum NSRunLoopMode {
[DefaultEnumValue]
[Field ("NSDefaultRunLoopMode")]
Default,
[Field ("NSRunLoopCommonModes")]
Common,
[Field (null)]
Other = 1000
}
interface MyType {
[Export ("performForMode:")]
void Perform (NSString mode);
[Wrap ("Perform (mode.GetConstant ())")]
void Perform (NSRunLoopMode mode);
}
В приведенном выше примере можно решить декорировать void Perform (NSString mode);
атрибутом [Internal]
. Это приведет к скрытию API на основе констант от потребителей привязки.
Однако это ограничивает подклассы типа в качестве альтернативы более удобному API использует [Wrap]
атрибут. Созданные методы не virtual
являются , т. е. вы не сможете переопределить их - что может, или нет, быть хорошим выбором.
Альтернативой является пометка исходного определения на NSString
основе определения [Protected]
. Это позволит работать подклассам, если это необходимо, и версия оболочки по-прежнему будет работать и вызывать метод переопределения.
Привязка NSValue
, NSNumber
и NSString
к лучшему типу
Атрибут [BindAs]
разрешает привязку NSNumber
и NSValue
NSString
(перечисления) в более точные типы C#. Атрибут можно использовать для создания более точного, более точного API .NET через собственный API.
Методы можно декорировать (при возвращаемом значении), параметрами и свойствами [BindAs]
.
Единственное ограничение заключается в том, что член не должен находиться внутри [Protocol]
или [Model]
интерфейс.
Например:
[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);
Выходные данные:
[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }
Внутренне мы будем делать bool?
преобразования ->NSNumber
и CGRect
<->NSValue
преобразования.<
[BindAs]
также поддерживает массивы NSNumber
NSValue
и NSString
(перечисления).
Например:
[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }
Выходные данные:
[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }
CAScroll
NSString
— это резервная перечисление, мы извлеким правильное NSString
значение и обработаем преобразование типов.
Ознакомьтесь [BindAs]
с документацией по поддерживаемым типам преобразования.
Уведомления о привязке
Уведомления — это сообщения, которые отправляются в приложение NSNotificationCenter.DefaultCenter
и используются в качестве механизма для трансляции сообщений из одной части приложения в другую. Разработчики обычно подписываются на уведомления с помощью метода AddObserver центра NSNotificationCenter. Когда приложение отправляет сообщение в центр уведомлений, обычно оно содержит полезные данные, хранящиеся в словаре NSNotification.UserInfo . Этот словарь слабо типичен, и получение информации из нее подвержено ошибкам, так как пользователям обычно необходимо прочитать в документации, какие ключи доступны в словаре и типы значений, которые могут храниться в словаре. Иногда наличие ключей используется как логическое значение.
Генератор привязки Xamarin.iOS обеспечивает поддержку для разработчиков для привязки уведомлений. Для этого установите [Notification]
атрибут для свойства, который также был помечен с помощью атрибута [Field]
свойство (оно может быть общедоступным или частным).
Этот атрибут можно использовать без аргументов для уведомлений, не имеющих полезных данных, или можно указать System.Type
, что ссылается на другой интерфейс в определении API, как правило, с именем, заканчивающимся "EventArgs". Генератор преобразует интерфейс в класс, который подклассы EventArgs
и будет включать все свойства, перечисленные там. Атрибут [Export]
должен использоваться в классе EventArgs для перечисления имени ключа, используемого для поиска Objective-C словаря, чтобы получить значение.
Например:
interface MyClass {
[Notification]
[Field ("MyClassDidStartNotification")]
NSString DidStartNotification { get; }
}
Приведенный выше код создаст вложенный класс MyClass.Notifications
со следующими методами:
public class MyClass {
[..]
public Notifications {
public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
}
}
Пользователи кода могут легко подписаться на уведомления, опубликованные в NSDefaultCenter , используя следующий код:
var token = MyClass.Notifications.ObserverDidStart ((notification) => {
Console.WriteLine ("Observed the 'DidStart' event!");
});
Возвращаемое значение ObserveDidStart
можно использовать для простого прекращения получения уведомлений, как показано ниже.
token.Dispose ();
Или можно вызвать NSNotification.DefaultCenter.RemoveObserver и передать маркер. Если уведомление содержит параметры, необходимо указать вспомогательный EventArgs
интерфейс, как показано ниже.
interface MyClass {
[Notification (typeof (MyScreenChangedEventArgs)]
[Field ("MyClassScreenChangedNotification")]
NSString ScreenChangedNotification { get; }
}
// The helper EventArgs declaration
interface MyScreenChangedEventArgs {
[Export ("ScreenXKey")]
nint ScreenX { get; set; }
[Export ("ScreenYKey")]
nint ScreenY { get; set; }
[Export ("DidGoOffKey")]
[ProbePresence]
bool DidGoOff { get; }
}
В приведенном выше примере будет создан MyScreenChangedEventArgs
класс с ScreenX
свойствами, ScreenY
которые будут получать данные из словаря NSNotification.UserInfo , используя ключи ScreenXKey и ScreenYKey соответственно и применить соответствующие преобразования. Атрибут [ProbePresence]
используется для проверки генератора, если ключ задан в объекте UserInfo
, а не пытается извлечь значение. Это используется для случаев, когда наличие ключа является значением (обычно для логических значений).
Это позволяет писать код следующим образом:
var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});
Категории привязки
Категории — это механизм, используемый Objective-C для расширения набора методов и свойств, доступных в классе. На практике они используются для расширения функциональных возможностей базового класса (например NSObject
, при связывании определенной платформы), что делает свои методы доступными, но только если новая платформа связана UIKit
. В некоторых других случаях они используются для упорядочивания функций в классе по функциям. Они похожи на методы расширения C#. Это то, что категория будет выглядеть следующим образом:Objective-C
@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end
Приведенный выше пример, если найден в библиотеке, расширит экземпляры UIView
с помощью метода makeBackgroundRed
.
Для привязки [Category]
этих атрибутов можно использовать в определении интерфейса. При использовании [Category]
атрибут, значение атрибута [BaseType]
Изменения атрибутов от использования для указания базового класса для расширения, чтобы быть типом для расширения.
Ниже показано, как UIView
расширения привязаны и преобразуются в методы расширения C#:
[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
[Export ("makeBackgroundRed")]
void MakeBackgroundRed ();
}
В приведенном выше примере будет создан MyUIViewExtension
класс, содержащий MakeBackgroundRed
метод расширения. Это означает, что теперь вы можете вызвать "MakeBackgroundRed" на любом UIView
подклассе, предоставляя вам те же функции, что и вы получите Objective-C. В некоторых других случаях категории используются не для расширения системного класса, а для организации функциональных возможностей, исключительно для украшения. Пример:
@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end
@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end
Хотя можно использовать [Category]
атрибут также для этого стиля оформления объявлений можно также добавить все в определение класса. Оба из них будут достичь одного и того же:
[BaseType (typeof (NSObject))]
interface SocialNetworking {
}
[Category]
[BaseType (typeof (SocialNetworking))]
interface Twitter {
[Export ("postToTwitter:")]
void PostToTwitter (Message message);
}
[Category]
[BaseType (typeof (SocialNetworking))]
interface Facebook {
[Export ("postToFacebook:andPicture:")]
void PostToFacebook (Message message, UIImage picture);
}
Это просто короче в этих случаях для объединения категорий:
[BaseType (typeof (NSObject))]
interface SocialNetworking {
[Export ("postToTwitter:")]
void PostToTwitter (Message message);
[Export ("postToFacebook:andPicture:")]
void PostToFacebook (Message message, UIImage picture);
}
Блоки привязки
Блоки — это новая конструкция, представленная Apple для привлечения функциональных эквивалентов анонимных методов Objective-CC#. Например, NSSet
класс теперь предоставляет этот метод:
- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block
Приведенное выше описание объявляет метод, который enumerateObjectsUsingBlock:
принимает один аргумент с именем block
. Этот блок аналогичен анонимному методу C# в том, что он поддерживает запись текущей среды (указатель "this", доступ к локальным переменным и параметрам). Приведенный выше метод вызывает NSSet
блок с двумя параметрами NSObject
( id obj
частью) и указателем на логическое значение (часть BOOL *stop
).
Чтобы привязать этот вид API к btouch, сначала необходимо объявить подпись типа блока в качестве делегата C#, а затем ссылаться на нее из точки входа API, как показано ниже.
// This declares the callback signature for the block:
delegate void NSSetEnumerator (NSObject obj, ref bool stop)
// Later, inside your definition, do this:
[Export ("enumerateObjectUsingBlock:")]
void Enumerate (NSSetEnumerator enum)
Теперь код может вызвать функцию из C#:
var myset = new NSMutableSet ();
myset.Add (new NSString ("Foo"));
s.Enumerate (delegate (NSObject obj, ref bool stop){
Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});
Вы также можете использовать лямбда-коды, если вы предпочитаете:
var myset = new NSMutableSet ();
mySet.Add (new NSString ("Foo"));
s.Enumerate ((obj, stop) => {
Console.WriteLine ("The first object is: {0} and stop is: {1}", obj, stop);
});
Асинхронные методы
Генератор привязки может превратить определенный класс методов в асинхронные методы (методы, возвращающие задачу или задачу<T>).
Вы можете использовать ,[Async]
атрибут для методов, возвращающих void и чьи последние аргументы являются обратным вызовом. При применении этого метода генератор привязки создаст версию этого метода с суффиксом Async
. Если обратный вызов не принимает параметров, возвращаемое значение будет Task
иметь значение, если обратный вызов принимает параметр, результат будет результатом Task<T>
. Если обратный вызов принимает несколько параметров, необходимо задать ResultType
или ResultTypeName
указать требуемое имя созданного типа, которое будет содержать все свойства.
Пример:
[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);
Приведенный выше код создаст как метод LoadFile, так и:
[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);
Отображение сильных типов для слабых параметров NSDictionary
Во многих местах в Objective-C API параметры передаются как слабо типизированные NSDictionary
API с определенными ключами и значениями, но они подвержены ошибкам (вы можете передать недопустимые ключи и не получать предупреждения; вы можете передать недопустимые значения и не получать предупреждения) и страстить использовать, так как им требуется несколько поездок в документацию для поиска возможных ключевых имен и значений.
Решение заключается в предоставлении строго типизированной версии, которая предоставляет строго типизированную версию API и за кулисами сопоставляет различные базовые ключи и значения.
Например, если Objective-C API принял и NSDictionary
задокументирован как принимая ключ XyzVolumeKey
, который принимает NSNumber
значение тома от 0.0 до 1.0 и XyzCaptionKey
строку, вы хотите, чтобы пользователи имели хороший API, который выглядит следующим образом:
public class XyzOptions {
public nfloat? Volume { get; set; }
public string Caption { get; set; }
}
Свойство Volume
определяется как nullable float, так как в соглашении Objective-C не требуется, чтобы эти словари имели значение, поэтому существуют сценарии, в которых значение может не быть задано.
Для этого необходимо выполнить несколько действий.
- Создайте строго типизированный класс, который подклассы DictionaryContainer и предоставляют различные методы получения и задания для каждого свойства.
- Объявите перегрузки для методов, которые принимают
NSDictionary
новую строго типизированную версию.
Можно создать строго типизированный класс вручную или использовать генератор для выполнения работы. Сначала мы рассмотрим, как это сделать вручную, чтобы понять, что происходит, а затем автоматический подход.
Для этого необходимо создать вспомогательный файл, он не входит в API контракта. Это то, что необходимо написать для создания класса XyzOptions:
public class XyzOptions : DictionaryContainer {
# if !COREBUILD
public XyzOptions () : base (new NSMutableDictionary ()) {}
public XyzOptions (NSDictionary dictionary) : base (dictionary){}
public nfloat? Volume {
get { return GetFloatValue (XyzOptionsKeys.VolumeKey); }
set { SetNumberValue (XyzOptionsKeys.VolumeKey, value); }
}
public string Caption {
get { return GetStringValue (XyzOptionsKeys.CaptionKey); }
set { SetStringValue (XyzOptionsKeys.CaptionKey, value); }
}
# endif
}
Затем необходимо предоставить метод оболочки, который отображает высокоуровневый API на вершине низкоуровневого API.
[BaseType (typeof (NSObject))]
interface XyzPanel {
[Export ("playback:withOptions:")]
void Playback (string fileName, [NullAllowed] NSDictionary options);
[Wrap ("Playback (fileName, options?.Dictionary")]
void Playback (string fileName, XyzOptions options);
}
Если API не требуется перезаписывать, вы можете безопасно скрыть API на основе NSDictionary с помощью [Internal]
.
Как видите, мы используем [Wrap]
атрибут для поверхности новой точки входа API, и мы обнастроим ее с помощью строго типизированного XyzOptions
класса. Метод-оболочка также позволяет передавать значение NULL.
Теперь, одна вещь, которую мы не упомянули, заключается в том, откуда XyzOptionsKeys
пришли значения. Обычно вы группируете ключи, которые поверхность API в статическом классе, как XyzOptionsKeys
показано ниже:
[Static]
class XyzOptionKeys {
[Field ("kXyzVolumeKey")]
NSString VolumeKey { get; }
[Field ("kXyzCaptionKey")]
NSString CaptionKey { get; }
}
Давайте рассмотрим автоматическую поддержку создания этих строго типизированных словарей. Это позволяет избежать большого количества шаблонов, и вы можете определить словарь непосредственно в контракте API, а не использовать внешний файл.
Чтобы создать строго типизированный словарь, введите интерфейс в API и украшайте его атрибутом StrongDictionary . Это сообщает генератору, что он должен создать класс с тем же именем, что и интерфейс, который будет производным от DictionaryContainer
него и предоставит для него надежные типизированные методы доступа.
Атрибут [StrongDictionary]
принимает один параметр, являющийся именем статического класса, содержащего ключи словаря. Затем каждое свойство интерфейса станет строго типизированным методом доступа. По умолчанию код будет использовать имя свойства с суффиксом Key в статичном классе для создания метода доступа.
Это означает, что для создания строго типизированного метода доступа больше не требуется внешний файл, а также не требуется вручную создавать методы получения и задания для каждого свойства, а также не запрашивать ключи вручную.
Это то, что будет выглядеть вся привязка:
[Static]
class XyzOptionKeys {
[Field ("kXyzVolumeKey")]
NSString VolumeKey { get; }
[Field ("kXyzCaptionKey")]
NSString CaptionKey { get; }
}
[StrongDictionary ("XyzOptionKeys")]
interface XyzOptions {
nfloat Volume { get; set; }
string Caption { get; set; }
}
[BaseType (typeof (NSObject))]
interface XyzPanel {
[Export ("playback:withOptions:")]
void Playback (string fileName, [NullAllowed] NSDictionary options);
[Wrap ("Playback (fileName, options?.Dictionary")]
void Playback (string fileName, XyzOptions options);
}
Если вам нужно ссылаться в XyzOption
элементах другого поля (то есть не имя свойства с суффиксом Key
), можно декорировать свойство с помощью [Export]
атрибут с именем, которое вы хотите использовать.
Сопоставления типов
В этом разделе описывается Objective-C сопоставление типов с типами C#.
Простые типы
В следующей таблице показано, как сопоставлять типы из мира CocoaTouch с Objective-C миром Xamarin.iOS:
Objective-C имя типа | Тип единого API Xamarin.iOS |
---|---|
BOOL , GLboolean |
bool |
NSInteger |
nint |
NSUInteger |
nuint |
CFTimeInterval / NSTimeInterval |
double |
NSString (дополнительные возможности привязки NSString) |
string |
char * |
string (см. также: [PlainString] ) |
CGRect |
CGRect |
CGPoint |
CGPoint |
CGSize |
CGSize |
CGFloat , GLfloat |
nfloat |
Типы CoreFoundation (CF* ) |
CoreFoundation.CF* |
GLint |
nint |
GLfloat |
nfloat |
Типы фундаментов (NS* ) |
Foundation.NS* |
id |
Foundation .NSObject |
NSGlyph |
nint |
NSSize |
CGSize |
NSTextAlignment |
UITextAlignment |
SEL |
ObjCRuntime.Selector |
dispatch_queue_t |
CoreFoundation.DispatchQueue |
CFTimeInterval |
double |
CFIndex |
nint |
NSGlyph |
nuint |
Массивы
Среда выполнения Xamarin.iOS автоматически заботится о преобразовании массивов NSArrays
C# в и обратном выполнении преобразования, поэтому, например, мнимый Objective-C метод, возвращающий следующие UIViews
NSArray
значения:
// Get the peer views - untyped
- (NSArray *)getPeerViews ();
// Set the views for this container
- (void) setViews:(NSArray *) views
Связан следующим образом:
[Export ("getPeerViews")]
UIView [] GetPeerViews ();
[Export ("setViews:")]
void SetViews (UIView [] views);
Идея заключается в том, чтобы использовать строго типизированный массив C#, так как это позволит интегрированной среде разработки обеспечить правильное завершение кода с фактическим типом, не заставляя пользователя угадывать, или искать документацию, чтобы узнать фактический тип объектов, содержащихся в массиве.
В случаях, когда вы не можете отслеживать фактический наиболее производный тип, содержащийся в массиве, можно использовать NSObject []
в качестве возвращаемого значения.
Селекторы
Селекторы отображаются в Objective-C API в качестве специального типа SEL
. При привязке селектора тип сопоставлялся с ObjCRuntime.Selector
. Обычно селекторы предоставляются в API как с объектом, целевым объектом, так и селектором для вызова в целевом объекте. Предоставление обоих из них в основном соответствует делегату C#: то, что инкапсулирует как метод для вызова, так и объекта для вызова метода.
Это то, как выглядит привязка:
interface Button {
[Export ("setTarget:selector:")]
void SetTarget (NSObject target, Selector sel);
}
И вот как метод обычно будет использоваться в приложении:
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
b.SetTarget (this, new Selector ("print"));
}
[Export ("print")]
void ThePrintMethod ()
{
// This does the printing
}
}
Чтобы сделать привязку более удобной NSAction
для разработчиков C#, обычно предоставляется метод, принимающий параметр, который позволяет делегатам И лямбда-файлам C# использовать вместо него Target+Selector
. Для этого, как правило, можно скрыть SetTarget
метод, пометив его с помощью [Internal]
атрибут, а затем вы предоставите новый вспомогательный метод, как показано ниже:
// API.cs
interface Button {
[Export ("setTarget:selector:"), Internal]
void SetTarget (NSObject target, Selector sel);
}
// Extensions.cs
public partial class Button {
public void SetTarget (NSAction callback)
{
SetTarget (new NSActionDispatcher (callback), NSActionDispatcher.Selector);
}
}
Теперь ваш пользовательский код можно написать следующим образом:
class DialogPrint : UIViewController {
void HookPrintButton (Button b)
{
// First Style
b.SetTarget (ThePrintMethod);
// Lambda style
b.SetTarget (() => { /* print here */ });
}
void ThePrintMethod ()
{
// This does the printing
}
}
Строки
При привязке метода, который принимает NSString
, можно заменить его типом строки C#, как для возвращаемых типов, так и параметров.
Единственным случаем, когда может потребоваться использовать прямую NSString
строку, является то, когда строка используется в качестве маркера. Дополнительные сведения о строках и NSString
см. в документе NSString в конструкторе API.
В некоторых редких случаях API может предоставлять строку типа C (char *
) вместо Objective-C строки (NSString *
). В таких случаях можно анонимировать параметр с помощью Атрибут [PlainString]
.
Параметры out/ref
Некоторые API возвращают значения в своих параметрах или передают параметры по ссылке.
Обычно подпись выглядит следующим образом:
- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref
В первом примере показан общий Objective-C идиом для возврата кодов ошибок, передается указатель на NSError
указатель и при возврате значения задано. Второй метод показывает, как Objective-C метод может принимать объект и изменять его содержимое. Это будет проход по ссылке, а не чистое выходное значение.
Ваша привязка будет выглядеть следующим образом:
[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);
Атрибуты управления памятью
При использовании [Export]
атрибута и передачи данных, которые будут храниться вызываемым методом, можно указать семантику аргумента, передав его в качестве второго параметра, например:
[Export ("method", ArgumentSemantic.Retain)]
Приведенный выше параметр помечает значение как семантику "Сохранить". Доступны семантики:
- Назначить
- Копия
- Сохранять
Рекомендации по стилю
Использование [internal]
Вы можете использовать ,[Internal]
атрибут для скрытия метода из общедоступного API. Это может потребоваться сделать в тех случаях, когда предоставляемый API слишком низкоуровневый, и вы хотите предоставить высокоуровневую реализацию в отдельном файле на основе этого метода.
Это также можно использовать при выполнении ограничений в генераторе привязки, например некоторые расширенные сценарии могут предоставлять типы, которые не привязаны, и вы хотите привязать самостоятельно, и вы хотите упаковать эти типы самостоятельно.
Обработчики событий и обратные вызовы
Objective-C классы обычно широковещательные уведомления или запросы, отправляя сообщение в класс делегата (Objective-C делегат).
Эта модель, хотя и полностью поддерживается и отображается Xamarin.iOS, иногда может быть громоздкой. Xamarin.iOS предоставляет шаблон событий C# и систему обратного вызова метода в классе, который можно использовать в этих ситуациях. Это позволяет выполнять следующий код:
button.Clicked += delegate {
Console.WriteLine ("I was clicked");
};
Генератор привязок может уменьшить количество типов, необходимых для сопоставления Objective-C шаблона с шаблоном C#.
Начиная с версии Xamarin.iOS 1.4, можно также указать генератору создавать привязки для определенных Objective-C делегатов и предоставлять делегату как события и свойства C# в типе узла.
Существует два класса, участвующих в этом процессе, класс узла, который будет тем, который в настоящее время выдает события и отправляет их в Delegate
или WeakDelegate
фактический класс делегата.
Учитывая следующую настройку:
[BaseType (typeof (NSObject))]
interface MyClass {
[Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
NSObject WeakDelegate { get; set; }
[Wrap ("WeakDelegate")][NullAllowed]
MyClassDelegate Delegate { get; set; }
}
[BaseType (typeof (NSObject))]
interface MyClassDelegate {
[Export ("loaded:bytes:")]
void Loaded (MyClass sender, int bytes);
}
Чтобы упаковать класс, необходимо выполнить следующее:
- В классе узла добавьте к вашему
[BaseType]
объявление типа, который выступает в качестве делегата и имени C#, который вы предоставили. В нашем примере выше этоtypeof (MyClassDelegate)
иWeakDelegate
соответственно. - В классе делегата для каждого метода, имеющего более двух параметров, необходимо указать тип, который вы хотите использовать для автоматически созданного класса EventArgs.
Генератор привязок не ограничивается только одним назначением события, возможно, что некоторые Objective-C классы отправляют сообщения нескольким делегатам, поэтому необходимо предоставить массивы для поддержки этой установки. Большинству настроек не потребуется, но генератор готов к поддержке этих случаев.
Результирующий код будет иметь следующий код:
[BaseType (typeof (NSObject),
Delegates=new string [] {"WeakDelegate"},
Events=new Type [] { typeof (MyClassDelegate) })]
interface MyClass {
[Export ("delegate", ArgumentSemantic.Assign)][NullAllowed]
NSObject WeakDelegate { get; set; }
[Wrap ("WeakDelegate")][NullAllowed]
MyClassDelegate Delegate { get; set; }
}
[BaseType (typeof (NSObject))]
interface MyClassDelegate {
[Export ("loaded:bytes:"), EventArgs ("MyClassLoaded")]
void Loaded (MyClass sender, int bytes);
}
Используется EventArgs
для указания имени создаваемого EventArgs
класса. Вы должны использовать одну сигнатуру (в этом примере EventArgs
будет содержать With
свойство типа nint).
В приведенных выше определениях генератор создаст следующее событие в созданном MyClass:
public MyClassLoadedEventArgs : EventArgs {
public MyClassLoadedEventArgs (nint bytes);
public nint Bytes { get; set; }
}
public event EventHandler<MyClassLoadedEventArgs> Loaded {
add; remove;
}
Таким образом, теперь можно использовать следующий код:
MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};
Обратные вызовы так же, как вызовы событий, разница заключается в том, что вместо наличия нескольких потенциальных подписчиков (например, несколько методов могут подключаться к Clicked
событию или DownloadFinished
событию) обратные вызовы могут иметь только одного подписчика.
Процесс идентичен, единственное различие заключается в том, что вместо предоставления имени класса, который будет создан, EventArgs фактически используется для имени полученного имени делегата EventArgs
C#.
Если метод в классе делегата возвращает значение, генератор привязки сопоставляет этот метод с методом делегата в родительском классе вместо события. В этих случаях необходимо указать значение по умолчанию, которое должно быть возвращено методом, если пользователь не подключается к делегату. Это можно сделать с помощью [DefaultValue]
или [DefaultValueFromArgument]
атрибуты.
[DefaultValue]
будет жестко кодировать возвращаемое значение, в то время как [DefaultValueFromArgument]
используется для указания возвращаемого входного аргумента.
Перечисления и базовые типы
Можно также ссылаться на перечисления или базовые типы, которые не поддерживаются непосредственно системой определения интерфейса btouch. Для этого поместите перечисления и основные типы в отдельный файл и включите его в состав одного из дополнительных файлов, которые вы предоставляете для btouch.
Связывание зависимостей
Если вы являетесь интерфейсами API привязки, которые не являются частью приложения, необходимо убедиться, что исполняемый файл связан с этими библиотеками.
Необходимо сообщить Xamarin.iOS, как связать библиотеки, это можно сделать, изменив конфигурацию сборки, чтобы вызвать mtouch
команду с некоторыми дополнительными аргументами сборки, которые указывают, как связаться с новыми библиотеками с помощью параметра "-gcc_flags", а затем кавычки, которая содержит все дополнительные библиотеки, необходимые для вашей программы, Типа того:
-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"
Приведенный выше пример будет ссылаться libMyLibrary.a
libSystemLibrary.dylib
на библиотеку платформы CFNetwork
в окончательный исполняемый файл.
Кроме того, вы можете воспользоваться преимуществами уровня [LinkWithAttribute]
сборки, которые можно внедрить в файлы контракта (например AssemblyInfo.cs
, ).
При использовании [LinkWithAttribute]
собственной библиотеки необходимо будет иметь доступ к собственной библиотеке в то время, когда вы сделаете привязку, так как это встраит собственную библиотеку в приложение. Например:
// Specify only the library name as a constructor argument and specify everything else with properties:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget = LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]
// Or you can specify library name *and* link target as constructor arguments:
[assembly: LinkWith ("libMyLibrary.a", LinkTarget.ArmV6 | LinkTarget.ArmV7 | LinkTarget.Simulator, ForceLoad = true, IsCxx = true)]
Возможно, вам интересно, почему вам нужна -force_load
команда, и причина в том, что флаг -ObjC, хотя он компилирует код, он не сохраняет метаданные, необходимые для поддержки категорий (компоновщик или компилятор мертвецов кода удаляет его), необходимых во время выполнения для Xamarin.iOS.
Справочные ссылки
Некоторые временные объекты, такие как листы действий и поля оповещений, являются громоздкими, чтобы отслеживать разработчиков и генератор привязок может помочь немного здесь.
Например, если у вас был класс, который показал сообщение, а затем создал Done
событие, традиционный способ обработки будет:
class Demo {
MessageBox box;
void ShowError (string msg)
{
box = new MessageBox (msg);
box.Done += { box = null; ... };
}
}
В приведенном выше сценарии разработчику необходимо сохранить ссылку на сам объект и либо утечку, либо активно очистить ссылку для поля самостоятельно. Хотя код привязки генератор поддерживает отслеживание ссылки для вас и очищает его при вызове специального метода, приведенный выше код станет следующим:
class Demo {
void ShowError (string msg)
{
var box = new MessageBox (msg);
box.Done += { ... };
}
}
Обратите внимание, что больше не требуется хранить переменную в экземпляре, что она работает с локальной переменной и что не нужно очищать ссылку при смерти объекта.
Чтобы воспользоваться преимуществами этого, класс должен иметь свойство Events, заданное в [BaseType]
объявлении, а также KeepUntilRef
переменную, заданную именем метода, вызываемого при завершении работы объекта, как показано ниже.
[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
[Export ("show")]
void Show (string message);
}
Наследование протоколов
Начиная с Xamarin.iOS версии 3.2 мы поддерживаем наследование от протоколов, помеченных свойством [Model]
. Это полезно в определенных шаблонах API, например MapKit
в тех случаях, когда MKOverlay
протокол наследуется от MKAnnotation
протокола и принимается рядом классов, наследуемых от NSObject
.
Исторически мы требовали копирования протокола в каждую реализацию, но в таких случаях теперь класс наследуется MKShape
от MKOverlay
протокола, и он автоматически создаст все необходимые методы.