Xcode requiring me to redeclare properties as instance variables - ios

I have an object called SCPFAd and it is declared in its header file as follows:
#interface SCPFAd : NSObject
#property (strong, nonatomic) NSArray *imageURLs;
#property (strong, nonatomic) NSString *title;
#property (strong, nonatomic) NSString *price;
#property (strong, nonatomic) NSString *longDescription;
#property (strong, nonatomic) SCPFLocation *location;
#property (strong, nonatomic) SCPFCategory *category;
#property (strong, nonatomic) NSArray *properties;
#property (readonly, strong, nonatomic) NSString *sellerID;
#property (readonly, strong, nonatomic) NSString *timePosted;
- (id)initWithRawData:(NSDictionary *)rawData;
- (BOOL)displaysPrice;
#end
In the implementation file, I have an SCPFAd extension declared this way:
#interface SCPFAd ()
{
NSMutableDictionary *_rawData;
NSMutableArray *_imageURLs;
NSString *_title;
NSString *_price;
NSString *_longDescription;
SCPFLocation *_location;
SCPFCategory *_category;
NSMutableArray *_properties;
}
#property (strong, nonatomic) NSDictionary *rawData;
#property (strong, nonatomic) NSString *sellerID;
#property (strong, nonatomic) NSString *timePosted;
#property (strong, nonatomic) NSString *adID;
#end
I deliberately redeclared the properties rawData, imageURLs, and properties as instance variables because I want external objects to access or assign them as immutable types, but I'll be changing them internally.
What I don't understand is why, when I override the setters, I get a compiler error that says it can't find the variables _title, _price, _longDescription, _location, and _category. The error goes away when I redeclare title, price, longDescription, location, and category as above, but I see it as unnecessary--nothing in the class extension changes their external declarations.
This is how I'm overriding setTitle, for example:
- (void)setTitle:(NSString *)title
{
_title = title;
_rawData[#"name"] = title;
}
- (NSString *)title
{
if (!_title) {
_title = _rawData[#"name"];
}
return _title;
}
If I comment out NSString *_title; in the extension, the compiler says it can't find _title in the first line of the setter, and wherever it occurs in the getter. The getter used to work just fine, though, even without the redeclaration.

If you declare a property and then override both the getter and setter, it won't auto-synthesize the property. But you can just add a line to synthesize it to your implementation:
#synthesize title = _title;
As for having a property be an immutable type, and its backing instance variable be mutable, you're going to have an issue when from outside your class the immutable type is assigned to it, and you treat it as the mutable version, because it won't respond to the methods to mutate it. For example, you assign an NSArray to a variable, then try to treat it as an NSMutableArray, it won't work.

If you implement a getter, the compiler doesn't automatically create an ivar.
This is for a good reason. The property may (and, in my experience, usually is) created on request and returned, so in that case no instance variable is needed to store it and it would add a significant memory overhead to classes with a large number of such properties if every getter had an associated ivar.
One other comment. This:
NSMutableDictionary *_rawData;
// ...
#property (strong, nonatomic) NSDictionary *rawData;
May cause you problems. If rawData is set with an immutable dictionary, it will raise an exception when you attempt to mutate it later. Make sure you copy it on assign using -mutableCopy. (I assume you aren't copying it because it's marked strong, not copy. If you are, it's fine)

When you override the setter and getter (not just the getter), Xcode assumes you want complete control and doesn't create the backing store (the _title). You have to do it yourself with
#synthesize title = _title

If you implement a getter and a setter for a read-write property, or a getter for a read-only property then Clang (Xcode) will not synthesise the backing instance variable - see Apple's Encapuslating Data, note in the section You Can Implement Custom Accessor Methods.
You are implementing both the setter and the getter so you must provide your own instance variable if needed.

Related

Why should I not use object_setIvar to set properties

I have a dictionary containing data for user from a REST endpoint. Here is my user class
#import <Foundation/Foundation.h>
#interface User : NSObject
#property (strong, nonatomic) NSString *uid;
#property (strong, nonatomic) NSString *email;
#property (strong, nonatomic) NSString *firstName;
#property (strong, nonatomic) NSString *lastName;
#property (assign, nonatomic) int status;
#end
I have a method to set all properties
/*
* set properties
*/
- (void)setPropertiesWithDictionary:(NSDictionary *)dictionary{
for(NSString *key in dictionary){
object_setIvar(self,
class_getInstanceVariable([self class], [[#"_" stringByAppendingString:key] UTF8String]),
dictionary[key]);
}
}
Instead of doing something like
[user setUid:#dictionary[#"uid"]];
I want to call my method like this
[user setPropertiesWithDictionary: dictionary];
Just wondering if implementing object_setIvar this way is fine. If not - Would be really great if you can explain why. Thanks in advance.
Do whatever you like, but why reinvent the wheel when key value coding (KVC) already exists? Just call this method:
https://developer.apple.com/documentation/objectivec/nsobject/1417515-setvaluesforkeyswithdictionary?language=objc
KVC does what you're trying to do, but it does it a lot better than you're likely to do it.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html
I think your problem might occur with "int status", because dictionary[# "status"] is not of type int.
In your code implementation,user.status = dictionary[# "status"],this result is unpredictable.
Unless you make a type judgment, user.status = [dictionary[# "status"]intValue];
I recommend a third-party framework on github called MJExtension that fulfills your needs.You can look at the source code.

making one property strong,nonatomic for objective-c

I have a multi view application and use an object to keep track of my logged in user. My User.h looks like this
#interface User : NSObject
#property (strong, nonatomic) NSDictionary *data;
#property (weak, nonatomic) NSString *uid;
#property (weak, nonatomic) NSString *firstName;
#property (weak, nonatomic) NSString *lastName;
#property (weak, nonatomic) NSString *dob;
#property (weak, nonatomic) NSString *gender;
#property (weak, nonatomic) NSString *avatarURL;
#property (assign, nonatomic) NSInteger status;
- (void)setPropertiesWith:(NSDictionary *)data;
And the User.m looks like this
#import "User.h"
#implementation User
/*
* set properties
*/
- (void)setPropertiesWith:(NSDictionary *)data{
self.data = data;
self.uid = self.data[#"uid"];
self.firstName = self.data[#"firstName"];
self.lastName = self.data[#"lastName"];
self.dob = self.data[#"dob"];
self.gender = self.data[#"gender"];
self.status = [[self.data valueForKeyPath:#"status"] intValue];
self.avatarURL = self.data[#"avatarURL"];
}
#end
I had the data as weak, but in one of the views it would turn up null - I believe ARC was releasing it. Please correct me if I am wrong.
I have 2 questions:
With this setup, the data being strong and the rest of the properties being weak, is there any potential risk to this?
Should I make the data an ivar and keep the rest as is?
There is no actual reason(other than my poor class design skills) for the existence of the properties. I just find it very interesting and wanted to understand what is going on.
You asked:
With this setup, the data being strong and the rest of the properties being weak, is there any potential risk to this?
Yes, if you nil the dictionary, all of your properties would likely become nil, assuming you don’t have other strong references to them elsewhere.
Should I make the data an ivar and keep the rest as is?
I wouldn’t even make it an ivar (unless there’s some other requirement for saving this that you haven’t shared with us). It should just be a local variable, and make your properties copy (or strong).
I’d suggest (a) getting rid of the NSDictionary property and (b) making the NSString properties be copy (or strong), not weak. Also, rather than having a setPropertiesWith method, I’d just define an initializer:
// User.h
#interface User : NSObject
#property (copy, nonatomic) NSString *uid;
#property (copy, nonatomic) NSString *firstName;
#property (copy, nonatomic) NSString *lastName;
#property (copy, nonatomic) NSString *dob;
#property (copy, nonatomic) NSString *gender;
#property (copy, nonatomic) NSString *avatarURL;
#property (assign, nonatomic) NSInteger status;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
#end
And
// User.m
#implementation User
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
if ((self = [super init])) {
self.uid = dictionary[#"uid"];
self.firstName = dictionary[#"firstName"];
self.lastName = dictionary[#"lastName"];
self.dob = dictionary[#"dob"];
self.gender = dictionary[#"gender"];
self.status = [dictionary[#"status"] intValue];
self.avatarURL = dictionary[#"avatarURL"];
}
return self;
}
#end
And then, the caller would do:
User *user = [[User alloc] initWithDictionary:someDictionary];
There are other refinements you could consider here (e.g. readonly public interface, declaring nullability, lightweight generics on the dictionary, etc.), but the above is probably a good starting point.
By the way, if your wondering why I made these copy instead of strong, we just want to protect ourselves in case the caller passed a NSMutableString (which is a NSString subclass) and accidentally mutated it later. This is just a bit safer, a little more defensive pattern.

How to define Objective-C lightweight generic for Array of Arrays of NSNumbers?

I have an object with a property of NSArray that contains other NSArrays of NSNumbers. I've added a lightweight generic to the property definition in the header file like so:
#property (strong, nonatomic, readonly) NSArray<NSArray *> *myArray;
The generated Swift interface shows:
public var myArray: [[AnyObject]] { get }
Is there a way that I can further mark up my property declaration to indicate that this is actually [[NSNumber]]? The compiler gets upset with me when I try NSArray<NSArray *<NSNumber *>> *myArray; or NSArray<NSArray *><NSNumber *> *myArray;.
#property (strong, nonatomic, readonly) NSArray<NSArray<NSNumber *> *> *myArray;
All the stars have move to the left.

accessing classes from another class in objective c

I have two classes.
GameData.h
#import "TeamData.h"
#property (assign, nonatomic) GameData* teamA;
TeamData.h
#interface TeamData : NSObject
#property (nonatomic, copy) NSString* teamName;
-(void) printTeamData;
A number of questions :
Inside GameData.m I have this code :
TeamData* team = self.teamA;
[team printTeamData];
The first line shows this warning :
Incompatible pointer types from TeamData* with an expression of type TeamData*
In another class, I am including GameData.h and I want to set the teamA name. How do I access that? So I want to fetch the teamA property from the GameData class and set its name property.
In GameData.h, your property points to its own class, not to TeamData
#property (assign, nonatomic) GameData* teamA;
assign is meant for primitive types such as BOOL or NSInteger.
The parent class should hold a strong reference to a child object.
So your property would be better off as
#property (strong, nonatomic) TeamData* teamA;
As for setting the teamA property, you would call setTeamA: on your GameData instance:
[myGameData setTeamA:...];

Core Data EXC_BAD_ACCESS for non-zero integer values

I have two core data models with int64_t properties. One of them works fine while the other throws EXC_BAD_ACCESS when I try to assign a non-zero value to the integer field. I've read the answers that say to recreate the NSManagedObject child class and I have done with no success. The broken class looks like this:
#interface NoteObject : NSManagedObject
#property (nonatomic) int64_t remoteID;
#property (nonatomic) int64_t remoteArticleID;
#property (strong, nonatomic) ArticleObject *article;
#property (strong, nonatomic) NSString *status;
#property (strong, nonatomic) NSString *token;
#property (strong, nonatomic) NSString *title;
#property (strong, nonatomic) NSString *noteContent;
#property (strong, nonatomic) NSDate *pubDate;
#property (strong, nonatomic) NSDate *modDate;
#end
#implementation NoteObject
#dynamic remoteID;
#dynamic remoteArticleID;
#dynamic article;
#dynamic status;
#dynamic token;
#dynamic title;
#dynamic noteContent;
#dynamic pubDate;
#dynamic modDate;
#end
The offending line is in this block:
_noteObject = [NSEntityDescription insertNewObjectForEntityForName:#"Note" inManagedObjectContext:self.managedObjectContext];
_noteObject.remoteArticleID = 0; // this works
_noteObject.remoteArticleID = 1; // this crashes
What really has me stumped is that in another model I have the same fields with the same types and they will accept non-zero values without any trouble:
bookmarkObject = [NSEntityDescription insertNewObjectForEntityForName:#"Bookmark" inManagedObjectContext:self.managedObjectContext];
bookmarkObject.remoteArticleID = 0; // this works
bookmarkObject.remoteArticleID = 1; // this works, too
Is there anything in my .xcdatamodeld file that could be causing this?
EDIT
My data models look like this:
I had exactly the same problem.
It appears that xcode (or perhaps the compiler, or perhaps the two between them) sometimes gets confused when you manually edit properties in the NSManagedObject - it ends up treating our integers as pointers and trying to access memory directly - hence the EXC_BAD_ACCESS.
Anyway, as this question explains: SO Question, the solution is to delete your old class (obviously copy out any custom code so you can paste it back again later) and then get xcode to regenerate it for you (select the entity in the data model and select "Editor / Create NSManagedObject subclass..."). In the dialogue that appears, make sure "Use scalar properties for primitive data types" is ticked.
You may have to manually edit the resulting class to turn some non scalar properties back into objects (I had a date object which it turned into something other than NSDate - I forget exactly what, but it accepted the manually made edit back to NSDate).
It worked for me. Hope it works for you.
Ali
Well, in case anyone else is having this issue, I never found a satisfactory answer for why one entity was working and the other wasn't. My workaround was to refactor the properties to use NSNumber wrappers instead of primitive int64_t values.
#property (strong, nonatomic) NSNumber *remoteID;
#property (strong, nonatomic) NSNumber *remoteArticleID;
Of course, that means boxing/unboxing the integer values.
_noteObject.remoteArticleID = [NSNumber numberWithInt:1];
int intVar = [_noteObject.remoteArticleID intValue];
In your model file, check that the entity's "Class" property is set to the appropriate class, and not the default NSManagedObject.
If you leave it as NSManagedObject, Core Data will create properties itself on a custom NSManagedObject subclass it generates itself, rather than using your own subclass. Most getters and setters will appear to work, but you may have issues with non-boxed primitive properties and custom getters and setters.

Resources