Make variable accessible outside conditional? - objective-c

About a year ago, when I first got into programming, I learned the hard way that variables don't escape the scope of the condition they're created in. For example:
-(void)someMethod {
if (x == y) {
NSString *string = [[NSString alloc] initWithString:#"Foo"];
NSLog(string); // outputs "Foo" to console successfully
}
...
NSLog(string); // Doesn't work, we're out of the scope of the "if" statement.
}
My question is, is there any way to dynamically create variables within a conditional statement and be able to access them at other times, kind of like if I declared it in my #interface?
EDIT I guess I didn't explain it well, I meant if I wanted to use it later in other methods.

You just need to declare (and optionally initialize) the variable outside of the if. So something like:
-(void)someMethod {
NSString *string = nil;
if (x == y) {
string = [[NSString alloc] initWithString:#"Foo"];
NSLog(string); // outputs "Foo" to console successfully
}
...
NSLog(string);
}
EDIT
To respond to your clarification, 'string' here is a local variable, local to this method. You have other options like instance variables for example. Instance methods like this one (ones that start with '-') will have access to this instance's (this object's) instance variables.
So you could do:
#interface MyClass : NSObject {
NSString *string; // <<-- This is an instance variable (aka "ivar")
}
- (void)someMethod;
- (void)someOtherMethod;
#end
#implementation MyClass
- (void)someMethod {
string = #"Foo";
}
- (void)someOtherMethod {
NSLog (string);
// will print "Foo" provided someMethod was called before this method
}
#end
Obviously there's more to this than you can get in a short answer. You can have global variables. If you're new to the language, you should read properties as a very useful tool for encapsulating instance variables (useful when you want to get the memory mgmt right). But hopefully that gets you pointed in the right direction at least.

-(void)someMethod {
NSString *string = nil;
if (x == y) {
string = [[NSString alloc] initWithString:#"Foo"];
NSLog(string); // outputs "Foo" to console successfully
}
...
NSLog(string); // Doesn't work, we're out of the scope of the "if" statement.
}

Consider what you're asking. You start with:
-(void)someMethod
{ if (x == y)
{ NSString *string = [[NSString alloc] initWithString:#"Foo"];
NSLog(string); // outputs "Foo" to console successfully
}
...
NSLog(string); // Doesn't work, we're out of the scope of the "if" statement.
}
What would you like the second NSLog to do? You seem to be requesting that it either work, if the body of the if statement has been executed, or what? Produce a dynamic "undeclared variable" error?
Now you seem to be wanting:
-(void)someMethod
{ if (x == y)
{ NSString *string = [[NSString alloc] initWithString:#"Foo"];
NSLog(string); // outputs "Foo" to console successfully
}
...
}
-(void)someOtherMethod
{
NSLog(string); // Doesn't work, we're out of the scope of the "if" statement.
}
What do you wish to happen in someOtherMethod if the body of the if statement in someMethod has not been executed?
As #Daniel's solution points out, you can reference a variable provided it is in scope. In the single method case you move the point of declaration out of the if and into the enclosing method body. In the two method case you move it to the class - as an instance variable.
Now scope isn't the same as lifetime - a variable can exist (be alive), but no be accessible (it is out of scope); the common example is when one method calls another, the calling method's variables stay alive but are inaccessible to the called method.
In a similar way that a variable is in scope does not mean that it has to contain a valid value. That is why #Daniel puts nil in the variable - you know if a value has been created inside the body of the if by the value of string not being nil.
So maybe this is the "dynamic" behaviour you seem to be seeking - you must declare some way to reference the value (the variable), but you indicate whether it has been created by storing some sentinel (nil in this case) in the variable.

Related

Modify parameters in Objective-C blocks

I would like to modify various variables which exist outside an Objective-C block within it's body.
I know I can directly access and modify a variable using the __block attribute while declaring the variable. So this works:
__block NSMutableString *alertMessage;
void(^appendMessage)(NSMutableString*, NSString*)= ^(NSString *append){
if (!alertMessage)
{
alertMessage = [NSMutableString new];
}
if ([append length] > 0)
{
[alertMessage appendString:#"\n"];
}
[alertMessage appendString:append];
};
appendMessage(#"Alert part 1"); //This works fine
However I want to create a block which can perform an operation on a passed variable, enabling me to use the operation on multiple variables outside the block without directly accessing the same. Something like the following:
__block NSMutableString *alertMessage;
__block NSMutableString *otherString;
void(^appendMessage)(NSMutableString*, NSString*)= ^(NSMutableString *string, NSString *append){
if (!string)
{
string = [NSMutableString new];
}
if ([append length] > 0)
{
[string appendString:#"\n"];
}
[string appendString:append];
};
//The following do not work as intended
appendMessage(alertMessage, #"Alert Part 1");
appendMessage(otherString, #"Bleh bleh");
I want to be able to use the above block to modify the variables declared before it.
How can I achieve such an operation? Is this even possible?
Your question shows some confusion over values and variables, maybe the following will help.
Modify parameters in Objective-C blocks
In (Objective-)C all parameters to methods/functions/blocks are passed by value, e.g. when in the call f(x) the value of the variable x is passed to f, not the variable itself. This is known as call-by-value.
There are languages which do allow variables to be passed, known as call-by-reference. When used the argument must be a variable and the parameter name within the function is effectively an alias to the supplied variable. This is not supported directly in (Objective-)C.
However you can emulate it in (Objective-)C. It is not commonly used, with one notable exception: many methods use it to return an NSError * value.
You later comment:
What I want to achieve includes object creation, which is essentially what the question now boils down to. "Can I create an object declared outside within a block?". The answer which I have gathered with the help of all the activity here is NO.
You can, it is just a question of whether you should (i.e. is the design right?) and the best way to do it.
The straightforward way to solve your particular issue is to write a function:
NSMutableString *alertMessage;
NSMutableString *otherString;
NSMutableString *(^appendMessage)(NSMutableString *, NSString *) =
^(NSMutableString *string, NSString *append)
{
if (!string)
string = [NSMutableString new];
if (append.length > 0)
{
[string appendString:#"\n"];
[string appendString:append];
}
return string;
};
alertMessage = appendMessage(alertMessage, #"Alert Part 1");
otherString = appendMessage(otherString, #"Bleh bleh");
If you really (really, really) want to you can instead "pass the variable" by passing its address (using the & operator) and indirection (using the * operator) inside the block to get/set the value:
void (^appendMessage)(NSMutableString **, NSString *) =
^(NSMutableString **stringPtr, NSString *append)
{
if (!stringPtr) return; // no "variable" passed
NSMutableString *string = *stringPtr; // use indirection to get the value in the passed variable
if (!string)
string = [NSMutableString new];
if (append.length > 0)
{
[string appendString:#"\n"];
[string appendString:append];
}
*stringPtr = string; // use indirection to set the passed variable
};
appendMessage(&alertMessage, #"Alert Part 1"); // pass "variable" by passing its address
appendMessage(&otherString, #"Bleh bleh");
While the above is valid code it is generally not recommended coding practice in Objective-C for simple cases such as yours.
Once you take the address of a variable you need to be concerned over the lifetime of that variable - if you attempt to use the address to access the variable after the variable has been destroyed your program will fail (the dangling pointer problem)
What about __block?
Neither of the above examples use __block anywhere.
When a block references a variable by default it captures the variables value at the time the block is created. The __block attribute changes this to capturing the variable (so its value can be changed by the block) and alters the lifetime of the capture variable if required (so the variable lives at least as long as the capturing block, avoiding the dangling pointer problem).
The __block attribute is not applicable in your situation as you wish to capture different variables based on the call.
HTH
The code, as written, seems to confuse operation on object with object creation.
For clarity's sake, you should either pass in a mutable object to be manipulated or you should define a single __block variable whose value will be set by the block (and you do the logic after to figure out where that value should be stuffed).
Passing in something by reference is inherently dangerous to the point of being an anti-pattern (what happens as soon as you try to refactor the code to be asynchronous? At least in the __block case, the code after the block will see nil).
i.e.:
__block NSMutableString *foo = [sourceString mutableCopy];
doIt(#"new stuff"); // appends to `foo`
whereItShouldReallyGo = foo;

How to pass ivar into a function and set it without losing the reference to the original object

I am passing an ivar (NSMutableArray) into some method. I was expecting that if I modify the object inside the function, it would be reflected outside the function, but in this case I need to set the object; something like the following:
- (void) someMethod:(SMResponseObject *)response onData:(NSMutableArray *)imAnIvar {
imAnIvar = [response objects];
//Some other stuff
}
But I noticed that the memory reference of imAnIvar inside the function changes when I set it, and given that, the actual ivar doesn't change. I understand that the problem is that I'm changing the reference of the object inside the method, so it stops pointing to the ivar and then it points to some other random memory direction.
I thought about one solution to this problem, and it can be to ensure that the ivar is not nil before calling the function and do something like this:
- (void) someMethod:(SMResponseObject *)response onData:(NSMutableArray *)imAnIvar {
NSMutableArray *data = [response objects];
[arrayForTableView removeAllObjects];
for(id element in data){
[imAnIvar addObject:element];
}
//Some other stuff
}
So I use the original object instead of setting it directly. The problem is that in order for this to work I need to ensure that the ivar is not nil, which I think is not clean, because I'll need to do something like this on every call to the method:
if(!_ivar){
//alloc it
}
So my question is: Is there a way to force the local scope variable to point to the original variable even if I'm setting it? if not, is there any cleaner way to make this work?
Do you mean this?
- (void)setFoo:(SomeClass **)objPtr
{
*objPtr = someOtherObject;
}
// call it as:
SomeClass *foo = someObject;
NSLog(#"Before: %#", foo);
[self setFoo:&foo];
NSLog(#"After: %#", foo);
Why not use a getter for the array so that you need not check for the array being nil while using it?
-(NSMutableArray *)iAmAnIvar {
if(_iAmAnIvar == nil) {
_iAmAnIvar = [NSMutableArray array];
}
return _iAmAnIvar;
}
And when you have to set a value to the array, as you mentioned in your question, you could use
[self.iAmAnIvar removeAllObjects];
[self.iAmAnIvar addObject:someObj];
I believe you can use the - (id)copy; function of NSObject
so your code might look like this:
- (void)someFunction:(NSString *)someArg
{
NSString *str = [someArg copy];
}

Creating objects in a loop, but ivar / instance variable not keeping state

I have a method that creates a dictionary from NSJSONSerialization class. I then enumerate the json, and create objects to store state for each instance.
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *moviesJson = [json objectForKey:#"movies"];
for(NSDictionary *element in moviesJson)
{
RABMovie *myMovie = [[RABMovie alloc] initWithJson:element];
// RABMovie *myMovie = [RABMovie new];
// myMovie.json = element;
[_myMovieNames addObject: myMovie];
myMovie = nil;
}
[self.movieTableView reloadData];
}
Problem: I want to create my object by passing in element in the allocator, however when I do this, my UITTableView rows all contain the same movie data. It is always the last item, leading me to believe I am working with only one memory address, and the last update affects the whole collection.
If I uncomment the code to use the json as a property instead of a alloc param, I no longer have the issue and all works well. I've also tried creating a completely new NSDictionary via a deep copy of element to no avail.
Does someone know why this is happening? BTW, I am using ARC. -Thanks for the time.
Edit: Added more code. I've included a property movieName to illustrate how I use the ivar _json.
#implementation RABMovie
NSDictionary *_json;
- (id) initWithJson: (NSDictionary*) jsonAsDictionary
{
if (self = [super init])
{
_json = jsonAsDictionary;
}
return self;
}
- (NSString*) movieName
{
return [_json objectForKey:#"title"];
}
I think you meant to declare _json as an instance variable. Instead it's a globally visible (at least within that class) variable - not 100% sure on the scoping rules, but regardless, it's not an instance variable - it's a single variable shared by all instances! Try this instead:
#implementation RABMovie {
NSDictionary *_json;
}
/* ...rest of class */
#end
Putting it inside the curly braces after the #implementation directive makes it an instance variable. Hope this helps!
EDIT: Do you have a property called json on RABMovie already? Then you can skip the instance declaration altogether and the compiler will generate the an instance variable for you. That's probably happening already actually, which is why it works when you go through the property - it's accessing the ivar rather than the "global".

Problem declaring and calling internal metthods

How do I declare and use small helper functions inside my normal methods ?
In on of my objective-c methods I need a function to find an item within a string
-(void) Onlookjson:(id) sender{
NSString * res = [[sender gstring] copy];
persInfoBirth.text = getKeyValue(res, #"Birth");
}
I came up with a normal C type declaration for helper function getKeyvalue like this
NSString * getKeyvalue(NSString * s, NSString * key){
NSString *trm = [[s substringFromIndex:2] substringToIndex:[s length]-3];
NSArray *list = [trm componentsSeparatedByString:#";"];
//....
NSString res;
res = [list objectAtIndex:1];
//...
return res;
}
Example input string in s:
s=#"{ Birth = "1910"; Death = "1936"; }";
Anyway I get an exception "unrecognized selector sent to instance" for any of the two first lines in the helper function
How do I declare helper functions that are just to be used internally and how to call them safely ?
regards
Martin
Is this the real code? Do you get zero errors and warnings from the compiler? You must not ignore compiler warnings and you should turn on the Static Analyser in addition to the standard warnings.
There are many things wrong with the above code, most of which are nothing todo with declaring and calling methods. There is no way the above code could compile so maybe it pasted incorrectly or something..
Anyway.. declaring and using methods. Why are using a c function? Unless you have a good reason why not use Objective-c ? If you do have a good reason to use a C function the your definition should be:-
NSString *getKeyvalue( NSString *s, NSString *key ){
...
}
note the arguments. As NSString instances reside in the heap (not on the stack) you always want to pass pointers to them.
You then need to put the declaration in the header file:-
NSString *getKeyvalue( NSString *s, NSString *key )
EDIT:
In Objective-c there is no distinction between normal methods and helper methods, there is only one kind, and you have aleray written one
- (void)onLookJson:(id)sender { .. }
Taking it apart..
All methods begin with + or –, indicating Class method or Instance method. As you are familiar with C++ i guess you know what this means.
(void) is the return type. ie this method doesn't return a value. If it did it might look like (float) or (NSString *) or (id).
onLookJson: is the method name and the method takes 1 argument. Notice that the ':' is actually part of the name. This method is never is any circumstance just 'onLookJson'. An argument must always follow the :, so a method that doesn't take any arguments must not have one.
Ex
- (NSString *)fullName { .. }
This is an instance method, for example of a Person Class, you would call it like:-
NSString *theName = [aPerson fullName];
So
a method name that takes no
arguments is like 'speak'
a method
name that takes 1 argument is like
'speakTo:'
a method name that takes 2
arguments is like 'speakTo: language:'
a method name that takes 3
arguments is like 'speakTo: language: volume:'
etc.
All that is left is to put in the argument types and names.
Your function definition:
NSString *getKeyvalue( NSString *s, NSString *key ){
would become..
- (NSString *)getValue:(NSString *)s key:(NSString *)key { .. }
again, you need to declare it in the header or you will get a compiler warning.
- (NSString *)getValue:(NSString *)s key:(NSString *)key;

objective c NSString comparision

I have three button named(titled) hello, nothing, heaven and one label (IBOutlet UIlabel lab). I want to display three diff messages for three diff button click. But the following code failed to accomplish this. Can anyone suggest any idea?
-(IBAction)buttonclick:(id)sender
{
NSString *title=[sender titleForState:UIControlStateNormal];
if([title isEqualToString:#"hello"])
{
NSString *str=[[NSString alloc] initWithFormat:#"abc"];
}
else if([title isEqualToString:#"nothing"]) {
NSString *str=[[NSString alloc] initWithFormat:#"def"];
}
else if([title isEqualToString:#"heaven"])
{
NSString *str=[[NSString alloc] initWithFormat:#"ijk"];
}
lab.text=str;
[str release];
}
output:
warning:unused variable str;
Don't use the title of the buttons to differentiate between your buttons. It wouldn't work if your buttons were to be localised. Either use different actions, or use the tag to differentiate them.
The warning is the clue to what you're doing wrong in this case. A local variable is only visible within the scope that it's declared, so your lab.text=str line is actually setting lab.text to a str that is defined elsewhere, either a static variable or an instance variable. Here's what you could do instead:
NSString *str;
switch ([sender tag]) {
case FirstButtonTag:
str = #"abc";
break;
case SecondButtonTag:
str = #"def";
break;
case ThirdButtonTag:
str = #"ijk";
break;
}
lab.text = str;
The problem is that in each 'then' clause of the various if statements, you're creating a new local variable named str, assigning it to a new string, and then the variable goes out of scope. The compiler warning should tick you off to this: you're writing to a variable but never reading from it.
Ordinarily, your code wouldn't compile, but you apparently have another variable named str in scope later on. Your new definitions of str are shadowing the old one: while the new name str is in scope, the name str refers to that variable, not the outer one, and the outer one is cannot be referred to.
The solution is to move the declaration of str up to the top of the function. Furthermore, it's simpler just to use [NSString stringWithFormat:#"blah"] instead of [[NSString alloc] initWithFormat:#"blah"], since the former gives you an autoreleased object. This saves you from having to manually release it later on. Note that assigning lab.text=str retains it, since the text property of the UILabel class has the retain modifier.
-(IBAction)buttonclick:(id)sender
{
NSString *title=[sender titleForState:UIControlStateNormal];
NSString *str;
if([title isEqualToString:#"hello"])
{
str=[NSString stringWithFormat:#"abc"];
}
else if([title isEqualToString:#"nothing"])
{
str=[NSString stringWithFormat:#"def"];
}
else if([title isEqualToString:#"heaven"])
{
str=[NSString stringWithFormat:#"ijk"];
}
lab.text=str;
}
Also note that with your original code, you had both a memory leak and memory corruption -- since you were allocating a string and then losing a reference to it (by the new local variable str going out of scope) without releasing it, and then you were calling release an extra time on whatever the outer str variable was. Moving the str declaration to the top of the function fixes both problems.
I'm also assuming that your format strings are more complicated than just plain strings. If you're actually assigning constant strings such as "abc", then of course it's much simpler to just do str=#"abc" instead of str=[NSString stringWithFormat:#"abc"].