Executing Blocks From NSArray? - iphone

I was just thinking, as you can treat Blocks like objects if I create two of them and then add them to an NSArray is there a way to execute them from the array?
int (^Block_001)(void) = ^{ return 101; };
int (^Block_002)(void) = ^{ return 202; };
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];
EDIT: Update for clarity Per #davedelong's excellent answer
int (^Block_001)(void) = [^{ return 101; } copy];
int (^Block_002)(void) = [^{ return 202; } copy];
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];
[Block_001 release];
[Block_002 release];

#KennyTM and #David are correct, but your code is potentially wrong. Here's why:
When creating an NSArray with objects, it will retain the objects put into it. In the case of blocks, it's using the Block_retain function. This means that the array has retained the blocks that you created, but that live on the stack (blocks are one of the very rare examples of Objective-C objects that can be created on the stack without delving into absurd tricks). That means that as soon as this method exits, your array now points to garbage, because the blocks it was pointing to no longer exist. To do this properly, you should do:
int (^Block_001)(void) = [^{ return 101; } copy];
int (^Block_002)(void) = [^{ return 202; } copy];
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];
[Block_001 release];
[Block_002 release];
By invoking copy on the block, you're explicitly moving the block off of the stack and onto the heap, where it can safely remain after the method/function exits. Then after you've added the blocks to the array, you have to balance your copy (because of the NARC rule) with a subsequent call to release. Make sense?

Sure, you just invoke it with () like any other block, but you need to typecast the value you retrieve from NSArray. Here's an example (with an added typedef, because otherwise my head hurts):
typedef int (^IntBlock)(void);
IntBlock Block_001 = ^{ return 101; };
IntBlock Block_002 = ^{ return 202; };
NSArray *array = [NSArray arrayWithObjects:Block_001, Block_002, nil];
int x = ((IntBlock)[array objectAtIndex:0]) (); // now x == 101

Of course you can.
int (^x)(void) = [array objectAtIndex:0];
printf("%d\n", x()); // prints 101.

Related

NSMutableArray release causes crash

I am using XCode for developing an iPhone app. I am new to this platform and need some help with a particular issue...
I have a method that processes some data and returns two integer values as NSNumber wrapped into a NSMutableArray.
Here is the method:
-(NSMutableArray *)processPoints:(int) x:(int) y
{
NSMutableArray *mutArray = [[NSMutableArray alloc] initWithCapacity:3];
int x9,y9;
// ...do some processing...
NSNumber* xNum = [NSNumber numberWithInt:x9];
NSNumber* yNum = [NSNumber numberWithInt:y9];
[mutArray addObject:xNum];
[mutArray addObject:yNum];
return [mutArray autorelease];
}
I call the above method from another method, where I copy the NSNumber stuff into local variables and then release the local copy of NSMutable array.
But the app crashes when releasing this NSMutable array (variable 'mutArray').
Here is the method:
-(void)doNinjaAction
{
NSMutableArray* mutArray = [self processPoints: x :y];
NSNumber* s1 = [[mutArray objectAtIndex:0] retain];
NSNumber* s2 = [[mutArray objectAtIndex:1] retain];
x = [s1 integerValue];
y = [s2 integerValue];
//...proceed with other stuff...
[mutArray autorelease]; //this is where the system crashes. same for 'release'
//instead of 'autorelease'
}
Can you please explain where I am going wrong with the process of memory release.
My understanding of the process is a bit shaky. Please help.
Because you're overreleasing the array. You alloc-init it in processPoints:, then you autorelease it - that's correct, this is how you dispose of its ownership.
After that, you don't need to and must not autorelease or release it once again. This is not malloc() from the standard library.
when you call the statement
NSMutableArray* mutArray = [self processPoints: x :y];
This itself acts as autorelease.
Hence releasing the array explicitly will cause the app to crash.
You are releasing mutArray more then once. Once in processPoints function and again in doNinjaAction.
To resolve the crash remove :
[mutArray autorelease];
-(NSMutableArray *)processPoints:(int) x:(int) y
{
NSMutableArray *mutArray = [[NSMutableArray alloc] initWithCapacity:3];
int x9,y9;
// ...do some processing...
NSNumber* xNum = [NSNumber numberWithInt:x9];
NSNumber* yNum = [NSNumber numberWithInt:y9];
[mutArray addObject:xNum];
[mutArray addObject:yNum];
[mutArray autorelase];
return mutArray;
}
try this one it'll resolve it.
-(NSMutableArray *)processPoints:(int) x:(int) y
{
NSMutableArray *mutArray =[[[NSMutableArray alloc] initWithCapacity:3]autorelease];
int x9,y9;
// ...do some processing...
NSNumber* xNum = [NSNumber numberWithInt:x9];
NSNumber* yNum = [NSNumber numberWithInt:y9];
[mutArray addObject:xNum];
[mutArray addObject:yNum];
return mutArray;
}
As #H2CO3 and #AppleDelegate suggested, it is right.
Still I would suggest to use ARC and convert your project to ARC enabled.
Go to Edit->Refactor->Convert to Objectiv-C ARC
Then you dont need to do any releases anywhere. It will take care itself of all the releases :)

How do I get the index of an object in an NSArray using string value?

I want to get the index of an object within the NSMutableArray of categories.
The category object has an attribute "category_title" and I want to be able to get the index by passing the value of category_title.
I have looked through the docs and can't find a simple way to go about this.
NSArray does not guarantee that you can only store one copy of a given object, so you have to make sure that you handle that yourself (or use NSOrderedSet).
That said, there are a couple approaches here. If your category objects implement isEqual: to match category_title, then you can just use -indexOfObject:.
If you can't do that (because the category objects use a different definition of equality), use -indexOfObjectPassingTest:. It takes a block in which you can do whatever test you want to define your "test" - in this case, testing category_title string equality.
Note that these are all declared for NSArray, so you won't see them if you are only looking at the NSMutableArray header/documentation.
EDIT: Code sample. This assumes objects of class CASCategory with an NSString property categoryTitle (I can't bring myself to put underscores in an ivar name :-):
CASCategory *cat1 = [[CASCategory alloc] init];
[cat1 setCategoryTitle:#"foo"];
CASCategory *cat2 = [[CASCategory alloc] init];
[cat2 setCategoryTitle:#"bar"];
CASCategory *cat3 = [[CASCategory alloc] init];
[cat3 setCategoryTitle:#"baz"];
NSMutableArray *array = [NSMutableArray arrayWithObjects:cat1, cat2, cat3, nil];
[cat1 release];
[cat2 release];
[cat3 release];
NSUInteger barIndex = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([[(CASCategory *)obj categoryTitle] isEqualToString:#"bar"]) {
*stop = YES;
return YES;
}
return NO;
}];
if (barIndex != NSNotFound) {
NSLog(#"The title of category at index %lu is %#", barIndex, [[array objectAtIndex:barIndex] categoryTitle]);
}
else {
NSLog(#"Not found");
}
Not sure that I understand the question but something like this might work (assuming the Mutable Array contains objects of Class "Category"):
int indx;
bool chk;
for (Category *aCategory in theArray)
{
chk = ([[aCategory category_title] isEqualToString:#"valOfCategoryTitle"])
if ( chk )
indx = [theArray indexOfObject:aCategory];
}
Try this code much more simpler:-
int f = [yourArray indexOfObject:#"yourString"];

Example of NSSet's objectsPassingTest function please?

I'm going nuts here. For some reason I can't find a single, complete example of how to use the objectsPassingTest: function of NSSet (cocoa). Before anyone points me to the pages about blocks, I've seen those. The example given only shows how to declare the function, not the block that operates it, and when I tried their example with my own code it didn't work. I just want to see a couple of working examples of how the function might be used, then I'll be able to work it out for myself.
Here is a quick example. Hope it helps.
NSSet *set = [NSSet setWithObjects:#"1",#"2",#"3",#"4",#"5",nil];
NSLog(#"%#",set); // Output (3,1,4,2,5) ... all objects
NSSet *o = [set objectsPassingTest:^(id obj,BOOL *stop){
NSString *so = (NSString *)obj;
int intval = [so intValue];
// accept objects less or equal to two
BOOL r = (intval <= 2);
return r;
}];
NSLog(#"%#",o); // Output (1,2) only objects smaller or equal to 2
I never used blocks. But I guess this is how it works.
NSSet *set = [NSSet setWithObjects:#"FooBar", #"Foo", #"Bar", #"Baz", nil];
NSSet *fooSet = [set objectsPassingTest:^(id obj, BOOL *stop) {
BOOL testResult = NO;
NSString *objStr = (NSString *)obj;
if ([objStr hasPrefix:#"Foo"]) {
testResult = YES;
}
if ([objStr hasSuffix:#"Bar"]) {
testResult = YES;
}
return testResult;
}];
This will create a set with #"FooBar", #"Foo" and #"Bar", because only those pass the test (ie return YES).

Ask about int array in Objective-C

Code below is a simple c# function return an int array.
I would like to know how to convert it to Objective-C
private int[] test()
{
int[] a = new int[2];
a[0] = 1;
a[1] = 2;
return a;
}
The following code should work:
- (NSArray *)test {
NSArray *a = [NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], nil];
return a;
}
Note that if we created the NSArray using [[NSArray alloc] initWithObjects:blah, blah, nil];, we'd have to explicitly autorelease the array before returning it in order to avoid a memory leak. In this case, however, the NSArray is created using a convenience constructor, so the array is already autoreleased for us.
Second Question:
Try this:
- (NSMutableArray *)test:(int)count {
NSMutableArray *a = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++) {
[a insertObject:[NSNumber numberWithInt:0] atIndex:i];
}
return [a autorelease];
}
A bit dummy example:
- (NSArray*) test{
return [NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:2], nil];
}
Some notes:
NSArray is a immutable type - so if you want to add/remove values you should use NSMutableArray instead
You can store only objects in NSArray so you need to wrap them into NSNumber (or another obj-c type)
Obj-c is a superset of C so you can freely use c-arrays in you obj-c code if you want
Or an alternative path would be to use normal c code (is allowed in objective c):
int a[2]={1, 2};
Or in a function:
int *somefunc(void){
static int a[2]={1, 2};
return b;
}
There are two kinds of array in Objective-C: standard C arrays and Cocoa NSArrays. C arrays hold primitive ints but are quite troublesome due to the limitations of C. NSArrays have to hold objects, but you can just wrap ints in NSNumbers and get the int value back out when you need it.
NSArray *array = [NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:500], [NSNumber numberWithInt:12000], nil];

Leaking Memory on iPhone :(

I'm a beginner at C, Obj-C and the iPhone, and I'm trying getting to grips with a lot of terminology used. I hope one of ye can help with a problem I have been struggling with for a few days now.
My code below is a method which call up a nib containing a search field and a table. The table is populated from a search of the array created for 'theList' below. Using 'Instruments', I am getting a Leak at the line:
NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,#"Name",clientId,#"Id",nil]; , but I can't figure out why :(
I know it's probably a difficult question to answer, but if any one can be of any help!
- (void)editClient:(id)sender {
if (pickList == nil) {
pickList = [[PickFromListViewController alloc] initWithNibName:#"PickList" bundle:nil];
}
TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableArray *theList = [[NSMutableArray alloc] init];
int i;
for (i=0;i < [appDelegate.clients count];i++) {
Client *thisClient = [appDelegate.clients objectAtIndex:i];
NSString *clientName = [[NSString alloc] initWithString: thisClient.clientsName];
NSNumber *clientId = [[NSNumber alloc] init];
clientId = [NSNumber numberWithInt:thisClient.clientsId];
NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,#"Name",clientId,#"Id",nil];
[theList addObject:theItem];
theItem = nil;
[clientName release];
[clientId release];
}
[pickList createSearchItems:theList :NSLocalizedString(#"Client",nil)];
[theList release];
appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
[self.navigationController pushViewController:pickList animated:YES];
}
Thanks in advance!
This returns allocated NSNumber instance.
NSNumber *clientId = [[NSNumber alloc] init];
This line overwrites the above clientId with another instance of NSNumber, numberWithInt returns autoreleased object, since you haven't allocated memory for it you should not call release, it will be released automatically.
clientId = [NSNumber numberWithInt:thisClient.clientsId];
You are calling release on clientId so you get memory problem.
To fix it remove the first line above which is useless in this case and update the second one to:
NSNumber * clientId = [NSNumber numberWithInt:thisClient.clientsId];
Then remove the:
[clientId release]
Because the clientId will be released automatically.
EDIT: Re still have problems ...
I'm not sure how to you manipulate the clients in app delegate, otherwise the code should work ok, I created small example, omitting the parts that I can't see (app delegate and clients):
// command line utility - foundation tool project:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableArray * theList = [[NSMutableArray alloc] init];
int i = 0;
for (i = 0; i < 10; ++i)
{
NSString * clientName = [NSString stringWithString:#"client"]; //no need to release
NSNumber * clientId = [NSNumber numberWithInt:i];
NSDictionary * theItem = [NSDictionary dictionaryWithObjectsAndKeys:
clientName, #"name",
clientId, #"id",
nil];
[theList addObject:theItem];
}
for (id item in theList) for (id key in item) NSLog(#"%# - %#", key, [item objectForKey:key]);
[theList release];
[pool drain];
return 0;
}
You are creating clientID with [[NSNumber alloc] init], and then immediately overwriting it with an autoreleased NSNumber instance [NSNumber numberWithInt], and then you are releasing it later in your code, which you shouldn't do. Get rid of the [[NSNumber alloc] init] line and the [clientId release] line and that should fix it up a little.
Aside from the obvious leak of the NSNumber, there are a few other things I'd fix that may help. Most are fairly minor, but in my experience with Objective-C, less code == clearer code, something that is not equally true for languages like Bash or Perl. ;-)
- (void) editClient:(id)sender {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (pickList == nil) {
pickList = [[PickFromListViewController alloc] initWithNibName:#"PickList" bundle:nil];
}
TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate*)[[UIApplication sharedApplication] delegate];
NSMutableArray *searchItems = [NSMutableArray array];
NSMutableDictionary *itemDict = [NSMutableDictionary dictionary];
for (Client *client in appDelegate.clients) {
[itemDict setObject:[client.clientsName copy] forKey:#"Name"];
[itemDict setObject:[NSNumber numberWithInt:client.clientsId] forKey:#"Id"];
[searchItems addObject:[[itemDict copy] autorelease]];
}
[pickList createSearchItems:searchItems :NSLocalizedString(#"Client",nil)];
[self.navigationController pushViewController:pickList animated:YES];
appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
[pool drain];
}
There are a few mysterious points that make me suspicious:
The line just after the for loop tells pickList to do something with the NSMutableArray. That method should retain the new array, as well as release the old array if one exists. If you just overwrite the pointer, the old array will be leaked. (Also, this method is poorly named. Anonymous arguments (a colon with no preceding text) are legal in Objective-C, but considered extremely bad practice. Consider renaming the method to better express what it does.)
The next line seems to associate the pick list with a navigation controller. If it is custom code, make sure the -pushViewController:animated: method properly releases an existing pick list when a new one is specified.
Assigning to appDelegate.returningID is assumed to call the setter for a returningID property. Be sure that property retains or copies the NSNumber as necessary.
Memory leaks can be tricky to track down, even in Instruments, and you'll often find that it looks like Foundation classes (such as NSDictionary) are leaking like a sieve, but I have always been able to trace it back to an abnormality in my code. :-)