標籤:
決定新開一坑,在不斷學習的同時分享自己的學習曆程給大家,既是對自己學習的記錄,又希望能對大家提供些微的協助。
這一篇文章主要來介紹泛型的意義、使用與聲明方法等。
1.泛型:限制類型
1.1.泛型使用情境:
1.在集合(數組NSArray、字典NSDictionary、集合NSSet)中使用泛型比較常見。
2.當聲明一個類,但是類裡面的某些屬性的類型不確定的時候,我們才使用泛型。
1.2.泛型書寫規範
在類型後面定義泛型:NSMutableArray<UITouch *> dataArray
1.3.泛型修飾
只能修飾方法的調用。
1.4.泛型好處:
1.提高開發的規範,減少程式員之間的交流。
2.通過集合取出來的對象,可以直接當做泛型對象使用。這樣我們就可以直接使用.點文法。
2.代碼使用泛型:
2.1.聲明一個泛型為NSString的數組
1 // 具體做法就是在 NSMutableArray 後帶一個 <NSString *> ,角括弧內部即為泛型型別
2 @property (nonatomic, strong, nullable) NSMutableArray<NSString *> *dataArray;
2.2.當我們要給數組添加對象或取出對象的時候,系統就會自動提示應該傳入或者取出來的對象的類型,這個類型就是你剛才聲明的泛型型別
1 // 1.沒有使用泛型 2 // [self.dataArray addObject:<#(nonnull id)#>]; 3 4 // 2.使用泛型 5 // [self.dataArray addObject:<#(nonnull NSString *)#>]; 6 7 // 3.添加不正確的類型,會出現警告 8 // [self.dataArray addObject:@1]; 9 10 // 4.我們可以直接將集合中取出來的對象當做泛型使用11 NSInteger length = [self.dataArray.firstObject length];
基本上實現了使用泛型過程中可能出現的情況。
3.泛型的自訂
剛才我們只是實現了系統類別NSMutableArray的泛型。接下來我們要考慮下,我們怎麼樣在我們自己的類中聲明一個泛型的屬性呢?
為了這個目的,我們建立一個 Language 的類表示 “語言”。並且建立兩個 Language 的子類,分別叫 Java 和 IOS 。很明顯這兩個是“某一個類型的語言”。我們建立一個Person的類,給類聲明一個泛型,在類的 .h 檔案中聲明一個聲明一個屬性,這個屬性工作表示這個人會的語言,即為 IOS 或者 Java 。那麼我們有以下兩種聲明方式:
1 // 語言2 // 1.id:任何對象都能傳進來3 //@property (nonatomic, strong, null_unspecified) id language;4 // 2.Language:在外面調用的時候不能提示具體是哪種語言5 //@property (nonatomic, strong, null_unspecified) Language *language;
因為 Language 這個語言並不能代表這個 Person 究竟會什麼語言,我們需要的屬性時 IOS 和 Java。這兩種都可以在調用的時候傳入 Java 和 IOS 兩種對象,但它們的缺點也非常明顯,若使用 id 則我們可以傳入任何對象,而不只是 Java 和 IOS ;使用 Language * 呢,我們沒有辦法在編譯的時候確定這個人究竟會什麼語言,而只能在運行時判斷。有沒有辦法讓我們在編譯的時候就能知道 Person 具體會哪種 Language 呢?
辦法就是泛型。
聲明自訂類的泛型,我們需要做這樣一步:
1 // (給類)聲明一個泛型2 @interface JTPerson<ObjectType> : NSObject
看出區別了嗎?我們在 interface 類名之後加了一對角括弧 <> ,中間是 ObjectType 。這個就代表泛型。這樣我們在聲明屬性的時候就可以這麼寫:
1 @property (nonatomic, strong, null_unspecified) ObjectType language;
也就是,我們現在不指定具體的類型,而在執行個體化這個類的時候確定這個泛型。若不確定,那麼所有的 ObjectType 會自動變成 id 。像這樣:
1 // iOS 2 JTPerson<IOS *> *iOSP = [[JTPerson alloc] init]; 3 4 // [iOSP setLanguage:@"123"]; 5 6 // [iOSP setLanguage:<#(IOS * _Null_unspecified)#>]; 7 8 // [iOSP setLanguage:[[Java alloc] init]]; 9 10 // Java11 JTPerson<Java *> *javaP = [[JTPerson alloc] init];12 13 // [javaP setLanguage:@"123"];14 15 // [javaP setLanguage:<#(Java * _Null_unspecified)#>];16 17 // [javaP setLanguage:[[IOS alloc] init]];
這樣,我們在聲明 Person這個類的時候,就順便聲明了這個類的泛型。這樣系統就會在你使用到泛型的屬性與方法的時候,自動提醒你聲明的泛型型別了。
4.泛型的協變與逆變
首先我們來看一個方法:
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {2 3 [touches anyObject];4 }
大家應該很熟悉這個方法了。這就是 UIViewController 繼承自 UIResponder 的方法,是點擊這個視圖控制器之後事件鏈的最後一環。這時候經過上面的學習,我們會看到這其中就使用了泛型: (NSSet<UITouch *> *) 。這樣我們點進集合 NSSet 會發現它是這樣寫的:
前後我們都很清楚: interface:聲明;NSSet:集合;ObjectType:泛型;NSObject:是NSSet的父類,也是根類;後面角括弧中表示的是遵守的協議。
那麼 __covariant 代表什嗎?
這裡我們先來學習兩個單詞: covariant:協變的 contravariant:逆變的。
那麼 __covariant協變 代表什嗎?
我們來試一下。在剛才的自訂類 Person 的泛型 ObjectType 前加這樣一條修飾: __covariant
1 @interface JTPerson<__covariant ObjectType> : NSObject
然後在調用的時候,我們聲明兩個執行個體,泛型分別為父類 Language 和子類 IOS。這樣若泛型為 IOS 的 Person 想給泛型為 Language 的 Person 賦值,會怎麼樣:
1 // 1.協變2 // iOS3 JTPerson<IOS *> *iOSP = [[JTPerson alloc] init];4 5 // Language6 JTPerson<Language *> *p = [[JTPerson alloc] init];7 8 // 如果子類想給父類賦值,協變9 p = iOSP;
並沒有報錯。
也就是說,若泛型為子類的對象想給泛型為父類的對象賦值的時候,我們需要在泛型前面添加 協變__convariant ,告訴編譯器,這樣轉換沒有問題。
同理,__contravariant逆變就是若泛型為父類的對象想給泛型為子類的對象賦值的時候,我們需要在泛型前面添加 逆變__contravariant ,告訴編譯器,這樣轉換沒有問題。
這就是協變與逆變的含義。
協變與逆變本身是物件導向繼承的語言的一個特性,也並不只應用在泛型這裡。
iOS學習筆記(01) - 泛型