How to wrap a 'CGPoint' as an object argument passing to performSelectorInBackground? - objective-c

I am pretty new for COCOS2D and I am now trying to pass a CGPoint as an argument to a thread function. I don't know how to have it wrapped into an object. Can anyone help? thanks in advance~

You can create an NSValue with the the CGPoint and pass that as the withObject parameter:
NSValue *value = [NSValue valueWithCGPoint:CGPointMake(1.2, 22.6)];
[self performSelectorInBackground:#selector(someSelector:) withObject:value];
And then inside your selector, you can access the CGPoint in the NSValue like so:
CGPoint point = [value CGPointValue];

Lets say, you have have
CGPoint p =ccp(10,10);
You could wrap it into an object using pointers and malloc, something like the following :-
CGPoint *wrappedP = malloc(sizeof(CGPoint));
wrappedP->x = p.x;
wrappedP->y = p.y;
Make sure to call
free(wrappedP);
when you are done with wrappedP so that the memory allocated for wrappedP can be released.

try this:
class.h:
-(void) method:(CGPoint )point;
class.m:
-(void) method:(CGPoint )point
{
NSLog(#"X:%f Y:%f",point.x,point.y);
}
// method call
[self method:CGPointMake(100.0f, 100.0f)];

Related

Calling Obj-C Methods

I want to call a method B using a different method A. The problem is that the aksed parameters of method B is not present in Method A.
 
here's what I've tried ..
-(void) methodA {
// some code
CGSize *size = [CGSizeMake(self.frame.size.width, self.frame.size.height)];
[self methodB:size];
}
-(void) methodB:(CGSize) size {
//some code
}
There is certainly a better way...
Thanks
I don't think your code will be compiled.
CGSize is not an object.
Refactor to:
CGSize size = CGSizeMake(self.frame.size.width, self.frame.size.height);
[self methodB:size];
You should only wrap [] around methods. CGSizeMake(self.frame.size.width, self.frame.size.height) is a function, not a method, so get rid of the square braces. You can tell it's a function because functions look like functionName(argument1, argument2, ...) whereas methods look like [object methodName:argument1 methondNameContinued:argument2].
Further, CGSizeMake returns a CGSize structure, not a pointer to a CGSize structure, so ditch the * in *size.
Then you'll be left with this:
CGSize size = CGSizeMake(self.frame.size.width, self.frame.size.height);
[self methodB:size];
Which is correct.

Saving an NSArray of custom objects

I've created a subclass of UIImage (UIImageExtra) as I want to include extra properties and methods.
I have an array that contains instances of this custom class.However when I save the array, it appears the extra data in the UIImageExtra class is not saved.
UIImageExtra conforms to NSCoding, but neither initWithCoder or encodeWithCoder are called, as NSLog statements I've added aren't printed.
My method to save the array looks like this:
- (void)saveIllustrations {
if (_illustrations == nil) {
NSLog(#"Nil array");
return;
}
[self createDataPath];
//Serialize the data and write to disk
NSString *illustrationsArrayPath = [_docPath stringByAppendingPathComponent:kIllustrationsFile];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_illustrations forKey:kIllustrationDataKey];
[archiver finishEncoding];
[data writeToFile:illustrationsArrayPath atomically: YES];
}
And the UIImageExtra has the following delegate methods for saving:
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog(#"Encoding origin data!");
[super encodeWithCoder:aCoder];
[aCoder encodeObject:originData forKey:kOriginData];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:(NSCoder *) aDecoder]) {
NSLog(#"Decoding origin data");
self.originData = [aDecoder decodeObjectForKey:kOriginData];
}
return self;
}
My code to create the array in the first place looks like this (in case that offers any clues)
for (NSDictionary *illustrationDict in illustrationDicts) {
NSString *illustrationString = [illustrationDict objectForKey:#"Filename"];
NSNumber *xCoord = [illustrationDict objectForKey:#"xCoord"];
NSNumber *yCoord = [illustrationDict objectForKey:#"yCoord"];
UIImageExtra *illustration = (UIImageExtra *)[UIImage imageNamed:illustrationString];
//Scale the illustration to size it for different devices
UIImageExtra *scaledIllustration = [illustration adjustForResolution];
NSValue *originData = [NSValue valueWithCGPoint:CGPointMake([xCoord intValue], [yCoord intValue])];
[scaledIllustration setOriginData:originData];
[self.illustrations addObject:scaledIllustration];
}
Or am I just going about saving this data the wrong way? Many thanks.
Your code to initialize the array is not actually creating instances of your UIImageExtra subclass.
UIImageExtra *illustration = (UIImageExtra *)[UIImage imageNamed:illustrationString];
returns a UIImage. Casting it doesn't do what you were intending.
UIImageExtra *scaledIllustration = [illustration adjustForResolution];
is still just a UIImage.
One straightforward-but-verbose way to approach this would be to make UIImageExtra a wrapper around UIImage. The wrapper would have a class method for initializing from a UIImage:
+ (UIImageExtra)imageExtraWithUIImage:(UIImage *)image;
And then every UIImage method you want to call would have to forward to the wrapped UIImage instance-- also being careful to re-wrap the result of e.g. -adjustForResolution lest you again end up with an unwrapped UIImage instance.
A more Objective-C sophisticated approach would be to add the functionality you want in a Category on UIImage, and then use method swizzling to replace the NSCoding methods with your category implementations. The tricky part of this (apart from the required Objective-C runtime gymnastics) is where to store your "extra" data, since you can't add instance variables in a category. [The standard answer is to have a look-aside dictionary keyed by some suitable representation of the UIImage instance (like an NSValue containing its pointer value), but as you can imagine the bookkeeping can get complicated fast.]
Stepping back for a moment, my advice to a new Cocoa programmer would be: "Think of a simpler way. If what you are trying to do is this complicated, try something else." For example, write a simple ImageValue class that has an -image method and an -extraInfo method (and implements NSCoding, etc.), and store instances of that in your array.
You can't add objects to an NSArray after init. Use NSMutableArray, that might be the issue.

iphone ARC - how to set an object to nil?

I have some logic that test if an object is nil or not, how can I set it to nil?
Like:
// in some method
if (true){
[self myObj] = [[myObj alloc]init];
} else{
[self myObject] = nil; //??? How to do this with Automatic Ref. Counting
}
// elsewhere
if([self myObj]){
}
Your code is incorrect.
You need to use properties and assign values to them, like [self setMyObject:nil]; or [self setMyObj:[[myObj alloc] init]];.
Your code is wrong. Try:
self.myObject=nil;
//or
[self setMyObject:nil];
Also, make sure that myObject is a property in your class, otherwise using self won't work.
[self myObj] is not assignable, as it is not a lvalue. To fix this, either reference the underlying variable, e.g. self->myObj, or use [self setMyObj:] if you are using properties.
You are using a getter as a setter. That isn't going to work. It should be
[self setMyObj:[myObj alloc]init]];
And
[self setMyObj:nil];
Assuming you have implemented the setters. Under ARC you don't really need to if you are just accessing an ivar - you can access it directly and the reference counting will be done for you:
myObj = [MyObj alloc] init];
And
myObj = nil;
Will set and remove everything for you.

Warning about making pointer from integer without a cast -- explanation needed

I have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic
NSLog(#"didSelectRowAtIndexPath");
//The hud will dispable all input on the view
HUD = [[MBProgressHUD alloc] initWithView:self.view];
// Add HUD to screen
[self.view addSubview:HUD];
// Regisete for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
HUD.labelText = #"Loading Events, Please Wait..";
int i = indexPath.row;
//Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
}
And I get this warning:
warning: passing argument 3 of 'showWhileExecuting:onTarget:withObject:animated:' makes pointer from integer without a cast
Can somebody please explain what I'm doing wrong here? Could someone also briefly explain the situation with ints in Objective-C, coming from Java I find it odd that they are so confusing to use.
The problem is that showWhileExecuting:onTarget:withObject:animated: takes an object as its third argument. To get aroung this, you can wrap integers as objects using the NSNumber class
[NSNumber numberWithInt:i]
You will then have to unwrap the argument in the loadData: method by calling
[argument intValue]
The method takes an object as a third argument (withObject), but you passed an int instead.
Apparently, you provided an integer(int i) instead of an object pointer(type of id). It is not safe. Use NSNumber instead.
int i;
...
NSNumber * numberI = [NSNumber numberWithInt:i];
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
All of the answers above are the "correct" ones. I.e. be a good boy and use and NSNumber to pass the value.
However, … the following will work
"damn you, compiler, i'm smarter than you are:"
(cast your integer, totally not a valid object, to id)
[HUD showWhileExecuting:#selector(loadData:)
onTarget:self
withObject:(id)i
animated:YES];
i'm guessing (you didn't say), that your load data method looked like this:
- (void)loadData:(int)i { …
you will see code like this, which is the only reason i mentioned it.
you should be familiar with it.
someone thinks that saving 1 object allocation is going to make their code efficient; don't sweat object allocations, and wrap it up in an NSNumber as shown above
most C compilers will handle this correctly, but it's not guaranteed

How to use performSelector:withObject:afterDelay: with primitive types in Cocoa?

The NSObject method performSelector:withObject:afterDelay: allows me to invoke a method on the object with an object argument after a certain time. It cannot be used for methods with a non-object argument (e.g. ints, floats, structs, non-object pointers, etc.).
What is the simplest way to achieve the same thing with a method with a non-object argument? I know that for regular performSelector:withObject:, the solution is to use NSInvocation (which by the way is really complicated). But I don't know how to handle the "delay" part.
Thanks,
Here is what I used to call something I couldn't change using NSInvocation:
SEL theSelector = NSSelectorFromString(#"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
invocationWithMethodSignature:
[MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];
[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];
[anInvocation performSelector:#selector(invoke) withObject:nil afterDelay:1];
Just wrap the float, boolean, int or similar in an NSNumber.
For structs, I don't know of a handy solution, but you could make a separate ObjC class that owns such a struct.
DO NOT USE THIS ANSWER. I HAVE ONLY LEFT IT FOR HISTORICAL PURPOSES. SEE THE COMMENTS BELOW.
There is a simple trick if it is a BOOL parameter.
Pass nil for NO and self for YES. nil is cast to the BOOL value of NO. self is cast to the BOOL value of YES.
This approach breaks down if it is anything other than a BOOL parameter.
Assuming self is a UIView.
//nil will be cast to NO when the selector is performed
[self performSelector:#selector(setHidden:) withObject:nil afterDelay:5.0];
//self will be cast to YES when the selector is performed
[self performSelector:#selector(setHidden:) withObject:self afterDelay:10.0];
Perhaps NSValue, just make sure your pointers are still valid after the delay (ie. no objects allocated on stack).
I know this is an old question but if you are building iOS SDK 4+ then you can use blocks to do this with very little effort and make it more readable:
double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self doSomethingWithPrimitive:primitiveValue];
});
PerformSelector:WithObject always takes an object, so in order to pass arguments like int/double/float etc..... You can use something like this.
//NSNumber is an object..
[self performSelector:#selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
afterDelay:1.5];
-(void) setUserAlphaNumber: (NSNumber*) number{
[txtUsername setAlpha: [number floatValue] ];
}
Same way you can use [NSNumber numberWithInt:] etc.... and in the receiving method you can convert the number into your format as [number int] or [number double].
Blocks are the way to go. You can have complex parameters, type safety, and it's a lot simpler and safer than most of the old answers here. For example, you could just write:
[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];
Blocks allow you to capture arbitrary parameter lists, reference objects and variables.
Backing Implementation (basic):
#interface MONBlock : NSObject
+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;
#end
#implementation MONBlock
+ (void)imp_performBlock:(void(^)())pBlock
{
pBlock();
}
+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
[self performSelector:#selector(imp_performBlock:)
withObject:[pBlock copy]
afterDelay:pDelay];
}
#end
Example:
int main(int argc, const char * argv[])
{
#autoreleasepool {
__block bool didPrint = false;
int pi = 3; // close enough =p
[MONBlock performBlock:^{NSLog(#"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];
while (!didPrint) {
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
}
NSLog(#"(Bye, World!)");
}
return 0;
}
Also see Michael's answer (+1) for another example.
I would always recomend that you use NSMutableArray as the object to pass on. This is because you can then pass several objects, like the button pressed and other values. NSNumber, NSInteger and NSString are just containers of some value. Make sure that when you get the object from the array
that you refer to to a correct container type. You need to pass on NS containers. There you may test the value. Remember that containers use isEqual when values are compared.
#define DELAY_TIME 5
-(void)changePlayerGameOnes:(UIButton*)sender{
NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
[array addObject:nextPlayer];
[self performSelector:#selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
if(gdata != nil){ //if game choose next player
[self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
}
}
I also wanted to do this, but with a method that receives a BOOL parameter. Wrapping the bool value with NSNumber, FAILED TO PASS THE VALUE. I have no idea why.
So I ended up doing a simple hack. I put the required parameter in another dummy function and call that function using the performSelector, where withObject = nil;
[self performSelector:#selector(dummyCaller:) withObject:nil afterDelay:5.0];
-(void)dummyCaller {
[self myFunction:YES];
}
I find that the quickest (but somewhat dirty) way to do this is by invoking objc_msgSend directly. However, it's dangerous to invoke it directly because you need to read the documentation and make sure that you're using the correct variant for the type of return value and because objc_msgSend is defined as vararg for compiler convenience but is actually implemented as fast assembly glue. Here's some code used to call a delegate method -[delegate integerDidChange:] that takes a single integer argument.
#import <objc/message.h>
SEL theSelector = #selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}
This first saves the selector since we're going to refer to it multiple times and it would be easy to create a typo. It then verifies that the delegate actually responds to the selector - it might be an optional protocol. It then creates a function pointer type that specifies the actual signature of the selector. Keep in mind that all Objective-C messages have two hidden first arguments, the object being messaged and the selector being sent. Then we create a function pointer of the appropriate type and set it to point to the underlying objc_msgSend function. Keep in mind that if the return value is a float or struct, you need to use a different variant of objc_msgSend. Finally, send the message using the same machinery that Objective-C uses under the sheets.
You Could just use NSTimer to call a selector:
[NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(yourMethod:) userInfo:nil repeats:NO]
Calling performSelector with an NSNumber or other NSValue will not work. Instead of using the value of the NSValue/NSNumber, it will effectively cast the pointer to an int, float, or whatever and use that.
But the solution is simple and obvious. Create the NSInvocation and call
[invocation performSelector:#selector(invoke) withObject:nil afterDelay:delay]
Pehaps...ok, very likely, I'm missing something, but why not just create an object type, say NSNumber, as a container to your non-object type variable, such as CGFloat?
CGFloat myFloat = 2.0;
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];
[self performSelector:#selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];