Bagikan melalui


Pustaka pengikatan Objective-C

Saat bekerja dengan Xamarin.iOS atau Xamarin.Mac, Anda mungkin mengalami kasus di mana Anda ingin menggunakan pustaka pihak Objective-C ketiga. Dalam situasi tersebut, Anda dapat menggunakan Proyek Pengikatan Xamarin untuk membuat pengikatan C# ke pustaka asli Objective-C . Proyek ini menggunakan alat yang sama yang kami gunakan untuk membawa API iOS dan Mac ke C#.

Dokumen ini menjelaskan cara mengikat Objective-C API, jika Anda hanya mengikat API C, Anda harus menggunakan mekanisme .NET standar untuk ini, kerangka kerja P/Panggil. Detail tentang cara menautkan pustaka C secara statis tersedia di halaman Menautkan Pustaka Asli.

Lihat Panduan Referensi Jenis Pengikatan pendamping kami. Selain itu, jika Anda ingin mempelajari lebih lanjut tentang apa yang terjadi di bawah tenda, periksa halaman Gambaran Umum Pengikatan kami.

Pengikatan dapat dibuat untuk pustaka iOS dan Mac. Halaman ini menjelaskan cara mengerjakan pengikatan iOS, namun pengikatan Mac sangat mirip.

Kode Sampel untuk iOS

Anda dapat menggunakan proyek Sampel Pengikatan iOS untuk bereksperimen dengan pengikatan.

Memulai

Cara term mudah untuk membuat pengikatan adalah dengan membuat Proyek Pengikatan Xamarin.iOS. Anda dapat melakukan ini dari Visual Studio untuk Mac dengan memilih jenis proyek, Pustaka Pengikatan Pustaka > iOS>:

Lakukan ini dari Visual Studio untuk Mac dengan memilih jenis proyek, Pustaka Pengikatan Pustaka iOS

Proyek yang dihasilkan berisi templat kecil yang dapat Anda edit, berisi dua file: ApiDefinition.cs dan StructsAndEnums.cs.

Di ApiDefinition.cs sinilah Anda akan menentukan kontrak API, ini adalah file yang menjelaskan bagaimana API yang mendasar Objective-C diproyeksikan ke dalam C#. Sintaksis dan isi file ini adalah topik utama diskusi dokumen ini dan isinya terbatas pada antarmuka C# dan deklarasi delegasi C#. File StructsAndEnums.cs adalah file tempat Anda akan memasukkan definisi apa pun yang diperlukan oleh antarmuka dan delegasi. Ini termasuk nilai dan struktur enumerasi yang mungkin digunakan kode Anda.

Mengikat API

Untuk melakukan pengikatan komprehensif, Anda ingin memahami Objective-C definisi API dan membiasakan diri dengan pedoman .NET Framework Design.

Untuk mengikat pustaka, Anda biasanya akan memulai dengan file definisi API. File definisi API hanyalah file sumber C# yang berisi antarmuka C# yang telah diannotasi dengan beberapa atribut yang membantu mendorong pengikatan. File inilah yang mendefinisikan apa kontrak antara C# dan Objective-C .

Misalnya, ini adalah file api sepele untuk pustaka:

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);
  }
}

Sampel di atas mendefinisikan kelas yang disebut Cocos2D.Camera yang berasal dari NSObject jenis dasar (jenis ini berasal dari Foundation.NSObject) dan yang menentukan properti statis (ZEye), dua metode yang tidak mengambil argumen dan metode yang mengambil tiga argumen.

Diskusi mendalam tentang format file API dan atribut yang dapat Anda gunakan tercakup dalam bagian file definisi API di bawah ini.

Untuk menghasilkan pengikatan lengkap, Anda biasanya akan berurusan dengan empat komponen:

  • File definisi API (ApiDefinition.cs dalam templat).
  • Opsional: enum, jenis, struct apa pun yang diperlukan oleh file definisi API (StructsAndEnums.cs dalam templat).
  • Opsional: sumber tambahan yang mungkin memperluas pengikatan yang dihasilkan, atau menyediakan API yang lebih ramah C# (file C# apa pun yang Anda tambahkan ke proyek).
  • Pustaka asli yang Anda ikat.

Bagan ini memperlihatkan hubungan antara file:

Bagan ini memperlihatkan hubungan antara file

File Definisi API hanya akan berisi namespace layanan dan definisi antarmuka (dengan anggota apa pun yang dapat dimuat antarmuka), dan tidak boleh berisi kelas, enumerasi, delegasi, atau struktur. File definisi API hanyalah kontrak yang akan digunakan untuk menghasilkan API.

Kode tambahan apa pun yang Anda butuhkan seperti enumerasi atau kelas pendukung harus dihosting pada file terpisah, dalam contoh di atas "Kamera Mode" adalah nilai enumerasi yang tidak ada dalam file CS dan harus dihosting dalam file terpisah, misalnya StructsAndEnums.cs:

public enum CameraMode {
    FlyOver, Back, Follow
}

File APIDefinition.cs dikombinasikan dengan StructsAndEnum kelas dan digunakan untuk menghasilkan pengikatan inti pustaka. Anda dapat menggunakan pustaka yang dihasilkan apa adanya, tetapi biasanya, Anda ingin menyetel pustaka yang dihasilkan untuk menambahkan beberapa fitur C# untuk kepentingan pengguna Anda. Beberapa contoh termasuk menerapkan ToString() metode, menyediakan pengindeks C#, menambahkan konversi implisit ke dan dari beberapa jenis asli atau menyediakan versi yang sangat di ketik dari beberapa metode. Peningkatan ini disimpan dalam file C# tambahan. Hanya menambahkan file C# ke proyek Anda dan file tersebut akan disertakan dalam proses build ini.

Ini menunjukkan bagaimana Anda akan menerapkan kode dalam file Anda Extra.cs . Perhatikan bahwa Anda akan menggunakan kelas parsial karena ini menambah kelas parsial yang dihasilkan dari kombinasi ApiDefinition.cs dan StructsAndEnums.cs pengikatan inti:

public partial class Camera {
    // Provide a ToString method
    public override string ToString ()
    {
         return String.Format ("ZEye: {0}", ZEye);
    }
}

Membangun pustaka akan menghasilkan pengikatan asli Anda.

Untuk menyelesaikan pengikatan ini, Anda harus menambahkan pustaka asli ke proyek. Anda dapat melakukan ini dengan menambahkan pustaka asli ke proyek Anda, baik dengan menyeret dan menjatuhkan pustaka asli dari Finder ke proyek di penjelajah solusi, atau dengan mengklik kanan proyek dan memilih Tambahkan>Tambahkan File untuk memilih pustaka asli. Pustaka asli menurut konvensi dimulai dengan kata "lib" dan diakhir dengan ekstensi ".a". Ketika Anda melakukan ini, Visual Studio untuk Mac akan menambahkan dua file: file .a dan file C# yang diisi secara otomatis yang berisi informasi tentang apa yang dimuat pustaka asli:

Pustaka asli menurut konvensi dimulai dengan kata lib dan diakhir dengan ekstensi .a

Konten libMagicChord.linkwith.cs file memiliki informasi tentang bagaimana pustaka ini dapat digunakan dan menginstruksikan IDE Anda untuk mengemas biner ini ke dalam file DLL yang dihasilkan:

using System;
using ObjCRuntime;

[assembly: LinkWith ("libMagicChord.a", SmartLink = true, ForceLoad = true)]

Detail lengkap tentang cara menggunakan [LinkWith] atribut didokumenkan dalam Panduan Referensi Jenis Pengikatan.

Sekarang ketika Anda membangun proyek, Anda akan berakhir dengan MagicChords.dll file yang berisi pengikatan dan pustaka asli. Anda dapat mendistribusikan proyek ini atau DLL yang dihasilkan ke pengembang lain untuk penggunaannya sendiri.

Terkadang Anda mungkin menemukan bahwa Anda memerlukan beberapa nilai enumerasi, mendelegasikan definisi, atau jenis lainnya. Jangan menempatkan mereka dalam file definisi API, karena ini hanyalah kontrak

File definisi API

File definisi API terdiri dari sejumlah antarmuka. Antarmuka dalam definisi API akan diubah menjadi deklarasi kelas dan harus didekorasi dengan [BaseType] atribut untuk menentukan kelas dasar untuk kelas .

Anda mungkin bertanya-tanya mengapa kami tidak menggunakan kelas alih-alih antarmuka untuk definisi kontrak. Kami memilih antarmuka karena memungkinkan kami menulis kontrak untuk metode tanpa harus menyediakan isi metode dalam file definisi API, atau harus menyediakan isi yang harus melempar pengecualian atau mengembalikan nilai yang bermakna.

Tetapi karena kami menggunakan antarmuka sebagai kerangka untuk menghasilkan kelas, kami harus menggunakan untuk mendekorasi berbagai bagian kontrak dengan atribut untuk mendorong pengikatan.

Metode pengikatan

Pengikatan paling sederhana yang dapat Anda lakukan adalah mengikat metode. Cukup deklarasikan metode dalam antarmuka dengan konvensi penamaan C# dan hiasi metode dengan Atribut [Export]. Atribut [Export] inilah yang menautkan nama C# Anda dengan Objective-C nama dalam runtime Xamarin.iOS. Parameter dari [Export] atribut adalah nama pemilih Objective-C . Beberapa contoh:

// 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);

Sampel di atas menunjukkan bagaimana Anda dapat mengikat metode instans. Untuk mengikat metode statis, Anda harus menggunakan [Static] atribut , seperti ini:

// A static method, that takes no arguments
[Static, Export ("beep")]
void Beep ();

Ini diperlukan karena kontrak adalah bagian dari antarmuka, dan antarmuka tidak memiliki gagasan deklarasi statis vs instans, sehingga perlu sekali lagi untuk menggunakan atribut. Jika Anda ingin menyembunyikan metode tertentu dari pengikatan, Anda dapat menghias metode dengan [Internal] atribut .

Perintah btouch-native akan memperkenalkan pemeriksaan agar parameter referensi tidak null. Jika Anda ingin mengizinkan nilai null untuk parameter tertentu, gunakan [NullAllowed] atribut pada parameter , seperti ini:

[Export ("setText:")]
string SetText ([NullAllowed] string text);

Saat mengekspor jenis referensi, dengan [Export] kata kunci Anda juga dapat menentukan semantik alokasi. Ini diperlukan untuk memastikan bahwa tidak ada data yang bocor.

Properti pengikatan

Sama seperti metode, Objective-C properti terikat menggunakan [Export] atribut dan petakan langsung ke properti C#. Sama seperti metode, properti dapat dihiasi dengan [Static] dan [Internal] Atribut.

Ketika Anda menggunakan [Export] atribut pada properti di bawah sampul btouch-native benar-benar mengikat dua metode: getter dan setter. Nama yang Anda berikan untuk diekspor adalah nama dasar dan setter dihitung dengan menambahkan kata "set", mengubah huruf pertama nama dasar menjadi huruf besar dan membuat pemilih mengambil argumen. Ini berarti bahwa diterapkan [Export ("label")] pada properti benar-benar mengikat metode "label" dan "setLabel:" Objective-C .

Objective-C Terkadang properti tidak mengikuti pola yang dijelaskan di atas dan namanya ditimpa secara manual. Dalam kasus tersebut, Anda dapat mengontrol cara pengikatan dihasilkan dengan menggunakan [Bind] atribut pada getter atau setter, misalnya:

[Export ("menuVisible")]
bool MenuVisible { [Bind ("isMenuVisible")] get; set; }

Ini kemudian mengikat "isMenuVisible" dan "setMenuVisible:". Secara opsional, properti dapat diikat menggunakan sintaks berikut:

[Category, BaseType(typeof(UIView))]
interface UIView_MyIn
{
  [Export ("name")]
  string Name();

  [Export("setName:")]
  void SetName(string name);
}

Di mana getter dan setter secara eksplisit didefinisikan seperti dalam name pengikatan dan setName di atas.

Selain dukungan untuk properti statis menggunakan [Static], Anda dapat menghias properti thread-static dengan [IsThreadStatic], misalnya:

[Export ("currentRunLoop")][Static][IsThreadStatic]
NSRunLoop Current { get; }

Sama seperti metode memungkinkan beberapa parameter ditandai dengan [NullAllowed], Anda dapat menerapkan [NullAllowed] ke properti untuk menunjukkan bahwa null adalah nilai yang valid untuk properti, misalnya:

[Export ("text"), NullAllowed]
string Text { get; set; }

Parameter [NullAllowed] juga dapat ditentukan langsung pada setter:

[Export ("text")]
string Text { get; [NullAllowed] set; }

Peringatan pengikatan kontrol kustom

Peringatan berikut harus dipertimbangkan saat menyiapkan pengikatan untuk kontrol kustom:

  1. Properti pengikatan harus statis - Saat menentukan pengikatan properti, [Static] atribut harus digunakan.
  2. Nama properti harus sama persis - Nama yang digunakan untuk mengikat properti harus cocok dengan nama properti dalam kontrol kustom dengan tepat.
  3. Jenis properti harus sama persis - Jenis variabel yang digunakan untuk mengikat properti harus cocok dengan jenis properti dalam kontrol kustom dengan tepat.
  4. Titik henti dan getter/setter - Titik henti yang ditempatkan di metode getter atau setter properti tidak akan pernah terpukul.
  5. Amati Panggilan Balik - Anda harus menggunakan panggilan balik pengamatan untuk diberi tahu tentang perubahan nilai properti kontrol kustom.

Kegagalan untuk mengamati salah satu peringatan yang tercantum di atas dapat mengakibatkan pengikatan gagal secara diam-diam pada runtime.

Objective-C pola dan properti yang dapat diubah

Objective-C kerangka kerja menggunakan idiom di mana beberapa kelas tidak dapat diubah dengan subkelas yang dapat diubah. Misalnya NSString adalah versi yang tidak dapat diubah, sementara NSMutableString adalah subkelas yang memungkinkan mutasi.

Di kelas-kelas tersebut, biasanya melihat kelas dasar yang tidak dapat diubah berisi properti dengan getter, tetapi tidak ada setter. Dan untuk versi yang dapat diubah untuk memperkenalkan setter. Karena ini tidak benar-benar mungkin dengan C#, kami harus memetakan idiom ini menjadi idiom yang akan bekerja dengan C#.

Cara ini dipetakan ke C# adalah dengan menambahkan getter dan setter pada kelas dasar, tetapi menandai setter dengan Atribut [NotImplemented].

Kemudian, pada subkelas yang dapat diubah, Anda menggunakan [Override] atribut pada properti untuk memastikan bahwa properti benar-benar mengesampingkan perilaku induk.

Contoh:

[BaseType (typeof (NSObject))]
interface MyTree {
    string Name { get; [NotImplemented] set; }
}

[BaseType (typeof (MyTree))]
interface MyMutableTree {
    [Override]
    string Name { get; set; }
}

Konstruktor pengikatan

Alat ini btouch-native akan secara otomatis menghasilkan empat konstruktor di kelas Anda, untuk kelas Footertentu , itu menghasilkan:

  • Foo (): konstruktor default (peta ke Objective-Ckonstruktor "init")
  • Foo (NSCoder): konstruktor yang digunakan selama deserialisasi file NIB (peta ke Objective-Ckonstruktor "initWithCoder:".
  • Foo (IntPtr handle): konstruktor untuk pembuatan berbasis handel, ini dipanggil oleh runtime ketika runtime perlu mengekspos objek terkelola dari objek yang tidak dikelola.
  • Foo (NSEmptyFlag): ini digunakan oleh kelas turunan untuk mencegah inisialisasi ganda.

Untuk konstruktor yang Anda tentukan, mereka perlu dideklarasikan menggunakan tanda tangan berikut di dalam definisi Antarmuka: mereka harus mengembalikan IntPtr nilai dan nama metode harus Konstruktor. Misalnya untuk mengikat initWithFrame: konstruktor, inilah yang akan Anda gunakan:

[Export ("initWithFrame:")]
IntPtr Constructor (CGRect frame);

Protokol pengikatan

Seperti yang dijelaskan dalam dokumen desain API, di bagian yang membahas Model dan Protokol, Xamarin.iOS memetakan Objective-C protokol ke dalam kelas yang telah ditandai dengan Atribut [Model]. Ini biasanya digunakan saat menerapkan Objective-C kelas delegasi.

Perbedaan besar antara kelas terikat reguler dan kelas delegasi adalah bahwa kelas delegasi mungkin memiliki satu atau beberapa metode opsional.

Misalnya pertimbangkan UIKit kelas UIAccelerometerDelegate, ini adalah bagaimana kelas terikat di Xamarin.iOS:

[BaseType (typeof (NSObject))]
[Model][Protocol]
interface UIAccelerometerDelegate {
        [Export ("accelerometer:didAccelerate:")]
        void DidAccelerate (UIAccelerometer accelerometer, UIAcceleration acceleration);
}

Karena ini adalah metode opsional pada definisi untuk UIAccelerometerDelegate tidak ada yang lain untuk dilakukan. Tetapi jika ada metode yang diperlukan pada protokol, Anda harus menambahkan [Abstract] atribut ke metode . Ini akan memaksa pengguna implementasi untuk benar-benar menyediakan tubuh untuk metode .

Secara umum, protokol digunakan dalam kelas yang merespons pesan. Ini biasanya dilakukan Objective-C dengan menetapkan ke properti "delegasi" instans objek yang merespons metode dalam protokol.

Konvensi di Xamarin.iOS adalah untuk mendukung Objective-C gaya yang digabungkan secara longgar di mana setiap instans NSObject dapat ditetapkan ke delegasi, dan untuk juga mengekspos versi yang sangat ditik. Untuk alasan ini, kami biasanya menyediakan Delegate properti yang sangat ditik dan WeakDelegate yang ditik secara longgar. Kami biasanya mengikat versi yang ditik secara longgar dengan [Export], dan kami menggunakan [Wrap] atribut untuk menyediakan versi yang sangat ditik.

Ini menunjukkan bagaimana kita terikat UIAccelerometer kelas:

[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; }
}

Baru di MonoTouch 7.0

Dimulai dengan MonoTouch 7.0 fungsionalitas pengikatan protokol baru dan yang ditingkatkan telah dimasukkan. Dukungan baru ini memudahkan penggunaan Objective-C idiom untuk mengadopsi satu atau beberapa protokol di kelas tertentu.

Untuk setiap definisi MyProtocol protokol di Objective-C, sekarang IMyProtocol ada antarmuka yang mencantumkan semua metode yang diperlukan dari protokol, serta kelas ekstensi yang menyediakan semua metode opsional. Di atas, dikombinasikan dengan dukungan baru di editor Xamarin Studio memungkinkan pengembang untuk menerapkan metode protokol tanpa harus menggunakan subkelas terpisah dari kelas model abstrak sebelumnya.

Definisi apa pun yang berisi [Protocol] atribut benar-benar akan menghasilkan tiga kelas pendukung yang sangat meningkatkan cara Anda menggunakan protokol:

// 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);
    }
}

Implementasi kelas menyediakan kelas abstrak lengkap yang dapat Anda ambil alih metode individual dan mendapatkan keamanan jenis penuh. Tetapi karena C# tidak mendukung beberapa warisan, ada skenario di mana Anda mungkin perlu memiliki kelas dasar yang berbeda, tetapi Anda masih ingin menerapkan antarmuka, di sanalah

Definisi antarmuka yang dihasilkan masuk. Ini adalah antarmuka yang memiliki semua metode yang diperlukan dari protokol. Hal ini memungkinkan pengembang yang ingin menerapkan protokol Anda untuk hanya menerapkan antarmuka. Runtime akan secara otomatis mendaftarkan jenis sebagai mengadopsi protokol.

Perhatikan bahwa antarmuka hanya mencantumkan metode yang diperlukan dan mengekspos metode opsional. Ini berarti bahwa kelas yang mengadopsi protokol akan mendapatkan pemeriksaan jenis penuh untuk metode yang diperlukan, tetapi harus menggunakan pengetikan yang lemah (secara manual menggunakan [Export] atribut dan mencocokkan tanda tangan) untuk metode protokol opsional.

Untuk memudahkan penggunaan API yang menggunakan protokol, alat pengikatan juga akan menghasilkan kelas metode ekstensi yang mengekspos semua metode opsional. Ini berarti bahwa selama Anda mengkonsumsi API, Anda akan dapat memperlakukan protokol sebagai memiliki semua metode.

Jika Anda ingin menggunakan definisi protokol di API, Anda harus menulis antarmuka kosong kerangka dalam definisi API Anda. Jika Anda ingin menggunakan MyProtocol dalam API, Anda harus melakukan ini:

[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 ();
}

Hal di atas diperlukan karena pada waktu IMyProtocol pengikatan tidak akan ada, itulah sebabnya Anda perlu menyediakan antarmuka kosong.

Mengadopsi antarmuka yang dihasilkan protokol

Setiap kali Anda menerapkan salah satu antarmuka yang dihasilkan untuk protokol, seperti ini:

class MyDelegate : NSObject, IUITableViewDelegate {
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

Implementasi untuk metode antarmuka yang diperlukan akan diekspor dengan nama yang tepat, sehingga setara dengan ini:

class MyDelegate : NSObject, IUITableViewDelegate {
    [Export ("getRowHeight:")]
    nint IUITableViewDelegate.GetRowHeight (nint row) {
        return 1;
    }
}

Ini akan berfungsi untuk semua anggota protokol yang diperlukan, tetapi ada kasus khusus dengan pemilih opsional yang harus diperhatikan. Anggota protokol opsional diperlakukan secara identik saat menggunakan kelas dasar:

public class UrlSessionDelegate : NSUrlSessionDownloadDelegate {
	public override void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

tetapi saat menggunakan antarmuka protokol, diperlukan untuk menambahkan [Ekspor]. IDE akan menambahkannya melalui pelengkapan otomatis saat Anda menambahkannya dimulai dengan penimpaan.

public class UrlSessionDelegate : NSObject, INSUrlSessionDownloadDelegate {
	[Export ("URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:")]
	public void DidWriteData (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)

Ada sedikit perbedaan perilaku antara keduanya saat runtime.

  • Pengguna kelas dasar (NSUrlSessionDownloadDelegate dalam contoh) menyediakan semua pemilih yang diperlukan dan opsional, mengembalikan nilai default yang wajar.
  • Pengguna antarmuka (INSUrlSessionDownloadDelegate dalam contoh) hanya merespons pemilih yang tepat yang disediakan.

Beberapa kelas langka dapat berulah secara berbeda di sini. Namun dalam hampir semua kasus, aman untuk digunakan.

Mengikat ekstensi kelas

Di Objective-C dalamnya dimungkinkan untuk memperluas kelas dengan metode baru, mirip dengan metode ekstensi C#. Ketika salah satu metode ini ada, Anda dapat menggunakan [BaseType] atribut untuk menandai metode sebagai penerima Objective-C pesan.

Misalnya, di Xamarin.iOS kami mengikat metode ekstensi yang didefinisikan pada kapan UIKit diimpor NSString sebagai metode di NSStringDrawingExtensions, seperti ini:

[Category, BaseType (typeof (NSString))]
interface NSStringDrawingExtensions {
    [Export ("drawAtPoint:withFont:")]
    CGSize DrawString (CGPoint point, UIFont font);
}

Mengikat Objective-C daftar argumen

Objective-C mendukung argumen variadik. Contohnya:

- (void) appendWorkers:(XWorker *) firstWorker, ...
  NS_REQUIRES_NIL_TERMINATION ;

Untuk memanggil metode ini dari C# Anda ingin membuat tanda tangan seperti ini:

[Export ("appendWorkers"), Internal]
void AppendWorkers (Worker firstWorker, IntPtr workersPtr)

Ini menyatakan metode sebagai internal, menyembunyikan API di atas dari pengguna, tetapi mengeksposnya ke pustaka. Kemudian Anda dapat menulis metode seperti ini:

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);
}

Bidang pengikatan

Terkadang Anda ingin mengakses bidang publik yang dideklarasikan dalam pustaka.

Biasanya bidang ini berisi string atau nilai bilangan bulat yang harus dirujuk. Mereka umumnya digunakan sebagai string yang mewakili pemberitahuan tertentu dan sebagai kunci dalam kamus.

Untuk mengikat bidang, tambahkan properti ke file definisi antarmuka Anda, dan hiasi properti dengan [Field] atribut . Atribut ini mengambil satu parameter: nama C simbol untuk dicari. Contohnya:

[Field ("NSSomeEventNotification")]
NSString NSSomeEventNotification { get; }

Jika Anda ingin membungkus berbagai bidang dalam kelas statis yang tidak berasal dari NSObject, Anda dapat menggunakan [Static] atribut pada kelas , seperti ini:

[Static]
interface LonelyClass {
    [Field ("NSSomeEventNotification")]
    NSString NSSomeEventNotification { get; }
}

Di atas akan menghasilkan LonelyClass yang tidak berasal dari NSObject dan akan berisi pengikatan ke yang NSSomeEventNotificationNSString diekspos sebagai NSString.

Atribut [Field] dapat diterapkan ke jenis data berikut:

  • NSString referensi (properti baca-saja)
  • NSArray referensi (properti baca-saja)
  • 32-bit ints (System.Int32)
  • 64-bit ints (System.Int64)
  • Float 32-bit (System.Single)
  • Float 64-bit (System.Double)
  • System.Drawing.SizeF
  • CGSize

Selain nama bidang asli, Anda dapat menentukan nama pustaka tempat bidang berada, dengan meneruskan nama pustaka:

[Static]
interface LonelyClass {
    [Field ("SomeSharedLibrarySymbol", "SomeSharedLibrary")]
    NSString SomeSharedLibrarySymbol { get; }
}

Jika Anda menautkan secara statis, tidak ada pustaka untuk diikat, jadi Anda perlu menggunakan nama:__Internal

[Static]
interface LonelyClass {
    [Field ("MyFieldFromALibrary", "__Internal")]
    NSString MyFieldFromALibrary { get; }
}

Enum pengikatan

Anda dapat menambahkan enum langsung dalam file pengikatan untuk mempermudah penggunaannya di dalam definisi API - tanpa menggunakan file sumber yang berbeda (yang perlu dikompilasi dalam pengikatan dan proyek akhir).

Contoh:

[Native] // needed for enums defined as NSInteger in ObjC
enum MyEnum {}

interface MyType {
    [Export ("initWithEnum:")]
    IntPtr Constructor (MyEnum value);
}

Anda juga dapat membuat enum Anda sendiri untuk mengganti NSString konstanta. Dalam hal ini generator akan secara otomatis membuat metode untuk mengonversi nilai enum dan konstanta NSString untuk Anda.

Contoh:

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);
}

Dalam contoh di atas, Anda dapat memutuskan untuk menghias void Perform (NSString mode);[Internal] dengan atribut . Ini akan menyembunyikan API berbasis konstanta dari konsumen pengikatan Anda.

Namun ini akan membatasi subkelas jenis karena alternatif API yang lebih baik menggunakan [Wrap] atribut . Metode yang dihasilkan tersebut bukan virtual, yaitu Anda tidak akan dapat mengambil alihnya - yang mungkin, atau tidak, menjadi pilihan yang baik.

Alternatifnya adalah menandai definisi asli, NSStringberbasis, sebagai [Protected]. Ini akan memungkinkan subkelas berfungsi, jika diperlukan, dan versi wrap'ed akan tetap berfungsi dan memanggil metode penimpaan.

Mengikat NSValue, NSNumber, dan NSString ke jenis yang lebih baik

Atribut memungkinkan [BindAs] pengikatan NSNumber, NSValue dan NSString(enum) ke dalam jenis C# yang lebih akurat. Atribut dapat digunakan untuk membuat API .NET yang lebih baik, lebih akurat, melalui API asli.

Anda dapat menghias metode (pada nilai pengembalian), parameter, dan properti dengan [BindAs]. Satu-satunya batasan adalah anggota Anda tidak boleh berada di dalam [Protocol] atau [Model] antarmuka.

Contohnya:

[return: BindAs (typeof (bool?))]
[Export ("shouldDrawAt:")]
NSNumber ShouldDraw ([BindAs (typeof (CGRect))] NSValue rect);

Akan keluaran:

[Export ("shouldDrawAt:")]
bool? ShouldDraw (CGRect rect) { ... }

Secara internal kita akan melakukan bool?konversi ->NSNumber dan CGRect<->NSValue .<

[BindAs] juga mendukung array dan NSNumberNSValueNSString(enum).

Contohnya:

[BindAs (typeof (CAScroll []))]
[Export ("supportedScrollModes")]
NSString [] SupportedScrollModes { get; set; }

Akan keluaran:

[Export ("supportedScrollModes")]
CAScroll [] SupportedScrollModes { get; set; }

CAScroll adalah enum yang NSString didukung, kami akan mengambil nilai yang tepat NSString dan menangani konversi jenis.

Silakan lihat [BindAs] dokumentasi untuk melihat jenis konversi yang didukung.

Pemberitahuan pengikatan

Pemberitahuan adalah pesan yang diposting ke NSNotificationCenter.DefaultCenter dan digunakan sebagai mekanisme untuk menyiarkan pesan dari satu bagian aplikasi ke bagian lain. Pengembang berlangganan pemberitahuan biasanya menggunakan metode AddObserver NSNotificationCenter. Saat aplikasi memposting pesan ke pusat pemberitahuan, biasanya berisi payload yang disimpan dalam kamus NSNotification.UserInfo . Kamus ini ditik dengan lemah, dan mendapatkan informasi darinya rawan kesalahan, karena pengguna biasanya perlu membaca dalam dokumentasi kunci mana yang tersedia pada kamus dan jenis nilai yang dapat disimpan dalam kamus. Kehadiran kunci kadang-kadang digunakan sebagai boolean juga.

Generator pengikatan Xamarin.iOS menyediakan dukungan bagi pengembang untuk mengikat pemberitahuan. Untuk melakukan ini, Anda mengatur [Notification] atribut pada properti yang juga telah ditandai dengan [Field] properti (dapat bersifat publik atau privat).

Atribut ini dapat digunakan tanpa argumen untuk pemberitahuan yang tidak membawa payload, atau Anda dapat menentukan yang mereferensikan System.Type antarmuka lain dalam definisi API, biasanya dengan nama yang diakhir dengan "EventArgs". Generator akan mengubah antarmuka menjadi kelas yang subkelas EventArgs dan akan menyertakan semua properti yang tercantum di sana. Atribut [Export] harus digunakan di kelas EventArgs untuk mencantumkan nama kunci yang digunakan untuk mencari Objective-C kamus untuk mengambil nilai.

Contohnya:

interface MyClass {
    [Notification]
    [Field ("MyClassDidStartNotification")]
    NSString DidStartNotification { get; }
}

Kode di atas akan menghasilkan kelas MyClass.Notifications berlapis dengan metode berikut:

public class MyClass {
   [..]
   public Notifications {
      public static NSObject ObserveDidStart (EventHandler<NSNotificationEventArgs> handler)
   }
}

Pengguna kode Anda kemudian dapat dengan mudah berlangganan pemberitahuan yang diposting ke NSDefaultCenter dengan menggunakan kode seperti ini:

var token = MyClass.Notifications.ObserverDidStart ((notification) => {
    Console.WriteLine ("Observed the 'DidStart' event!");
});

Nilai yang dikembalikan dari ObserveDidStart dapat digunakan untuk dengan mudah berhenti menerima pemberitahuan, seperti ini:

token.Dispose ();

Atau Anda dapat memanggil NSNotification.DefaultCenter.RemoveObserver dan meneruskan token. Jika pemberitahuan Anda berisi parameter, Anda harus menentukan antarmuka pembantu EventArgs , seperti ini:

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; }
}

Di atas akan menghasilkan MyScreenChangedEventArgs kelas dengan ScreenX properti dan ScreenY yang akan mengambil data dari kamus NSNotification.UserInfo menggunakan kunci "ScreenXKey" dan "ScreenYKey" masing-masing dan menerapkan konversi yang tepat. Atribut [ProbePresence] digunakan untuk generator untuk memeriksa apakah kunci diatur dalam UserInfo, alih-alih mencoba mengekstrak nilai. Ini digunakan untuk kasus di mana keberadaan kunci adalah nilai (biasanya untuk nilai boolean).

Ini memungkinkan Anda menulis kode seperti ini:

var token = MyClass.NotificationsObserveScreenChanged ((notification) => {
    Console.WriteLine ("The new screen dimensions are {0},{1}", notification.ScreenX, notification.ScreenY);
});

Kategori pengikatan

Kategori adalah mekanisme yang Objective-C digunakan untuk memperluas serangkaian metode dan properti yang tersedia di kelas. Dalam praktiknya, mereka digunakan untuk memperluas fungsionalitas kelas dasar (misalnya NSObject) ketika kerangka kerja tertentu ditautkan dalam (misalnya UIKit), membuat metode mereka tersedia, tetapi hanya jika kerangka kerja baru ditautkan. Dalam beberapa kasus lain, fitur tersebut digunakan untuk mengatur fitur di kelas berdasarkan fungsionalitas. Mereka mirip dengan metode ekstensi C#. Seperti inilah kategori akan terlihat di Objective-C:

@interface UIView (MyUIViewExtension)
-(void) makeBackgroundRed;
@end

Contoh di atas jika ditemukan di pustaka akan memperluas instans UIView dengan metode makeBackgroundRed.

Untuk mengikatnya, Anda dapat menggunakan [Category] atribut pada definisi antarmuka. Saat menggunakan [Category] atribut, arti dari [BaseType] perubahan atribut agar tidak digunakan untuk menentukan kelas dasar yang akan diperluas, menjadi jenis yang akan diperluas.

Berikut ini menunjukkan bagaimana ekstensi terikat UIView dan berubah menjadi metode ekstensi C#:

[BaseType (typeof (UIView))]
[Category]
interface MyUIViewExtension {
    [Export ("makeBackgroundRed")]
    void MakeBackgroundRed ();
}

Di atas akan membuat MyUIViewExtension kelas yang berisi MakeBackgroundRed metode ekstensi. Ini berarti bahwa Anda sekarang dapat memanggil "MakeBackgroundRed" pada subkelas apa pun UIView , memberi Anda fungsionalitas yang sama yang akan Anda dapatkan di Objective-C. Dalam beberapa kasus lain, kategori digunakan untuk tidak memperluas kelas sistem, tetapi untuk mengatur fungsionalitas, murni untuk tujuan dekorasi. Seperti ini:

@interface SocialNetworking (Twitter)
- (void) postToTwitter:(Message *) message;
@end

@interface SocialNetworking (Facebook)
- (void) postToFacebook:(Message *) message andPicture: (UIImage*)
picture;
@end

Meskipun Anda dapat menggunakan [Category] atribut juga untuk gaya deklarasi dekorasi ini, Anda mungkin juga hanya menambahkan semuanya ke definisi kelas. Kedua hal ini akan mencapai hal yang sama:

[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);
}

Hanya lebih pendek dalam kasus ini untuk menggabungkan kategori:

[BaseType (typeof (NSObject))]
interface SocialNetworking {
    [Export ("postToTwitter:")]
    void PostToTwitter (Message message);

    [Export ("postToFacebook:andPicture:")]
    void PostToFacebook (Message message, UIImage picture);
}

Blok pengikatan

Blok adalah konstruksi baru yang diperkenalkan oleh Apple untuk membawa fungsional yang setara dengan metode anonim C# ke Objective-C. Misalnya, NSSet kelas sekarang mengekspos metode ini:

- (void) enumerateObjectsUsingBlock:(void (^)(id obj, BOOL *stop) block

Deskripsi di atas menyatakan metode yang disebut enumerateObjectsUsingBlock: yang mengambil satu argumen bernama block. Blok ini mirip dengan metode anonim C# karena memiliki dukungan untuk menangkap lingkungan saat ini (pointer "ini", akses ke variabel dan parameter lokal). Metode di atas dalam NSSet memanggil blok dengan dua parameter an NSObject ( id obj bagian) dan pointer ke bagian boolean (bagian BOOL *stop) .

Untuk mengikat API semacam ini dengan btouch, Anda harus terlebih dahulu mendeklarasikan tanda tangan jenis blok sebagai delegasi C# dan kemudian mereferensikannya dari titik masuk API, seperti ini:

// 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)

Dan sekarang kode Anda dapat memanggil fungsi Anda dari 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);
});

Anda juga dapat menggunakan lambda jika Anda lebih suka:

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);
});

Metode asinkron

Generator pengikatan dapat mengubah kelas metode tertentu menjadi metode ramah asinkron (metode yang mengembalikan Tugas atau Tugas<T>).

Anda dapat menggunakan [Async] atribut pada metode yang mengembalikan kekosongan dan argumen terakhirnya adalah panggilan balik. Ketika Anda menerapkan ini ke metode, generator pengikatan akan menghasilkan versi metode tersebut dengan akhiran Async. Jika panggilan balik tidak mengambil parameter, nilai yang dikembalikan akan menjadi Task, jika panggilan balik mengambil parameter, hasilnya akan menjadi Task<T>. Jika panggilan balik mengambil beberapa parameter, Anda harus mengatur ResultType atau ResultTypeName untuk menentukan nama yang diinginkan dari jenis yang dihasilkan yang akan menyimpan semua properti.

Contoh:

[Export ("loadfile:completed:")]
[Async]
void LoadFile (string file, Action<string> completed);

Kode di atas akan menghasilkan metode LoadFile, serta:

[Export ("loadfile:completed:")]
Task<string> LoadFileAsync (string file);

Memunculkan jenis kuat untuk parameter NSDictionary yang lemah

Di banyak tempat di API, parameter diteruskan Objective-C sebagai API yang ditik NSDictionary lemah dengan kunci dan nilai tertentu, tetapi ini rawan kesalahan (Anda dapat meneruskan kunci yang tidak valid, dan tidak mendapatkan peringatan; Anda dapat meneruskan nilai yang tidak valid, dan tidak mendapatkan peringatan) dan frustrasi untuk digunakan karena memerlukan beberapa perjalanan ke dokumentasi untuk mencari nama dan nilai kunci yang mungkin.

Solusinya adalah menyediakan versi yang ditik dengan kuat yang menyediakan versi API yang sangat ditik dan di belakang layar memetakan berbagai kunci dan nilai yang mendasar.

Jadi misalnya, jika Objective-C API menerima NSDictionary dan didokumenkan sebagai mengambil kunci yang mengambil NSNumber nilai XyzVolumeKey volume dari 0,0 hingga 1,0 dan XyzCaptionKey yang mengambil string, Anda ingin pengguna Anda memiliki API bagus yang terlihat seperti ini:

public class  XyzOptions {
    public nfloat? Volume { get; set; }
    public string Caption { get; set; }
}

Properti Volume didefinisikan sebagai float nullable, karena konvensi di Objective-C tidak mengharuskan kamus ini untuk memiliki nilai, sehingga ada skenario di mana nilai mungkin tidak diatur.

Untuk melakukan ini, Anda perlu melakukan beberapa hal:

  • Buat kelas yang sangat diketik, yang subkelas DictionaryContainer dan menyediakan berbagai getter dan setter untuk setiap properti.
  • Nyatakan kelebihan beban untuk metode yang diambil NSDictionary untuk mengambil versi baru yang diketik dengan kuat.

Anda dapat membuat kelas yang ditik dengan kuat baik secara manual, atau menggunakan generator untuk melakukan pekerjaan untuk Anda. Kami pertama-tama menjelajahi cara melakukan ini secara manual sehingga Anda memahami apa yang terjadi, dan kemudian pendekatan otomatis.

Anda perlu membuat file pendukung untuk ini, file tersebut tidak masuk ke API kontrak Anda. Inilah yang harus Anda tulis untuk membuat kelas XyzOptions Anda:

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
}

Anda kemudian harus menyediakan metode pembungkus yang menampilkan API tingkat tinggi, di atas API tingkat rendah.

[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);
}

Jika API Anda tidak perlu ditimpa, Anda dapat dengan aman menyembunyikan API berbasis NSDictionary dengan menggunakan Atribut [Internal].

Seperti yang Anda lihat, kami menggunakan [Wrap] atribut untuk memunculkan titik masuk API baru, dan kami memunculkannya menggunakan kelas kami yang sangat ditik XyzOptions . Metode pembungkus juga memungkinkan null diteruskan.

Sekarang, satu hal yang tidak kami sebutkan adalah dari mana XyzOptionsKeys nilai berasal. Anda biasanya akan mengelompokkan kunci yang dimunculkan API di kelas statis seperti XyzOptionsKeys, seperti ini:

[Static]
class XyzOptionKeys {
    [Field ("kXyzVolumeKey")]
    NSString VolumeKey { get; }

    [Field ("kXyzCaptionKey")]
    NSString CaptionKey { get; }
}

Mari kita lihat dukungan otomatis untuk membuat kamus yang sangat ditik ini. Ini menghindari banyak boilerplate, dan Anda dapat menentukan kamus langsung dalam kontrak API Anda, alih-alih menggunakan file eksternal.

Untuk membuat kamus yang diketik dengan kuat, perkenalkan antarmuka di API Anda dan hiasi dengan atribut StrongDictionary . Ini memberi tahu generator bahwa ia harus membuat kelas dengan nama yang sama dengan antarmuka Anda yang akan berasal dan DictionaryContainer akan menyediakan aksesor berjenis yang kuat untuk itu.

Atribut [StrongDictionary] mengambil satu parameter, yang merupakan nama kelas statis yang berisi kunci kamus Anda. Kemudian setiap properti antarmuka akan menjadi aksesor yang sangat ditik. Secara default, kode akan menggunakan nama properti dengan akhiran "Kunci" di kelas statis untuk membuat aksesor.

Ini berarti bahwa membuat aksesor yang sangat ditik tidak lagi memerlukan file eksternal, atau harus membuat getter dan setter secara manual untuk setiap properti, atau harus mencari kunci secara manual sendiri.

Seperti inilah seluruh pengikatan Anda:

[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);
}

Jika Anda perlu mereferensikan di anggota Anda XyzOption bidang yang berbeda (itu bukan nama properti dengan akhiran Key), Anda dapat menghias properti dengan [Export] atribut dengan nama yang ingin Anda gunakan.

Ketik pemetaan

Bagian ini mencakup bagaimana Objective-C jenis dipetakan ke jenis C#.

Jenis sederhana

Tabel berikut menunjukkan bagaimana Anda harus memetakan jenis dari Objective-C dunia cocoaTouch dan ke dunia Xamarin.iOS:

Objective-C nama jenis Jenis API Terpadu Xamarin.iOS
BOOL, GLboolean bool
NSInteger nint
NSUInteger nuint
CFTimeInterval / NSTimeInterval double
NSString (selengkapnya tentang mengikat NSString) string
char * string (lihat juga: [PlainString])
CGRect CGRect
CGPoint CGPoint
CGSize CGSize
CGFloat, GLfloat nfloat
Jenis CoreFoundation (CF*) CoreFoundation.CF*
GLint nint
GLfloat nfloat
Jenis fondasi (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

Larik

Runtime Xamarin.iOS secara otomatis mengurus konversi array C# ke NSArrays dan melakukan konversi kembali, jadi misalnya metode imajiner Objective-C yang mengembalikan NSArray dari UIViews:

// Get the peer views - untyped
- (NSArray *)getPeerViews ();

// Set the views for this container
- (void) setViews:(NSArray *) views

Apakah terikat seperti ini:

[Export ("getPeerViews")]
UIView [] GetPeerViews ();

[Export ("setViews:")]
void SetViews (UIView [] views);

Idenya adalah menggunakan array C# yang ditik dengan kuat karena ini akan memungkinkan IDE untuk menyediakan penyelesaian kode yang tepat dengan jenis aktual tanpa memaksa pengguna untuk menebak, atau mencari dokumentasi untuk mengetahui jenis aktual objek yang terkandung dalam array.

Dalam kasus di mana Anda tidak dapat melacak jenis aktual yang paling turunan yang terkandung dalam array, Anda dapat menggunakan NSObject [] sebagai nilai pengembalian.

Pemilih

Pemilih muncul di Objective-C API sebagai jenis SELkhusus . Saat mengikat pemilih, Anda akan memetakan jenis ke ObjCRuntime.Selector. Biasanya pemilih diekspos dalam API dengan objek, objek target, dan pemilih untuk dipanggil di objek target. Menyediakan kedua hal ini pada dasarnya sesuai dengan delegasi C#: sesuatu yang merangkum kedua metode untuk memanggil serta objek untuk memanggil metode.

Seperti inilah pengikatannya:

interface Button {
   [Export ("setTarget:selector:")]
   void SetTarget (NSObject target, Selector sel);
}

Dan ini adalah bagaimana metode biasanya akan digunakan dalam aplikasi:

class DialogPrint : UIViewController {
    void HookPrintButton (Button b)
    {
        b.SetTarget (this, new Selector ("print"));
    }

    [Export ("print")]
    void ThePrintMethod ()
    {
       // This does the printing
    }
}

Untuk membuat pengikatan lebih baik kepada pengembang C#, Anda biasanya akan menyediakan metode yang mengambil NSAction parameter, yang memungkinkan delegasi C# dan lambda digunakan alih-alih Target+Selector. Untuk melakukan ini, Anda biasanya akan menyembunyikan SetTarget metode dengan menandainya dengan [Internal] atribut dan kemudian Anda akan mengekspos metode pembantu baru, seperti ini:

// 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);
     }
}

Jadi sekarang kode pengguna Anda dapat ditulis seperti ini:

class DialogPrint : UIViewController {
    void HookPrintButton (Button b)
    {
        // First Style
        b.SetTarget (ThePrintMethod);

        // Lambda style
        b.SetTarget (() => {  /* print here */ });
    }

    void ThePrintMethod ()
    {
       // This does the printing
    }
}

String

Saat Anda mengikat metode yang mengambil NSString, Anda dapat menggantinya dengan jenis string C#, baik pada jenis pengembalian maupun parameter.

Satu-satunya kasus ketika Anda mungkin ingin menggunakan secara NSString langsung adalah ketika string digunakan sebagai token. Untuk informasi selengkapnya tentang string dan NSString, baca desain API pada dokumen NSString .

Dalam beberapa kasus yang jarang terjadi, API mungkin mengekspos string seperti C (char *) alih-alih Objective-C string (NSString *). Dalam kasus tersebut, Anda dapat membuat anotasi parameter dengan Atribut [PlainString].

parameter out/ref

Beberapa API mengembalikan nilai dalam parameternya, atau meneruskan parameter berdasarkan referensi.

Biasanya tanda tangan terlihat seperti ini:

- (void) someting:(int) foo withError:(NSError **) retError
- (void) someString:(NSObject **)byref

Contoh pertama menunjukkan idiom umum Objective-C untuk mengembalikan kode kesalahan, penunjuk ke NSError penunjuk diteruskan, dan setelah mengembalikan nilai diatur. Metode kedua menunjukkan bagaimana Objective-C metode mungkin mengambil objek dan memodifikasi kontennya. Ini akan menjadi referensi pass by, bukan nilai output murni.

Pengikatan Anda akan terlihat seperti ini:

[Export ("something:withError:")]
void Something (nint foo, out NSError error);
[Export ("someString:")]
void SomeString (ref NSObject byref);

Atribut manajemen memori

Saat Anda menggunakan [Export] atribut dan Anda meneruskan data yang akan disimpan oleh metode yang disebut, Anda dapat menentukan semantik argumen dengan meneruskannya sebagai parameter kedua, misalnya:

[Export ("method", ArgumentSemantic.Retain)]

Di atas akan menandai nilai sebagai memiliki semantik "Pertahankan". Semantik yang tersedia adalah:

  • Tugaskan
  • Menyalin
  • Mempertahankan

Panduan gaya

Menggunakan [Internal]

Anda dapat menggunakan [Internal] atribut untuk menyembunyikan metode dari API publik. Anda mungkin ingin melakukan ini dalam kasus di mana API yang diekspos terlalu rendah dan Anda ingin memberikan implementasi tingkat tinggi dalam file terpisah berdasarkan metode ini.

Anda juga dapat menggunakan ini ketika Anda mengalami batasan dalam generator pengikatan, misalnya beberapa skenario tingkat lanjut mungkin mengekspos jenis yang tidak terikat dan Anda ingin mengikat dengan cara Anda sendiri, dan Anda ingin membungkus jenis-jenis itu sendiri dengan cara Anda sendiri.

Penanganan aktivitas dan panggilan balik

Objective-C kelas biasanya menyiarkan pemberitahuan atau meminta informasi dengan mengirim pesan pada kelas delegasi (Objective-C delegasi).

Model ini, sementara didukung sepenuhnya dan muncul oleh Xamarin.iOS terkadang bisa rumit. Xamarin.iOS mengekspos pola peristiwa C# dan sistem method-callback pada kelas yang dapat digunakan dalam situasi ini. Ini memungkinkan kode seperti ini dijalankan:

button.Clicked += delegate {
    Console.WriteLine ("I was clicked");
};

Generator pengikatan mampu mengurangi jumlah pengetikan yang diperlukan untuk memetakan Objective-C pola ke dalam pola C#.

Dimulai dengan Xamarin.iOS 1.4 dimungkinkan juga untuk menginstruksikan generator untuk menghasilkan pengikatan untuk delegasi tertentu Objective-C dan mengekspos delegasi sebagai peristiwa dan properti C# pada jenis host.

Ada dua kelas yang terlibat dalam proses ini, kelas host yang akan menjadi kelas yang saat ini memancarkan peristiwa dan mengirimkannya ke dalam Delegate atau WeakDelegate dan kelas delegasi aktual.

Mempertimbangkan penyiapan berikut:

[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);
}

Untuk membungkus kelas, Anda harus:

  • Di kelas host Anda, tambahkan ke [BaseType]
    deklarasi jenis yang bertindak sebagai delegasinya dan nama C# yang Anda ekspos. Dalam contoh kami di atas, masing-masing adalah typeof (MyClassDelegate) dan WeakDelegate .
  • Di kelas delegasi Anda, pada setiap metode yang memiliki lebih dari dua parameter, Anda perlu menentukan jenis yang ingin Anda gunakan untuk kelas EventArgs yang dihasilkan secara otomatis.

Generator pengikatan tidak terbatas hanya untuk membungkus satu tujuan peristiwa, ada kemungkinan bahwa beberapa Objective-C kelas untuk memancarkan pesan ke lebih dari satu delegasi, jadi Anda harus menyediakan array untuk mendukung pengaturan ini. Sebagian besar pengaturan tidak akan membutuhkannya, tetapi generator siap untuk mendukung kasus-kasus tersebut.

Kode yang dihasilkan adalah:

[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 digunakan untuk menentukan nama kelas yang EventArgs akan dihasilkan. Anda harus menggunakan satu per tanda tangan (dalam contoh ini, EventArgs akan berisi With properti jenis nint).

Dengan definisi di atas, generator akan menghasilkan peristiwa berikut dalam MyClass yang dihasilkan:

public MyClassLoadedEventArgs : EventArgs {
        public MyClassLoadedEventArgs (nint bytes);
        public nint Bytes { get; set; }
}

public event EventHandler<MyClassLoadedEventArgs> Loaded {
        add; remove;
}

Jadi Anda sekarang dapat menggunakan kode seperti ini:

MyClass c = new MyClass ();
c.Loaded += delegate (sender, args){
        Console.WriteLine ("Loaded event with {0} bytes", args.Bytes);
};

Panggilan balik sama seperti pemanggilan peristiwa, perbedaannya adalah alih-alih memiliki beberapa pelanggan potensial (misalnya, beberapa metode dapat terhubung ke dalam Clicked peristiwa atau DownloadFinished peristiwa) panggilan balik hanya dapat memiliki satu pelanggan.

Prosesnya identik, satu-satunya perbedaan adalah alih-alih mengekspos nama EventArgs kelas yang akan dihasilkan, EventArgs sebenarnya digunakan untuk memberi nama delegasi C# yang dihasilkan.

Jika metode di kelas delegasi mengembalikan nilai, generator pengikatan akan memetakan ini ke dalam metode delegasi di kelas induk alih-alih peristiwa. Dalam kasus ini, Anda perlu memberikan nilai default yang harus dikembalikan oleh metode jika pengguna tidak terhubung ke delegasi. Anda melakukan ini menggunakan [DefaultValue] atau [DefaultValueFromArgument] atribut.

[DefaultValue] akan mengodekan nilai yang dikembalikan, sementara [DefaultValueFromArgument] digunakan untuk menentukan argumen input mana yang akan dikembalikan.

Enumerasi dan jenis dasar

Anda juga dapat mereferensikan enumerasi atau jenis dasar yang tidak didukung langsung oleh sistem definisi antarmuka btouch. Untuk melakukan ini, masukkan enumerasi dan jenis inti Anda ke dalam file terpisah dan sertakan ini sebagai bagian dari salah satu file tambahan yang Anda sediakan untuk btouch.

Menautkan dependensi

Jika Anda mengikat API yang bukan bagian dari aplikasi, Anda perlu memastikan bahwa executable Anda ditautkan terhadap pustaka ini.

Anda perlu memberi tahu Xamarin.iOS cara menautkan pustaka Anda, ini dapat dilakukan dengan mengubah konfigurasi build Anda untuk memanggil mtouch perintah dengan beberapa argumen build tambahan yang menentukan cara menautkan dengan pustaka baru menggunakan opsi "-gcc_flags", diikuti dengan string yang dikutip yang berisi semua pustaka tambahan yang diperlukan untuk program Anda, Seperti ini:

-gcc_flags "-L$(MSBuildProjectDirectory) -lMylibrary -force_load -lSystemLibrary -framework CFNetwork -ObjC"

Contoh di atas akan menautkan libMyLibrary.aCFNetwork , libSystemLibrary.dylib dan pustaka kerangka kerja ke dalam executable akhir Anda.

Atau Anda dapat memanfaatkan tingkat [LinkWithAttribute]perakitan , yang dapat Anda sematkan dalam file kontrak Anda (seperti AssemblyInfo.cs). Ketika Anda menggunakan [LinkWithAttribute], Anda harus memiliki pustaka asli yang tersedia pada saat Anda membuat pengikatan, karena ini akan menyematkan pustaka asli dengan aplikasi Anda. Contohnya:

// 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)]

Anda mungkin bertanya-tanya, mengapa Anda memerlukan -force_load perintah, dan alasannya adalah bahwa bendera -ObjC meskipun mengkompilasi kode masuk, itu tidak mempertahankan metadata yang diperlukan untuk mendukung kategori (linker/compiler kode mati menghilangkannya) yang Anda butuhkan pada runtime untuk Xamarin.iOS.

Referensi yang dibantu

Beberapa objek sementara seperti lembar tindakan dan kotak pemberitahuan rumit untuk dilacak bagi pengembang dan generator pengikatan dapat sedikit membantu di sini.

Misalnya jika Anda memiliki kelas yang menunjukkan pesan dan kemudian menghasilkan Done peristiwa, cara tradisional menangani ini adalah:

class Demo {
    MessageBox box;

    void ShowError (string msg)
    {
        box = new MessageBox (msg);
        box.Done += { box = null; ... };
    }
}

Dalam skenario di atas, pengembang perlu menyimpan referensi ke objek itu sendiri dan membocorkan atau secara aktif menghapus referensi untuk kotak sendiri. Saat mengikat kode, generator mendukung pelacakan referensi untuk Anda dan menghapusnya ketika metode khusus dipanggil, kode di atas kemudian akan menjadi:

class Demo {
    void ShowError (string msg)
    {
        var box = new MessageBox (msg);
        box.Done += { ... };
    }
}

Perhatikan bagaimana tidak perlu lagi menyimpan variabel dalam instans, bahwa variabel tersebut berfungsi dengan variabel lokal dan tidak perlu menghapus referensi ketika objek mati.

Untuk memanfaatkan ini, kelas Anda harus memiliki properti Peristiwa yang diatur dalam [BaseType] deklarasi dan juga KeepUntilRef variabel yang diatur ke nama metode yang dipanggil ketika objek telah menyelesaikan pekerjaannya, seperti ini:

[BaseType (typeof (NSObject), KeepUntilRef="Dismiss"), Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (SomeDelegate) }) ]
class Demo {
    [Export ("show")]
    void Show (string message);
}

Mewarisi protokol

Pada Xamarin.iOS v3.2, kami mendukung pewarisan dari protokol yang telah ditandai dengan [Model] properti . Ini berguna dalam pola API tertentu, seperti di MapKit mana MKOverlay protokol, mewarisi dari MKAnnotation protokol, dan diadopsi oleh sejumlah kelas yang mewarisi dari NSObject.

Secara historis kami memerlukan penyalinan protokol ke setiap implementasi, tetapi dalam kasus ini sekarang kita dapat memiliki MKShape kelas yang diwarisi dari MKOverlay protokol dan itu akan menghasilkan semua metode yang diperlukan secara otomatis.