new to objective c here... I am trying to initialize 2 strings and set them later, but I got this error for r1 and r2:
the variable is not assignable (missing _block type specifier)
my code looks like:
NSMutableString *r1 = [NSMutableString stringWithFormat:#"None"];
NSMutableString *r2 = [NSMutableString stringWithFormat:#"None"];
NSURLRequest *request = [client URLRequestWithMethod:#"GET" URL:statusesShowEndpoint parameters:params error:&clientError];
if (request) {
[client sendTwitterRequest:request completion:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (data) {
// handle the response data e.g.
NSError *jsonError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
NSLog(#"ok!");
for(NSString *key in [json allKeys]) {
NSLog(#"%#",key);
}
r1 = json[#"statuses"][0][#"text"];
r2 = json[#"statuses"][1][#"text"];
I am confused about if this is the correct way to set strings....
like all i want to do is to initialize a string and set it later.
Your assignments to your two string variables are inside a block you pass to sendTwitterRequest:completion:. By default a block captures the value of any local variables it uses declared outside the block, in your case r1 and r2, it does not capture the variables themselves, which means it cannot alter the values stored in those variables. Note that if the value captured is a reference to a mutable object, in your case the values are references to mutable strings, then you can mutate the object - doing that does not alter the reference itself.
You can fix your code in two ways:
1) I think the values you are trying to assign are references to immutable strings, NSString *, so I suspect you have only declared the two variables as NSMutableString * in an attempt to fix your problem.
To allow assignment to captured local variables you must annotate the local variable declaration with __block, this changes the behaviour capture so that the variable, rather than its value, is captured. To go this route you just need to change your declarations to:
__block NSString *r1;
__block NSString *r2;
There is no need to give the variables an initial value, they will automatically have the value nil. Your block can now assign directly to r1 and r2, however see the BIG BUT below.
2) If you do require mutable strings, NSMutableString *, then your two declarations are fine and the issue is that you use assignment to the variables (r1 & r2) instead of mutating the strings they reference. You can change the value of a mutable string using setString: (Apple documentation. To do this replace your two assignments with:
[r1 setString:json[#"statuses"][0][#"text"]];
[r2 setString:json[#"statuses"][1][#"text"]];
These two will mutate your strings referenced by variables and those changes will be visible outside your block via the references stored in r1 and r2. However as with the first method above see the following BIG BUT...
BIG BUT
The code fragment you supply suggests you might also be making a common error: assuming values assigned within a block used as an asynchronous handler will be visible after the call taking the block, in your case sendTwitterRequest:completion:. This will normally not be true, the call to sendTwitterRequest:completion: returns before the completion block has run - which is the whole point of asynchronous methods such as sendTwitterRequest:completion:; they schedule the work (accessingTwitter in your case) to run concurrently in the background and then return, later when the asynchronous work is completed the completion block is called.
If you have made this error you need to read up on asynchronous design, you can use Apple's documentation if you wish, you can also search on SO - there are plenty of Q & A on the topic. After that is done you can redesign your code, if you have problems at that point ask a new question.
HTH
Related
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;
I’ve written a piece of test code "AlertTest" to make sure I’m implementing NSAlert object the proper way. It consists of a button which triggers [doSomething: cstr] printing a C-string, and a method posting an alert, which passes the same string to the same method, but from within the completion handler.
This is the debugger console printout:
-from [buttonPressed]
2015-01-09 18:28:09.832 AlertTest[1260:40881] Application must quit
Application must quit
-from [mPostError:cstr]
2015-01-09 18:28:12.276 AlertTest[1260:40881] #Èø_ˇ
#\351\277_\377
I’m a bit confused on what I have to do to properly pass a string from within the completion handler. No compiler errors reported. I must be missing something very obvious. Thanks in advance. Here’s the code:
//___________________________________________________________
- (IBAction)buttonPressed:(NSButton *)sender {
char cstr[256];
strcpy(cstr, "Application must quit");
[self mDoSomething:cstr];
[self mPostError:cstr];
}
//___________________________________________________________
-(void)mDoSomething: (char*)cstr
{
NSLog(#"%s",cstr);
printf("%s\n", cstr);
}
//___________________________________________________________
-(void) mPostError: (char *)cstr
{
char cMessage[64] = "Critical Error";
NSString *message = [NSString stringWithCString:cMessage encoding:NSASCIIStringEncoding];
NSString *infoText = [NSString stringWithCString:cstr encoding:NSASCIIStringEncoding];
NSAlert* mAlert = [[NSAlert alloc] init];
[mAlert setMessageText:message];
[mAlert setInformativeText:infoText];
[mAlert setAlertStyle: NSCriticalAlertStyle];
[mAlert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
[self mDoSomething: cstr]; //this is the suspect line!
}];
}
Your problem is nothing to do with C-strings but with variable lifetime.
Your execution flow goes as follows:
buttonPressed: is called. This creates a local variable cstr and calls mPostError:
mPostError: is called. This calls beginSheetModalForWindow:completionHandler: passing it a block. That block references cstr so a copy is made of the value in cstr, and that value is the address of buttonPressed:'s local variable of the same name.
mPostError: returns which gets us back to...
buttonPressed: which in turn returns destroying its local variable cstr. The block passed to beginSheetModalForWindow:completionHandler: now has the address pointing into released memory.
The user dismisses the alert and the block is called. That block calls mDoSomething: passing it the, now invalid, address.
mDoSomething: calls printf which tries to interpret the memory at the passed address as a C-string, but by now that memory has been re-used for something else and it produces the nonsense you see.
Passing a C-string is not a problem, but whatever value (a char * value in this case) is passed to a block must be valid at the time the block is executed.
You can "fix" the issue in your example by making cstr a global variable:
char cstr[256]; // global
- (IBAction)buttonPressed:(NSButton *)sender {
strcpy(cstr, "Application must quit");
Now cstr always exists and the address the block copies is therefore always valid.
Of course you've now also permanently used up 256 bytes of memory and if this string is only required for a short period that is a (small) waste.
In your real code this may not be an issue - the C strings may be managed already so they live as long as required. If not and using C strings is a requirement then you may need to look at dynamically allocating them (see malloc and friends) and releasing them when no longer needed (see free).
HTH
I understand blocks are objective c objects and can be put in NSDictionary directly without Block_copy when using ARC. But I got EXC_BAD_ACCESS error with this code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self method1:^(BOOL result){
NSLog(#"method1WithBlock finished %d", result);
}];
}
- (void) method1:(void (^)(BOOL))finish{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:^(NSData *rcb){
finish(YES);
}, #"success",
^(NSError *error){
finish(NO);
}, #"failure", nil];
[self method2:dict];
}
- (void) method2:(NSDictionary *)dict{
void (^success)(NSData *rcb) = [dict objectForKey:#"success"];
success(nil);
}
If I change method1: to this, no error raised.
- (void) method1:(void (^)(BOOL))finish{
void (^success)(NSData *) = ^(NSData *rcb){
finish(YES);
};
void (^failure)(NSError *error) = ^(NSError *error){
finish(NO);
};
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:success, #"success",
failure, #"failure", nil];
[self method2:dict];
}
Can anybody explain why I have to use automatic variables to store the blocks before putting them to dictionary ?
I am using iOS SDK 6.1.
According to the "Transitioning to ARC Release Notes", you have to copy a block stored
in a dictionary (emphasis mine):
Blocks “just work” when you pass blocks up the stack in ARC mode, such
as in a return. You don’t have to call Block Copy any more.
You still need to use [^{} copy] when passing “down” the stack into
arrayWithObjects: and other methods that do a retain.
The second method works "by chance" because success and failure are a
__NSGlobalBlock__ and not a "stack based block" that needs to be copied to the heap.
I understand blocks are objective c objects and can be put in NSDictionary directly without Block_copy when using ARC.
No, they're not common objects. When you create a block it is on the stack, and it doesn't matter of what is it's retain count, when you exit form the function it will be popped from the stack. Copy it to make it stay alive.
You must copy blocks before passing them to a method when 1) the block will be stored for longer than the duration of the call, and 2) the parameter you are passing it to is a normal object pointer type (i.e. id or NSObject *) instead of a block type.
This is the case for your call. dictionaryWithObjectsAndKeys: stores the argument in the resulting dictionary, and it simply expects normal object pointer arguments (of type id), and does not know whether you are passing blocks or not.
The reason for the second condition I said is because if the method parameter already takes a block type (e.g. for any completion handler parameters), then that method is already aware of the special memory management requirements of blocks, and therefore will take responsibility for copying the block if it needs to store it. In that case the caller doesn't need to worry about it. However, in your case, you are passing it to a general method that doesn't know it's getting a block, and thus doesn't know to copy it. So the caller must do it.
Can anybody explain why I have to use automatic variables to store the
blocks before putting them to dictionary ?
About this, your second example happens to work because recent versions of the ARC compiler is super conservative about blocks and inserts copies whenever you assign it to a block type variable. However, this is not guaranteed by the ARC specification, and is not guaranteed to work in the future, or in another compiler. You should not rely on this behavior.
In the debugger (gdb and llvm),
I usually do:
po self
po myIvar
p (CGPoint)whatEver
and works fine except when I am inside of a block. How can I access them in the debugger? I don't like very much writing NSLogs everywhere ...
I suppose inside blocks In the debugger I need to access ivars in a different way but I don't know how :(
Blocks are their own environment when they're executed. The neat thing about them is that they'll capture any variables from the surrounding scope that you mention in their bodies. The flip side of that is that there's no access to variables that aren't captured.
Take a look at this snippet:
NSArray * a = [NSArray array];
NSDictionary * d = [NSDictionary dictionary];
NSString * s = #"This is my string. There are many others like it.";
void (^myB)(NSInteger) = ^(NSInteger i){
NSString * lS = [s lowercaseString];
lS = [lS stringByReplacingOccurrencesOfString:#"many" withString:[NSString stringWithFormat:#"%ld", i]];
/* Breakpoint here */ NSLog(#"%#", lS);
};
myB(7);
The Block captures s and uses it. The NSInteger parameter, i, is also used and accessible inside the Block. The breakpoint gets hit when the Block is executed, though, which means that the creating scope, with the array a and dictionary d, no longer exists. You can see this if you look at the local variable display in Xcode:
Aside from globals, that's all you or the debugger have access to when the Block is executing. If you really need to know the values of other variables during that time, I think you'll have to mention them inside the Block. This will capture them, which will mean (for objects) they'll be retained and then released when the Block is deallocated.
- (void)createAString:(NSString **)str
{
*str = [NSString stringWithString:#"Hi all!"];
[*str autorelease]; // ???? is this right ?
}
How should I use release or autorelease ? I don't want to release outside of the function of course :)
...
NSString *createStr;
[self createAString:&createStr];
NSLog(#"%#", createStr);
You're correct that you'd generally want to return autoreleased (or the like) objects from out params when you use this form. Your assignment statement in the function that sets *str to a string:
*str = [NSString stringWithString:#"foo"];
is already doing the right thing, because that method returns an instance of NSString that the caller doesn't own. Just like you could return this string object from your function without any further memory management, you can set it as the outparam as you've done. Your second snippet showing the call site is fine.
This said, I'm worried about a few things in your code that you should be sure you understand:
The value of str inside the method is still a **, and sending that a message (as you've done for the speculative autorelease) is nonsense. Be sure you fully understand doubly indirected pointers before using them too liberally. :) If you need to send str a message after creating it, send it to *str, which is what contains the NSString *.
Setting an outparam like this when the function returns void is not idiomatic Cocoa. You would normally just return the NSString * directly. Outparams are rare in Cocoa. (Usually just NSErrors get this treatment from framework calls. Otherwise they conventionally use name like getString to differentiate them from normal get accessors which don't use the word "get".)
I hope -stringWithString was just an example. That method is almost never used in practice, since it's equivalent (in this case) to just using a #"string literal" (although that would muddy your example).
Instead of using a double pointer, would it not be more elegant to use an NSMutableString instead?
- (void)createAString:(NSMutableString *)str
{
[str setString:#"Hi all!"];
}
....
NSMutableString *createStr = [[NSMutableString alloc] init];
[self createAString: createStr];
NSLog(#"%#", createStr);
[createStr release];
Or, even better, just have the createAString method return an NSString.
- (NSString *)createAString
{
return #"Hi all!"; // this is autoreleased automatically
}
I wouldn't want to presume that your needs are this simple, though. =)