I have the following class in my iOS application (it is like an abstract class from the Java world).
#implementation WSObject
static NSDictionary* _dictionary = nil;
+(NSDictionary*) dictionary {
if (_dictionary == nil) {
_dictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]];
}
return _dictionary;
}
...
#end
I then have multiple classes which implement this above WSObject with the class method dictionary. The problem is, that each of these classes should have their own _dictionary, but they are all sharing the same object from the super class. I could, of course, copy to all the subclasses, but that would break the reusability. Besides this getter, there are other class methods in WSObject which mutate the dictionary. Because of this, there would be a several class methods which should be in every subclass.
How can I solve this in a smart way? Please tell me if my description is insufficient.
Associative references seem like they'll do the trick. You can essentially tack some storage on to the class object itself. (I'm using NSStrings here, in place of the dictionaries you want to use, just for demonstration.)
Superclass:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface Stuper : NSObject
// Accessor method for the "class variable"
+ (NSString *) str;
// Analog to your +localStorePath
+ (NSString *) quote;
#end
#import "Stuper.h"
// The doc suggests simply using the address of a static variable as the key.
// This works fine, even though every class is (as in your problem) using
// the same key, because we are associating to a different class each time.
static char key;
#implementation Stuper
+ (NSString *) str {
NSString * s = objc_getAssociatedObject(self, &key);
if( !s ){
s = [self quote];
// You'll probably want to use OBJC_ASSOCIATION_RETAIN for your dictionary.
// self inside a class method is the class object; use that as
// the associator. The string is now tied to the associator, i.e.,
// has the same lifetime.
objc_setAssociatedObject(self, &key, s, OBJC_ASSOCIATION_COPY);
}
return s;
}
+ (NSString *) quote {
return #"It was the best of times, it was the worst of times.";
}
#end
Subclass:
#import "Stuper.h"
#interface Stub : Stuper #end
#import "Stub.h"
#implementation Stub
+ (NSString *) quote {
return #"Call me Ishmael.";
}
#end
Trying this out:
#import <Foundation/Foundation.h>
#import "Stuper.h"
#import "Stub.h"
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"%#", [Stuper str]);
NSLog(#"%#", [Stub str]);
[pool drain];
return 0;
}
Each class object now has its own string, associated with it.
2011-12-05 23:11:09.031 SubClassVariables[36254:903] It was the best of times, it was the worst of times.
2011-12-05 23:11:09.034 SubClassVariables[36254:903] Call me Ishmael.
The only downside here is that you'll have to call the accessor method every time you want the object; you don't have a pointer you can use directly. You can call objc_getAssociatedObject in the superclass as an accessor, too, of course, since it has access to key.
In order to give each subclass its own dictionary, store a second dictionary object in your primary dictionary using the class name as the key. For example:
static NSMutableDictionary *_dictionary = nil;
+ (NSDictionary*)dictionary
{
if (_dictionary == nil)
_dictionary = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]] mutableCopy];
NSString *key = NSStringFromClass( [self class] );
if ( [_dictionary objectForKey:key] == nil )
[_dictionary setObject:[NSMutableDictionary dictionary] forKey:key];
return [_dictionary objectForKey:key];
}
Perhaps you can return a copy of the dictionary
#implementation WSObject
static NSDictionary* _dictionary = nil;
+(NSDictionary*) dictionary {
if (_dictionary == nil) {
_dictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]];
}
return [_dictionary copy];
}
...
#end
Keep in mind that if you modify _dictionary you will get a copy of that modified dictionary which may differ from what is on disk.
How often is this being called? is it really necessary to cache the file contents in this static _dictionary object?
Why not just fetch it every time form disk, assuming it isn't too often that performance comes into question.
#implementation WSObject
+(NSDictionary*) dictionary {
return [NSKeyedUnarchiver unarchiveObjectWithFile:[self localStorePath]];
}
...
#end
Related
In my macOS Objective-C application, I have created a subclass of NSMutableSet. What I want to achieve is a NSMutableSet that does not use isEqual: as the comparing strategy. Specifically, The set will contain objects of type NSRunningApplication, and I want the set to work based on the equality of the objects bundle identifiers. Following is my implementation:
Header file:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface BundleIdentifierAwareMutableSet : NSMutableSet
#property (atomic, strong) NSMutableSet *backStorageMutableSet;
#property (atomic, strong) NSMutableArray *backStorageMutableArray;
#end
NS_ASSUME_NONNULL_END
Implementation file:
#import "BundleIdentifierAwareMutableSet.h"
#implementation BundleIdentifierAwareMutableSet
#synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
This seems to work, and indeed, When applicable, Xcode logs that an ordinary NSMutableSet would have not considered two members equal. I would like to bring this implementation to the Production App, but I am afraid I have not considered something important, since this is the first time I subclass NSMutableSet. For example, I am worried about the following method:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
This is the only use I do of the backStorageMutableSet since the rest is backed to the array. Is this fine or can bring troubles ? Will other parts of the subclass bring problems ? Any help will be greatly appreciated. Thanks
Don't do this. Subclassing collections should be the last resort. It can have implications on performance, ... Try to use highest possible abstraction and go down if it doesn't work for you for some reason.
Wrapper object
Wrap the NSRunningApplication in another object and provide your own hash & isEqual: methods.
Application.h:
#interface Application: NSObject
#property (nonatomic, strong, readonly, nonnull) NSRunningApplication *application;
#end
Application.m:
#interface Application ()
#property (nonatomic, strong, nonnull) NSRunningApplication *application;
#end
#implementation Application
- (nonnull instancetype)initWithRunningApplication:(NSRunningApplication *_Nonnull)application {
if ((self = [super init]) == nil) {
// https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
//
// The init() method defined in the NSObject class does no initialization; it simply
// returns self. In terms of nullability, callers can assume that the NSObject
// implementation of init() does not return nil.
return nil;
}
self.application = application;
return self;
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[Application class]]) {
return NO;
}
Application *app = (Application *)object;
return [self.application.bundleIdentifier isEqualToString:app.application.bundleIdentifier];
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash?language=objc
- (NSUInteger)hash {
return self.application.bundleIdentifier.hash;
}
#end
Toll-free bridging & CFMutableSetRef
CFSet is bridged with the NSSet, CFMutableSet is bridged with the NSMutableSet, etc. It means that you can create a set via Core Foundation
API and then use it as NSSet for example. Core Foundation is a powerful
framework which exposes more stuff to you.
You can provide a custom set of callbacks for the CFSet.
/*!
#typedef CFSetCallBacks
Structure containing the callbacks of a CFSet.
#field version The version number of the structure type being passed
in as a parameter to the CFSet creation functions. This
structure is version 0.
#field retain The callback used to add a retain for the set on
values as they are put into the set. This callback returns
the value to store in the set, which is usually the value
parameter passed to this callback, but may be a different
value if a different value should be stored in the set.
The set's allocator is passed as the first argument.
#field release The callback used to remove a retain previously added
for the set from values as they are removed from the
set. The set's allocator is passed as the first
argument.
#field copyDescription The callback used to create a descriptive
string representation of each value in the set. This is
used by the CFCopyDescription() function.
#field equal The callback used to compare values in the set for
equality for some operations.
#field hash The callback used to compare values in the set for
uniqueness for some operations.
*/
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
There're predefined sets of callbacks like:
/*!
#constant kCFTypeSetCallBacks
Predefined CFSetCallBacks structure containing a set of callbacks
appropriate for use when the values in a CFSet are all CFTypes.
*/
CF_EXPORT
const CFSetCallBacks kCFTypeSetCallBacks;
Which means that you're not forced to provide all of them, but you're free to modify just some of them. Let's prepare two callback functions:
// typedef CFHashCode (*CFSetHashCallBack)(const void *value);
CFHashCode runningApplicationBundleIdentifierHash(const void *value) {
NSRunningApplication *application = (__bridge NSRunningApplication *)value;
return [application.bundleIdentifier hash];
}
// typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2);
Boolean runningApplicationBundleIdentifierEqual(const void *value1, const void *value2) {
NSRunningApplication *application1 = (__bridge NSRunningApplication *)value1;
NSRunningApplication *application2 = (__bridge NSRunningApplication *)value2;
return [application1.bundleIdentifier isEqualToString:application2.bundleIdentifier];
}
You can use them in this way:
- (NSMutableSet<NSRunningApplication *> *_Nullable)bundleIdentifierAwareMutableSetWithCapacity:(NSUInteger)capacity {
// > Predefined CFSetCallBacks structure containing a set of callbacks
// > appropriate for use when the values in a CFSet are all CFTypes.
//
// Which means that you shouldn't bother about retain, release, ... callbacks,
// they're already set.
//
// CFSetCallbacks can be on stack, because this structure is copied in the
// CFSetCreateMutable function.
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
// Overwrite just the hash & equal callbacks
callbacks.hash = runningApplicationBundleIdentifierHash;
callbacks.equal = runningApplicationBundleIdentifierEqual;
// Try to create a mutable set.
CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, capacity, &callbacks);
if (set == NULL) {
// Failed, do some error handling or just return nil
return nil;
}
// Transfer the ownership to the Obj-C & ARC => no need to call CFRelease
return (__bridge_transfer NSMutableSet *)set;
}
&
NSMutableSet<NSRunningApplication *> *set = [self bundleIdentifierAwareMutableSetWithCapacity:50];
[set addObjectsFromArray:[[NSWorkspace sharedWorkspace] runningApplications]];
NSLog(#"%#", set);
Suppose that I have a class that holds a mutable array. I want to make sure that if other classes ask for the array they will get a non-mutable type, but in the owning class, it is actually an instance of NSMutableArray, so that I can add and remove items.
#import "Person.h"
#class Asset;
#interface Employee : Person
{
NSMutableArray *_assets;
}
#property (nonatomic,copy) NSArray *assets;
-(void)addAssets:(Asset *)a;
The question is, do I have to modify the accessor methods into something like this, or will it automatically behave like I want?
#import "Employee.h"
#import "Asset.h"
#implementation Employee
/* Accessors for assets properties
-(NSArray *)assets
{
return [_assets copy];
}
-(void)setAssets:(NSArray *)assets
{
_assets = [assets mutableCopy ];
}
*/
-(void)addAssets:(Asset *)a
{
//is assets nil?
if (!_assets) {
//Create Array
_assets = [[NSMutableArray alloc]init];
}
[_assets addObject:a];
}
ppalancica's answer is incorrect. The copy attribute means only that the setter will take a copy when the property is set. The synthesized getter will not return a copy. You must implement that behavior yourself:
- (NSArray *)assets
{
return [_assets copy];
}
You might want to make an internal-only accessor that doesn't make a copy. You could also redeclare the property privately; client code will then be contracted to treat the array it requests as immutable.
This code demonstrates that the synthesized getter returns the uncopied object:
#import <Foundation/Foundation.h>
#interface ArrayReturner : NSObject
#property (copy, nonatomic) NSArray * array;
#end
#implementation ArrayReturner
{
NSMutableArray * _array;
}
- (BOOL)myArrayIsIdenticalTo:(NSArray *)otherArray
{
return _array == otherArray;
}
#end
int main(int argc, const char *argv[])
{
#autoreleasepool {
ArrayReturner * a = [ArrayReturner new];
[a setArray:#[#1, #2]];
NSArray * returnedArray = [a array];
// Does not throw
NSCAssert([a myArrayIsIdenticalTo:returnedArray],
#"Returned array is a separate instance.");
}
return 0;
}
Because you already specified the attribute "copy" for the array property, there is no need to override the getter and setter. The compiler will do all the heavy work for you.
If you specify "strong" instead, the getter and setter would look like:
-(NSArray *)assets
{
return _assets;
}
-(void)setAssets:(NSArray *)assets
{
_assets = assets;
}
And that may be a problem.
There is actually a WWDC conference that explains all these details. For NSString properties it is more recommended to use copy, and you can see it a lot like that in the iOS SDK frameworks.
I'm having trouble creating a nice way of passing a collection around to different view controllers. For example, I created a custom class called Message with a bunch of attributes. I want to have a global NSMutableArray of those stored in a global variable of sorts called messages that I can add to or get from anywhere. Everyone on Stackoverflow says not to use your delagate class to store global variables so I created a singleton class called Shared. In there I created a property for the NSMutableArray called messages like this:
#interface Shared : NSObject {
}
#property (nonatomic, retain) NSMutableArray *messages;
+(Shared *) sharedInstance;
#end
And my .h file is (the important part):
#import "Shared.h"
static Shared* sharedInstance;
#implementation Shared
#synthesize messages;
static Shared *sharedInstance = nil;
-(id) init {
self = [super init];
if (self != nil){
}
return self;
}
-(void) initializeSharedInstance {
}
+ (Shared *) sharedInstance{
#synchronized(self) {
if (sharedInstance == nil){
sharedInstance = [[self alloc] init];
[sharedInstance initializeSharedInstance];
}
return (sharedInstance);
}
}
In my other view controller, I first import "Shared.h", then try this:
[[Shared sharedInstance].messages addObject:m];
NSLog([NSString stringWithFormat:#"Shared messages = %#", [Shared sharedInstance].messages]);
It keeps printing null instead of the the collection of m objects. Any thoughts?
You need to have a static variable.
In .h:
#interface Shared : NSObject
{
NSMutableArray *messages;
}
#property (nonatomic, retain) NSMutableArray *messages;
+ (Shared*)sharedInstance;
#end
in .m:
static Shared* sharedInstance;
#implementation Shared
#synthesize messages;
+ (Shared*)sharedInstance
{
if ( !sharedInstance)
{
sharedInstance = [[Shared alloc] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if ( self )
{
messages = [[NSMutableArray alloc] init];
}
return self;
}
A thought:
#synthesize generates setter and getter methods, it doesn't init your variable. Where do you do that? I can't see it in the excerpts you posted.
The following is not an answer to your issue, but instead a suggestion to an alternative approach that (in my opinion) is 'cleaner' in use.
An alternative to using a Singleton to store app-wide could be to define a class with class methods that retrieves values from the NSUserDefaults. This class could be imported into the prefix header (*.pch) so you can access it from every other class in the project.
Methods inside this class could look like this:
inside Settings.h:
// for this example I'll use the prefix for a fictional company called SimpleSoft (SS)
extern NSString *kSSUserLoginNameKey;
+ (NSString *)userLoginName;
+ (void)setUserLoginName:(NSString *)userLoginName;
inside Settings.m:
kSSUserLoginNameKey = #"SSUserLoginNameKey";
+ (NSString *)userLoginName
{
return [[NSUserDefaults standardUserDefaults] valueForKey:kSSUserLoginNameKey];
}
+ (void)setUserLoginName:(NSString *)userLoginName
{
[[NSUserDefaults standardUserDefaults] setValue:userLoginName forKey:kSSUserLoginNameKey];
[[NSUserDefaults standardUserDefaults] synthesize];
}
Of course in a setup like this NSUserDefaults is the singleton that is being accessed through a convenience class. This class acts as a wrapper around the NSUserDefaults singleton. Values can be accessed like this:
NSString userLoginName = [Settings userLoginName];
[Settings setUserLoginName:#"Bob"];
Other objects -like Arrays- could be accessed in much the same way. One thing to be careful with (much the same as with your current approach) is to be careful not to access a class like this from every other class. Components that are intended to be reusable should pass values, so the components of the application don't become too tightly coupled to the settings class.
Is there a way to force NSMutableArray to hold one specific object type only?
I have classes definitions as follow:
#interface Wheel:NSObject
{
int size;
float diameter;
}
#end
#interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray *wheels;
}
#end
How can I force wheels array to hold Wheel objects only with code? (and absolutely not other objects)
Update in 2015
This answer was first written in early 2011 and began:
What we really want is parametric polymorphism so you could declare, say, NSMutableArray<NSString>; but alas such is not available.
In 2015 Apple apparently changed this with the introduction of "lightweight generics" into Objective-C and now you can declare:
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
But all is not quite what it seems, notice the "lightweight"... Then notice that the initialisation part of the above declaration does not contain any generic notation. While Apple have introduced parametric collections, and adding a non-string directly to the above array, onlyStrings, as in say:
[onlyStrings addObject:#666]; // <- Warning: Incompatible pointer types...
will illicit the warning as indicated, the type security is barely skin deep. Consider the method:
- (void) push:(id)obj onto:(NSMutableArray *)array
{
[array addObject:obj];
}
and the code fragment in another method of the same class:
NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self push:#"asda" onto:oops]; // add a string, fine
[self push:#42 onto:oops]; // add a number, no warnings...
What Apple have implemented is essentially a hinting system to assist with automatic inter-operation with Swift, which does have a flavour of type-safe generics. However on the Objective-C side, while the compiler provides some extra hints the system is "lightweight" and type-integrity is still ultimately down to the programmer - as is the Objective-C way.
So which should you use? The new lightweight/pseudo generics, or devise your own patterns for your code? There really is no right answer, figure out what makes sense in your scenario and use it.
For example: If you are targeting interoperation with Swift you should use the lightweight generics! However if the type integrity of a collection is important in your scenario then you could combine the lightweight generics with your own code on the Objective-C side which enforces the type integrity that Swift will on its side.
The Remainder of the 2011 Answer
As another option here is a quick general subclass of NSMutableArray which you init with the kind of object you want in your monomorphic array. This option does not give you static type-checking (in as much as you ever get it in Obj-C), you get runtime exceptions on inserting the wrong type, just as you get runtime exceptions for index out of bounds etc.
This is not thoroughly tested and assumes the documentation on overriding NSMutableArray is correct...
#interface MonomorphicArray : NSMutableArray
{
Class elementClass;
NSMutableArray *realArray;
}
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;
#end
And the implementation:
#implementation MonomorphicArray
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems
{
elementClass = element;
realArray = [NSMutableArray arrayWithCapacity:numItems];
return self;
}
- (id) initWithClass:(Class)element
{
elementClass = element;
realArray = [NSMutableArray new];
return self;
}
// override primitive NSMutableArray methods and enforce monomorphism
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match
{
[realArray insertObject:anObject atIndex:index];
}
else
{
NSException* myException = [NSException
exceptionWithName:#"InvalidAddObject"
reason:#"Added object has wrong type"
userInfo:nil];
#throw myException;
}
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[realArray removeObjectAtIndex:index];
}
// override primitive NSArray methods
- (NSUInteger) count
{
return [realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [realArray objectAtIndex:index];
}
// block all the other init's (some could be supported)
static id NotSupported()
{
NSException* myException = [NSException
exceptionWithName:#"InvalidInitializer"
reason:#"Only initWithClass: and initWithClass:andCapacity: supported"
userInfo:nil];
#throw myException;
}
- (id)initWithArray:(NSArray *)anArray { return NotSupported(); }
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); }
- (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); }
- (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); }
- (id)initWithObjects:(id)firstObj, ... { return NotSupported(); }
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); }
#end
Use as:
MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];
[monoString addObject:#"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:#"Another string"];
Since Xcode 7, generics are available in Objective-C.
You can declare a NSMutableArray as:
NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:#[[Wheel new],[Wheel new]];
The compiler will give you a warning if you try to put non-Wheel objects in the array.
I could be wrong (I'm a noob), but I think, if you create a custom protocol and make sure the objects you are adding to the array follow the same protocol, then when you declare the array you use
NSArray<Protocol Name>
That should prevent objects being added that do not follow the said protocol.
as per i know.. before you added any object in wheels mutableArray, u have to add some check mark. Is the object which i am adding is class "wheel". if it is then add, other wise not.
Example:
if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id)
}
Something like this. i dont remember the exact syntax.
I hope this will help (and work... :P )
Wheel.h file:
#protocol Wheel
#end
#interface Wheel : NSObject
#property ...
#end
Car.h file:
#import "Wheel.h"
#interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray<Wheel, Optional> *wheels;
}
#end
Car.m file:
#import "Car.h"
#implementation Car
-(id)init{
if (self=[super init]){
self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
}
return self;
}
#end
Xcode 7 allows you to define Arrays, Dictionaries, and even your own Classes as having generics. The array syntax is as follows:
NSArray<NSString*>* array = #[#"hello world"];
I don't believe there's any way to do it with NSMutableArray out of the box. You could probably enforce this by subclassing and overriding all the constructors and insertion methods, but it's probably not worth it. What are you hoping to achieve with this?
That's not possible; an NSArray (whether mutable or not) will hold any object type. What you can do is to create your own custom subclasses as already suggested by Jim. Alternatively, if you wanted to filter an array to remove objects that weren't of the type you want, then you could do:
- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type
{
int c = 0;
while(c < [array length])
{
NSObject *object = [array objectAtIndex:c];
if([object isKindOfClass:type])
c++;
else
[array removeObjectAtIndex:c];
}
}
...
[self removeObjectsFromArray:array otherThanOfType:[Car class]];
Or make other judgments based on the result of isKindOfClass:, e.g. to divide an array containing a mixture of Cars and Wheels into two arrays, each containing only one kind of object.
You can use the nsexception if you dont have the specific object.
for (int i = 0; i<items.count;i++) {
if([[items objectAtIndex:i] isKindOfClass:[Wheel class]])
{
// do something..!
}else{
[NSException raise:#"Invalid value" format:#"Format of %# is invalid", items];
// do whatever to handle or raise your exception.
}
}
Here's something I've done to avoid subclassing NSMutableArray: use a category. This way you can have the argument and return types you want. Note the naming convention: replace the word "object" in each of the methods you will use with the name of the element class. "objectAtIndex" becomes "wheelAtIndex" and so on. This way there's no name conflict. Very tidy.
typedef NSMutableArray WheelList;
#interface NSMutableArray (WheelList)
- (wheel *) wheelAtIndex: (NSUInteger) index;
- (void) addWheel: (wheel *) w;
#end
#implementation NSMutableArray (WheelList)
- (wheel *) wheelAtIndex: (NSUInteger) index
{
return (wheel *) [self objectAtIndex: index];
}
- (void) addWheel: (wheel *) w
{
[self addObject: w];
}
#end
#interface Car : NSObject
#property WheelList *wheels;
#end;
#implementation Car
#synthesize wheels;
- (id) init
{
if (self = [super init]) {
wheels = [[WheelList alloc] initWithCapacity: 4];
}
return self;
}
#end
protocol maybe a good idea:
#protocol Person <NSObject>
#end
#interface Person : NSObject <Person>
#end
to use:
NSArray<Person>* personArray;
There is one-header file project which allows this:
Objective-C-Generics
Usage:
Copy ObjectiveCGenerics.h to your project.
When defining a new class use the GENERICSABLE macro.
#import "ObjectiveCGenerics.h"
GENERICSABLE(MyClass)
#interface MyClass : NSObject<MyClass>
#property (nonatomic, strong) NSString* name;
#end
Now you can use generics with arrays and sets just as you normally do in Java, C#, etc.
Code:
I understand the basic syntax of Objective-C, have installed Cygwin, and want to experiment. However i am unsure of two things:
What i would import, and
what the primitive type names are.
Can someone help me?
The only object you can inherit from is called Object. Bare in mind that this offers nowhere near the same amount of functionality as NeXTStep's or Cocoa's NSObject. Object does not even have anything like reference counting. In order to get the same sort of reference counting memory management that NSObject has you'll need to implement it yourself.
#import <objc/Object.h>
#interface MyObject : Object
{
int retainCount;
}
- (id) retain;
- (int) retainCount;
- (void) release;
#end
#implementation MyObject
+ (MyObject *) alloc
{
// In Cocoa, allocated objects have an implicit retain.
MyObject *anObject = [super alloc];
anObject->retainCount = 1;
return anObject;
}
- (void) release
{
retainCount--;
if (retainCount == 0)
[self free];
}
- (id) retain
{
retainCount++;
return self;
}
- (int) retainCount
{
return retainCount;
}
#end
int main (int argc, char *argv[])
{
MyObject *test = [[MyObject alloc] init];
[test retain];
[test release];
[test release];
// (test should be deallocated now)
return 0;
}
When linking, you have to make sure you link with -lobjc, this is where the definition of Object lies (I think).
The other big catch is with static string instances, i.e. strings in code that appear #"like this". With the GNU runtime, static instances of strings need to have a particular ivar layout, which is:
// Let's assume that we have a protocol <MyObject> that defines all the basic methods
// like retain, release etc. In this case, these should be no-ops because the static
// string is never deallocated. In Cocoa, there is a protocol <NSObject> which provides
// the same common methods.
#interface MyStaticStringClass : Object <MyObject>
{
char *str;
unsigned len;
}
- (const char *) cString;
#end
#implementation MyStaticStringClass
- (void) retain
{
return;
}
- (id) retain
{
return self;
}
- (int) retainCount
{
return INT_MAX;
}
- (const char *) cString
{
return str;
}
#end
int main (int argc, char *argv[])
{
id aString = #"Hello world!";
fprintf (stdout, "aString has the contents: %s\n", [aString cString]);
return 0;
}
When compiling, you can use the flag -fconstant-string-class=MyStaticStringClass. You can provide whatever methods you like for the string class but it must have only two ivars and they must be in the right order. If you don't want to use Objective-C style strings, then you don't have to define a static string class. If you do define a static string class it should be able to replicate the behaviour of your dynamic string class (i.e. string objects that are allocated during run time) so that you can use either in a given situation.
For command-line utilities and basic apps I choose not to use Cocoa or GNUstep but rather define my own classes. It has many drawbacks, but I find that object abstraction and metamorphism in Objective-C is much easier to implement than in the other languages that I program in.