มีคอลเลกชันที่พิมพ์อย่างมากใน Objective-C หรือไม่


140

ฉันยังใหม่กับการเขียนโปรแกรม Mac / iPhone และ Objective-C ใน C # และ Java เรามี "generics" คลาสการรวบรวมที่สมาชิกสามารถเป็นประเภทที่ประกาศเท่านั้น ตัวอย่างเช่นใน C #

Dictionary<int, MyCustomObject>

สามารถมีคีย์ที่เป็นจำนวนเต็มและค่าที่เป็นประเภท MyCustomObject เท่านั้น มีกลไกที่คล้ายกันอยู่ใน Objective-C หรือไม่?


เพิ่งเริ่มเรียนรู้เกี่ยวกับ ObjC ด้วยตัวเอง บางทีคุณสามารถใช้ ObjC ++ เพื่อทำการยกของหนัก
Toybuilder

คุณอาจสนใจคำตอบสำหรับคำถามนี้: มีวิธีการบังคับใช้การพิมพ์บน NSArray, NSMutableArray หรือไม่? . มีการให้ข้อโต้แย้งว่าเพราะเหตุใดจึงไม่ใช่เรื่องธรรมดาใน Objective-C / Cocoa
mouviciel

2
ObjC ++ ไม่ใช่ภาษาจริงๆ ... เป็นอีกวิธีในการอ้างอิงความสามารถของ ObjC ในการจัดการอินไลน์ C ++ เหมือนกับที่จัดการกับ C คุณไม่ควรทำสิ่งนี้เว้นแต่คุณจะต้องทำเช่นนั้น (เช่นถ้าคุณต้องการ เพื่อใช้ไลบรารีบุคคลที่สามที่เขียนใน C ++)
Marc W

สวยมากแน่นอนซ้ำกันของstackoverflow.com/questions/649483/...
แบร์รี่ Wark

@ Mark W - "ไม่ควรทำอย่างนี้" ทำไมล่ะ? ฉันใช้ ObjC ++ และใช้งานได้ดี ฉันสามารถทำ #import <map> และ @property std :: map <int, NSString *> myDict; ฉันสามารถใช้ API โกโก้แบบเต็มและมีคอลเลกชันที่พิมพ์ได้ดี ฉันไม่เห็นข้อเสียใด ๆ
John Henckel

คำตอบ:


211

ใน Xcode 7 Apple ได้เปิดตัว 'Lightweight Generics' ให้แก่ Objective-C ใน Objective-C พวกเขาจะสร้างคำเตือนคอมไพเลอร์หากมีประเภทไม่ตรงกัน

NSArray<NSString*>* arr = @[@"str"];

NSString* string = [arr objectAtIndex:0];
NSNumber* number = [arr objectAtIndex:0]; // Warning: Incompatible pointer types initializing 'NSNumber *' with an expression of type 'NSString *'

และในรหัส Swift พวกเขาจะสร้างข้อผิดพลาดของคอมไพเลอร์:

var str: String = arr[0]
var num: Int = arr[0] //Error 'String' is not convertible to 'Int'

Lightweight Generics มีวัตถุประสงค์เพื่อใช้กับ NSArray, NSDictionary และ NSSet แต่คุณสามารถเพิ่มลงในคลาสของคุณเอง:

@interface GenericsTest<__covariant T> : NSObject

-(void)genericMethod:(T)object;

@end

@implementation GenericsTest

-(void)genericMethod:(id)object {}

@end

วัตถุประสงค์ -C จะทำตัวเหมือนที่เคยทำมาก่อนด้วยคำเตือนของคอมไพเลอร์

GenericsTest<NSString*>* test = [GenericsTest new];

[test genericMethod:@"string"];
[test genericMethod:@1]; // Warning: Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'

แต่ Swift จะไม่สนใจข้อมูลทั่วไปอย่างสมบูรณ์ (ไม่เป็นจริงใน Swift 3+ อีกต่อไป)

var test = GenericsTest<String>() //Error: Cannot specialize non-generic type 'GenericsTest'

นอกเหนือจากคลาสการรวบรวมของมูลนิธิเหล่านี้แล้วนายพล Swift น้ำหนักเบา Objective-C ยังถูกละเว้น ประเภทอื่น ๆ ที่ใช้ยาชื่อสามัญที่มีน้ำหนักเบาจะถูกนำเข้าสู่ Swift ราวกับว่าพวกเขาไม่มีพารามิเตอร์

การโต้ตอบกับ Objective-C APIs


เนื่องจากฉันมีคำถามเกี่ยวกับ generics และประเภทที่ส่งกลับมาในวิธีการฉันถามคำถามของฉันในหัวข้อที่แตกต่างกันเพื่อให้ทุกอย่างชัดเจน: stackoverflow.com/questions/30828076/…
lvp

2
@rizzes ใช่มันเพิ่งเปิดตัว
คอนเนอร์

หนึ่งข้อแม้ที่นี่เป็นที่สวิฟท์ไม่ได้อย่างสิ้นเชิงไม่สนใจคำอธิบายประกอบประเภทในชั้นเรียนของคุณ ObjC ทั่วไป ถ้าคุณระบุข้อ จำกัด เช่นMyClass <Foo: id<Bar>>รหัส Swift ของคุณจะถือว่าค่าเป็นประเภทของข้อ จำกัด ของคุณซึ่งช่วยให้คุณมีบางสิ่งบางอย่างที่จะทำงานร่วมกับ อย่างไรก็ตามคลาสย่อยเฉพาะของMyClassจะมีประเภทพิเศษที่ละเว้น (ดูได้อย่างมีประสิทธิภาพเหมือนกับสามัญMyClass) ดูgithub.com/bgerstle/LightweightGenericsExample
Brian Gerstle

ดังนั้นสิ่งที่รวบรวมสำหรับ 10.10, 10.9 และระบบปฏิบัติการรุ่นก่อนหน้า?
p0lAris

ตราบใดที่คุณตั้งค่าเป้าหมายการปรับใช้ของคุณเพื่อสนับสนุนพวกเขา
Connor

91

คำตอบนี้ล้าสมัย แต่ยังคงอยู่สำหรับมูลค่าในอดีต ในฐานะของ Xcode 7 คำตอบของ Connor จาก 8 มิ.ย. 58 นั้นแม่นยำยิ่งขึ้น


ไม่ไม่มี Generics ใน Objective-C เว้นแต่คุณต้องการใช้แม่แบบ C ++ ในคลาสคอลเล็กชันที่คุณกำหนดเอง (ซึ่งฉันไม่แนะนำอย่างยิ่ง)

Objective-C มีการพิมพ์แบบไดนามิกเป็นคุณสมบัติซึ่งหมายความว่ารันไทม์ไม่สนใจเกี่ยวกับประเภทของวัตถุเนื่องจากวัตถุทั้งหมดสามารถรับข้อความ เมื่อคุณเพิ่มวัตถุในคอลเลกชันในตัววัตถุเหล่านั้นจะได้รับการปฏิบัติเสมือนว่าเป็นidวัตถุประเภทนั้น แต่ไม่ต้องกังวลเพียงแค่ส่งข้อความไปยังวัตถุเหล่านั้นเช่นปกติ มันจะทำงานได้ดี(เว้นแต่ของหลักสูตรหนึ่งหรือมากกว่าหนึ่งของวัตถุในคอลเลกชันที่ไม่ตอบสนองต่อข้อความที่คุณส่ง)

จำเป็นต้องใช้ยาสามัญในภาษาต่าง ๆ เช่น Java และ C # เนื่องจากเป็นภาษาที่แข็งแกร่งและพิมพ์แบบคงที่ ballgame ที่แตกต่างกันอย่างสิ้นเชิงกว่าคุณสมบัติการพิมพ์แบบไดนามิกของ Objective-C


88
ฉันไม่เห็นด้วยกับ "ไม่ต้องกังวลเพียงแค่ส่งข้อความไปยังวัตถุเหล่านั้น" หากคุณใส่วัตถุประเภทที่ไม่ถูกต้องลงในคอลเลกชันซึ่งไม่ตอบสนองต่อข้อความเหล่านี้สิ่งนี้จะทำให้เกิดข้อผิดพลาดรันไทม์ การใช้ยาชื่อสามัญในภาษาอื่น ๆ ช่วยหลีกเลี่ยงปัญหานี้ด้วยการตรวจสอบเวลาคอมไพล์
henning77

8
@ henning77 ใช่ แต่ Objective-C เป็นภาษาแบบไดนามิกมากกว่าภาษาเหล่านี้ หากคุณต้องการความปลอดภัยระดับสูงให้ใช้ภาษาเหล่านั้น
Raffi Khatchadourian

36
ฉันยังไม่เห็นด้วยกับปรัชญากังวลไม่ได้ - เช่นถ้าคุณดึงรายการแรกออกจาก NSArray และโยนมันทิ้งไปยัง NSNumber แต่รายการที่เป็นจริง NSString คุณจะเมา ...
jjxtra

13
@RaffiKhatchadourian - ไม่มีทางเลือกมากนักถ้าคุณกำลังเขียนแอพ iOS ถ้ามันง่ายที่จะเขียนด้วย Java และได้รับประโยชน์ทั้งหมดจากการเขียนแอพที่มีอยู่ให้เชื่อฉัน: ฉันจะ
ericsoco

11
ข้อร้องเรียนที่ใหญ่ที่สุดที่ฉันมีเกี่ยวกับเรื่องนี้ไม่ได้เกี่ยวกับภาษาแบบไดนามิกกับการตรวจสอบเวลาแบบคอมไพล์ แต่เป็นการสื่อสารกับนักพัฒนาอย่างง่าย ฉันไม่สามารถดูประกาศคุณสมบัติและรู้ว่าวัตถุชนิดใดที่จะส่งคืนเว้นแต่ว่ามีการจัดทำเอกสารไว้ที่ใดที่หนึ่ง
devios1

11

ไม่ แต่เพื่อให้ชัดเจนคุณสามารถแสดงความคิดเห็นกับประเภทของวัตถุที่คุณต้องการจัดเก็บฉันได้เห็นสิ่งนี้ทำสองสามครั้งเมื่อคุณจำเป็นต้องเขียนอะไรบางอย่างใน Java 1.4 ในปัจจุบัน) เช่น:

NSMutableArray* /*<TypeA>*/ arrayName = ....

หรือ

NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...

ฉันเดาว่านี่เป็นวิธีที่ดีในการบันทึกไว้ในกรณีที่มีคนอ่านโค้ดของคุณ อย่างไรก็ตามชื่อของตัวแปรควรมีความชัดเจนที่สุดเท่าที่จะเป็นไปได้เพื่อให้ทราบว่ามีวัตถุใดอยู่
htafoya

6

ไม่มีข้อมูลทั่วไปใน Objective-C

จากเอกสาร

อาร์เรย์เป็นชุดของวัตถุที่ได้รับคำสั่ง Cocoa มีคลาสอาเรย์หลายคลาส ได้แก่ NSArray, NSMutableArray (subclass ของ NSArray) และ NSPointerArray


เชื่อมโยงไปยังเอกสารในคำตอบของตาย - "ขออภัยหน้าเว็บที่ไม่สามารถพบ"
ปาง

6

Apple ได้เพิ่มข้อมูลทั่วไปใน ObjC ใน XCode 7:

@property NSArray<NSDate *>* dates;
- (NSArray<NSDate *> *)datesBeforeDate:(NSDate *)date;
- (void)addDatesParsedFromTimestamps:(NSArray<NSString *> *)timestamps;

ดูที่นี่: https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6-ID61


5

สิ่งนี้ได้รับการปล่อยตัวใน Xcode 7 (ในที่สุด!)

โปรดทราบว่าในรหัสวัตถุประสงค์ C มันเป็นเพียงการตรวจสอบเวลารวบรวม จะไม่มีข้อผิดพลาดรันไทม์เพียงแค่ใส่ชนิดที่ไม่ถูกต้องลงในคอลเลกชันหรือกำหนดให้กับคุณสมบัติที่พิมพ์

ประกาศ:

@interface FooClass <T> : NSObject
@property (nonatomic) T prop;
@end

ใช้:

FooClass<NSString *> *foo = [[FooClass alloc] init];
NSArray<FooClass<NSString *> *> *fooAry = [NSArray array];

โปรดใช้ความระมัดระวังเกี่ยวกับผู้*s


4

NSArrays ทั่วไปสามารถรับรู้ได้โดยการแบ่งคลาสย่อยNSArrayและกำหนดวิธีการที่ให้ไว้ทั้งหมดด้วยวิธีที่ จำกัด มากขึ้น ตัวอย่างเช่น,

- (id)objectAtIndex:(NSUInteger)index

จะต้องมีการนิยามใหม่ใน

@interface NSStringArray : NSArray

เช่น

- (NSString *)objectAtIndex:(NSUInteger)index

สำหรับ NSArray ที่จะมี NSStrings เท่านั้น

คลาสย่อยที่สร้างสามารถใช้แทนการดร็อปอินและนำคุณสมบัติที่มีประโยชน์มากมาย: คำเตือนคอมไพเลอร์, การเข้าถึงคุณสมบัติ, การสร้างโค้ดที่ดีขึ้นและ -completion ใน Xcode ทั้งหมดนี้เป็นคุณสมบัติการคอมไพล์เวลาไม่จำเป็นต้องกำหนดการนำไปใช้งานจริง - วิธีการของ NSArray ยังคงสามารถใช้งานได้

เป็นไปได้ที่จะทำให้เป็นแบบนี้โดยอัตโนมัติและต้มให้เหลือเพียงสองข้อความซึ่งนำมาซึ่งความใกล้เคียงกับภาษาที่รองรับคำศัพท์ทั่วไป ฉันได้สร้างระบบอัตโนมัติด้วยWMGenericCollectionซึ่งมีเทมเพลตให้ไว้ในมาโคร C ตัว

หลังจากอิมพอร์ตไฟล์ส่วนหัวที่มีแมโครคุณสามารถสร้าง NSArray ทั่วไปโดยมีสองข้อความสั่ง: หนึ่งคำสั่งสำหรับอินเตอร์เฟสและอีกหนึ่งข้อความสำหรับการนำไปใช้ คุณจะต้องระบุประเภทข้อมูลที่คุณต้องการจัดเก็บและชื่อสำหรับคลาสย่อยของคุณ WMGenericCollection ให้แม่แบบดังกล่าวสำหรับNSArray, NSDictionaryและNSSetเช่นเดียวกับคู่ของพวกเขาแน่นอน

ตัวอย่าง: List<int>สามารถรับรู้ได้โดยคลาสที่กำหนดเองที่เรียกว่าNumberArrayซึ่งสร้างขึ้นด้วยคำสั่งต่อไปนี้:

WMGENERICARRAY_INTERFACE(NSNumber *, // type of the value class
                         // generated class names
                         NumberArray, MutableNumberArray)

เมื่อคุณสร้างNumberArrayคุณสามารถใช้มันได้ทุกที่ในโครงการของคุณ มันไม่มีไวยากรณ์<int>แต่คุณสามารถเลือกรูปแบบการตั้งชื่อของคุณเองเพื่อติดป้ายเหล่านี้เป็นคลาสเป็นเทมเพลต


โปรดทราบว่ามีอยู่ใน CoreLib: github.com/core-code/CoreLib/blob/master/CoreLib/CoreLib.h#L105
user1259710


2

ตอนนี้ความฝันเป็นจริง - มี Generics ใน Objective-C ตั้งแต่วันนี้ (ขอบคุณ WWDC) ไม่ใช่เรื่องตลก - ในหน้าอย่างเป็นทางการของ Swift:

คุณลักษณะไวยากรณ์ใหม่ช่วยให้คุณสามารถเขียนโค้ดที่แสดงออกได้มากขึ้นในขณะที่ปรับปรุงความสอดคล้องในภาษาต่างๆ SDK ได้ใช้คุณสมบัติใหม่ของ Objective-C เช่นข้อมูลทั่วไปและคำอธิบายประกอบแบบ nullability เพื่อให้รหัส Swift สะอาดและปลอดภัยยิ่งขึ้น นี่เป็นเพียงตัวอย่างของการปรับปรุง Swift 2.0

และภาพที่พิสูจน์สิ่งนี้:generics-C วัตถุประสงค์


2

แค่อยากจะกระโดดเข้าไปที่นี่ ฉันเขียนโพสต์บล็อกที่นี่เกี่ยวกับ Generics

สิ่งที่ฉันต้องการมีส่วนร่วมคือGenerics สามารถเพิ่มในชั้นเรียนใด ๆไม่ใช่แค่คลาสคอลเล็กชันตามที่ Apple ระบุ

ฉันได้เพิ่มเรียบร้อยแล้วไปยังคลาสต่าง ๆ ตามที่พวกเขาทำงานเหมือนกับคอลเลกชันของ Apple ทำ กล่าวคือ การตรวจสอบเวลาการคอมไพล์, การทำให้โค้ดสมบูรณ์, การเปิดใช้งานการลบการปลดเปลื้อง ฯลฯ

สนุก.


-2

คลาส Collections ที่จัดทำโดยเฟรมเวิร์กของ Apple และ GNUStep เป็นแบบกึ่งทั่วไปซึ่งพวกเขาคิดว่าพวกมันได้รับอ็อบเจกต์บางอันสามารถเรียงลำดับได้และบางอันตอบสนองต่อข้อความบางอย่าง สำหรับ primitives เช่น float, ints, ฯลฯ โครงสร้าง C array ทั้งหมดยังคงอยู่และสามารถใช้งานได้และมีวัตถุ wrapper พิเศษสำหรับพวกมันเพื่อใช้ในคลาส collection ทั่วไป (เช่น NSNumber) นอกจากนี้คลาส Collection อาจถูกแบ่งย่อย (หรือแก้ไขเฉพาะผ่านหมวดหมู่) เพื่อยอมรับวัตถุประเภทใดก็ได้ แต่คุณต้องเขียนรหัสการจัดการประเภทด้วยตัวเองทั้งหมด ข้อความอาจถูกส่งไปยังวัตถุใดก็ได้ แต่ควรคืนค่าว่างหากไม่เหมาะสมสำหรับวัตถุหรือข้อความควรถูกส่งต่อไปยังวัตถุที่เหมาะสม ข้อผิดพลาดประเภท True ควรได้รับการรวบรวม ณ เวลารวบรวมไม่ใช่เวลาทำงาน ณ รันไทม์พวกเขาควรได้รับการจัดการหรือละเว้น ในที่สุด Objc จัดให้มีสิ่งอำนวยความสะดวกการสะท้อนเวลาทำงานเพื่อจัดการกรณีที่ยุ่งยากและการตอบสนองข้อความชนิดเฉพาะและบริการสามารถตรวจสอบบนวัตถุก่อนที่จะส่งข้อความหรือใส่ลงในคอลเลกชันที่ไม่เหมาะสม ระวังว่าไลบรารีและเฟรมเวิร์กที่แตกต่างกันนำมาใช้อนุสัญญาที่แตกต่างกันกับวิธีการทำงานของวัตถุเมื่อส่งข้อความพวกเขาไม่ได้มีการตอบสนองต่อรหัสดังนั้น RTFM นอกเหนือจากโปรแกรมของเล่นและการสร้างการดีบักโปรแกรมส่วนใหญ่ไม่ควรมีข้อผิดพลาดเว้นแต่ว่าพวกเขาจะพลาดและพยายามเขียนข้อมูลที่ไม่ดีไปยังหน่วยความจำหรือดิสก์ทำการดำเนินการที่ผิดกฎหมาย (เช่นหารด้วยศูนย์ แต่คุณสามารถตรวจจับได้) ไม่ จำกัด ทรัพยากรระบบ พลวัตและเวลาเรียกใช้งานของ Objective-C ช่วยให้สิ่งต่าง ๆ ล้มเหลวได้อย่างงดงามและควรติดตั้งไว้ในโค้ดของคุณ (คำแนะนำ) ถ้าคุณมีปัญหาเกี่ยวกับความเป็นอยู่ในหน้าที่ของคุณ ลองใช้ความจำเพาะ เขียนฟังก์ชั่นที่มีประเภทเฉพาะและให้รันไทม์เลือก (นั่นคือเหตุผลที่พวกเขาเรียกว่า selectors!) สมาชิกฟังก์ชันที่เหมาะสมในเวลาทำงาน

Example:
    -(id) sort (id) obj;  // too generic. catches all.
     // better
    -(id) sort: (EasilySortableCollection*) esc;
    -(id) sort: (HardToSortCollection*) hsc; 
    ...
    [Sorter  sort: MyEasyColl];
    [Sorter  sort: MyHardColl];
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.