__weak NSString *text = self.textField.text has inconsistent behaviour - objective-c

I think I may made a silly mistake, but I can't figure out why:
I have a method and Block to handle some network API like:
-(IBAction)confirm:(id)sender {
__weak typeof(self) weakSelf = self;
__weak NSString *anotherNumber = self.nextPhoneTextField.text;
[SharedInstance bindNewPhoneNumber:self.nextPhoneTextField.text pinCode:self.verifyCodeTextField.text sucess:^(id result) {
// update phone number
SharedInstance.phoneNumber = anotherNumber;
}];
}
before the block, I can see newNumber has value correctly,
However when the block is invoked, the newNumber is nil, instead of the text. But I was able to print weakSelf.nextPhoneTextField.text, which is not changed.
Any explainations is appreciated!
UPDATE:
After creating a sample project, I found it's not reproducible. the weak string pointer has valid text. Then I start debugging it, and I found that,
In order to avoid the new keyword, I changed the pointer name to anotherNumber
In my real project, when calling __weak NSString *anotherNumber = self.nextPhoneTextField.text; the anotherNumber has a new address, rather than the self.nextPhoneTextField.text; address:
(lldb) p anotherNumber
(__NSCFString *) $2 = 0x00007f88b3ff2960 #"12345678901"
(lldb) p self.nextPhoneTextField.text
(__NSCFString *) $3 = 0x00007f88b15f8690 #"12345678901"
However in the sample project, I have the similar function,
- (void)clickBlock:(void (^)(NSString * string))block {
if (block) {
block(#"haha");
}
}
- (IBAction)clicked:(id)sender {
__weak typeof(self) weakSelf = self;
__weak NSString *text = self.textField.text;
[self clickBlock:^(NSString *string) {
NSLog(text);
NSLog(string);
}];
}
it is the same address:
(lldb) p text
(NSTaggedPointerString *) $2 = 0xa000000747365744 #"test"
(lldb) p self.textField.text
(NSTaggedPointerString *) $3 = 0xa000000747365744 #"test"
and the class type changed also... Looking for answers!!!
Another update:
I delete the block, simply create two weak pointers with some strings like "hello" and "12345678901", the formmer one has the same address and marked as NSTaggedPointerString, however the latter one has different address and marked as NSCFString
It seems to me that once the text reach a specific length, it will have the NSCFString and different address, and after some tests, the bounty is 9. once more than 9 words, it will be NSCFString, tested on iOS 9.1 iPhone 6S simulator.
on iOS 8.4 simulator, all the strings with different length result in different mem adress and NSCFString
sample project:https://github.com/liuxuan30/WeakStringPointer

__weak NSString *anotherNumber = self.nextPhoneTextField.text;
with this line , NSString is copied by value and NOT by reference, so after assigning a NSString to another NSString , it creates new copy of it and creates a reference to newly created copy and not the original one, and because the reference is weak , the object will be nil after the current function goes out of context ,
If you try to change the textfield's text it will only change the textFields text and not anotherNumber object.
NSString *test = self.nextPhoneTextField.text;
self.nextPhoneTextField.text = #"Something else";
NSSLog(#"Test object contains %# , the textField contains %# ",test,self.nextPhoneTextField.text);
Your code does following:
Creates a new Copy NSString from self.nextPhoneTextField.text
Asigns a new Copy NSString to anotherNumber
Since anotherNumber is __weak , it will not retain the object (NSString), it will hold __weak reference to this object and after this function goes out of context and it becomes nil.
To confirm this behavior, you can directly log anotherNumber after setting its value and in different context
__weak NSString *anotherNumber = self.nextPhoneTextField.text;
NSString *strongAnotherNumber = self.nextPhoneTextField.text;
NSLog(#"Weak number - %# , strong - %#",anotherNumber,strongAnotherNumber);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Block Weak number - %# , strong - %#",anotherNumber,strongAnotherNumber);
});
please also have a look at Why do weak NSString properties not get released in iOS?

Related

Singleton dictionary is empty, and I don't see why

I have an iOS app that matches incoming text fields to standard fields used to import records. My problem is that a NSMutableDictionary that uses those fields is empty! Here is the code that saves the mapping:
-(void)mapUserFields: (id) sender { // move contents of each textField when user has finished entering it
SingletonDictionary *sd = [SingletonDictionary sharedDictionary];
UITextField *tf = (UITextField *)sender; // textfield contains the pointer to user's half of the equation
int tagValue = (int)tf.tag; // get the tag value
[sd.dictionaryOfUserIndexes setObject:tf.text forKey:[NSString stringWithFormat:#"%d", tagValue]]; // value found in textField id'd by tag
NSLog(#"\nfield.text: %# tagValue: %d nsd.count: %d\n",tf.text, tagValue, sd.dictionaryOfUserIndexes.count);
}
This is the result of the NSLog:
field.text: 1 tagValue: 38 nsd.count: 0
This is the definition of the singleton in the .h file:
#property (nonatomic, retain) NSMutableDictionary *dictionaryOfUserIndexes;
This is the code to initialize the singleton in the .m file:
//-- SingletonDictionaryOfUserIDs --
+ (id) sharedDictionary {
static dispatch_once_t dispatchOncePredicate = 0;
__strong static id _sharedObject = nil;
dispatch_once(&dispatchOncePredicate, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-(id) init {
self = [super init];
if (self) {
dictionaryOfUserIndexes = [NSMutableDictionary new];
}
return self;
}
#end
I believe my problem is because the sd.dictionaryOfUserIndexes is not initialized, but I am not sure if that's true, and if so, how to initialize it (I tried several different variants, all of which created build errors). I looked on SO and Google, but found nothing that addresses this particular issue. Help would be greatly appreciated!
There are a few things we could improve in this code, but the only thing wrong with it is the reference to dictionaryOfUserIndexes in the init method. The code as posted wouldn't compile, unless: (a) you have a line like:
#synthesize dictionaryOfUserIndexes = dictionaryOfUserIndexes;
so that the backing variable is named without the default _ prefix, or (b) you refer to the ivar with the default prefix, as in:
_dictionaryOfUserIndexes = [NSMutableDictionary new];
The other way -- preferable in most every context except within an init method -- is to use the synthesized setter, like:
self.dictionaryOfUserIndexes = [NSMutableDictionary new];
But with that change alone (so it will compile) your code runs fine, adds a value to the dictionary and logs an incremented count.

Using typecast for (void *) in Objective C

i am using the addToolTipRect: method to set a tooltip rect
- (NSToolTipTag)addToolTipRect:(NSRect)aRect owner:(id)anObject userData:(void *)userData
and method stringForToolTip: to obtain string value for tooltip.
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
However the above functions work fine if i send something like
[self addToolTipRect:someRect owner:self userData:#"Tool tip string"];
But doesn't work when i send the following string. Error: BAD_ACCESS
const NSString * tooltipStr = #"Tool tip string";
[self addToolTipRect:someRect owner:self userData:tooltipStr];
In both the cases, the stringForToolTip looks like:
- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
{
id obj = (id)data;
NSString * str=nil;
if ([obj isKindOfClass:[SomeClass class]]) //This is my system defined class and works fine
{
SomeClass * someClassObj = (SomeClass *) data;
str = someClassObj.title;
}
else if([obj isKindOfClass:[NSString class]])
str = (NSString*)obj;
return str;
}
NOTE: In the stringForToolTip: method I also want to check for some other class example [obj isKindOF:[SomeClass class]] and i don't want to assert that value. The problem here is just in getting the string value by proper cast but I can't figure out how! Please tell me where I am going wrong?
edit:
What should be the right way to get the String value for tooltip in that case? should the point or tag be considered?
(void *) is not an object pointer.
That #"Tool tip string" worked was by coincidence based on the fact that is is a compile-time constant with a (essentially) permanent allocation and permanent address.
But in the code:
const NSString * tooltipStr = #"Tool tip string";
[self addToolTipRect:someRect owner:self userData:tooltipStr];
tooltipStr is an object that is kept in memory by a strong reference (retain count > 0). Since userData: does not handle objects it does not make a strong reference (does not increase the retain count) so it is released, will disappear soon becoming invalid.
Notes from the documentation:
The tooltip string is obtained from the owner. The owner must respond to one of two messages, view:stringForToolTip:point:userData: or description, use the latter. Note that NSString responds to description so you can pass an NSString for the value of owner. So, what you want is: [self addToolTipRect:someRect owner:tooltipStr userData:NULL];. There is still an issue that something must hole a strong reference to the NSString instance.
You can: [self addToolTipRect:someRect owner:#"Tool tip string" userData:NULL];
Probably the best way to go is to pass self as owner and NULL as data and implement the delegate method: view:stringForToolTip:point:userData: in the class.

UIImage converts to NSData mysteriously

Please take a look at these two simple pieces of code. This
- (void)testMethod
{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar replaceObjectAtIndex:i withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
if ([[self.avatar objectAtIndex:0] isKindOfClass:[UIImage class]])
NSLog(#"UIImage");//at this moment it's UIImage
}
and this:
[currentProfile testMethod];
if ([[currentProfile.avatar objectAtIndex:0] isKindOfClass:[NSData class]])
NSLog(#"NSData");//Moment later it is NSData
In the first one i fetch a custom object from the NSUserDefaults and work with a NSMutableArray variable named "avatar". I convert its each object from NSData to UIImage. Then i check what i've got by using NSLog . It's UIImage. At the second piece of code you can see how a moment later what was UIImage turns back to NSData by its own will. Seems like i described my issue clearly. Do you understand what's going on? I don't. Thanks a lot in advance for your attention
Why are you changing the self object in your -testMethod method? This is highly illegal.
What you're actually doing is setting a local variable self, which is passed as a parameter to your method, to a new value. This means you're not editing the receiver of the method, you're just editing your parameter.
When your method is called at runtime the C function objc_msgSend() is called:
// Declaration of objc_msgSend
id objc_msgSend(id receiver, SEL selector, ...);
Now when you call your method ...
[myInst testMethod];
... this is what actually gets called at runtime:
objc_msgSend(myInst, #selector(testMethod));
Do you already see what's happening? In your method implementation the self variable is set to the first argument of objc_msgSend. When you're reassigning self, your not editing what the variable myInst contains and thus you're not editing your the original instance you passed. You're just setting myInst, aka self, a local variable, to your knew pointer. The caller of the function will not notice the change.
Compare you're code to the following C code:
void myFunction(int a) {
a = 3;
}
int b = 2;
myFunction(b);
printf("%d\n", b);
// The variable b still has the original value assigned to it
The above code does the same you do:
// Variation on objc_msgSend
void myMethodWrittenInC(id myInst) {
// Local variable changes, but will not change in the calling code
myInst = nil;
}
MyClass *myObj;
myObj = [[MyClass alloc] init];
myMethodWrittinInC(myObj);
// At this point myObj is not nil
And finally this is what you do:
- (void)testMethod
{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
// You assign the local variable self (passed as an invisible argument
// to your method) to your new instance, but you do not edit the original
// instance self pointed to. The variable currentProfile does not change.
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar
replaceObjectAtIndex:i
withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
if ([[self.avatar objectAtIndex:0] isKindOfClass:[UIImage class]])
NSLog(#"UIImage");//at this moment it's UIImage
}
// (1) Here currentProfile points to an instance of your class
[currentProfile testMethod];
// (2) it calls the method, but the local variable does not change
// and still points to the same instance.
if ([[currentProfile.avatar objectAtIndex:0] isKindOfClass:[NSData class]])
NSLog(#"NSData");//Moment later it is NSData

autoreleasing NSString in class method causing app crash in iOS

The error I receive is as follows:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil); //breakpoint that says Thread 1: Program Received Signal: "EXC_BAD_ACCESS".
[pool release];
return retVal;
}
My two questions can be found at the bottom of this post :)
I am currently working on an assignment for an iOS programming class and have hit a road bump.
I have found a fix, shown below, but it doesn't make sense to me. Check it out:
#implementation MyClass
// This class method takes an (NSMutableArray *) and returns an NSString with its contents printed out.
+ (NSString *)myString:(NSMutableArray)anArray
{
// NSString *myString = [[NSString alloc] init]; OLD CODE THAT CAUSES MEMORY LEAK
NSString *myString = [[[NSString alloc] init] autorelease]; //NEW CODE THAT RELEASES FIRST ALLOCATION OF myString WHEN THE FIRST stringByAppendingFormat: IS CALLED
NSString *vp = VARIABLE_PREFIX; //#defined above to be #"%
for (id object in anArray) {
if ([object isKindOfClass:[NSString class]]) {
if ([object hasPrefix:vp]) {
myString = [myString stringByAppendingFormat:#"%#",[object substringFromIndex:1]];
}else{
myString = [myString stringByAppendingFormat:#"%#",object];
}
}else if ([object isKindOfClass:[NSNumber class]]) {
myString = [myString stringByAppendingFormat:#"%#",object];
}
}
return myString; //shouldn't I autorelease myString right before this line? NO NOT ANY MORE. THIS myString IS NOT THE ORIGINAL THAT I alloc-init, BUT AN AUTORELEASED OBJECT RETURNED BY THE stringByAppendingFormat: message.
}
When I try to send the message [myString autorelease];, the program crashes with the above error. It is working fine now as shown above, but I do not understand why.
Every time I send a message containing the "magic words" alloc, init, copy I have to call release, it don't I? Or are the rules different in a Class method (can the Class itself own a file?). I do not call retain in the object that is calling this file.
Here are my two questions:
Why does this crash when I try to release theDescription using autorelease?
Does my code create a memory leak?
This is my very first question on stack overflow! Thank you for your help!
Why does this crash when I try to release theDescription using autorelease?
Assuming you mean myString, it crashes because myString is already autoreleased. You got it by calling -stringByAppendingFormat:, which returns an autoreleased string. Now, you're probably thinking: "But I created it by calling +alloc, so I should release it." That's true, but NSStrings are immutable, and when you call -stringByAppendingFormat: you get a different string back, and that string is autoreleased. Autoreleasing it a second time is an error.
Does my code create a memory leak?
Yes, but not really. The "leaked" object is the empty string that you allocate in the beginning. You never release that string, so you've got a leak. However, NSString is apparently optimized so that [[NSString alloc] init] returns a singleton, so in this particular case it doesn't make any difference that the empty string isn't released. The other strings that are assigned to myString are all autoreleased, so none of those objects are leaked.
Why does this crash when I try to release theDescription using
With the updated code, the problem is that ypu are reassigning the pointer to myString using the methid which already returns an autoreleases object: stringbyappending, therefore if you call autorelease on this object which is already going to get autoreleased i will crash.
Aditionaly the first assugnment in the alloc init gives a memory leak when ypu reassign with stringbyappendingstring, since you lose the reference to the previously created string with alloc init and therefore you will never be able to release it.
Sorry for the formatting on my iPhone atm =)

Can't have an ivar with a name of description in Objective-C description method?

I'm trying to implement the Objective-C description method for my NSObject-derived object.
However, my derived object has an ivar of name description. And for some reason this is causing a crash.
- (NSString *) description {
NSMutableString *output = [NSMutableString string];
[output appendFormat:#"MyObject.description = %#\n", self.description];
return output;
}
Why would this be an issue?
Short Answer: The crash is a result of a stack overflow because your -description method calls itself repeatedly. To do what you want to do (accessing the ivar from within the description method), you should not use the prefix self. in front of the ivar.
More Detail:
In Objective-C, self.description is shorthand for [self description]. Using the dot-syntax informs the compiler that you want to access a property named description, and not the ivar itself.
It's an issue because you're creating an infinite loop. self.description will call [self description], which is exactly the method you're within. Hence you have the method calling itself repeatedly.
- (NSString *) description {
NSMutableString *output = [NSMutableString string];
[output appendFormat:#"super's description = %#\n", [super description]];
[output appendFormat:#"MyObject.description = %#\n", description];
return output;
}
You can access the instance variable directly, rather than using self.description. Also, I added an extra line to show how you can call super's description method (which doesn't create an infinite loop).