I am getting an EXC_BAD_ACCESS error when attempting to add objects to an array. I understand that this could mean I am pointing to something that doesn't exist in memory, or that the objects contain a nil value.
Code:
- (void)fadeInPlayer:(AVAudioPlayer *)player withMaxVolume:(float)maxVolume {
NSLog(#"player: %#", player);
NSLog(#"maxVolume: %f", maxVolume);
NSMutableArray *playerAndVolume = [NSMutableArray arrayWithObjects: player, maxVolume, nil];
if (player.volume <= maxVolume) {
player.volume = player.volume + 0.1;
NSLog(#"%# Fading In", player);
NSLog(#"Volume %f", player.volume);
[self performSelector:#selector(fadeInPlayer:withMaxVolume:) withObject:playerAndVolume afterDelay:0.5];
//playerAndVolume array used here because performSelector can only accept one argument with a delay and I am using two...
}
}
The strange thing is that when I print the objects I am trying to add to the console (shown as NSLogs above), they return data:
player: <AVAudioPlayer: 0x913f030>
maxVolume: 0.900000
The app crashes immediately after the NSLogs. The rest of the code works fine without the array, but I need to use it to call performselector:withObject:AfterDelay on the method.
So there must be a problem with how I am initialising the array, or maybe the object types, but I can't figure it out.
Any help appreciated.
You can't add a float to an NSArray. You'll have to wrap it in an NSNumber.
But
The real problem is that the first argument passed in is the NSArray you created, the second parameter passed into your function is the NSTimer that backs the performSelector:afterDelay:... methods. It doesn't spread out the objects in your array, it just passes the array as the first argument. If you are insistent upon remaining with this API design then you need to test the class of the first argument to see if it is an NSArray or an AVAudioPlayer. You could implement this function like this:
-(void)fadeInPlayer:(AVAudioPlayer *)player withMaxVolume:(NSNumber *)maxVolume {
if ([player isKindOfClass:[NSArray class]]){
// This is a redundant self call, and the player and max volume are in the array.
// So let's unpack them.
NSArray *context = (NSArray *)player;
player = [context objectAtIndex:0];
maxVolume = [context objectAtIndex:1];
}
NSLog(#"fading in player:%# at volume:%f to volume:%f",player,player.volume,maxVolume.floatValue);
if (maxVolume.floatValue == player.volume || maxVolume.floatValue > 1.0) return;
float newVolume = player.volume + 0.1;
if (newVolume > 1.0) newVolume = 1.0;
player.volume = newVolume;
if (newVolume < maxVolume.floatValue){
NSArray *playerAndVolume = [NSArray arrayWithObjects: player, maxVolume, nil];
[self performSelector:#selector(fadeInPlayer:withMaxVolume:) withObject:playerAndVolume afterDelay:0.5];
}
}
You would use this, wrapping the float in an NSNumber, like so:
[self fadeInPlayer:player withMaxVolume:[NSNumber numberWithFloat:1.0]];
Please note, this would be considered a very odd function, but this code does run.
Related
I have been trying to work out how to return an MSMutableArray in objective c, I have this code here.
- (NSMutableArray*)generateRandomNumber{
NSMutableArray *unqArray=[[NSMutableArray alloc]init];
int randNum;
int counter = 0;
while (counter< 6) {
randNum = arc4random_uniform(40.0);
if (![unqArray containsObject:[NSNumber numberWithInt:randNum]]) {
[unqArray addObject:[NSNumber numberWithInt:randNum]];
counter++;
}
}
return unqArray;
}
- (IBAction)button:(id)sender {
NSMutableArray *results = [[NSMutableArray alloc]init];
*results = generateRandomNumber();
}
This is my code at the moment, where it says,
NSMutableArray *results = [[NSMutableArray alloc]init];
I get the following errors...
Implicit declaration of function 'generateRandomNumber' is invalid in C99
Assigning to 'NSMutableArray' from incompatible type 'int'
If anybody is willing to show me my mistake and help me out as well as many others I will appreciate it as much as possible.
Thanks to all who help me out!!!
generateRandomNumber is a method, not a C function, so use [self generateRandomNumber] to call it, and you are assigning results incorrectly, so:
results = [self generateRandomNumber];
Alternatively if you want to define it as a C function, use:
NSMutableArray *generateRandomNumber() {
...
}
Also, as pointed-out by #Larme, there is no need to allocate results before assigning it from generateRandomNumber.
- (IBAction)button:(id)sender {
NSMutableArray *results = [[NSMutableArray alloc]init];
results = [self generateRandomNumber];
}
Or more simply:
- (IBAction)button:(id)sender {
NSMutableArray *results = [self generateRandomNumber];
// Do something with this Array
}
Though you should consider a different name for that method as currently it sounds like it is returning one number, not an array of random numbers.
When calling/using a function in Objective-C
it's [self generateRandomNumber]
When you declare a variables it's NSMutableArray *results = [[NSMutableArray alloc]init]; and use if like results = mutableArry, * before it is not needed..
about your problem, you better do it like this:
- (IBAction)button:(id)sender
{
NSMutableArray *results = [self generateRandomNumber];
}
you dont need to allocate anymore, because you are passing an mutable array that is already allocated..
Your method generateRandomNumbers should work as expect the problem is here:
- (IBAction)button:(id)sender {
NSMutableArray *results = [[NSMutableArray alloc]init];
*results = generateRandomNumber();
}
The first thing is that you are creating an unneeded mutable array, second your are trying to substitute the value of the pointer that points to result array, third generateRandonNumber is a method not a function you should call it like [self generateRandomNumber].
Also I would implement a optimization since I'm pretty sure that you are not going to modify the random number array, the returned instance should be an immutable copy.
Here the final code:
- (NSArray*)generateRandomNumber{
NSMutableArray *unqArray=[[NSMutableArray alloc]init];
int randNum;
int counter = 0;
while (counter< 6) {
randNum = arc4random_uniform(40.0);
if (![unqArray containsObject:[NSNumber numberWithInt:randNum]]) {
[unqArray addObject:[NSNumber numberWithInt:randNum]];
counter++;
}
}
return unqArray.copy;
}
- (IBAction)button:(id)sender {
NSArray *results = nil;
results = [self generateRandomNumber];
}
Your generateRandomNumbers method should work with no problem, but in the IBAction there you try to put an INT directly into the array, you need to wrap it in an NSNumber like you have in the other method there
Your method is correct.
What you are doing wrong is here: *results = generateRandomNumber();
There is no need of an * here,because in this way you are trying to assign the pointer address of your array to results object.
Secondly you are trying to call an Objective-C method in C syntax.
So the correct syntax would be: results = [self generateRandomNumber];
This question already has answers here:
Cannot AddObject to NSMutableArray from Block
(2 answers)
Closed 8 years ago.
I have city names and coordinates stored on my core data database and trying to get the state.
In order to do this I am reverse geocoding like this
code moved below
The problem is the locationStr object is not getting stored in the tempCities array. I have tested inside the block and I know the locationStr is getting created and exists.
I've been trying to figure this out for hours. Can someone clear this up for me?
Tell me if you need any other info.
EDIT:
The array is being used to fill a table view. The code is in a helper method which returns an array (the tempCities array). I'm checking the array against nil and 0 right after the for loop.
Heres what the UISearchControllerDelegate method looks like in the View controller
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
NSString *searchText = searchController.searchBar.text;
if ([searchText length] >= 3)
{
self.resultsArray = [[[CityHelper sharedInstance]testWithSearch:searchText]mutableCopy];
[self.resultsTableView reloadData];
}
}
And in the CityHelper class
- (NSArray *) testWithSearch: (NSString *)search
{
NSArray *cities = [self getCitiesStartingWith:search];//stores NSManagedObject subclass instances with cityName, lat, and long.
NSArray *coords = [self coordinatesForCities:cities];
NSMutableArray *tempCities = [NSMutableArray new];
for (MMCoordinate *coordinate in coords) {
CLGeocoder *geocoder = [CLGeocoder new];
CLLocation *location = [[CLLocation alloc]initWithLatitude:[coordinate.latitude floatValue]
longitude:[coordinate.longitude floatValue]];
if (![geocoder isGeocoding]) {
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = placemarks[0];
NSString *locationStr = [NSString stringWithFormat:#"%#, %#", placemark.locality, placemark.administrativeArea];
[tempCities addObject:locationStr];
}];
}
}
if (!tempCities) {
NSLog(#"its nil");
}
if ([tempCities count] == 0) {
NSLog(#"its 0");
}
return tempCities;
}
This always returns an empty (0 count) array
Essentially, the situation you have can be made clear with a simple code snippet:
- (void)someMethod
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// this will be executed in two seconds
NSLog(#"I'm the last!");
});
NSLog(#"I'm first!");
}
the block passed to dispatch_after is invoked after the method invocation ended, just like the block you passed to reverseGeocodeLocation:completionHandler:. You'll see on the console I'm first first, and then I'm the last!.
What should you do?
You need to solve that problem by introducing a callback to your method, because your method does things in the background and needs to call back when it's done. When you want to know how to declare a block for example in a method, this website explains how to use block syntax: http://fuckingblocksyntax.com/
In your case you need also to determine in the reverseGeocodeLocation:completionHandler block when to invoke the callback you should add to testWithSearch as a parameter. You could for example increase a counter every time you call reverseGeocodeLocation:completionHandler and decrease it every time when the completionHandler got invoked. When the counter reaches 0 you invoke the testWithSearch callback with the array result.
Example:
- (void)doSomething:(dispatch_block_t)callback
{
// we need __block here because we need to
// modify that variable inside a block
// try to remove __block and you'll see a compiler error
__block int counter = 0;
for (int i = 0; i < 15; i ++) {
counter += 1;
dispatch_async(dispatch_get_main_queue(), ^{
counter -= 1;
if (counter <= 0 && callback) {
callback();
}
});
}
}
Root cause is declaring the tempCities as a __block variable. Because of __block, iOS doesn't retain the tempCities on entering to the completion block. Since the completion block is executed later and the tempCities is a local variable, it actually gets deallocated at time when the completion block starts execution.
Please declare the tempCities as follows:
NSMutableArray *tempCities = [NSMutableArray new];
I am making an app which asks the user a series of questions. The question asked depends on the random int produced. When an int is used, I want to add it to an NSMutableArray, and then check if the array contains a number the next time a random number is chosen. I am currently using the following code to do this:
- (void) selectQuestionNumber {
textNum = lowerBounds + arc4random() % (upperBounds - lowerBounds);
if ([previousQuestions containsObject:[NSNumber numberWithInt:textNum]]) {
[self selectQuestionNumber];
NSLog(#"The same question number appeared!");
} else {
questionLabel.text = [self nextQuestion];
[self questionTitleChange];
NSLog(#"New question made");
}
[previousQuestions addObject:[NSNumber numberWithInt:textNum]];
}
However, the code NSLog(#"The same question number appeared!"); is never shown in the console, even when the same question will appear twice.
My code is obviously non-functional, so what code can I use to check if an NSMutable array contains an int?
Original solution (works with Array and Set):
-(void)selectQuestionNumber
{
textNum = lowerBounds + arc4random() % (upperBounds - lowerBounds);
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"intValue=%i",textNum];
NSArray *filteredArray = [previousQuestions filteredArrayUsingPredicate:predicate];
if ([filteredArray count]) {
[self selectQuestionNumber];
NSLog(#"The same question number appeared!");
} else {
questionLabel.text = [self nextQuestion];
[self questionTitleChange];
NSLog(#"New question made");
}
[previousQuestions addObject:[NSNumber numberWithInt:textNum]];
}
Best solution, and better performance, especialy with mutableSet ( According to Duncan C).
-(void)selectQuestionNumber
{
textNum = lowerBounds + arc4random() % (upperBounds - lowerBounds);
if ([previousQuestions containsObject:[NSNumber numberWithInteger:textNum]]) {
[self selectQuestionNumber];
NSLog(#"The same question number appeared!");
} else {
questionLabel.text = [self nextQuestion];
[self questionTitleChange];
NSLog(#"New question made");
// And add the new number to mutableSet of mutableArray.
[previousQuestions addObject:[NSNumber numberWithInteger:textNum]];
}
}
Your problem is likely something other than detecting membership of NSNumbers in an NSArray. It can take a large number of tries for a set of random numbers to repeat. It is theoretically possible for it to not repeat until every possible value has been generated once. For a large range of legal values it can take quite a while.
I suggest logging the values that you add to the array on each pass, and the new value.
Your code above always adds the new value to the array even it if matched, so your array is going to grow with duplicates. You would be better off only adding the new number to the array if it did not match. you would probably also be better off using an NSMutableSet instead of an array. NSSets contain at most one instance of an object, and their containsObject method is faster than that of NSArray.
Instead of using NSArray, you can use NSMutableIndexSet. This is the same as NSSet, with just NSUIntegers instead of objects. Very useful.
//during init
NSMutableIndexSet *tSet = [[NSMutableIndexSet alloc] init];
//...
//later in the code, in whatever loop you have on new values
NSUInteger newInt = lowerBounds + arc4random() % (upperBounds - lowerBounds);
if ([tSet containsIndex:newInt]){
//value already exists in the set
}
else {
//value does not exist, add it
[tSet addIndex:newInt];
}
NSMutableSet *myNumbers = [NSMutableSet Set]; // or NSMutableArray..
NSNumber *aNumber = [NSNumber numberWithInt:getRandomInt() ]; //let us say it returns 1.
[myNumbers addObject:aNumber];
-(BOOL)succesfullyAddNewUniqueRandomMember{
NSInteger randInt = getRandomInt(); //let us say it returns 1 again..
NSNumber *aSubsequentNumber = [NSNumber numberWithInt:randInt;
for (NSNumber *previousEntry in myNumbers){
if ([previousEntry isEqual:aSubsequentNumber]) return NO;
}
[myNumbers addObject:aSubsequentNumber];
return YES;
}
^ are these objects equal (aNumber, aSubsequentNumber) ? YES
^ are they the same object ? NO, two different NSNumbers made with equal integer..
NSSet will also happily add both, because they are not the same object.
therefore you need to loop through and compare directly to each previous member, the (already contains object) filter of NSSet will not do the trick.
by wrapping this in a -(BOOL) type method we can repeat it with a
while(![self succesfullyAddNewUniqueRandomMember])
In other words, in your code
if ([previousQuestions containsObject:[NSNumber numberWithInt:textNum]])
always returns NO because it is comparing NSNumber objects, not their integerValue.
So I am using restkit for asynchronous calls and such, and felt the one of the best ways to handle possible connection issues was to have a requestManager Which holds a single an array of requests and it will keep looping through it and remove whenever they are successful, this way the payload for the request can be created and added to the manager and the rest of the app can continue without having to worry about returns or not unless it is a GET... Now my issue is that some of the requests are using closures or code Blocks as you will.
And i am having trouble wrapping my head around the syntax and usage of it properly. Especially when it comes to blocks.
This is how I plan to implement it...
so I first Make this Call
GameInfo *game = [[GameInfo alloc] init];
game.myWTFID = [playerInfo _wtfID];
game.opponentWTFID = [gameInfoObj GetOpponentWTFID];
game.gameType = [NSNumber numberWithInt:[gameInfoObj gameType]];
game.turnNumber = [gameInfoObj GetTurnNumber];
game.lastMove = [gameInfoObj GetGameID];
[[RequestPayload sharedRequestPayload] addPutRequestForObject : game withSerializationMapping : serializedGameMap forClass : [Game class] withBlock :^(RKObjectLoader *loader)
{
[loader setObjectMapping:gameMap];
loader.delegate = self;
loader.onDidLoadResponse = ^(RKResponse *response)
{
[game release];
};
loader.targetObject = nil;
loader.backgroundPolicy = RKRequestBackgroundPolicyRequeue;
}
];
here Is the Implementation
- ( void ) addPutRequestForObject : (id) object withSerializationMapping : (RKObjectMapping*) serialMapping forClass : (Class) class withBlock : ( void(^)(RKObjectLoader*) ) block
{
NSMutableDictionary *dict = [NSMutableDictionary new];
NSNumber *postType = [[NSNumber alloc]initWithInt:1];
[dict setObject:postType forKey:#"request"];
[dict setObject:object forKey:#"data"];
[dict setObject:serialMapping forKey:#"serialMapping"];
[dict setObject:class forKey:#"class"];
void (^ copyBlockLoader)(id,RKObjectMapping*) = Block_copy(block);
[dict setObject:copyBlockLoader forKey:#"block"];
Block_release(copyBlockLoader);
[postType release];
postType = nil;
[_RequestsToInvoke addObject:dict];
}
and then within a for loop after going though each object in the array which will be a dictionary containing the needed information to do something like this.(apologies if this does not make sense it is highly contrived as the actual method is alot longer but the vital part i thought was here.)
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:[dict objectForKey:#"serialMapping"] forClass:[dict objectForKey:#"class"]];
void(^block)(RKObjectLoader *) = [dict objectForKey:#"block"];
[[RKObjectManager sharedManager] putObject:object usingBlock:block];
So my questions are
in the first snippet where i ask it to release the game object...Will that work? or will it result in a leak the game object is declared within the same scope as that call so the only pointer i have left is the one in the code block.
am i saving the block into the dictionary correctly?
and last does anyone have a better alternative? or spot something else with my implementation that needs correcting/modification?
I'm almost there understanding simple reference counting / memory management in Objective-C, however I'm having a difficult time with the following code. I'm releasing mutableDict (commented in the code below) and it's causing detrimental behavior in my code. If I let the memory leak, it works as expected, but that's clearly not the answer here. ;-) Would any of you more experienced folks be kind enough to point me in the right direction as how I can re-write any of this method to better handle my memory footprint? Mainly with how I'm managing NSMutableDictionary *mutableDict, as that is the big culprit here. I'd like to understand the problem, and not just copy/paste code -- so some comments/feedback is ideal. Thanks all.
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *mutableArray = [[[NSMutableArray alloc] init] autorelease];
//NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
CXMLDocument *theXMLDocument = [[[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError] retain];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
int i, j, cnt = [nodes count];
for(i=0; i < cnt; i++) {
CXMLElement *xmlElement = [nodes objectAtIndex:i];
if(nil != xmlElement) {
NSArray *attributes = [NSArray array];
attributes = [xmlElement attributes];
int attrCnt = [attributes count];
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
for(j = 0; j < attrCnt; j++) {
if([[[attributes objectAtIndex:j] name] isKindOfClass:[NSString class]])
[mutableDict setValue:[[attributes objectAtIndex:j] stringValue] forKey:[[attributes objectAtIndex:j] name]];
else
continue;
}
if(nil != mutableDict) {
[mutableArray addObject:mutableDict];
}
[mutableDict release]; // This is causing bad things to happen.
}
}
return (NSArray *)mutableArray;
}
Here's an equivalent rewrite of your code:
- (NSArray *)attributeDictionaries:(NSString *)xmlDocument withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return attributeDictionaries;
}
Notice I only did reference counting on theXMLDocument. That's because the arrays and dictionaries live beyond the scope of this method. The array and dictionary class methods create autoreleased instances of NSArray and NSMutableDictionary objects. If the caller doesn't explicitly retain them, they'll be automatically released on the next go-round of the application's event loop.
I also removed code that was never going to be executed. The CXMLNode name method says it returns a string, so that test will always be true.
If mutableDict is nil, you have bigger problems. It's better that it throws an exception than silently fail, so I did away with that test, too.
I also used the relatively new for enumeration syntax, which does away with your counter variables.
I renamed some variables and the method to be a little bit more Cocoa-ish. Cocoa is different from most languages in that it's generally considered incorrect to use a verb like "create" unless you specifically want to make the caller responsible for releasing whatever object you return.
You didn't do anything with theError. You should either check it and report the error, or else pass in nil if you're not going to check it. There's no sense in making the app build an error object you're not going to use.
I hope this helps get you pointed in the right direction.
Well, releasing mutableDict really shouldn't be causing any problems because the line above it (adding mutableDict to mutableArray) will retain it automatically. While I'm not sure what exactly is going wrong with your code (you didn't specify what "bad things" means), there's a few general things I would suggest:
Don't autorelease mutableArray right away. Let it be a regular alloc/init statement and autorelease it when you return it ("return [mutableArray autorelease];").
theXMLDocument is leaking, be sure to release that before returning. Also, you do not need to retain it like you are. alloc/init does the job by starting the object retain count at 1, retaining it again just ensures it leaks forever. Get rid of the retain and release it before returning and it won't leak.
Just a tip: be sure that you retain the return value of this method when using it elsewhere - the result has been autoreleased as isn't guaranteed to be around when you need it unless you explicitly retain/release it somewhere.
Otherwise, this code should work. If it still doesn't, one other thing I would try is maybe doing [mutableArray addObject:[mutableDict copy]] to ensure that mutableDict causes you no problems when it is released.
In Memory Management Programming Guide under the topic Returning Objects from Methods (scroll down a bit), there are a few simple examples on how to return objects from a method with the correct memory managment.