I am getting a strange warning with my initWithData: method:
warning: incompatible Objective-C
types 'struct NSDictionary *',
expected 'struct NSData *' when
passing argument 1 of 'initWithData:'
from distinct Objective-C type
in TRDevice.h:
#interface TRDevice : NSObject
{
NSString *name;
}
#property (nonatomic, copy) NSString *name;
-(id)initWithData:(NSDictionary *)inData;
#end
in TRDevice.m:
- (id)initWithData:(NSDictionary *)inData
{
if ((self = [super init]))
{
self.name = [inData valueForKey:TRDeviceNameKey];
}
return self;
}
And where I try to instantiate the above object:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSArray *dataArray = [prefs arrayForKey:TRDevicesKey];
for (NSDictionary *data in dataArray)
{
TRDevice *device = [[TRDevice alloc] initWithData:data];
[self.devices addObject:device];
[device release];
}
This warning goes away if I 1) rename the init method to something else such as initWithDictionary:. or 2) if I pass in nil to initWithData:.
AFAIK NSObject implements no initWithData: method so I am not overriding it either. I have no clue why I'm getting this warning.
+alloc does not return NSObject, it returns id. that's the proper declaration. the type is (from the compiler's perspective) erased when it's returned via + (id)alloc.
most uses of initWithData: take NSData as their first argument. this is part of the reason objc 'reads so well' and is descriptive.
anyways... personally, i'd just rename it to initWithDictionary: for clarity, convention, and to avoid the warning.
another option is to simply declare your initializer to include the type:
- (id)initTRDeviceWithData:(NSDictionary *)data;
this is detailed enough that it is not likely to conflict with other classes in the translation.
or you can declare a convenience constructor:
+ (TRDevice *)newTRDeviceWithData:(NSDictionary *)data;
or a variant for an autoreleased type:
+ (TRDevice *)trDeviceWithData:(NSDictionary *)data; // eww...
Related
Hello can you give me an example of the usage of this method
+(NSString *)description
Do I use description with an instance of a NSObject (any kind of object) or NSString?
or do I use without an instance, directly using NSObject (any kind of object) or NSString?
The description of the instance gives you information about the specific instance you have created.
- (NSString *)description;
NSString *string = [NSString alloc] initwithString:#"aString"]];
[string description];
Gives you information about this instance (location in memory etc)
On the other side:
+ (NSString *)description;
[NSString description];
Gives you information about the class NSString.
The same rules apply to all NSObject subclasses and other classes that conform to NSObject protocol such NSArray, NSDictionary *NSProxy* etc
Let's say we have:
#interface randomObject : NSObject
{
NSString *yourString;
}
and somewhere:
yourString = [[NSString alloc] initWithString:#"random text"];
then we can override randomObject like this...
- (NSString *)description
{
return [NSString stringWithFormat:#"%#", yourString];
}
after we done this we can call a NSLog with our NSObject:
-(void)viewDidLoad {
randomObject *ourObj;
ourObj = [[randomObject alloc] init];
NSLog(#"%#", ourObj); // this will output "random text"
}
You seem to mainly be confused about the difference between class and instance methods.
NSObject declares the class method +[NSObject description], which, as the docs tell you "Returns a string that represents the contents of the receiving class.". If you send the message description to a class object, like so:
[NSArray description];
[NSNumber description];
[[someObject class] description];
this method will be called and you'll get the string the class uses to describe itself.
On the other hand, the NSObject protocol declares a required instance method -[id<NSObject> description], which will return "a string that describes the contents of the receiver". When you send this to an instance, you get a string representing it:
NSNumber * n = [NSNumber numberWithInt:10];
[n description];
NSArray * arr = [NSArray arrayWithObjects:#"Lemon", #"curry", #"?", nil];
[arr description];
NSDicitonary * d = [NSDictionary dictionaryWithObject:arr forKey:n];
[d description];
All subclasses of NSObject inherit both of these methods, and they can be overridden just like any other. Notice, for example, that NSDictionary and NSArray format themselves and send description to the objects they contain.
It should also be noted that, when using NSLog(), the %# format specifier causes description to be sent to its argument (whether it's a class object or an instance).
The call most normally used it actually
- (NSString *)description;
It is used on normally instances, not classes. It can be overridden in custom classes to provide detailed information about an object. If you attempt to access a class as as string, the description method will automatically be called.
NSLog(#"array: %#", array); //Identical
NSLog(#"array: %#", [array description]); //Identical
You can use it on classes just as you stated
[NSArray description];
+(NSString *)description
Is used mainly for debug and is used by instances. It allows to print a description of the object.
I'm having issues placing a custom object (WSWCMPost) into an NSMutableArray and then accessing the data stored in it later. Below is the relevant code.
Here is "WSWCMPost.h"
#import <Foundation/Foundation.h>
#interface WSWCMPost : NSObject
{
NSString *postBody;
NSString *postTitle;
NSString *postID;
}
#property (nonatomic, retain) NSString *postBody, *postTitle, *postID;
- init;
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title;
- (NSString*)postBody;
- (NSString*)postTitle;
- (NSString*)postID;
Here is "WSWCMPost.m"
#import "WSWCMPost.h"
#implementation WSWCMPost
#synthesize postBody, postTitle, postID;
- (id)init {
self = [super init];
if(self) {
postID = #"none";
postBody = #"none";
postTitle = #"none";
}
}
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title {
postTitle = title;
postID = ID;
postBody = body;
}
#end
And here is the "viewDidLoad" method that is causing my issues
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.detailViewController = (WSWCMDetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
// getting an NSString
NSLog(#"Pulling saved blogs...");
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"wswcmt1"];
if (dataRepresentingSavedArray != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
_objects = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
_objects = [[NSMutableArray alloc] init];
}
NSLog(#"Pulled saved blogs...");
NSLog(!_objects ? #"Yes" : #"No");
#try {
NSLog(#"_objects description: %#",[_objects description]);
NSLog(#"_objects[0] postID: %#",[[_objects objectAtIndex:0] postID]);
}
#catch (NSException *exception) {
NSLog(#"Caught exception %#", exception);
NSLog(#"Objects doesnt exist, allocating memory...");
_objects = [[NSMutableArray alloc] init];
WSWCMPost *testPost = [[WSWCMPost alloc] initWithID:#"noID" AndBody:#"noBody" AndTitle:#"noTitle"];
[_objects insertObject:testPost atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"wswcmt1"];
}
if (!_objects ) {
NSLog(#"Objects doesnt exist...");
_objects = [[NSMutableArray alloc] init];
WSWCMPost *testPost = [[WSWCMPost alloc] initWithID:#"dne" AndBody:#"Dne" AndTitle:#"DNe"];
[_objects insertObject:testPost atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"wswcmt"];
}
[self refreshButton:nil];
}
And finally, here is the output
2012-06-25 22:39:49.345 WSWCM[4406:907] Pulling saved blogs...
2012-06-25 22:39:49.352 WSWCM[4406:907] Pulled saved blogs...
2012-06-25 22:39:49.355 WSWCM[4406:907] Yes
2012-06-25 22:39:49.356 WSWCM[4406:907] _objects description: (null)
2012-06-25 22:39:49.358 WSWCM[4406:907] _objects[0] postID: (null)
2012-06-25 22:39:49.360 WSWCM[4406:907] Objects doesnt exist...
2012-06-25 22:39:49.363 WSWCM[4406:907] Refresh Triggered...
I think that is all of the relevant code. If i forgot anything let me know please. This issue has been bothering me for hours...
While I'm not positive why it's giving you NSStrings instead of just blowing up normally, the problem seems to stem from the fact that your custom class, WSWCMPost, does not conform to the NSCoding protocol. Make sure that your custom objects implement this protocol if you want to store them in NSUserDefaults, since it doesn't know how to serialize the data otherwise.
To be more exact, you'll have to add these methods to your class implementation:
- (id)initWithCoder:(NSCoder *)coder {
self = [self initWithID:[coder decodeObjectForKey:#"id"] AndBody:[coder decodeObjectForKey:#"body"] AndTitle:[coder decodeObjectForKey:#"title"]];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[encoder encodeObject:postID forKey:#"id"];
[encoder encodeObject:postBody forKey:#"body"];
[encoder encodeObject:postTitle forKey:#"title"];
}
This will allow the data to be serialized by NSCoder. Once you've done this, you should clear all the information currently stored by NSUserDefaults to make sure that it doesn't contain any more NSStrings, but then everything should work properly. Of course, you'll have to update these two methods if you change the data stored by your WSWCMPost object.
Another thing to mention, you're having collisions with your getters/setters and their respective instance variables. So your implementation is:
interface
#interface WSWCMPost : NSObject
{
NSString *postBody; // don't need to do these anymore for properties
NSString *postTitle;
NSString *postID;
}
#property (nonatomic, retain) NSString *postBody, *postTitle, *postID;
implementation
#implementation WSWCMPost
#synthesize postBody, postTitle, postID;
- (id)init {
self = [super init];
if(self) {
postID = #"none"; // not prefixing your variables with 'self' so they are not getting retained
postBody = #"none";
postTitle = #"none";
}
}
#end
Here's how you should be writing those out:
interface
/** NOTE: No need to specify your instance variables here anymore, just the properties */
#interface WSWCMPost : NSObject
#property (nonatomic, retain) NSString *postID;
#property (nonatomic, retain) NSString *postTitle;
#property (nonatomic, retain) NSString *postBody;
implementation
#implementation WSWCMPost
/** Now you specify the corresponding instance variable name alongside the property name */
#synthesize postBody=_postBody, postTitle=_postTitle, postID=_postID;
- (id)init {
self = [super init];
if(self) {
self.postID = #"none"; //getting retained
self.postBody = #"none";
self.postTitle = #"none";
}
}
That would definitely cause data to be released too soon.
So the previous way you could type in self.postID or postID and the compiler wouldn't complain. The difference is when you type postID it is actually setting the member variable and not retaining it... where self.postID will release whatever it is currently set to and retain the new value if it's different.
By declaring your properties the new way, you have to either call the setter as self.postID or set the underlying instance variable as _postID. A lot of early iPhone books had you bang out properties that way and it just ends up causing all sorts of memory issues.
Hope this helps!
UPDATE!!!
You forgot to return self in your constructor ;) I bet that's it
- (id)init {
self = [super init];
if(self) {
self.postID = #"none"; //getting retained
self.postBody = #"none";
self.postTitle = #"none";
}
return self; // THIS IS WHY, you're constructor doesn't return an instance of the class... add this please
}
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title {
if(( self = [super init] ))
{
self.postTitle = title;
self.postID = ID;
self.postBody = body;
}
return self;
}
Your output definitely shows what was wrong in your code.
2012-06-25 21:51:07.691 WSWCM[4049:907] -[__NSCFString postID]: unrecognized selector sent to instance 0x1d003e80
2012-06-25 21:51:07.696 WSWCM[4049:907] Caught exception -[__NSCFString postID]: unrecognized selector sent to instance 0x1d003e80
These two lines tell you that NSString object does not recognize selector postID. This hint should be enough to find out where you need to see in depth.
See this Storing custom objects in an NSMutableArray in NSUserDefaults for more information.
I have the following properties defined:
#property (nonatomic, retain) NSString *name_;
#property (nonatomic, retain) NSString *profilePicture_;
#property (nonatomic, retain) NSString *username_;
#property (nonatomic, retain) NSString *id_;
and I set them up in my init... like this:
-(id)initWithData:(NSDictionary *)data
{
self = [super init];
if (!self) {
return nil;
}
name_ = [data valueForKey:#"full_name"];
profilePicture_ = [data valueForKey:#"profile_picture"];
username_ = [data valueForKey:#"username"];
id_ = [data valueForKey:#"id"];
return self;
}
with the following dealloc:
-(void)dealloc
{
[name_ release];
[username_ release];
[profilePicture_ release];
[id_ release];
[super dealloc];
}
However the dealloc gives me an error:
pointer being freed was not allocated
Why is this? Do I have to do [[NSString alloc] init...] or [NSString stringWithString:]?
valueForKey: will return an autoreleased object, therefore you have no ownership. As they are all strings you can just call copy like this
name_ = [[data valueForKey:#"full_name"] copy];
profilePicture_ = [[data valueForKey:#"profile_picture"] copy];
username_ = [[data valueForKey:#"username"] copy];
id_ = [[data valueForKey:#"id"] copy];
you should also change your #property declarations to use copy as this is generally recommended for strings.
The other alternative is to go through the synthesised accessors but I generally avoid doing this in either init or dealloc
This is because you are assigning to backing variables in your initWithData. You should use rewrite your code as follows:
self.name_ = [data valueForKey:#"full_name"];
self.profilePicture_ = [data valueForKey:#"profile_picture"];
self.username_ = [data valueForKey:#"username"];
self.id_ = [data valueForKey:#"id"];
This would assign values through properties, which calls [retain] for you. The way your code is written now, the pointer is simply copied into ivars without calling [retain], which ultimately causes the issue that you describe.
Since you are using data from dictionary, you should set values through properties using self keyword.
If it doesn't solve, then problem is probably not inside your class but perhaps where you create the instance of it. Try examining the Code where you allocate and release the instance of this Class.
You should also profile your app using Simulator & NSZombies n Determine where you over-release the object.
How can I call a method with multiple params like below with performSelectorInBackground?
Sample method:
-(void) reloadPage:(NSInteger)pageIndex firstCase:(BOOL)firstCase;
The problem is that performSelectorInBackground:withObject: takes only one object argument. One way to get around this limitation is to pass a dictionary (or array) of arguments to a "wrapper" method that deconstructs the arguments and calls your actual method:
- (void)callingMethod {
NSDictionary * args = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInteger:pageIndex], #"pageIndex",
[NSNumber numberWithBool:firstCase], #"firstCase",
nil];
[self performSelectorInBackground:#selector(reloadPageWrapper:)
withObject:args];
}
- (void)reloadPageWrapper:(NSDictionary *)args {
[self reloadPage:[[args objectForKey:#"pageIndex"] integerValue]
firstCase:[[args objectForKey:#"firstCase"] boolValue]];
}
- (void)reloadPage:(NSInteger)pageIndex firstCase:(BOOL)firstCase {
// Your code here...
}
This way you're only passing a "single" argument to the backgrounding call, but that method can construct the multiple arguments you need for the real call (which will take place on the same backgrounded thread).
I've just found this question and wasn't happy with any of the answers. In my opinion neither make good use of the tools available, and passing around arbitrary information in arrays and dictionaries generally worries me.
So, I went and wrote a small NSObject category that will invoke an arbitrary selector with a variable number of arguments:
Category Header
#interface NSObject (NxAdditions)
-(void)performSelectorInBackground:(SEL)selector withObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
#end
Category Implementation
#implementation NSObject (NxAdditions)
-(void)performSelectorInBackground:(SEL)selector withObjects:(id)object, ...
{
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
// Setup the invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
// Associate the arguments
va_list objects;
va_start(objects, object);
unsigned int objectCounter = 2;
for (id obj = object; obj != nil; obj = va_arg(objects, id))
{
[invocation setArgument:&obj atIndex:objectCounter++];
}
va_end(objects);
// Make sure to invoke on a background queue
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithInvocation:invocation];
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperation:operation];
}
#end
Usage
-(void)backgroundMethodWithAString:(NSString *)someString array:(NSArray *)array andDictionary:(NSDictionary *)dict
{
NSLog(#"String: %#", someString);
NSLog(#"Array: %#", array);
NSLog(#"Dict: %#", dict);
}
-(void)someOtherMethod
{
NSString *str = #"Hello world";
NSArray *arr = #[#(1337), #(42)];
NSDictionary *dict = #{#"site" : #"Stack Overflow",
#"url" : [NSURL URLWithString:#"http://stackoverflow.com"]};
[self performSelectorInBackground:#selector(backgroundMethodWithAString:array:andDictionary:)
withObjects:str, arr, dict, nil];
}
Well, I have used this:
[self performSelectorInBackground:#selector(reloadPage:)
withObject:[NSArray arrayWithObjects:pageIndex,firstCase,nil] ];
for this:
- (void) reloadPage: (NSArray *) args {
NSString *pageIndex = [args objectAtIndex:0];
NSString *firstCase = [args objectAtIndex:1];
}
with performSelectorInBackground you can only pass one argument, so make a custom object for this method to hold your data, itll be more concise than an ambiguous dictionary or array. The benefit of this is you can pass the same object around when done containing several return properties.
#import <Foundation/Foundation.h>
#interface ObjectToPassToMethod : NSObject
#property (nonatomic, strong) NSString *inputValue1;
#property (nonatomic, strong) NSArray *inputArray;
#property (nonatomic) NSInteger returnValue1;
#property (nonatomic) NSInteger returnValue2;
#end
and pass that object to your method:
ObjectToPassToMethod *obj = [[ObjectToPassToMethod alloc] init];
obj.inputArray = #[];
obj.inputValue1 = #"value";
[self performSelectorInBackground:#selector(backgroundMethod:) withObject:obj];
-(void)backgroundMethod:(ObjectToPassToMethod*)obj
{
obj.returnValue1 = 3;
obj.returnValue2 = 90;
}
make sure to clean up the object when done to prevent memory leaks
Assume I have a interface like:
#interface it:NSObject
{
NSString* string;
}
#end
#implement it
-(id)init
{
if(self = [super init]){
self.string = [[NSString alloc]init];
}
}
-(void)dealloc
{
[self.string release];
[super release];
}
#end
If I use this class in another file and I call this:
it ait = [[it allow] init];
NSString* anotherString = [[NSString alloc] init];
ait.string = anotherString;
[anotherString release];
Will this cause the string alloced in init method of it a memory leak?
Since the string is not referenced and not autoreleased.
If I don't alloc a string in the init method, what will happen when I call ait.string right before assign anotherString to it?
I think you need
#property (nonatomic, retain) NSString *string;
in your interface and
#synthesize string;
in your implementation for self.string to work.
Then, when you do
self.string = [[NSString alloc] init];
in your init method the string will actually have a retain count of 2 because [[NSString alloc] init]will return a string with a retain count of 1, and using self.string = will retain the object again because the property is declared as 'retain'. This will result in a memory leak, but you can fix by having:
-(id)init
{
if(self = [super init]){
self.string = #"initString";
}
}
or similar.
Then, onto the actual question, if you do as above the string allocated in init wont leak when you reassign with self.string = because properties marked as 'retain' release their current object before retaining the new one.
If you don't assign a string to self.string in the init method it doesn't matter because self.string will just return nil, which you can then deal with in your program.
Will this cause the string alloced in
init method of it a memory leak? Since
the string is not referenced and not
autoreleased.
Yes, exactly. You seem to have got it.
If I don't alloc a string in the init
method, what will happen when I call
ait.string right before assign
anotherString to it?
Do you mean in the following case you are unsure what unknownObject would refer to?:-
It *ait = [[It allow] init];
NSString *unknownObject = ait.string;
This is nonsense. Did you forget to add the accessor methods?
Objective-c doesn't use 'dot syntax' to access instance variables, like, say, Java. If you have an instance of your class 'it' you can only access the 'string' variable from outside that instance by calling the accessor 'getter' method. This is not optional self.string is just a shortcut for the method call [self string] and this method doesn't exist in the code you showed.
Assuming the accessor method are defined elsewhere, the instance variable you called string (this is the worlds worst variable name) is equal to nil. In Objective-c you have to treat nil objects very carefully as the behaviour is different from how many other languages treat the similar null.
In Objective-c this is fine:
NSString *nilString = nil;
[nilString writeToFile:#"/this_file_cannot_exist.data"];
Many other languages would crash here or throw an exception; This can be dangerous, as an operation may fail but your app will continue running. It can also be great tho, because in other languages you will see lots of this..
someObject = controller.currentValue()
if( someObject!=null )
someObject.writeToFile("myFile.data")
In Objective-c the 'if(..)' line isn't needed at all.
You must be careful not to call the accessor method inside your init and dealloc methods, as this can break subclasses. Instead of
- (void)dealloc {
[self.string release]; // This is [[self string] release]
...
you should just use
- (void)dealloc {
[string release];
...
As well as being dangerous the call to [self string] is unnecessary. The same is true in your init methods
if(self=[super init]){
self.string = [[NSString alloc]init]; // shortcut for [self setString:[[NSString alloc] init]]
...
just use
if(self=[super init]){
string = [[NSString alloc] init];
...
You're missing the #property in order to use self.string.
Add this to your .h file
#property (readwrite, copy) NSString *string;
Using Copy instead of Retain will prevent the string from memory-leak even if you release anotherString.
#interface it:NSObject
{
NSString* string;
}
//you should declare a property in order to call it with 'self' prefix
#property (nonatomic, retain) NSString* string;
#end
#implementation it
//you should synthesize your property
#synthesize string;
-(id)init
{
if(self = [super init]){
//you don't to initialize NSString right here (memory leak will have place)
//self.string = [[NSString alloc]init];
//you can store a default value like this (can be omitted):
self.string = #"Default value";
}
return self;
}
-(void)dealloc
{
[self.string release];
[super release];
}
#end
And everything regarding memory management in this class will be fine.