Подтвердить что ты не робот

IOS __kindof NSArray?

Я проверял новые функции iOS 9 как разработчика, и некоторые из них, такие как StackView, выглядят потрясающе.

Когда я зашел в заголовочный файл UIStackView, я увидел это:

@property(nonatomic,readonly,copy) NSArray<__kindof UIView *> *arrangedSubviews;

Что такое __kindof на NSArray *. Можем ли мы теперь указать тип в NSArray *?

Маленький тест:

@interface DXTEST ()
@property (strong, nonatomic) NSMutableArray <__kindof NSString *>  *strings;
@end

@implementation DXTEST

- ( instancetype ) init {
    self = [super init];

    if( self ) {
        _strings = [NSMutableArray new];

        [_strings addObject:@(1)]; <-- compiler warning wieeeeee
    }

    return self;
}

@end
4b9b3361

Ответ 1

Можно ли указать тип в NSArray * сейчас?

Да, через Objective-C новые легкие дженерики. В приведенном примере у вас есть свойство типа NSArray, , которое будет принимать элементы UIView s.

Теперь это можно указать следующим образом (без __kindof).

@property(nonatomic,readonly,copy) NSArray<UIView *> *arrangedSubviews;

И в этом случае массив будет принимать объекты, класс которых UIView, но не любые объекты, которые являются подклассами UIView. Объявление __kindof отмечает тип массива как общий, который может принимать оба экземпляра класса UIView и экземпляры любого из подклассов UIView.

Edit:

Я удалил основную часть моего первоначального ответа, так как я ошибочно считал, что указание типа массива помешает вам вставлять объекты неправильного типа, и это не так. (Спасибо Артем Абрамов за это. см. Его ответ ниже для дополнительной информации)

Objective-C Генералы, похоже, существуют, чтобы предоставить вам информацию о типе при доступе к элементам общей коллекции. Например, рассмотрим следующий код, который добавляет UIView и a UIImageView в NSMutableArray<UIView *>. Оба объекта вставляются в массив без каких-либо жалоб со стороны компиляторов или среды выполнения, но когда вы пытаетесь получить доступ к элементам, вы будете предупреждены компилятором, если тип вашей переменной - это нечто иное, чем общий тип массива (UIView), даже если это один из подклассов UIView.

NSMutableArray<UIView *> *subviews = [[NSMutableArray alloc] init];

[subviews addObject:[[UIView alloc] init]]; // Works
[subviews addObject:[[UIImageView alloc] init]]; // Also works

UIView *sameView = subviews[0]; // Works
UIImageView *sameImageView = subviews[1]; // Incompatible pointer types initializing 'UIImageView *' with an expression of type 'UIView *'

NSLog(@"%@", NSStringFromClass([sameView class])); // UIView
NSLog(@"%@", NSStringFromClass([sameImageView class])); // UIImageView

Теперь это создает предупреждение о времени компиляции, но не сбой во время выполнения. Ключевое различие между этим и тем же примером, где тип generic типа помечен как __kindof, заключается в том, что компилятор не будет жаловаться, если вы попытаетесь получить доступ к своим элементам и сохраните результат в переменной, которая имеет тип UIView или один из его подклассов.

NSMutableArray<__kindof UIView *> *subviews = [[NSMutableArray alloc] init];

[subviews addObject:[[UIView alloc] init]]; // Works
[subviews addObject:[[UIImageView alloc] init]]; // Also works

UIView *sameView = subviews[0]; // No problem
UIImageView *sameImageView = subviews[1]; // No complaints now!

NSLog(@"%@", NSStringFromClass([sameView class])); // UIView
NSLog(@"%@", NSStringFromClass([sameImageView class])); // UIImageView

Ответ 2

К сожалению, в настоящее время голосовые ответы на голосование немного неправильны.

Фактически вы можете добавить любой подкласс <T> к общей коллекции:

@interface CustomView : UIView @end
@implementation CustomView @end

NSMutableArray<UIView *> *views;
UIView *view = [UIView new];
CustomView *customView = [CustomView new];
[views addObject:view];
[views addObject:customView];//compiles and runs!

Но когда вы попытаетесь восстановить объект, он будет строго напечатан как <T> и потребует кастинга:

//Warning: incompatible pointer types initializing 
//`CustomView *` with an expression of type `UIView * _Nullable`
CustomView *retrivedView = views.firstObject;

Но если вы добавите ключевое слово __kindof, возвращаемый тип будет изменен на kindof T, и никакое кастинг не потребуется:

NSMutableArray<__kindof UIView *> *views;
<...>
CustomView *retrivedView = views.firstObject;

TL;DR: Generics в Objective-C может принимать подклассы из <T>, __kindof ключевых слов, которые могут быть подклассом <T>.

Ответ 3

iOS9 представил легкие дженерики на ObjC. ObjC - действительно динамический язык, в то время как SWIFT предпочитает статические типы, чтобы максимизировать интероперабельность и проверку типов теперь в ObjC, вы можете объявить такой массив:

NSArray<UIView *> *views;

Это означает, что все объекты views являются экземплярами объектов UIView, представьте, что свойство subviews UIView может содержать элементы, которые могут быть разных типов, UIView и объекты, которые наследуются от UIView, но компилятор будет жалуются, потому что даже если они наследуют, они разные.
То есть были __kindof вступили в игру. Как сказать, массив содержит объекты типа типа UIView.
Похоже, что все еще использует преимущество типа id, но ограничивается каким-то классом.

Ответ 4

@Артем Абрамов прав. Я хочу указать дополнительную точку, как указано в Objective-C Generics

__kindof говорит, что эта вещь должна быть X или более производным классом. Почему это необходимо? Документы не называют это, но я подозреваю, что это потому, что добавление дженериков в Objective-C вводит массовую загадку: If UIView.subviews теперь NSArray<UIView *>, тогда много кода теперь недействителен в соответствии с проверкой типа компилятора: [view.subviews[0] setImage:nil] является фиктивным, потому что UIView не имеет свойство изображения. Мы были испорчены тем, что Objective-Cкомпилятор позволит вам отправлять любой видимый селектор сообщений только на id... только теперь массив subviews не возвращает id, он возвращает UIView *. К сожалению.

__kindof решает эту проблему, говоря, что массив subviews явно не является массивом UIView *, а представляет собой массив подклассов UIView *. Затем компилятор позволит вам отправлять любые селекторы, которые могут применяться к UIView * или все его подклассы или назначить элемент из этого массива до UIImageView *, но он все равно будет жаловаться, если вы попытаетесь сделать это: NSNumber *n = view.subviews[0].

И это особенность Clang, введенная в Xcode 7. Я не думаю, что это что-то связано с версией iOS

Ответ 5

Если у вас есть функция, которая возвращает __kindof SomeType *, то вы можете назначить результат этой функции переменной типа SubtypeOfSomeType * без явного приведения. Без __kindof компилятор будет жаловаться на несовместимые типы.

На самом деле существует как минимум одна функция, такая как в UIKit: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/index.html#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:

Теперь распространите это знание на NSArray. Указание __kindof в параметре generic type добавляет, что __kindof возвращает тип -objectAtIndex: и -objectAtIndexedSubscript: (ну, собственно, к любому методу общего NSArray, который возвращает ObjectType), и теперь вы можете легко назначить результат вызова stackView.arrangedSubviews[i] для любой переменной, которая имеет тип, являющийся подклассом UIView или UIView.

Важное замечание: вы можете хранить UIView подклассы в NSArray<UIView *> просто отлично, даже без __kindof. __kindof касается только доступа к элементам массива.