I'm working on some CoreAnimation stuff. A navigation controller with a few view controllers. And the view controllers have UISCrollViews for different "pages" of a "brochure." On each page, there might be some animation that gets triggered when the user flips to that page.
I was trying something like this (for one-shot animations).
void (^animationBlock)() =
^{
static bool alreadyTriggered = NO;
if(alreadyTriggered)
return;
[CATransaction begin];
[CATransaction setCompletionBlock:
^{
alreadyTriggered = YES;
}];
// Do me some animations...
[CATransaction commit];
};
NSMutableDictionary* pageBlocks = [[NSMutableDictionary alloc] init];
[pageBlocks setObject:[animationBlock copy] forKey:<animation's name>];
[self.animationBlocks setObject:pageBlocks forKey:<some page number>];
[pageBlocks release];
[animationBlock release];
"animation's name" and "some page number" are placeholders for the sake of explanation (they are arbitrary NSString literals).
And the code that triggers these animations is:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
int pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
NSMutableDictionary* pageBLocks = [self.animationBlocks objectForKey:[NSString stringWithFormat:#"page%i",pageNumber]];
[CATransaction begin];
for(id key in pageBLocks)
((void (^)())[pageBLocks objectForKey:key])();
[CATransaction commit];
}
So far so good, only that If I pop the brochure from the navigation controller (aka calls dealloc on the brochure) and then push it in again, the static bool is still set.
My thoughts:
- am I retaining the block?
I don't know.. I'm calling release after adding it (with copy) to the dictionary and also the brochure's dealloc method calls release on the dictionary.
- am I keeping another copy of the static bool somewhere?
My first bool is allocated when I declare the block as static within the scope of a method.. well depends on Objective-C's activation record scheme which I haven't looked into. But assuming so, that copy is gone when releasing the object on popViewcOntroller. And another copy of it from invoking copy on the block when adding it to the dictionary should be released when the dictionary is killed?
am I retaining the whole brochure object itself? I didn't completely get it from the Apple docs, but they say if I access an instance variable by reference I retain self. I tried releasing self from inside the block and everything keeps running just fine...?
Make the block return a value and then decide whether or not to remove the block from the dictionary.
// ...
BOOL shouldRemove = block();
if (shouldRemove) {
[pageBlocks removeObjectForKey:key];
}
// ...
Let's test the static variable
#interface TestClass : NSObject
#end
#implementation TestClass
- (void(^)(void))block;
{
return [[^{
static BOOL staticBOOL = NO;
NSLog(#"%d", staticBOOL);
staticBOOL = YES;
} copy] autorelease];
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
TestClass *test1 = [[TestClass alloc] init];
test1.block();
test1.block();
TestClass *test2 = [[TestClass alloc] init];
test2.block();
test2.block();
[p release];
}
This outputs
#=> 2012-04-23 00:43:38.501 Untitled[8380:707] 0
#=> 2012-04-23 00:43:38.503 Untitled[8380:707] 1
#=> 2012-04-23 00:43:38.503 Untitled[8380:707] 1
#=> 2012-04-23 00:43:38.504 Untitled[8380:707] 1
How do we solve the problem? I would probably remove the object from the dictionary once it had been executed like this
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
{
NSInteger pageNumber = floor(self.scrollView.contentOffset.x / self.scrollView.frame.size.width);
NSMutableDictionary *pageBlocks = [self.animationBlocks objectForKey:[NSString stringWithFormat:#"page%i", pageNumber]];
[CATransaction begin];
for (id key in [pageBlocks copy]) {
void (^block)(void) = [pageBlocks objectForKey:key];
if (block) {
block();
}
[pageBlocks removeObjectForKey:key];
}
[CATransaction commit];
}
Your comment
So far so good, only that If I pop the brochure from the navigation
controller (aka calls dealloc on the brochure) and then push it in
again, the static bool is still set.
seems to imply you want a BOOL member variable on the pushed view controllers... If that's correct you can achieve this using objc_setAssociatedObject().
I might add a category to UIViewController to achieve this:
#interface UIViewController (OneShotAnimation)
#property ( nonatomic ) BOOL animationHasBeenPlayed ;
#end
#implementation UIViewController (OneShotAnimation)
-(void)setAnimationHasBeenPlayed:(BOOL)b
{
objc_setAssociatedObject( self, #"animationHasBeenPlayed", [ NSNumber numberWithBool:b ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
}
-(BOOL)animationHasBeenPlayed
{
return [ objc_getAssociatedObject( self, #"animationHasBeenPlayed" ) boolValue ] ;
}
#end
Related
I use lazy instantiation on my properties, to have my class created and used as fast as possible. To achieve this, I write lots of 'empty' getters like this:
- (VMPlacesListFilter *)currentFilter
{
if (!_currentFilter) {
_currentFilter = [[VMPlacesListFilter alloc] init];
}
return _currentFilter;
}
They are all the same: if the instance variable is nil, call the -alloc and -init on the class of the property, then return the instance variable. Very common and straightforward.
If I don't create this getter by myself, Objective-C's automatic synthesization creates a getter for me, which does only the returning part (does not init the object if the instance variable is nil).
Is there any way to avoid writing this boilerplate code?
Nope, I'm afraid there's no good way around it, if you really want to have lazy initialization. Personally, I usually save lazy initialization for stuff that could really be time consuming or memory intensive (say, loading images or view controllers), and initialize cheap stuff (like simple data structures or model objects) in init.
- (instancetype) init {
self = [super init];
if( self ) {
_cheapThing1 = [NSMutableArray array];
_cheapThing2 = [[MyModelObject alloc] init];
}
return self;
}
- (ExpensiveThing*) expensiveThing
{
if( _expensiveThing == nil ) {
_expensiveThing = [[ExpensiveThing alloc] init];
}
return _expensiveThing;
}
Unless you're loading something from disk or the network, I wouldn't worry too much about initialization time. Of course, profile it.
I know this is an Objective-C question, but it's worth noting that Swift has lazy initialization built-in.
lazy var currentFilter = VMPlacesListFilter()
First off, I totally agree with #zpasternack that "lazy load" should not be misused. However, automatically generating setters and getters is completely doable with the power of Objective-C runtime. In fact, CoreData is doing this.
Anyway, I have come up with some stupid code implementing a class called LazyClass, in which you can declare dynamic properties like lazyArray (see below). Using dynamic method resolution, when the property is accessed for the first time, a getter that calls the corresponding class's default +alloc and -init method will be automatically added to the class. All underlying instance variables are stored in an NSMutableDictionary called myVars. Of course you can manipulate ivars through the runtime API as well, but using a dictionary should save some work.
Please note that this implementation just shows the basic idea of how it works. It lacks error checking and is not supposed to be shipped.
LazyClass.h
#interface LazyClass : NSObject
#property NSMutableDictionary *myVars;
// lazily initialized property
#property NSArray *lazyArray;
#end
LazyClass.m
#import "LazyClass.h"
#import <objc/objc-runtime.h>
#implementation LazyClass
#dynamic lazyArray;
- (instancetype)init {
self = [super init];
self.myVars = [NSMutableDictionary dictionary];
return self;
}
- (NSMutableDictionary *)getMyVars {
return self.myVars;
}
// the generated getter method
id dynamicGetterMethodIMP(id self, SEL _cmd) {
// selector name, which is also the property name
const char *selName = sel_getName(_cmd);
NSString *selNSName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding];
NSString *keyPath = [NSString stringWithFormat:#"myVars.%#", selNSName];
if (![self valueForKeyPath:keyPath]) {
// get the actual type of the property
objc_property_t property = class_getProperty([self class], selName);
const char *attr = property_getAttributes(property);
NSString *attrString = [[NSString alloc] initWithCString:attr encoding:NSUTF8StringEncoding];
NSString *typeAttr = [[attrString componentsSeparatedByString:#","] firstObject];
NSString *typeName = [typeAttr substringWithRange:NSMakeRange(3, typeAttr.length - 4)];
// the default initialization
Class typeClass = NSClassFromString(typeName);
[self setValue:[[typeClass alloc] init] forKeyPath:keyPath];
}
return [self valueForKeyPath:keyPath];
}
// the generated setter method
void dynamicSetterMethodIMP(id self, SEL _cmd, id value) {
// get the property name out of selector name
// e.g. setLazyArray: -> lazyArray
NSString *propertyName = NSStringFromSelector(_cmd);
propertyName = [propertyName stringByReplacingOccurrencesOfString:#"set" withString:#""];
propertyName = [propertyName stringByReplacingOccurrencesOfString:#":" withString:#""];
propertyName = [NSString stringWithFormat:#"%#%#", [propertyName substringToIndex:1].lowercaseString, [propertyName substringFromIndex:1]];
NSString *keyPath = [NSString stringWithFormat:#"myVars.%#", propertyName];
[self setValue:value forKeyPath:keyPath];
}
// dynamic method resolution
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if ([NSStringFromSelector(aSEL) containsString:#"set"]) {
class_addMethod([self class], aSEL, (IMP)dynamicSetterMethodIMP, "^?");
} else {
class_addMethod([self class], aSEL, (IMP)dynamicGetterMethodIMP, "v#:");
}
return YES;
}
#end
Documentation
If it's the verboseness that bothers you, I suppose you could compress lazy initialisers that only need one-line initialization using the ternary operator:
- (VMPlacesListFilter *)currentFilter
{
return _currentFilter ? : (_currentFilter = [[VMPlacesListFilter alloc] init]);
}
DISCLAIMER: I don't do this, but it's interesting that it can be done
I need some clarifications on a crash I'm encountering using NSArray, blocks and Manual Reference Counting. My goal is to store blocks on a collection (NSArray in this case) in order to reuse them in the future.
I've setup a small sample to replicate the issue. In particular, I have a class Item that looks like the following:
#import <Foundation/Foundation.h>
typedef void(^MyBlock)();
#interface Item : NSObject
- (instancetype)initWithBlocks:(NSArray*)blocks;
#end
#import "Item.h"
#interface Item ()
#property (nonatomic, strong) NSArray *blocks;
#end
#implementation Item
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
NSMutableArray *temp = [NSMutableArray array];
for (MyBlock block in blocks) {
[temp addObject:[[block copy] autorelease]];
}
_blocks = [temp copy];
}
return self;
}
The usage is described below (I'm using in the app delegate).
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = ^() {
[weakSelf doSomething1];
};
MyBlock myBlock2 = ^() {
[weakSelf doSomething1];
};
NSArray *blocks = #[myBlock1, myBlock2];
// As MartinR suggested the code crashes even
// if the following line is commented
Item *item = [[Item alloc] initWithBlocks:blocks];
}
If I run the app, it crashes with an EXC_BAD_INSTRUCTION (note that I've already enabled All Exceptions breakpoints). In particular, the app stops in the main.
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
Note: As suggested by Ken Thomases, if you use bt command on llvm console, you are to see the back trace. In this case it shows the following:
-[__NSArrayI dealloc]
If I comment the [weakSelf doSomethingX]; it works without crashes (it does not mean that is correct).
Modifying the code a little bit like the following, all runs ok.
// Item does not do anymore the copy/autorelease dance
// since used in the declaration of the blocks
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
_blocks = [blocks retain];
}
return self;
}
and
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
MyBlock myBlock2 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
NSArray *blocks = #[myBlock1, myBlock2];
Item *item = [[Item alloc] initWithBlocks:blocks];
What is the point here? I think I'm missing something but I don't know what.
Update 1
Ok. I'll try to recap my thoughts based on the comments with #Martin R and #Ken Thomases.
A block, by default, is created on stack if a copy message is not sent to it (ARC does this for us) in order to move it on the heap. So, the situation in this case is the following. I create an autorelease array and I add two blocks where retain is called in a implicit manner. When the applicationDidFinishLaunching method finishes is execution, the blocks, since created on the stack (they are automatic variables) disappear. In a later moment, the array called blocks will be released since has been marked as autorelease. So, it will crash since it will send a release object to blocks that do not exist anymore.
So, my question is the following: What does it mean to send a retain message to a block that is on the stack? Why the array is the source of the crash (see the back trace)? In other words, since a block is on the stack, will it bump the retain count of it? And when it goes out of scope? In addiction, why if I comment the [weakSelf doSomething1] line the code works without problems? Not very clear to me this part.
You are sticking an object from the stack into an autoreleased array. BOOM ensues.
Consider:
typedef void(^MyBlock)();
int main(int argc, char *argv[]) {
#autoreleasepool {
NSObject *o = [NSObject new];
MyBlock myBlock1 = ^() {
[o doSomething1];
};
NSLog(#"o %p", o);
NSLog(#"b %p", myBlock1);
NSLog(#"b retain %p", [myBlock1 retain]);
NSLog(#"b copy %p", [myBlock1 copy]);
NSLog(#"s %p", ^{});
sleep(1000000);
}
}
Compiled/run as -i386 (because the #s are smaller and more obvious):
a.out[11729:555819] o 0x7b6510f0
a.out[11729:555819] b 0xbff2dc30
a.out[11729:555819] b retain 0xbff2dc30
a.out[11729:555819] b copy 0x7b6511a0
a.out[11748:572916] s 0x67048
Since the object is at 0x7b, we can assume that is the heap. 0xb is really high memory and, thus, the stack.
The retain doesn't cause a copy (because doing so would have invariably led to leaks) and retain on a stack based object is meaningless.
If you change the [o doSomething1]; to [nil doSomething1]; then that becomes a static block and that lives in readonly mapped memory (readonly-executable pages from the mach-o's TEXT segment) and, thus, there is no allocation to deallocate and retain/release/autorelease are no-ops.
As you can see, the static block ended up around 0x67048 (this number may change from run to run, btw, for a variety of reasons. Low in memory.
In fact, because of the sleep(), we can run vmmap against the a.out process and see:
==== Writable regions for process 11772
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
__DATA 00067000-00068000 [ 4K] rw-/rwx SM=ZER /tmp/a.out
That is, the static block was in the first 4K segment of mapped writable regions from the mach-o file. Note that this doesn't mean the code is in that writable region (SECURITY HOLE if it were). The code is in the TEXT segment mapped into the readable regions.
I have found that if I alloc a new object inside a Class method and return it to main() it seems to cause a memory leak when I no longer want the object.
For example, here is a simple Class that includes a Class method that returns an instance of itself:
#interface Stinker : NSObject
{
int a;
}
+(instancetype) makeAStink;
-(void) showThem;
-(void) setEm: (int) z;
#end
#implementation Stinker
-(void) showThem
{
NSLog(#"%d",a);
}
-(void) setEm: (int) z
{
a = z;
}
-(void) dealloc
{
NSLog(#"Arrrgggggh!");
}
+(instancetype) makeAStink
{
id temp = [[self alloc] init];
return temp;
}
#end
Now if I create an instance directly from main():
Stinker *aStink =[[self alloc] init];
and subsequently set aStink to nil:
aStink = nil;
the overridden dealloc method is called and the Argggggh! message is logged. That's fine and as expected.
But if I use the Class method I wrote to create an instance:
Stinker *aNewStink = [Stinker makeAStink];
the behaviour is different.
Now if I set aNewStink to nil, it will no longer point to the object but the object is not destroyed. dealloc is not called and the Arggggh message is not logged.
It seems like it still has an owner somewhere.
Of course when main() terminates the object is destroyed and dealloc is eventually called.
But this seems to suggest that unused and unloved objects are still hanging around on the heap until the program terminates.
Isn't this a memory leak?
Should I just avoid using Class methods to alloc new instances?
When using ARC, the following code
+(instancetype) makeAStink
{
id temp = [[self alloc] init];
return temp;
}
will be same with Non-ARC like this:
+(instancetype) makeAStink
{
id temp = [[self alloc] init];
return [temp autorelease];
}
Thanks to autorelease, aNewStink = nil will make aNewStink do release in next runloop.
So if you do this:
#autoreleasepool {
Stinker *aNewStink = [Stinker makeAStink];
aNewStink = nil;
}
Dealloc method is called immediately.
this is MRC (without ARC) code for your example
+(instancetype) makeAStink
{
id temp = [[self alloc] init];
return [temp autorelease];
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [Stinker makeAStink]; // obj is autoreleased object
id obj2 = [[Stinker alloc] init]; // obj2 is not autoreleased
[obj2 release]; // so you need to release it
[pool release]; // now obj is released and deallocated
so obj have an extra retain count which will be released (and deallocated) in next runloop whereas obj2 will be released immediately when release is called
this is not memory leak, it is usual behaviour and (normally) doesn't affect program performance in noticeable way
I wonder whether Objective-C offers any support for generics?
For instance, consider a method:
-(void) sort: (NSMutableArray *) deck {
}
Is there any way for me to make it only deal with Deck of Cards?
Is something like this possible to enforce?
-(void) sort: (NSMutableArray <Card *>) deck {
}
Objective-C supports lightweight Generics since 2015, with the Xcode 7.
The Xcode 7 compiler will give you the compiler warning if there is a type mismatch.
For example, the following line will raise a compiler warning as the second object in the array causes type mismatch. The array allows only NSString objects.
NSArray <NSString *> *myArray = [#"str2", #1, #"str2"];
You can use the introspection tools offered by the objective-c runtime.
Basically, it means you can check if all objects in an array either are a kind of class (Class A or one subclass of it) or a member of class (class A), or if a objects conforms to a protocol or responds to a selector (a certain method is present).
-(void) sort: (NSMutableArray *) deck {
for(id obj in deck){
if(obj isKindOfClass:[A class]]){
//this is of right class
}
}
}
You could write a Category method on NSArray that checkouts this on every object.
BOOL allAreKindOfA = [array allObjectsAreKindOfClass:[A class]];
Normally you actually don't need this very often, as you know what you put inside a collection.
If you need to check the type or ability of an object in a Array, this might be an indicator, that your Architecture is broken
Another option could be a subclass of NSMutableArray that only accepts certain classes. But be aware of the subclassing notes for NSMutableArray and NSArray, as these are Class-Clusters and therefore not easy to subclass.
Note: In my other answer I created a NSMutableArray subclass, that uses a block to test, if a certain requirement is fulfilled. If you test against class-membership, this will do exactly what you want. Use the second block for error handling.
As of Xcode 7's release, Apple has added support for Objective-C generics.
NSArray <NSString *> *arrayOfStrings = #[#"a", #"b"];
NSDictionary <NSString *, NSDate *> *dictionaryOfDates = #{ #"a" : #1 };
Inspired by MonomorphicArray I came up with another idea:
Create a subclass on NSMutableArray, that takes two blocks:
AddBlock — a block that test, if one or more requirements are full filed and adds the object only, if its passes the test
FailBlock — a block, that defines what happens, if the test was not successful.
The AddBlock could test for a certain class membership like
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
and the FailBlock can raise an exception, fail silently or add the element, that failed the test, to another Array. If no failBlock is provided, a default block will raise an error.
The blocks will define, if an array acts like an generic array, or as a filter.
I will give an complete example for the second case.
VSBlockTestedObjectArray.h
#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element);
typedef void(^FailBlock)(id element);
#interface VSBlockTestedObjectArray : NSMutableArray
#property (nonatomic, copy, readonly) AddBlock testBlock;
#property (nonatomic, copy, readonly) FailBlock failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;
#end
VSBlockTestedObjectArray.m
#import "VSBlockTestedObjectArray.h"
#interface VSBlockTestedObjectArray ()
#property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
#end
#implementation VSBlockTestedObjectArray
#synthesize testBlock = _testBlock;
#synthesize failBlock = _failBlock;
#synthesize realArray = _realArray;
-(id)initWithCapacity:(NSUInteger)capacity
{
if (self = [super init]) {
_realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock
FailBlock:(FailBlock)failBlock
Capacity:(NSUInteger)capacity
{
self = [self initWithCapacity:capacity];
if (self) {
_testBlock = [testBlock copy];
_failBlock = [failBlock copy];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}
-(id)initWithTestBlock:(AddBlock)testBlock
{
return [self initWithTestBlock:testBlock FailBlock:^(id element) {
[NSException raise:#"NotSupportedElement" format:#"%# faild the test and can't be add to this VSBlockTestedObjectArray", element];
} Capacity:0];
}
- (void)dealloc {
[_failBlock release];
[_testBlock release];
self.realArray = nil;
[super dealloc];
}
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if(self.testBlock(anObject))
[self.realArray insertObject:anObject atIndex:index];
else
self.failBlock(anObject);
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[self.realArray removeObjectAtIndex:index];
}
-(NSUInteger)count
{
return [self.realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [self.realArray objectAtIndex:index];
}
-(void)errorWhileInitializing:(SEL)selector
{
[NSException raise:#"NotSupportedInstantiation" format:#"not supported %#", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}
#end
Use it like:
VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
NSLog(#"%# can't be added, didn't pass the test. It is not an object of class NSString", element);
}];
VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
NSLog(#"%# can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];
[stringArray addObject:#"test"];
[stringArray addObject:#"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:#"test2"];
[stringArray addObject:#"test3"];
[numberArray addObject:#"test"];
[numberArray addObject:#"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:#"test2"];
[numberArray addObject:#"test3"];
NSLog(#"%#", stringArray);
NSLog(#"%#", numberArray);
Note: This code is not fully tested. Probably some of the unimplemented method should be implemented for usage in real world programs.
Not directly, no. There a few ways to simulate it, but it requires a lot of wrapper code, boilerplate code, and runtime overhead. I just switch to Objective-C++ and use C++ templates when I want or need proper generics.
So if you wanted to introduce typesafety/checks to an NSArray, you could approach it using something like this:
template <typename T>
class t_typed_NSMutableArray {
public:
t_typed_NSMutableArray() : d_array([NSMutableArray new]) {}
~t_typed_NSMutableArray() { [d_array release]; }
/* ... */
T* operator[](const size_t& idx) {
T* const obj([this->d_array objectAtIndex:idx]);
assert([obj isKindOfClass:[T class]]);
return obj;
}
void addObject(T* const obj) {
assert([obj isKindOfClass:[T class]]);
[this->d_array addObject:obj];
}
private:
NSMutableArray * const d_array;
};
in use:
t_typed_NSMutableArray<Card> array([self cards]); // < note this exact constructor is not defined
Card * firstCard = array[0]; // << ok
NSString * string = array[0]; // << warning
then you also get type safety and overloading when passing the collection, so you could not pass t_typed_NSArray<Card> as an t_typed_NSArray<NSURL>.
There is an easy, effective way of doing this (I've been using it on projects for a couple of years now). Sadly, someone deleted the answer, and my attempts to get it re-instated were rejected. Here goes again:
You can re-implement a cut-down version of C++ templating within Obj-C because Obj-C encapsulates all of C (and C++ templates are C-macros with some improved compiler/debugger support):
This only needs to be done once, using a single header file. Someone has done it for you:
https://github.com/tomersh/Objective-C-Generics
You end up with 100% legal Obj-C code that looks like this:
NSArray<CustomClass> anArray= ...
CustomClass a = anArray[0]; // works perfectly, and Xcode autocomplete works too!
This all works fine in XCode, with autocomplete, etc.
is almost a week that i can't fix a brain painful problem:
I have a UIViewController subclass named StreamingViewControllerCommon that implement this property:
#property (nonatomic, assign, readonly) BOOL isMusicStopped;
and of course i #synthesize it in the .m file
then i have 2 subclasses of this class: Listen_UIViewController and LastNews_UIViewController that modify (not calling self.isMusicStopped but accessing directly to it) the var isMusicStopped.
In another Class i have a NSMutableDictionary that contains 2 instance (1 for each class) of these two classes but when i try to do this:
if (streamingViews){
for(StreamingViewControllerCommon* aView in streamingViews){
BOOL stopped = aView.isMusicStopped;
NSLog(#"%#",stopped);
if(stopped){
[aView closeStream];
[streamingViews removeObjectForKey:[aView class]];
[aView release];
aView = nil;
}
}
}
i obtain this error:
2011-02-08 15:55:09.760 ProjectName[6182:307] +[LastNews_UIViewController isMusicStopped]: unrecognized selector sent to class 0x2143c
2011-02-08 15:55:09.768 ProjectName[6182:307] CoreAnimation: ignoring exception: +[LastNews_UIViewController isMusicStopped]: unrecognized selector sent to class 0x2143c
But the weird thing is that in the StreamingViewControllerCommon implement also these methods:
-(void) destroyStreamer{
}
-(void) closeStream{
[self destroyStreamer];
}
and when i do:
NSMutableArray* keys = [[NSMutableArray alloc] initWithArray:[streamingViews allKeys]];
[keys removeObject:[thisView class]];
NSMutableArray *tmp = [[NSMutableArray alloc] initWithArray:[streamingViews objectsForKeys:keys notFoundMarker:#"404"]];
if (tmp){
for (StreamingViewControllerCommon* vc in tmp)
[vc closeStream];
[tmp release];
}
[streamingViews removeObjectsForKeys:keys];
i am not getting any error and the subclasse's overridden closeStream methods are right called.
What am i doing wrong?
Best Regards, Antonio
EDIT: As I wrote in the comment: Changing this:
if (streamingViews){
for(StreamingViewControllerCommon* aView in streamingViews){
BOOL stopped = aView.isMusicStopped;
NSLog(#"%#",stopped);
if(stopped){
[aView closeStream];
[streamingViews removeObjectForKey:[aView class]];
[aView release];
aView = nil;
}
}
}
in this:
if (streamingViews){
for (id aViewClass in streamingViews){
StreamingViewControllerCommon* aView = [[streamingViews objectForKey:aViewClass] retain];
//NSLog(#"%#",aView.isMusicStopped);
if(aView.isMusicStopped){
[aView closeStream];
[streamingViews removeObjectForKey:aViewClass];
[aView release];
aView = nil;
}
}
}
Did the trick :P The for each cycle of a NSDictionary returns its keys not the objects and, since i was using the Class of objects as key i was obtaining that weird exception
For whatever reason, it looks like streamingViews contains not an instance of LastNews_UIViewController, but the class itself. That's what the plus sign in +[LastNews_UIViewController isMusicStopped] signifies.