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
Ответы
Ответ 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
касается только доступа к элементам массива.