바인딩 Objective-C 라이브러리

Xamarin.iOS 또는 Xamarin.Mac으로 작업할 때 타사 Objective-C 라이브러리를 사용하려는 경우가 발생할 수 있습니다. 이러한 상황에서는 Xamarin 바인딩 프로젝트를 사용하여 네이티브 Objective-C 라이브러리에 대한 C# 바인딩을 만들 수 있습니다. 이 프로젝트는 iOS 및 Mac API를 C#으로 가져오는 데 사용하는 것과 동일한 도구를 사용합니다.

이 문서에서는 API를 바인딩 Objective-C 하는 방법을 설명합니다. C API만 바인딩하는 경우 표준 .NET 메커니즘인 P/Invoke 프레임워크를 사용해야 합니다. C 라이브러리를 정적으로 연결하는 방법에 대한 자세한 내용은 네이티브 라이브러리 연결 페이지에서 확인할 수 있습니다.

도우미 바인딩 형식 참조 가이드를 참조하세요. 또한 내부적으로 발생하는 상황에 대해 자세히 알아보려면 바인딩 개요 페이지를 검사.

바인딩은 iOS 및 Mac 라이브러리 모두에 대해 빌드할 수 있습니다. 이 페이지에서는 iOS 바인딩에서 작업하는 방법을 설명합니다. 그러나 Mac 바인딩은 매우 유사합니다.

iOS용 샘플 코드

iOS 바인딩 샘플 프로젝트를 사용하여 바인딩을 실험할 수 있습니다.

시작하기

바인딩을 만드는 가장 쉬운 방법은 Xamarin.iOS 바인딩 프로젝트를 만드는 것입니다. 프로젝트 유형인 iOS > 라이브러리 > 바인딩 라이브러리를 선택하여 Mac용 Visual Studio 이 작업을 수행할 수 있습니다.

Do this from Visual Studio for Mac by selecting the project type, iOS Library Bindings Library

생성된 프로젝트에는 편집할 수 있는 작은 템플릿이 포함되어 있으며StructsAndEnums.cs, 두 개의 파일이 ApiDefinition.cs 포함됩니다.

ApiDefinition.cs 여기서 API 계약을 정의합니다. 이 파일은 기본 Objective-C API가 C#으로 프로젝팅되는 방법을 설명하는 파일입니다. 이 파일의 구문과 내용은 이 문서에 대한 논의의 기본 항목이며 해당 내용은 C# 인터페이스 및 C# 대리자 선언으로 제한됩니다. 이 StructsAndEnums.cs 파일은 인터페이스 및 대리자가 요구하는 정의를 입력하는 파일입니다. 여기에는 코드에서 사용할 수 있는 열거형 값 및 구조체가 포함됩니다.

API 바인딩

포괄적인 바인딩을 수행하려면 API 정의를 이해하고 Objective-C .NET Framework 디자인 지침을 숙지해야 합니다.

라이브러리를 바인딩하려면 일반적으로 API 정의 파일로 시작합니다. API 정의 파일은 바인딩을 구동하는 데 도움이 되는 몇 가지 특성으로 주석이 추가된 C# 인터페이스를 포함하는 C# 소스 파일일 뿐입니다. 이 파일은 C#과 Objective-C 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);
  }
}

위의 샘플에서는 기본 형식에서 NSObject 파생되는 클래스Cocos2D.Camera(이 형식Foundation.NSObject은 파생됨)를 정의하고 정적 속성(ZEye), 인수를 사용하지 않는 두 가지 메서드 및 세 개의 인수를 사용하는 메서드를 정의합니다.

API 파일 형식 및 사용할 수 있는 특성에 대한 자세한 내용은 아래 API 정의 파일 섹션에서 설명합니다.

전체 바인딩을 생성하려면 일반적으로 네 가지 구성 요소를 처리합니다.

  • 템플릿의 API 정의 파일ApiDefinition.cs 입니다.
  • 선택 사항: API 정의 파일(StructsAndEnums.cs 템플릿)에 필요한 열거형, 형식, 구조체입니다.
  • 선택 사항: 생성된 바인딩을 확장하거나 C# 친숙한 API(프로젝트에 추가하는 모든 C# 파일)를 제공할 수 있는 추가 원본입니다.
  • 바인딩할 네이티브 라이브러리입니다.

이 차트는 파일 간의 관계를 보여 줍니다.

This chart shows the relationship between the files

API 정의 파일은 네임스페이스 및 인터페이스 정의(인터페이스에 포함할 수 있는 멤버 포함)만 포함하며 클래스, 열거형, 대리자 또는 구조체를 포함해서는 안 됩니다. API 정의 파일은 API를 생성하는 데 사용되는 계약일 뿐입니다.

열거형 또는 지원 클래스와 같이 필요한 추가 코드는 별도의 파일에서 호스트되어야 합니다. 위의 예제에서 "카메라Mode"는 CS 파일에 존재하지 않는 열거형 값이며 별도의 파일에서 호스트되어야 합니다. 예를 들면 StructsAndEnums.cs다음과 같습니다.

public enum CameraMode {
    FlyOver, Back, Follow
}

APIDefinition.cs 파일은 클래스와 StructsAndEnum 결합되며 라이브러리의 핵심 바인딩을 생성하는 데 사용됩니다. 결과 라이브러리를 있는 그대로 사용할 수 있지만, 일반적으로 결과 라이브러리를 조정하여 사용자의 이익을 위해 일부 C# 기능을 추가하려고 합니다. 몇 가지 예로는 메서드 구현 ToString() , C# 인덱서 제공, 일부 네이티브 형식과 암시적 변환 추가 또는 강력한 형식의 일부 메서드 버전 제공 등이 있습니다. 이러한 향상된 기능은 추가 C# 파일에 저장됩니다. C# 파일을 프로젝트에 추가하기만 하면 이 빌드 프로세스에 포함됩니다.

파일에서 코드를 구현하는 방법을 보여 줍니다 Extra.cs . 부분 클래스를 사용하여 코어 바인딩과 StructsAndEnums.cs 코어 바인딩의 ApiDefinition.cs 조합에서 생성된 부분 클래스를 보강합니다.

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

라이브러리를 빌드하면 네이티브 바인딩이 생성됩니다.

이 바인딩을 완료하려면 네이티브 라이브러리를 프로젝트에 추가해야 합니다. 네이티브 라이브러리를 프로젝트에 추가하거나, 네이티브 라이브러리를 Finder에서 솔루션 탐색기의 프로젝트로 끌어다 놓거나, 프로젝트를 마우스 오른쪽 단추로 클릭하고 파일 추가>선택하여 네이티브 라이브러리를 선택하여 이 작업을 수행할 수 있습니다. 규칙에 따라 네이티브 라이브러리는 "lib"라는 단어로 시작하고 확장 ".a"로 끝납니다. 이렇게 하면 Mac용 Visual Studio .a 파일과 네이티브 라이브러리에 포함된 내용에 대한 정보가 포함된 자동으로 채워진 C# 파일이라는 두 개의 파일을 추가합니다.

Native libraries by convention start with the word lib and end with the extension .a

파일 내용에는 이 라이브러리를 libMagicChord.linkwith.cs 사용하는 방법에 대한 정보가 있으며 IDE에 이 이진 파일을 결과 DLL 파일로 패키징하도록 지시합니다.

using System;
using ObjCRuntime;

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

사용 방법에 대한 전체 세부 정보 [LinkWith] 특성은 바인딩 형식 참조 가이드에 설명되어 있습니다.

이제 프로젝트를 빌드할 때 바인딩과 MagicChords.dll 네이티브 라이브러리가 모두 포함된 파일로 끝납니다. 이 프로젝트 또는 결과 DLL을 다른 개발자에게 직접 배포할 수 있습니다.

경우에 따라 몇 가지 열거형 값, 대리자 정의 또는 기타 형식이 필요할 수 있습니다. 계약일 뿐이므로 API 정의 파일에 배치하지 마세요.

API 정의 파일

API 정의 파일은 여러 인터페이스로 구성됩니다. API 정의의 인터페이스는 클래스 선언으로 바뀌고 클래스의 기본 클래스를 지정하려면 특성으로 [BaseType] 데코레이팅되어야 합니다.

계약 정의에 대한 인터페이스 대신 클래스를 사용하지 않은 이유가 궁금할 수 있습니다. API 정의 파일에 메서드 본문을 제공할 필요 없이 메서드에 대한 계약을 작성하거나 예외를 throw하거나 의미 있는 값을 반환해야 하는 본문을 제공할 필요 없이 인터페이스를 선택했습니다.

그러나 인터페이스를 기본으로 사용하여 클래스를 생성하기 때문에 바인딩을 구동하기 위해 특성으로 계약의 다양한 부분을 데코레이팅해야 했습니다.

바인딩 메서드

수행할 수 있는 가장 간단한 바인딩은 메서드를 바인딩하는 것입니다. 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);

참조 형식을 내보낼 때 키워드(keyword) 사용하여 [Export] 할당 의미 체계를 지정할 수도 있습니다. 이는 데이터가 유출되지 않도록 하는 데 필요합니다.

바인딩 속성

메서드와 마찬가지로 속성은 다음을 Objective-C 사용하여 바인딩됩니다. [Export] 특성을 지정하고 C# 속성에 직접 매핑합니다. 메서드와 마찬가지로 속성은 다음과 같이 데코레이팅할 수 있습니다. [Static][Internal] 특성.

커버 btouch 네이 [Export] 티브 아래의 속성에서 특성을 사용하면 실제로 getter와 setter라는 두 가지 메서드를 바인딩합니다. 내보내기 위해 제공하는 이름은 basename이고 setter는 단어 앞에 "set"를 추가하고, basename첫 글자를 대문자로 바꾸고, 선택기가 인수를 사용하도록 하여 계산됩니다. 즉, 속성에 적용하면 [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);
}

getter 및 setter가 위 및 setName 바인딩에서 name 와 같이 명시적으로 정의된 위치입니다.

사용 하 여 [Static]정적 속성에 대 한 지원 외에도 다음과 같은 [IsThreadStatic]스레드 정적 속성을 데코레이트할 수 있습니다.

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

메서드에서 일부 매개 변수에 플래그를 지정할 [NullAllowed]수 있는 것처럼 적용할 수 있습니다. [NullAllowed] 속성에 null이 속성의 유효한 값임을 나타내는 입니다. 예를 들면 다음과 같습니다.

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

매개 변수는 [NullAllowed] setter에서 직접 지정할 수도 있습니다.

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

바인딩 사용자 지정 컨트롤의 주의 사항

사용자 지정 컨트롤에 대한 바인딩을 설정할 때는 다음과 같은 주의 사항을 고려해야 합니다.

  1. 바인딩 속성은 정적 이어야 합니다. 속성 [Static] 의 바인딩을 정의할 때는 특성을 사용해야 합니다.
  2. 속성 이름은 정확히 일치해야 합니다. 속성을 바인딩하는 데 사용되는 이름은 사용자 지정 컨트롤의 속성 이름과 정확히 일치해야 합니다.
  3. 속성 형식은 정확히 일치해야 합니다. 속성을 바인딩하는 데 사용되는 변수 형식은 사용자 지정 컨트롤의 속성 형식과 정확히 일치해야 합니다.
  4. 중단점 및 getter/setter - 속성의 getter 또는 setter 메서드에 배치된 중단점은 적중되지 않습니다.
  5. 콜백 관찰 - 사용자 지정 컨트롤의 속성 값 변경에 대한 알림을 받으려면 관찰 콜백을 사용해야 합니다.

위에 나열된 주의 사항을 관찰하지 못하면 런타임에 바인딩이 자동으로 실패할 수 있습니다.

Objective-C 변경 가능한 패턴 및 속성

Objective-C 프레임워크는 변경 가능한 하위 클래스를 사용하여 일부 클래스를 변경할 수 없는 관용구를 사용합니다. 예를 들어 NSString 변경할 수 없는 버전은 NSMutableString 변형을 허용하는 하위 클래스입니다.

이러한 클래스에서는 변경할 수 없는 기본 클래스에 getter가 있지만 setter가 없는 속성을 포함하는 것이 일반적입니다. 그리고 변경 가능한 버전에 대해 setter를 소개합니다. C#에서는 불가능하므로 이 관용구를 C#에서 작동하는 관용구로 매핑해야 했습니다.

C#에 매핑되는 방법은 기본 클래스에 getter와 setter를 모두 추가하지만 setter에 플래그를 지정하는 것입니다. [NotImplemented] 특성.

그런 다음 변경 가능한 하위 클래스에서 다음을 사용합니다. [Override] 속성이 실제로 부모의 동작을 재정의하는지 확인하기 위한 속성의 특성입니다.

예시:

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

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

바인딩 생성자

이 도구는 btouch-native 지정된 클래스 Foo에 대해 클래스에서 4개 생성자를 자동으로 생성합니다.

  • Foo (): 기본 생성자('s "init" 생성자에 매핑 Objective-C됨)
  • Foo (NSCoder): NIB 파일을 역직렬화하는 동안 사용되는 생성자입니다('s 'initWithCoder:' 생성자에 매핑 Objective-C됨).
  • Foo (IntPtr handle): 핸들 기반 생성을 위한 생성자이며, 런타임이 관리되지 않는 개체에서 관리되는 개체를 노출해야 하는 경우 런타임에 의해 호출됩니다.
  • Foo (NSEmptyFlag): 이중 초기화를 방지하기 위해 파생 클래스에서 사용됩니다.

정의한 생성자의 경우 인터페이스 정의 내에서 다음 서명을 사용하여 선언해야 합니다. 값을 반환 IntPtr 해야 하며 메서드의 이름은 생성자여야 합니다. 예를 들어 생성자를 바인딩 initWithFrame: 하는 방법은 다음과 같습니다.

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

바인딩 프로토콜

API 디자인 문서에 설명된 대로 모델 및 프로토콜에 대해 설명하는 섹션에서 Xamarin.iOS는 프로토콜을 플래그가 지정된 클래스에 매핑 Objective-C 합니다.[Model] 특성. 이는 일반적으로 대리자 클래스를 구현할 Objective-C 때 사용됩니다.

일반 바인딩된 클래스와 대리자 클래스 간의 큰 차이점은 대리자 클래스에 하나 이상의 선택적 메서드가 있을 수 있다는 것입니다.

예를 들어 클래스UIAccelerometerDelegateUIKit 고려합니다. Xamarin.iOS에 바인딩되는 방법은 다음과 같습니다.

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

정의에 대한 UIAccelerometerDelegate 선택적 메서드이므로 수행할 작업이 없습니다. 그러나 프로토콜에 필요한 메서드가 있는 경우 다음을 추가해야 합니다. [Abstract] 메서드에 대한 특성입니다. 이렇게 하면 구현 사용자가 메서드에 대한 본문을 실제로 제공하도록 강제합니다.

일반적으로 프로토콜은 메시지에 응답하는 클래스에서 사용됩니다. 이 작업은 일반적으로 프로토콜의 메서드에 Objective-C 응답하는 개체의 인스턴스를 "대리자" 속성에 할당하여 수행됩니다.

Xamarin.iOS의 규칙은 대리자 인스턴스 NSObject 를 할당할 수 있는 느슨하게 결합된 스타일을 모두 Objective-C 지원하고 강력한 형식의 버전을 노출하는 것입니다. 이러한 이유로 일반적으로 강력한 형식의 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 정의에 기본 빈 인터페이스를 작성해야 합니다. API에서 MyProtocol을 사용하려면 다음을 수행해야 합니다.

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

그러나 프로토콜 인터페이스를 사용하는 경우 [내보내기]를 추가해야 합니다. 재정의로 시작하는 IDE를 추가하면 자동 완성을 통해 IDE가 추가됩니다.

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)는 제공된 정확한 선택기에만 응답합니다.

일부 드문 클래스는 여기에서 다르게 동작할 수 있습니다. 그러나 거의 모든 경우에 둘 중 하나를 사용하는 것이 안전합니다.

바인딩 클래스 확장

C Objective-C #의 확장 메서드와 비슷한 새 메서드를 사용하여 클래스를 확장할 수 있습니다. 이러한 방법 중 하나가 있는 경우 다음을 사용할 수 있습니다. [BaseType] 메시지 수신자로 메서드에 플래그를 지정하는 특성입니다 Objective-C .

예를 들어 Xamarin.iOS에서 다음과 같이 메서드로 가져올 때 UIKit 정의된 NSString 확장 메서드를 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 == 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 하고 노출된 에 대한 바인딩 NSSomeEventNotificationNSString 을 포함합니다NSString.NSObject

특성은 [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의할 수 없으며, 이는 좋은 선택일 수도 있고 그렇지 않을 수도 있습니다.

대안은 원래 -based 정의를 NSString.로 표시하는 것입니다 [Protected]. 이렇게 하면 필요한 경우 서브클래싱이 작동할 수 있으며 wrap'ed 버전은 여전히 작동하고 재정의된 메서드를 호출합니다.

바인딩 NSValue, NSNumberNSString 더 나은 형식으로

[BindAs] 특성을 사용하면 보다 정확한 C# 형식으로 바인딩 NSNumberNSValueNSString(열거형)할 수 있습니다. 이 특성을 사용하여 네이티브 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?<>NSValue<NSNumberCGRect>수행할 것입니다.

[BindAs] 는 및(열거형) 배열 NSNumberNSValueNSString도 지원합니다.

예시:

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

다음을 출력합니다.

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

CAScrollNSString 백업된 열거형입니다. 올바른 NSString 값을 가져오고 형식 변환을 처리합니다.

지원되는 변환 유형을 보려면 설명서를 참조 [BindAs] 하세요.

바인딩 알림

알림은 게시 NSNotificationCenter.DefaultCenter 되는 메시지이며 애플리케이션의 한 부분에서 다른 부분으로 메시지를 브로드캐스트하는 메커니즘으로 사용됩니다. 개발자는 일반적으로 NSNotificationCenter의 AddObserver 메서드를 사용하여 알림을 구독합니다. 애플리케이션이 알림 센터에 메시지를 게시하는 경우 일반적으로 NSNotification.UserInfo 사전에 저장된 페이로드가 포함됩니다. 이 사전은 약한 형식이며, 사용자가 일반적으로 사전에서 사용할 수 있는 키와 사전에 저장할 수 있는 값의 유형을 설명서에서 읽어야 하므로 정보를 가져오는 것은 오류가 발생하기 쉽습니다. 키의 존재는 때때로 부울로도 사용됩니다.

Xamarin.iOS 바인딩 생성기는 개발자가 알림을 바인딩할 수 있도록 지원합니다. 이렇게 하려면 다음을 설정합니다. [Notification] 으로 태그가 지정된 속성의 특성 [Field] 속성(public 또는 private일 수 있습니다).

이 특성은 페이로드가 없는 알림에 대한 인수 없이 사용할 수 있으며, 일반적으로 이름이 "EventArgs"로 끝나는 API 정의에서 다른 인터페이스를 참조하는 인수를 지정할 System.Type 수 있습니다. 생성기는 인터페이스를 서브클래싱 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; }
}

위에서는 각각 "ScreenXKey" 및 ScreenY "ScreenYKey" 키를 사용하여 NSNotification.UserInfo 사전에서 데이터를 가져오고 적절한 변환을 적용하는 속성 ScreenX 과 클래스를 생성 MyScreenChangedEventArgs 합니다. 이 특성은 [ProbePresence] 생성기에서 UserInfo값을 추출하는 대신 키가 설정되어 있는지 검색하는 데 사용됩니다. 이는 키의 존재가 값(일반적으로 부울 값의 경우)인 경우에 사용됩니다.

이렇게 하면 다음과 같은 코드를 작성할 수 있습니다.

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

바인딩 범주

범주는 클래스에서 Objective-C 사용할 수 있는 메서드 및 속성 집합을 확장하는 데 사용되는 메커니즘입니다. 실제로 특정 프레임워크가 연결된 경우(예NSObjectUIKit:) 기본 클래스의 기능을 확장하여 메서드를 사용할 수 있지만 새 프레임워크가 연결된 경우에만 해당 메서드를 사용할 수 있도록 하는 데 사용됩니다. 다른 경우에는 기능별로 클래스의 기능을 구성하는 데 사용됩니다. 이러한 메서드는 C# 확장 메서드와 매우 유사합니다. 범주는 다음과 같습니다.Objective-C

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

라이브러리에 있는 경우 위의 예제는 메서드makeBackgroundRed를 사용하여 인스턴스를 확장합니다UIView.

이러한 특성을 바인딩하려면 인터페이스 정의에서 [Category] 특성을 사용할 수 있습니다. 를 사용하는 경우 [Category] 특성, 의 의미 [BaseType] 확장할 기본 클래스를 지정할 때 특성이 변경되어 확장할 형식이 됩니다.

다음은 확장이 UIView 바인딩되어 C# 확장 메서드로 변환되는 방법을 보여 줍니다.

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

위의 내용은 확장 메서드를 MyUIViewExtension 포함하는 클래스를 MakeBackgroundRed 만듭니다. 즉, 이제 하위 UIView 클래스에서 "MakeBackgroundRed"를 호출하여 동일한 기능을 사용할 수 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);
}

바인딩 블록

블록은 C# 익명 메서드에 해당하는 기능을 가져오기 위해 Apple에서 도입한 새로운 구문입니다 Objective-C. 예를 들어 클래스는 NSSet 이제 이 메서드를 노출합니다.

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

위의 설명은 이름이 block1개인 인수를 사용하는 메서드를 enumerateObjectsUsingBlock: 선언합니다. 이 블록은 현재 환경 캡처("이" 포인터, 지역 변수 및 매개 변수에 대한 액세스)를 지원한다는 측면에서 C# 익명 메서드와 유사합니다. 위의 메서드는 NSSet (파트) 및 부울BOOL *stop(id obj) 파트에 대한 포인터라는 두 개의 매개 변수 NSObject 를 사용하여 블록을 호출합니다.

이러한 종류의 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);
});

비동기 메서드

바인딩 생성기는 특정 메서드 클래스를 비동기 친화적인 메서드(Task 또는 Task<T>를 반환하는 메서드)로 전환할 수 있습니다.

를 사용하여[Async] void를 반환하고 마지막 인수가 콜백인 메서드에 대한 특성입니다. 이 메서드를 메서드에 적용하면 바인딩 생성기에서 접미사를 Async사용하여 해당 메서드의 버전을 생성합니다. 콜백이 매개 변수를 사용하지 않는 경우 반환 값은 a 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 매개 변수에 대한 강력한 형식 표시

API의 Objective-C 여러 위치에서 매개 변수는 특정 키와 값을 사용하여 약한 형식 NSDictionary 의 API로 전달되지만 오류가 발생하기 쉽습니다(잘못된 키를 전달하고 경고를 받지 않고 잘못된 값을 전달하고 경고를 받을 수 있습니다.) 가능한 키 이름과 값을 조회하기 위해 설명서로 여러 번 이동해야 하므로 사용하기가 불편합니다.

솔루션은 강력한 형식의 API 버전을 제공하는 강력한 형식의 버전을 제공하고 백그라운드에서 다양한 기본 키와 값을 매핑하는 것입니다.

예를 들어 API가 Objective-C 이를 수락 NSDictionary 하고 볼륨 값이 0.0에서 1.0 XyzCaptionKey 까지이고 문자열을 사용하는 NSNumber 키를 XyzVolumeKey 사용하는 것으로 문서화된 경우 사용자에게 다음과 같은 멋진 API를 갖도록 할 수 있습니다.

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

속성은 Volume nullable float로 정의됩니다. 규칙에서 이러한 사전이 Objective-C 값을 가질 필요가 없으므로 값을 설정하지 않을 수 있는 시나리오가 있습니다.

이렇게 하려면 몇 가지 작업을 수행해야 합니다.

  • DictionaryContainer를 서브클래싱하고 각 속성에 대한 다양한 getter 및 setter를 제공하는 강력한 형식의 클래스를 만듭니다.
  • 강력한 형식의 새 버전을 사용하는 메서드 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 == null ? null : options.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

API를 덮어쓸 필요가 없는 경우 다음을 사용하여 NSDictionary 기반 API를 안전하게 숨길 수 있습니다. [Internal] 특성.

여기서 볼 수 있듯이 다음을 사용합니다. [Wrap] 특성은 새 API 진입점을 표시하고 강력한 형식 XyzOptions 의 클래스를 사용하여 표시합니다. 래퍼 메서드를 통해 null을 전달할 수도 있습니다.

이제 우리가 멘션 않은 한 가지는 값이 XyzOptionsKeys 어디에서 왔는지입니다. 일반적으로 다음과 같이 XyzOptionsKeysAPI가 정적 클래스에서 노출하는 키를 그룹화합니다.

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

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

강력한 형식의 사전을 만들기 위한 자동 지원을 살펴보겠습니다. 이렇게 하면 많은 상용구가 방지되고 외부 파일을 사용하는 대신 API 계약에서 직접 사전을 정의할 수 있습니다.

강력한 형식의 사전을 만들려면 API에 인터페이스를 도입하고 StrongDictionary 특성으로 데코레이트합니다. 이렇게 하면 생성기에서 DictionaryContainer 파생되고 강력한 형식의 접근자를 제공하는 인터페이스와 이름이 같은 클래스를 만들어야 한다는 것을 알 수 있습니다.

이 특성은 [StrongDictionary] 사전 키를 포함하는 정적 클래스의 이름인 하나의 매개 변수를 사용합니다. 그런 다음 인터페이스의 각 속성은 강력한 형식의 접근자가 됩니다. 기본적으로 코드는 정적 클래스에 접미사 "Key"가 있는 속성의 이름을 사용하여 접근자를 만듭니다.

즉, 강력한 형식의 접근자를 만들면 더 이상 외부 파일이 필요하지 않으며, 모든 속성에 대해 getter 및 setter를 수동으로 만들거나 키를 직접 조회할 필요가 없습니다.

전체 바인딩은 다음과 같습니다.

[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 == null ? null : options.Dictionary")]
    void Playback (string fileName, XyzOptions options);
}

멤버에서 XyzOption 다른 필드(접미사가 Key있는 속성의 이름이 아님)를 참조해야 하는 경우 속성을 로 데코레이트할 수 있습니다. [Export] 사용하려는 이름을 가진 특성입니다.

형식 매핑

이 섹션에서는 형식이 C# 형식에 매핑되는 방법을 Objective-C 설명합니다.

단순 형식

다음 표에서는 CocoaTouch 세계와 CocoaTouch 세계의 형식 Objective-C 을 Xamarin.iOS 세계로 매핑하는 방법을 보여줍니다.

Objective-C 형식 이름 Xamarin.iOS 통합 API 유형
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 런타임은 자동으로 C# 배열 NSArrays 을 변환하고 변환을 다시 수행합니다. 예를 들어 다음을 UIViews반환 NSArray 하는 가상 Objective-C 메서드입니다.

// 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# 배열을 사용하면 IDE가 사용자가 추측하도록 강요하지 않고 실제 형식으로 적절한 코드 완성을 제공하거나 설명서를 조회하여 배열에 포함된 개체의 실제 형식을 확인할 수 있습니다.

배열에 포함된 실제 가장 파생된 형식을 추적할 수 없는 경우 반환 값으로 사용할 NSObject [] 수 있습니다.

선택기

선택기는 API에 Objective-C 특수 형식 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
    }
}

C# 개발자에게 바인딩을 더 좋게 만들기 위해 일반적으로 매개 변수를 사용하는 NSAction 메서드를 제공하므로 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 수 있는 유일한 경우는 문자열이 토큰으로 사용되는 경우입니다. 문자열 및 NSStringNSString 문서의 API 디자인에 대한 자세한 내용을 참조하세요.

드물게 API는 문자열() 대신 C와 유사한 문자열(char *NSString *)을 노출할 Objective-C 수 있습니다. 이 경우 다음을 사용하여 매개 변수에 주석을 달 수 있습니다. [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)]

위의 내용은 값에 "Retain" 의미 체계가 있는 것으로 플래그를 지정합니다. 사용 가능한 의미 체계는 다음과 같습니다.

  • 할당
  • 복사
  • 유지

스타일 지침

[내부] 사용

를 사용하여[Internal] 공용 API에서 메서드를 숨기는 특성입니다. 노출된 API가 너무 낮은 수준이고 이 메서드를 기반으로 하는 별도의 파일에 상위 수준 구현을 제공하려는 경우 이 작업을 수행할 수 있습니다.

바인딩 생성기에서 제한 사항이 발생할 때 이를 사용할 수도 있습니다. 예를 들어 일부 고급 시나리오에서는 바인딩되지 않은 형식을 노출하고 고유한 방식으로 바인딩하려는 경우 이러한 형식을 직접 래핑하려고 할 수 있습니다.

이벤트 처리기 및 콜백

Objective-C 클래스는 일반적으로 대리자 클래스(Objective-C 대리자)에 메시지를 전송하여 알림을 브로드캐스트하거나 정보를 요청합니다.

Xamarin.iOS에서 완전히 지원되고 노출되는 이 모델은 번거로울 수 있습니다. Xamarin.iOS는 이러한 상황에서 사용할 수 있는 클래스에 C# 이벤트 패턴 및 메서드 콜백 시스템을 노출합니다. 이렇게 하면 다음과 같은 코드를 실행할 수 있습니다.

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

바인딩 생성기는 패턴을 C# 패턴에 매핑 Objective-C 하는 데 필요한 입력 양을 줄일 수 있습니다.

Xamarin.iOS 1.4부터 생성기에 특정 Objective-C 대리자의 바인딩을 생성하고 대리자를 호스트 형식의 C# 이벤트 및 속성으로 노출하도록 지시할 수도 있습니다.

이 프로세스에는 현재 이벤트를 내보내고 실제 대리자 클래스로 DelegateWeakDelegate 전송하는 호스트 클래스인 두 개의 클래스가 있습니다.

다음 설정을 고려합니다.

[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 에서는 nint 형식의 속성이 With 포함됨).

위의 정의를 사용하여 생성기는 생성된 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);
};

콜백은 이벤트 호출과 비슷하며, 차이점은 여러 잠재적 구독자를 갖는 대신(예: 여러 메서드가 이벤트 또는 DownloadFinished 이벤트에 연결할 Clicked 수 있음) 콜백에는 단일 구독자만 있을 수 있다는 것입니다.

프로세스는 동일하며 유일한 차이점은 생성될 클래스의 EventArgs 이름을 노출하는 대신 EventArgs가 실제로 결과 C# 대리자 이름을 지정하는 데 사용된다는 것입니다.

대리자 클래스의 메서드가 값을 반환하는 경우 바인딩 생성기는 이벤트가 아닌 부모 클래스의 대리자 메서드에 매핑됩니다. 이러한 경우 사용자가 대리자를 연결하지 않는 경우 메서드에서 반환해야 하는 기본값을 제공해야 합니다. 다음을 사용하여 이 작업을 수행합니다. [DefaultValue] 또는 [DefaultValueFromArgument] 특성입니다.

[DefaultValue] 는 반환 값을 하드 코딩하는 동안 [DefaultValueFromArgument] 는 반환될 입력 인수를 지정하는 데 사용됩니다.

열거형 및 기본 형식

btouch 인터페이스 정의 시스템에서 직접 지원하지 않는 열거형 또는 기본 형식을 참조할 수도 있습니다. 이렇게 하려면 열거형 및 코어 형식을 별도의 파일에 넣고 btouch에 제공하는 추가 파일 중 하나의 일부로 포함합니다.

종속성 연결

애플리케이션에 속하지 않는 API를 바인딩하는 경우 실행 파일이 이러한 라이브러리에 대해 연결되어 있는지 확인해야 합니다.

Xamarin.iOS에 라이브러리를 연결하는 방법을 알려야 합니다. "-gcc_flags" 옵션을 사용하여 새 라이브러리와 연결하는 방법을 지정하는 추가 빌드 인수를 사용하여 명령을 호출 mtouch 하도록 빌드 구성을 변경한 다음 프로그램에 필요한 모든 추가 라이브러리를 포함하는 따옴표 붙은 문자열을 사용하여 이 작업을 수행할 수 있습니다. 이런 식으로:

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

위의 예제에서는 프레임워크 라이브러리를 CFNetwork 최종 실행 파일에 연결 libMyLibrary.alibSystemLibrary.dylib 합니다.

또는 계약 파일(예: AssemblyInfo.cs)에 포함할 수 있는 어셈블리 수준을 [LinkWithAttribute]활용할 수 있습니다. 이 [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 += { ... };
    }
}

더 이상 인스턴스에 변수를 유지할 필요가 없고, 지역 변수와 함께 작동하며, 개체가 죽을 때 참조를 지울 필요가 없다는 것을 알 수 있습니다.

이를 활용하려면 클래스에 선언에 [BaseType] Events 속성이 설정되고 다음과 같이 개체가 작업을 완료할 때 호출되는 메서드의 이름으로 설정된 변수가 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 v3.2부터 속성으로 [Model] 표시된 프로토콜에서 상속을 지원합니다. 프로토콜이 프로토콜에서 상속되는 위치와 같은 MapKit 특정 API 패턴에서 MKAnnotation 유용하며 상속되는 여러 클래스에서 NSObject채택 MKOverlay 됩니다.

지금까지는 프로토콜을 모든 구현에 복사해야 했지만, 이러한 경우 이제는 클래스가 프로토콜에서 MKOverlay 상속되도록 할 MKShape 수 있으며 필요한 모든 메서드를 자동으로 생성합니다.