Objective-C method return type with generics and Xcode autocomplete - objective-c

I have custom collection class with generic parameterisation. I create collection of cats, add cat. When I try to get cat back Xcode is showing error: "Property 'name' not found on object of type 'id'". This is nonsense, because Cat has property name and MyCustomCollection doesn't return id but ObjectType. How do I declare method so the autocompletion could understand which type is returning by the method?
MyCustomCollection *collection = [[MyCustomCollection<Cat *> alloc] init];
[collection addCustomObject:[[Cat alloc] init]];
NSString *string = [collection customObjectAtIndex:0].name; // Property 'name' not found on object of type 'id'
MyCustomCollection.h file
#interface MyCustomCollection<ObjectType> : NSObject
-(ObjectType)customObjectAtIndex:(NSUInteger)index;
-(void)addCustomObject:(ObjectType)object;
#end
Cat.h file
#interface Cat : NSObject
#property (nonatomic, strong) NSString *name;
#end

The problem isn't with the method declaration. It is the declaration of the collection variable. You have to tell the compiler what type of objects are in the collection:
MyCustomCollection<Cat *> *collection = [[MyCustomCollection<Cat *> alloc] init];
Otherwise it won't know what sort of objects the collection variable references and assumes that they are of type id (hence the error).
You theoretically can also cast the result of customObjectAtIndex, but that seems to defeat the purpose of using generics.

Related

Can I use __kindof with protocols in Objective-C?

I know I can make use of Objective-C's lightweight generics using the __kindof keyword, e.g.
NSArray<__kindof BaseClass*> *myArray;
This would remove any warnings for assigning any object from the array to a derived class.
However, instead of BaseClass, I have BaseProtocol, e.g. all my classes in question will conform the BaseProtocol, regardless of their base class. I want to use lightweight generics to dictate "my array consists of elements conforming to BaseProtocol, but they can be any class".
For example in C#, I can say: List<IMyInterface> which would mean that the list consists of elements that implement the IMyInterface interface (I know C# has strong generics and Objective-C has only lightweight generics that doesn't prevent compilation, but you get the idea).
Is there any way to achieve this functionality on Objective-C?
E.g. I want to write
NSArray<__kindof id<MyProtocol>> //compiles, but as the generic argument is "id", it accepts any object, including invalid ones
or
NSArray<id<__kindof MyProtocol>> //doesn't compile
Is this possible?
UPDATE:
Here is a complete self-contained code:
#protocol MyProtocol
#end
#interface MyClass : NSObject<MyProtocol>
#end
#implementation MyClass
#end
#interface AnotherClass : NSObject
#end
#implementation AnotherClass
#end
NSMutableArray<__kindof id<MyProtocol>> *myArray;
void test(){
MyClass *myClassInstance = [[MyClass alloc] init];
AnotherClass *anotherClassInstance = [[AnotherClass alloc] init];
myArray = #[].mutableCopy;
[myArray addObject:myClassInstance];
[myArray addObject:anotherClassInstance]; //i get warning. good.
MyClass *returnedInstance = myArray[0];
AnotherClass *anotherInstance = myArray[1]; //why don't I get a warning here?
}
This syntax is correct:
NSArray <__kindof id <MyProtocol>> *array = ...
You can also omit __kindof, too, and still enjoy lightweight generics. It will still warn you about adding objects of the wrong type even without that keyword. The __kindof is used if you want to pull an object out of that array and assign it to a subtype without a cast, but otherwise __kindof is not needed:
NSArray <id <MyProtocol>> *array = ...
Both of these patterns will warn you if you add an object of a specific type to the array, but that type doesn't conform to MyProtocol.
What this won't warn you about is if you try to add an object of type id by itself. So avoid using unqualified id types within your code and you'll enjoy lightweight generics.
If you're still not seeing the warnings, make sure you have -Wobjc-literal-conversion warning turned on. So, go back to your build settings for the first project and search for "literal" and you'll see the setting (called "Implicit Objective-C Literal Conversions").
Consider this example:
#protocol MyProtocol
#end
#interface Foo: NSObject <MyProtocol>
#end
#interface Bar: Foo
#end
#interface Baz: NSObject
#end
Then consider:
Foo *foo = [[Foo alloc] init];
Bar *bar = [[Bar alloc] init];
Baz *baz = [[Baz alloc] init];
id qux = [[Baz alloc] init];
NSArray <id <MyProtocol>> *array1;
array1 = #[foo, bar, baz, qux]; // warning: object of type 'Baz *' is not compatible with array element type 'Foo *'
Note, that warns us about baz, but not qux. So be wary of using id types.
id <MyProtocol> object1 = array1[0]; // no warning, great
So, that's using the protocol as a lightweight generic and it works as expected.
The only reason you'd add __kindof is if you wanted to avoid this warning:
Foo *foo1 = array1[0]; // warning: initializing 'Foo *__strong' with an expression of incompatible type 'id<MyProtocol> _Nullable'
In that case, you'd use __kindof:
NSArray <__kindof id <MyProtocol>> *array2;
array2 = #[foo, bar, baz]; // again, warning: object of type 'Baz *' is not compatible with array element type 'Foo *'
id <MyProtocol> object2 = array2[0]; // no warning, great
Foo *foo2 = array2[0]; // no warning, great

Objective C: array of class object as property

Is it possible in an Objective-C class to store an array of instances of another class as a property?
Simple example: I have a class called "Classroom" and a class called "Students".
#interface Student
#property int m_id;
#end
...
#interface Classroom
#property Student *m_students[20]; // this causes a compilation error: property cannot have an array or function type 'Student *[20]'
#end
How can I do the equivalent of this?
Use a NSArray or NSMutableArray instead:
#interface Classroom
#property NSMutableArray *m_students; // this causes a compilation error.
#end
Then in your implementation:
Student *student = [[Student alloc] init];
[self.m_students addObject:student];
NSArray (and it's subclass NSMutableArray) can contain any Objective-C object. And you can even mix them in the same array.

Class Method Exposure and Property Definition

I have an academic question about Class Method exposure. There is something that I obviously don't understand about this and would like some clarification from those in the know.
Background:
I have a simple example of two classes named ViewController and ClassB. Class B contains an array with a method named returnArray. The ViewController accesses the array's data. I have exposed the returnArray method in the ClassB.h file.
Question:
Why is it that I can access the array's data in ViewController without having to define a property? I thought that the property would create a getter to allow access to the array. My example (only exposing the method) allows me to access the data without the creation of the #property.
Class Method:
ClassB.h
#interface ClassB : UIViewController
+(NSArray *) returnArray;
//#property (nonatomic, strong) NSArray *returnArray;
ClassB.m
#implementation ClassB
+(NSArray *) returnArray
{
NSArray *locationArray = #[#"Place1", #"Place2"];
return locationArray;
}
ViewController.m
- (void)viewDidLoad
{
NSArray *location = [ClassB returnArray];
NSLog (#"The count of the location is %d", [location count]);
NSLog (#"The second item in testArray is %#", location[1]);
}
Instance method: After reviewing answers
ClassB.h
*/
{
#private
NSArray *returnArray;
}
- (void)setReturnArray:(NSArray*)returnArray;
-(NSArray *) returnArray;
*/
#property (nonatomic, strong) NSArray *returnArray;
#end
ClassB.m - no change
ViewController.h - no change
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
//Create instance of ClassB
ClassB *classB = [ClassB new];
//Access the instance of returnArray
NSArray *location = [classB returnArray];
NSLog (#"The count of the location is %d", [location count]);
NSLog (#"The second item in testArray is %#", location[1]);
}
#property is a shorthand notation for creating an instance variable and associated accessor methods (with defined access / modification criteria).
What you have is a class method, which internally creates an array and returns it.
That's why you call [ClassB returnArray]; instead of [instanceOfB array];.
These are completely different things. If you wanted to use a property then you would need to create an instance of ClassB and then access the property. This would work, assuming that the array was created when the instance of ClassB was created.
Wain's answer addresses the difference between #property and Class methods, so it's worth a read. My answer assumes you know the difference between class and instance methods, and focuses on the difference between creating a #property versus creating an instance variable with an associate setter and getter.
The reason is because returnArray is a public method that returns an NSArray object on your ClassB.
A #property is merely a convenient way of creating three things at the same time: an instance variable, a setter, and a getter. It has the added bonus of allowing dot-syntax.
But at the end of the day, dot-syntax aside, all you're doing by declaring a #property is equivalently equal to this:
#interface ClassB : NSObject {
#private
NSArray *returnArray;
}
- (void)setReturnArray:(NSArray*)returnArray;
- (NSArray*)returnArray;
This is the same as this:
#property NSArray *returnArray;
Except of course, the dot syntax.
When you do:
NSArray *myArray = classB.returnArray;
You're not actually directly accessing the array you created when you declared the #property.
What you're doing is calling the getter method that was automatically generated when you declared the #property.

Navigating a to-many relationship with Core Data

How do I navigate a Core Data to-many relationship in Objective-C?
I have an Event model and each instances has many EventOccurace objects that are exposed via an occurances [sic] relationship. Apple's docs say that the standard property accessor should be available but I keep getting compile-time errors:
DetailViewController.h
#interface DetailViewController : UIViewController <UISplitViewControllerDelegate>
#property (strong, nonatomic) id detailItem;
#end
DetailViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSSet * foo;
foo = [[self detailItem] occurances];
///No known instance method for selector 'occurances'
foo = self.detailItem.occurances;
///Property 'occurances' not found on object of type 'id'
//try casting to NSManagedObject to access
NSManagedObject * casted = (NSManagedObject *)self.detailItem;
foo = casted.occurances;
///Property 'occurances' not found on object of type 'NSManagedObject *'
foo = [casted occurances];
///No visible #interface for 'NSManagedObject' declares the selector 'occurances'
}
To use the (dynamically created) accessor methods, you have to create subclasses of NSManagedObject for your entities.
This is easily done in Xcode: Select the entities in the model editor and choose "Editor -> Create NSManagedObject subclass ..." from the menu. Include the header files (Event.h, EventOccurance.h) in your source code. Then
Event *event = self.detailItem;
NSSet *foo = event.occurances;
should work.
Alternatively, you can use Key-value coding:
NSSet *foo = [[self detailItem] valueForKey:#"occurances"];
which works even without the managed object subclasses.
Some things that could be not working:
First of all check that your ManagedObject classes have been generated.
Second, you should specify the type of the detailItem somewhere. Or in the .h, or by casting it to a type in the .m. Now its just an "id", the compiler cant know which is its type.
Third, this is one possible way of accesing it:
NSArray * occurances = [self.detailItem.occurances allObjects];
for(EventOccurance * ocu in occurances){
//blablabla
}

Question about class member declarations

I`m new to the Objective-C world, so I have a couple of questions about class member declarations. Please notice the comments in the code below:
In header file I have code such
#interface MyClass : NSObject {
//what we points here ? Object or something else ?
NSString *myString;
}
// In interface we declare NSTring *myString in #property declaration is (NSString *) myString.
// What is the difference here ? Why we don`t use the same declaration as above ?
#property(nonatomic, retain) (NSString *) myString;
#end
The thing you're missing is that instance variables (defined between curly braces) are not accessed from the outside (i.e. other objects). To do that - you have to define a property for the instance var (by using #property keyword) to know how outside objects can access a value of given instance var. Also in implementation file (.m) you have to #synthesize instance variable accessor methods for it's appropriate property. Please note that #property declaration not only defines what it holds (NSString *myString), but also how it's being accessed and set. You can define property as read only (#property (readonly)...) or accessible from few threads at a time (#property (nonatomic)).
Also - if your instance var is named differently from the property it represents to other objects - you must show that in implementation file (#synthesize propertyName=instanveVariableName)
update
MyClass *myInstance = [[MyClass alloc] init];
[myInstance myString]; // returns myString property
Try running above 2 lines of code without #property and you'll see the difference.
Actually you are defining a Property of yar class.#interface MyClass : NSObject {
//public object
#public
NSString *myString;
//private object
NSString *myString2;
}
class structure for obj-c
.h file
#interface MyClass : NSObject {
//Your member variable;
// you member objects;
}
//property declarations
//function declarations
#end
so it should look like
#interface MyClass : NSObject {
NSString *str;
}
#property(nanatomic,retain) NSString *str;
-(void)method;
#end