What is the proper way to avoid Retain Cycle while using blocks - iphone

What is the proper way to add objects in NSMutableArray which is strongly defined by property.
[tapBlockView setTapBlock:^(UIImage* image) {
[self.myImageArray addObject:image]; // self retain cycle
}
If I will create weak reference something like
__weak NSMutableArray *array = self.myImageArray;
[tapBlockView setTapBlock:^(UIImage* image) {
[array addObject:image]; // If I will do this then how will I update original Array ?
}
I have also tried
__weak id weakSelf = self;
[tapBlockView setTapBlock:^(UIImage* image) {
[weakSelf storeImageInaNewMethod:image]; // Calling SToreImageInaNewMethod
}
and
-(void)storeImageInaNewMethod:(UIImage*)image {
[self.myImageArray addObject:image]; // This again retaining cycle
}
What is the proper way to update original object defined by property ?

After maddy's answer - this is from 2012 WWDC lecture on GCD and asynchronous programming:
__weak MyClass *weakSelf = self;
[tapBlockView setTapBlock:^(UIImage* image) {
__strong MyClass *strongSelf = weakSelf;
if(strongSelf) {
[strongSelf.myImageArray addObject:image];
}
}];

Try a combination of the 2nd and 3rd.
__weak id weakSelf = self;
[tapBlockView setTapBlock:^(UIImage* image) {
[weakSelf.myImageArray addObject:image];
}

In your case you only need to reference an array which is referenced by self, so:
NSMutableArray *array = self.myImageArray;
[tapBlockView setTapBlock:^(UIImage* image)
{
[array addObject:image]; // No cycle
}];
Works fine provided that self.myImageArray does not return different array references at different times. There is no cycle: the current object references the array and the block, and in turn the block references the array.
If self.myImageArray does return different array references as different times then use a weak reference to self, your case 3.

Your second and third ones appear correct. The second one works because you did not create a copy of the array, so that still points to the original one. The third one works because the reference to self is weak.

Related

objective c class method returned value, assigned to weak/strong properties

I'm facing a bit of a confusion involving weak and strong properties. For the sake of brevity I won't include the entire code.
I created a class convenience method which returns a UIView object, and I implemented it in a UIView category as an alternative to subclassing.
#implementation UIView (CSMonthView)
+ (UIView *)monthViewFromDateArray:(NSArray *)arrayOfAllShiftsAndEvents withNibOwner:(id)owner selectedDate:(NSDate *)selectedDate withCompletionHandler:(void(^)(CSCalendarButton *selectedButton))block
{ // .. do some stuff
// Create an instance of UIView
UIView *monthView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320.0, 200.0)];
// Create UIButtons and set the passed down 'owner' value, as the target for an
// action event.
// Add UIButton as subviews to monthView....
return monthView;
}
I should note that inside the method I do not have anything pointing to monthView.
Now inside the implementation of the 'owner', which is a class called CSCalendarViewController, I create the above UIView by calling the class convenience method and assign it to a UIView property called _monthView.
#interface CSCalendarViewController : UIViewController
#property (weak, nonatomic) UIView *monthView;
#end
#implementation CSCalendarViewController
__weak CSCalendarViewController *capturedSelf = self;
// Create the current month buttons and populate with values.
_monthView = [UIView monthViewFromDateArray:_arrayOfAllShiftsAndEvents withNibOwner:self selectedDate:_selectedDate withCompletionHandler:^(CSCalendarButton *selectedButton) {
capturedSelf.selectedButton = selectedButton;
[capturedSelf.selectedButton setSelected:YES];
}
Now my confusion is this. Even though I defined the property 'monthView' as weak, 'monthView' still holds on to the value of the returned UIView.
If I go ahead and do something like this:
_monthView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 200.0)];
The compiler gives a warning (as it should) saying "Assigned retained object to weak variable".
Why am I not getting the same error message when I assign 'monthView' to the UIView that returns from the class method?
I don't have a deep understanding when it comes to pre-ARC memory management, and I think I'm missing something obvious. Thanks.
'monthView' still holds on to the value of the returned UIView.
It won't for long. This question demonstrates the underlying workings of ARC, and how it is a translation to the traditional retain/release methods, rather than a whole new memory management system.
Pre ARC
Before ARC, there was no concept of weak or strong, instead it referred to retain and assign. Assigning to variables did nothing to the reference count, it was up to the developer to manage it.
Now, in regards to the memory management policies, a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy”, will return a retained object (Documentation). This meant, on assignment to a variable, the developer didn't need to explicitly retain (they had to explicitly release, or autorelease):
// Will have a retain count of 1 here
var = [NSString alloc] initWithString:#"Test"];
// Will have a retain count of 2 here
var = [[NSString alloc] initWithString:#"Test"] retain]
// Will have a retain count of 1 here, but will be released later on automatically
var = [[NSString alloc] initWithString:#"Test"] autorelease];
// Will have a retain count of 0 here, and will be released before it reaches the variable!
var = [[NSString alloc] initWithString:#"Test"] release];
Methods that don't have that naming convention, suggest they return an autoreleased object. The developer needs to say something explicitly, to keep the object around longer:
// Will have a retain count of 1 here, but will be released later on automatically
var = [NSString stringWithString:#"Test"];
// Will have a retain count of 1 here
var = [[NSString alloc] initWithString:#"Test"] retain]
// Will have a retain count of 1 here, but will be released twice later on (Over-released!)
var = [[NSString alloc] initWithString:#"Test"] autorelease];
// Will have a retain count of 0 here, and will be released again later on (Over-released!)
var = [[NSString stringWithString:#"Test"] release];
ARC + MRC
ARC removes this unnecessary need of releasing and retaining, and instead decides what to do with the memory management based on the type of variable it will be assigned to. This doesn't mean the memory management model changed; it is still all retain and release under the hood. As such, how does this affect you? For brevity, this answer will only take into account weak variables.
Assigning to a weak variable does not do anything with the retain count of the object. Lets see a practical example to explain:
__weak UIView* monthView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 200.0)];
Because (in reality, behind the ARCness) this is returning a retained object, but weak variables don't affect the retain count, the compiler has found the earliest point to release the object in order to prevent a memory leak; on allocation! As such, it will be translated to the following, and cause an error:
UIView* monthView = [[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 200.0)] release];
Now, in regards to monthViewFromDateArray:, this is suggesting to the compiler (due to its name), that it will return an autoreleased object (Documentation). Because the compiler is aware that an autoreleased object will be released automatically later on in the run loop (when the autorelease pool is drained), it will not insert a release call like before. As such, the assignment to a weak variable isn't an issue, but it's only really valid within the scope it's being used in.
Say we have method
+(UIView*) create {
return [[UIView alloc] init];
}
It is converted to this when compiled
+(UIView*) create {
return [[[UIView alloc] init] autorelease];
}
Now here:
UIView* __weak view;
//warning here
view = [[UIView alloc] init]; //1
view = [AppDelegate create]; //2
The first line is converted to this:
tempVar = [[UIView alloc] init];
storeWeak(tempVar, &view); //view = tempVar and marked as weak
[view release]; //view is nil after this because retain count == 0 (assignment to nil is done in release internally)
The second line:
tempVar = [MyClass create];
[tempVar retainAutoreleasedReturnValue];
storeWeak(tempVar, &view); //view = tempVar and marked as weak
[release tempVar]; //view is not nil because tempVar is autoreleased later
If we have the code like this:
#autoreleasepool {
view = [[UIView alloc] init];
//view is nil here
view = [AppDelegate create];
//view equals to the return value
}
//view becomes nil here because [AppDelegate create] return value is released
You can see all of this by looking at code disassembly.

Error when using NSMutableSet

I get the error
* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0x6b66390> was mutated while being enumerated.'
when adding an new delegate to my class. Or at least, that's where I think the problem is.
This is my code: MyAppAPI.m
[...]
static NSMutableSet *_delegates = nil;
#implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSMutableSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
[_delegates addObject:delegate];
}
+ (void)removeDelegate:(id)delegate
{
[_delegates removeObject:delegate];
}
[...]
#end
MyAppAPI is a singleton which I can use throughout my application. Wherever I can (or should be able to) do: [MyAppAPI addDelegate:self].
This works great, but only in the first view. This view has a UIScrollView with PageViewController which loads new views within itself. These new views register to MyAppAPI to listen to messages until they are unloaded (which in that case they do a removeDelegate).
However, it seems to me that it dies directly after I did a addDelegate on the second view in the UIScrollView.
How could I improve the code so that this doesn't happen?
Update
I'd like to clarify me a bit further.
What happens is that view controller "StartPage" has an UIScrollView with a page controller. It loads several other views (1 ahead of the current visible screen).
Each view is an instans PageViewController, which registers itself using the addDelegate function shown above to the global singleton called MyAppAPI.
However, as I understand this viewcontroller 1 is still reading from the delegate when viewcontroller 2 registers itself, hence the error shows above.
I hope I made the scenario clear. I have tried a few things but nothing helps.
I need to register to the delegate using addDelegate even while reading from the delegates. How do I do that?
Update 2
This is one of the reponder methods:
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
Scott Hunter is right. This error is thrown when you try to edit a list while iterating.
So here is an example of what you may be doing.
+ (void)iteratingToRemove:(NSArray*)items {
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[MyAppAPI removeDelegate:delegate]; //error you are editing an NSSet while enumerating
}
}
}
And here is how you should handle this correctly:
+ (void)iteratingToRemove:(NSArray*)items
{
NSMutableArray *delegatesToRemove = [[NSMutableArray alloc] init];
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[delegatesToRemove addObject:delegate];
}
}
for(id delegate in delegatesToRemove) {
[MyAppAPI removeDelegate:delegate]; //This works better
}
[delegatesToRemove release];
}
The error suggests that, while some code somewhere is in the middle of going through your list, you are modifying the list (which explains the crash after addDelegate is called). If the code doing the enumerating is the one modifying the list, then you just have to put off the modifications until the enumeration is done (say, by collecting them up in a different list). Without knowing anything about the code doing the enumerating, can't say much more than that.
A simple solution, don't use a mutable set. They are dangerous for a variety of reasons, including this one.
You can use -copy and -mutableCopy to convert between mutable and non-mutable versions of NSSet (and many other classes). Beware all copy methods return a new object with a retain count of 1 (just like alloc), so you need to release them.
Aside from having less potential for bugs, non-mutable objects are faster to work with and use less memory.
[...]
static NSSet *_delegates = nil;
#implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable addObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
+ (void)removeDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable removeObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
[...]
#end
Scott Hunter is right - it's a problem with modifying the NSSet while you're enumerating over the set's items. You should have a stack trace from where the application crashes. It probably has a line where you're adding to/remove from the _delegates set. This is where you need to make the modification. It's easy to do. Instead of adding to/deleting from the set, do the following:
NSMutableSet *tempSet = [_delegates copy];
for (id delegate in _delegates)
{
//add or remove from tempSet instead
}
[_delegates release], _delegates = tempSet;
Additionally, NSMutableSet is not thread safe, so you should call your methods always from the main thread. If you haven't explicitly added any extra threads, you have nothing to worry about.
A thing to always remember about the Objective-C "fast enumeration".
There is 2 big difference between "fast enumeration" and a for loop.
"fast enumeration" is quicker than a for loop.
BUT
You can't modify the collection your enumerating over.
You can ask your NSSet for - (NSArray *)allObjects and enumerate over that array while modifying your NSSet.
You get this error when a thread tries to modify (add,delete) the array while other thread is iterating over it.
One way to solve this using NSLock or synchronizing the methods. That ways add, remove and iterate methods cannot be called in parallel.
But this will have effect on performance and/or responsiveness because any add/delete will have to wait for the thread that was iterating over the array.
A better solution inspired from Java's CopyOnWriteArrayList would be to create a copy of the array and iterate over the copy. So the only change in your code will be:-
//better solution
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
NSArray *copyOfDelegates = [_delegates copy]
for (id delegate in copyOfDelegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
Solution using locks with performance impact
//not a good solution
+ (void)addDelegate:(id)delegate
{
#synchronized(self){
[_delegates addObject:delegate];
}
}
+ (void)removeDelegate:(id)delegate
{
#synchronized(self){
[_delegates removeObject:delegate];
}
}
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
#synchronized(self){
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
}

Problem with allocating memory for an Objective-C data object

I've been programming objective-C for a few months now and have done pretty well so far without having to post any questions. This would be my first. The problem is that I'm getting a memory leak warning from a data object in one of it's methods. I can see that the problem is that I'm sending an alloc to it without releasing it, but I don't know how else to get it to retain the object in memory. If I take the alloc out, the program crashes. If I leave it in, it leaks memory. Here is the method in question:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
newFeature.featureID = fID;
newFeature.featureName = fName;
newFeature.featureSecure = fSecure;
return [newFeature autorelease];
}
This method is called by another method in my view controller. This method is as follows:
+ (NSMutableArray*) createFeatureArray {
NSString *sqlString = #"select id, name, secure from features";
NSString *file = [[NSBundle mainBundle] pathForResource:#"productname" ofType:#"db"];
sqlite3 *database = NULL;
NSMutableArray *returnArray = [NSMutableArray array];
if(sqlite3_open([file UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = [sqlString UTF8String];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
Feature *myFeature = [Feature featureWithID:sqlite3_column_int(compiledStatement,0)
name:[NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)]
secure:sqlite3_column_int(compiledStatement,2)];
[returnArray addObject:myFeature];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return returnArray;
}
I have tried several things, such as creating a featureWithFeature class method, which would allow me to alloc init the feature in the calling method, but that crashed the program also.
Please let me know if you need any clarification or any other parts of the code. Thank you in advance for your help.
UPDATE: 4/14/2011
After reading the first two responses I implemented the suggestion and found that the program is now crashing. I am at a complete loss as to how to track down the culprit. Hoping this helps, I am posting the calling method from the view controller as well:
- (void)setUpNavigationButtons {
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
int i = 0;
for (i = 0; i < [featureArray count]; i++) {
Feature *myFeature = [featureArray objectAtIndex:i];
CGRect buttonRect = [self makeFeatureButtonFrame:[featureArray count] withMember:i];
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[aButton setFrame:buttonRect];
[aButton addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[aButton setTitle:[NSString stringWithFormat:#"%#",myFeature.featureName] forState:UIControlStateNormal];
aButton.tag = myFeature.featureID;
[self.view addSubview:aButton];
}
}
NOTE: These methods are posted in reverse of the order they are invoked. This last method calls the second method, which in turn, calls the first.
UPDATE: I've updated these functions to show what is in there now: Below, I will post the header files for the object - maybe that will help
#interface Feature : NSObject {
int featureID;
int featureSecure;
NSString *featureName;
}
#property (nonatomic, assign) int featureID;
#property (nonatomic, assign) int featureSecure;
#property (nonatomic, retain) NSString *featureName;
- (id) init;
- (void) dealloc;
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure;
#end
#interface FeatureController : NSObject {
}
- (id) init;
- (void) dealloc;
+ (NSMutableArray*) createFeatureArray;
+ (Feature*) getFeatureWithID:(int)fetchID;
#end
Convenience methods should follow the convention of returning autoreleased objects. Change this:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
...
return newFeature;
}
to:
+ (id) featureWithID:(int)fID name:(NSString*)fName secure:(int)fSecure {
Feature *newFeature = [[self alloc] init];
...
return [newFeature autorelease];
}
The name of your method - +featureWithID:name:secure: - indicates that it returns an object that the caller does not own. Instead, it is returning an object that has been retained, that the caller therefore owns and must release. To fix this (and your leak), simply replace return newFeature with return [newFeature autorelease].
There's nothing more you need to do, because your own code doesn't need a long-lasting ownership claim, and the array to which you're adding the object will manage its own ownership claim over it.
In +createFeatureArray, you’re over releasing the array:
+ (NSMutableArray*) createFeatureArray {
…
NSMutableArray *returnArray = [[[NSMutableArray alloc] init] autorelease];
…
return [returnArray autorelease];
}
In the first line, you used +alloc, so you own the array. Then you used -autorelease, so you do not own the array any more. This means that you shouldn’t send -release or -autorelease to it, which you are doing in the return line.
You can fix that by changing those lines to:
+ (NSMutableArray*) createFeatureArray {
…
NSMutableArray *returnArray = [NSMutableArray array];
…
return returnArray;
}
Also, unless it is relevant to callers that the array is mutable, you should change that method to return NSArray instead of NSMutableArray. You could keep your code as is, i.e., return a mutable array even though the method declaration states that the return type is NSArray.
As for your convenience constructor, there are essentially two choices depending on whether you want to return an owned or a non-owned object:
if you want to return an owned object, allocate it with +alloc or +new and return it without autoreleasing it. Your method name should contain new, e.g. +newFeatureWithId:…
if you want to return an object that’s not owned by the caller, allocate it with +alloc or new and autorelease it before/upon returning it to the caller. Your method name should not contain new, alloc, or copy.
In -setUpNavigationButtons, you obtain a non-owned array via +createFeatureArray, allocate a mutable array based on it, and release the mutable array without adding or removing elements from it. A mutable array makes sense when you need to add/remove elements. If you don’t have this need, you could change your method to:
- (void)setUpNavigationButtons {
// get array of features from feature data controller object
NSArray *featureArray = [FeatureController createFeatureArray];
…
// [featureArray release];
You’d remove that [featureArray release] since you do not own featureArray inside that method.
Edit: In -setUpNavigationButtons, you’re retaining the button you create and soon after you’re releasing it. In that particular method, those are idempotent operations — they aren’t wrong per se but are not necessary at all. You could replace that code with
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
…
[self.view addSubview:aButton];
// [aButton release];
i.e., do not retain it and do not release it.

UIView as dictionary key?

I want to have a NSDictionary that maps from UIViews to something else.
However, since UIViews do not implement the NSCopying protocol, I can't use them directly as dictionary keys.
You can use an NSValue holding the pointer to the UIView and use this as key. NSValues
are copyable. but, if the view is destroyed, the NSValue will hold a
junk pointer.
Here is the actual code (based on the answer by luvieere and further suggestion by Yar):
// create dictionary
NSMutableDictionary* dict = [NSMutableDictionary new];
// set value
UIView* view = [UILabel new];
dict[[NSValue valueWithNonretainedObject:view]] = #"foo";
// get value
NSString* foo = dict[[NSValue valueWithNonretainedObject:view]];
Although this isn't really what they're intended for, you could whip up a functional dictionary-like interface using Associative References:
static char associate_key;
void setValueForUIView(UIView * view, id val){
objc_setAssociatedObject(view, &associate_key, val, OBJC_ASSOCIATION_RETAIN);
}
id valueForUIView(UIView * view){
return objc_getAssociatedObject(view, &associate_key);
}
You could even wrap this up in a class ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable*; in that case you might want to retain the views that you use as keys.
Something like this (untested):
#import "ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable.h"
#import <objc/runtime.h>
static char associate_key;
#implementation ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable
- (void)setObject: (id)obj forKey: (id)key
{
// Remove association and release key if obj is nil but something was
// previously set
if( !obj ){
if( [self objectForKey:key] ){
objc_setAssociatedObject(key, &associate_key, nil, OBJC_ASSOCIATION_RETAIN);
[key release];
}
return;
}
[key retain];
// retain/release for obj is handled by associated objects functions
objc_setAssociatedObject(key, &associate_key, obj, OBJC_ASSOCIATION_RETAIN);
}
- (id)objectForKey: (id)key
{
return objc_getAssociatedObject(key, &associate_key);
}
#end
*The name may need some work.
Provided you don't need to support before iOS 6, NSMapTable (suggested by neilsbot) works well because it can provide an enumerator over the keys in the collection. That's handy for code common to all of the text fields, like setting the delegate or bi-directionally syncing the text values with an NSUserDefaults instance.
in viewDidLoad
self.userDefFromTextField = [NSMapTable weakToStrongObjectsMapTable];
[self.userDefFromTextField setObject:#"fooUserDefKey" forKey:self.textFieldFoo];
[self.userDefFromTextField setObject:#"barUserDefKey" forKey:self.textFieldBar];
// skipped for clarity: more text fields
NSEnumerator *textFieldEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [textFieldEnumerator nextObject]) {
textField.delegate = self;
}
in viewWillAppear:
NSEnumerator *keyEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [keyEnumerator nextObject]) {
textField.text = [self.userDefaults stringForKey:[self.textFields objectForKey:textField]];
}
in textField:shouldChangeCharactersInRange:replacementString:
NSString *resultingText = [textField.text stringByReplacingCharactersInRange:range withString:string];
if(resultingText.length == 0) return YES;
NSString *preferenceKey = [self.textFields objectForKey:textField];
if(preferenceKey) [self.userDefaults setString:resultingText forKey:preferenceKey];
return YES;
And now I will go cry, because I implemented all of this before realizing that my iOS 5.1-targeted app can't use it. NSMapTable was introduced in iOS 6.
Rather than store a pointer to the view and risk the garbage issue, just give the UIView a tag and store the tag's value in the dictionary. Much safer.
I'm using a simple solution under ARC provided by Objective-C++.
MyClass.mm:
#import <map>
#implementation MyClass
{
std::map<UIView* __weak, UIColor* __strong> viewMap;
}
- (void) someMethod
{
viewMap[self.someView] = [UIColor redColor];
}
In this example I am getting stronger type checking by making all the values have to be a UIColor* which is all I needed this for. But you could also use id as the value type if you want to allow any object as the value, ex: std::map<UIView* __weak, id __strong> viewMap; Likewise for keys: id __weak, id __strong> viewMap;
You can also vary the __strong and __weak attributes as needed. In my case, the views are already retained by the view controller that I use this in, so I saw no need to take a strong pointer to them.
a simple solution when you just want UIView as key occasionally,I use it to store UILabel and UIColor
NSArray<UIView *> *views = #[viewA,viewB,viewC,viewD];
NSArray *values = #[valueA,valueB,valueC,valueD];
for(int i = 0;i < 4;i++) {
UIView *key = views[i];
id value = values[i]
//do something
}
id value = values[[views indexOfObject:key]]

Changing pointer of self

I have an object that I alloc/init like normal just to get a instance. Later in my application I want to load state from disk for that object. I figure I could unarchive my class (which conforms to NSCoding) and just swap where my instance points to. To this end I use this code...
NSString* pathForDataFile = [self pathForDataFile];
if([[NSFileManager defaultManager] fileExistsAtPath:pathForDataFile] == YES)
{
NSLog(#"Save file exists");
NSData *data = [[NSMutableData alloc] initWithContentsOfFile:pathForDataFile];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
[data release];
Person *tempPerson = [unarchiver decodeObjectForKey:#"Person"];
[unarchiver finishDecoding];
[unarchiver release];
if (tempPerson)
{
[self release];
self = [tempPerson retain];
}
}
Now when I sprinkled some NSLogs throughout my application I noticed
self.person: <Person: 0x3d01a10> (After I create the object with alloc/init)
self: <Person: 0x3d01a10> (At the start of this method)
tempPerson: <Person: 0x3b1b880> (When I create the tempPerson)
self: <Person: 0x3b1b880> (after i point self to the location of the tempPerson)
self.person: <Person: 0x3d01a10> (After the method back in the main program)
What am I missing?
Don't do this. Besides that it breaks identity rules, you can't change the pointer values other parts of a program hold.
A better approach would be to use the PIMPL idiom: your class holds a pointer to an implementation object and you only swap that.
E.g. something along the lines of this:
#class FooImpl;
#interface Foo {
FooImpl* impl;
}
// ...
- (void)load;
#end
#implementation Foo
- (void)load {
FooImpl* tmp = loadFromDisk();
if (tmp) {
FooImpl* old = impl;
impl = tmp;
[old release];
}
}
#end
self is a function argument to instance methods. Assigning to self is perfectly reasonable, just like assigning values to other function arguments is perfectly reasonable. Since the scope of self is the current function, your code leaks one object and releases another in a way that will most likely cause a crash.
The only time it is meaningful to assign to self is in an init method. Although it is almost never used, init methods are allowed to release self and allocate a new object to return or just return nil. The only reason this works is because the return value is self and callers of init expect to use the return value.
As gf pointed out, the correct approach is a load function that assigns new values to the members of your instance, not that tries to replace the instance.