Xamarin.iOS에서 NSObject의 제네릭 서브클래스

NSObjects에서 제네릭 사용

NSObject의 서브클래스(예: UIView)에서 제네릭을 사용할 수 있습니다.

class Foo<T> : UIView {
    public Foo (CGRect x) : base (x) {}
    public override void Draw (CoreGraphics.CGRect rect)
    {
        Console.WriteLine ("T: {0}. Type: {1}", typeof (T), GetType ().Name);
    }
}

NSObject를 서브클래싱하는 개체는 Objective-C 런타임에 등록되므로 NSObject 형식의 제네릭 서브클래스를 사용하여 가능한 작업에 관한 몇 가지 제한 사항이 있습니다.

NSObject의 제네릭 서브클래스에 대한 고려 사항

이 문서에서는 NSObjects의 제네릭 서브클래스에 대한 제한된 지원의 제한 사항을 자세히 설명합니다.

멤버 시그니처의 제네릭 형식 인수

Objective-C에 노출된 멤버 시그니처의 모든 제네릭 형식 인수에는 NSObject 제약 조건이 있어야 합니다.

양호:

class Generic<T> : NSObject where T: NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
    }
}

이유: 제네릭 형식 매개 변수는 NSObject이므로 myMethod:에 대한 선택기 시그너처가 Objective-C에 안전하게 노출될 수 있습니다(항상 NSObject 또는 해당 서브클래스).

잘못됨:

class Generic<T> : NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
    }
}

이유: 서명은 제네릭 형식T의 정확한 형식에 따라 다르므로 코드에서 Objective-C 호출할 수 있는 내보낸 멤버에 대한 서명을 만들 Objective-C 수 없습니다.

양호:

class Generic<T> : NSObject
{
    T storage;

    [Export ("myMethod:")]
    public void MyMethod (NSObject value)
    {
    }
}

이유: 내보낸 멤버 시그니처의 일부를 사용하지 않는 한 제한되지 않은 제네릭 형식 인수를 사용할 수 있습니다.

양호:

class Generic<T, U> : NSObject where T: NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
        Console.WriteLine (typeof (U));
    }
}

이유: Objective-C에서 내보낸 MyMethodT 매개 변수는 NSObject로 제한되며, 제한되지 않은 형식 U는 시그니처의 일부가 아닙니다.

잘못됨:

class Generic<T> : NSObject
{
    public T Storage { get; }

    public Generic(T value)
    {
        Storage = value;
    }
}

[Register("Foo")]
class Foo: NSView
{
    [Export("Test")]
    public Generic<int> Test { get; set; } = new Generic<int>(22);

    [Export("Test1")]
    public Generic<object> Test1 { get; set; } = new Generic<object>(new object());

    [Export("Test2")]
    public Generic<NSObject> Test2 { get; set; } = new Generic<NSObject>(new NSObject());

}

이유: 이 시나리오는 registrar 아직 지원되지 않습니다. 자세한 내용은 이 GitHub 문제를 참조 하세요.

Objective-C에서 제네릭 형식 인스턴스화

Objective-C에서 제네릭 형식 인스턴스화는 허용되지 않습니다. 이는 일반적으로 xib 또는 스토리보드에서 관리되는 형식을 사용할 때 발생합니다.

IntPtr을 사용하는 생성자를 노출하는 다음 클래스 정의를 살펴보세요(Xamarin.iOS가 네이티브 Objective-C 인스턴스에서 C# 개체를 구성하는 방법).

class Generic<T> : NSObject where T : NSObject
{
    public Generic () {}
    public Generic (IntPtr ptr) : base (ptr) {}
}

위 구문에는 문제가 없지만, 런타임에 Objective-C가 해당 인스턴스를 만들려고 시도하는 경우 예외를 throw합니다.

Objective-C에 제네릭 형식 개념이 없고 만들 정확한 제네릭 형식을 지정할 수 없기 때문에 이 문제가 발생합니다.

이 문제는 제네릭 형식의 특수화된 서브클래스를 만들어 해결할 수 있습니다. 예시:

class Generic<T> : NSObject where T : NSObject
{
    public Generic () {}
    public Generic (IntPtr ptr) : base (ptr) {}
}

class GenericUIView : Generic<UIView>
{
}

이제 더 이상 모호성이 없으므로 클래스 GenericUIView를 xib 또는 스토리보드에서 사용할 수 있습니다.

제네릭 메서드 지원 안 함

제네릭 메서드는 지원되지 않습니다.

다음 코드는 컴파일되지 않습니다.

class MyClass : NSObject
{
    [Export ("myMethod")]
    public void MyMethod<T> (T argument)
    {
    }
}

이유: 이는 메서드가 Objective-C에서 호출될 때 Xamarin.iOS가 형식 인수 T에 사용할 형식을 알 수 없기 때문에 허용되지 않습니다.

대신 특수화된 메서드를 만들어 대신 내보내는 것이 좋습니다.

class MyClass : NSObject
{
    [Export ("myMethod")]
    public void MyUIViewMethod (UIView argument)
    {
        MyMethod<UIView> (argument);
    }
    public void MyMethod<T> (T argument)
    {
    }
}

내보낸 정적 멤버가 허용되지 않음

NSObject의 제네릭 서브클래스 내에서 호스트되는 정적 멤버는 Objective-C에 노출할 수 없습니다.

지원되지 않는 시나리오 예:

class Generic<T> : NSObject where T : NSObject
{
    [Export ("myMethod:")]
    public static void MyMethod ()
    {
    }

    [Export ("myProperty")]
    public static T MyProperty { get; set; }
}

이유: 제네릭 메서드와 마찬가지로 Xamarin.iOS 런타임은 제네릭 형식 인수 T에 사용할 형식을 알 수 있어야 합니다.

인스턴스 멤버의 경우 인스턴스 자체가 사용되지만(인스턴스 Generic<T>가 없고 항상 Generic<SomeSpecificClass>이기 때문) 정적 멤버의 경우에는 이 정보가 제공되지 않습니다.

이는 문제의 멤버가 형식 인수 T를 사용하지 않는 경우에도 적용됩니다.

이 경우에는 특수화된 하위 클래스를 만드는 방법이 있습니다.

class GenericUIView : Generic<UIView>
{
    [Export ("myUIViewMethod")]
    public static void MyUIViewMethod ()
    {
        MyMethod ();
    }

    [Export ("myProperty")]
    public static UIView MyUIViewProperty {
        get { return MyProperty; }
        set { MyProperty = value; }
    }
}

class Generic<T> : NSObject where T : NSObject
{
    public static void MyMethod () {}
    public static T MyProperty { get; set; }
}

성능

정적 registrar는 일반적으로 하는 것처럼 빌드 시 제네릭 형식의 내보낸 멤버를 확인할 수 없습니다. 이 멤버는 런타임에 조회되어야 합니다. 즉, 이러한 메서드 Objective-C 를 호출하는 것은 제네릭이 아닌 클래스에서 멤버를 호출하는 것보다 약간 느립니다.