I'm developing an iOS 5.0+ app and I'm creating a Category for an UIButton:
#interface UIButton (NotificationBall)
#property (nonatomic, assign) NSInteger type;
#property (nonatomic, assign) NSInteger index;
#end
And its implementation:
#import "UIButton+NotificationBall.h"
#implementation UIButton (NotificationBall)
#dynamic type;
#dynamic index;
#end
Searching on internet I've found this question, but I haven't found any examples with NSInteger.
Do I need to use NSNumber instead of NSInteger?
If I use, NSNumber, what do I have to do?
Only Objective-C objects can be set as
associated objects, scalars cannot be used directly.
So you could declare the property as
#property (nonatomic, strong) NSNumber *type;
and directly use the code from the answer https://stackoverflow.com/a/5500525/1187415
that you referenced to.
Or you keep the NSInteger property, and wrap/unwrap it to NSNumber
in the getter/setter method like this:
-(void)setType:(NSInteger)type
{
objc_setAssociatedObject(self, &UIB_TYPE_KEY, #(type), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSInteger)type
{
return [objc_getAssociatedObject(self, &UIB_TYPE_KEY) integerValue];
}
Remark: "type" and "index" are quite common names. You should consider prepending the property names with some prefix, to
avoid a possible name collision with existing properties of UIButton.
Related
I'm trying to implement a Dynamic property in my project
This is my code.
MyClass.h
#interface MyClass : UIView
#property (strong, nonatomic) NSString *name;
#end
MyClass.m
#implementation MyClass
#dynamic name;
-(void)setName:(NSString *)name{
self.name = name;
}
#end
But when I run my app has crashed.
When I use an ivar had this error.
A property is just a bundle of two methods: a getter and a setter. So, when you write
#property (strong, nonatomic) NSString *name;
what you are really saying is
- (NSString *)name;
- (void)setName:(NSString *)name;
After that, each time the compiler encounters an expression of the form obj.name, it translates it to [obj name]. And each time you see a statement like obj.name = #"hello";, the compiler translates it to [obj setName:#"hello"].
The next thing is you have to make sure the property behaves properly. You have many options:
Write getters and setters manually, referring to an iVar
Synthesize getter and setter
Autosynthesize getter and setter
Write custom getters and setters
Use #dynamic to avoid compile time warnings, because you intend to do runtime magic. (Really, that's not what you want to do, because you need to understand the basics first.)
Write getters and setters manually, referring to an iVar
#interface MyClass : UIView {
NSString *_name;
}
#property (strong, nonatomic) NSString *name;
#end
and in the implementation
#implementation MyClass
- (NSString *)name {
return _name;
}
- (void)setName:(NSString *)name {
_name = name;
}
#end
Synthesize getter and setter
The last section is basically equivalent to this
#interface MyClass : UIView {
NSString *_name;
}
#property (strong, nonatomic) NSString *name;
#end
#implementation MyClass
#synthesize name = _name;
#end
Autosynthesize getter and setter
In practice, you would just use "autosynthetisation".
#interface MyClass : UIView
#property (strong, nonatomic) NSString *name;
#end
#implementation MyClass
#end
This means,
if you just declare a property
don't call #synthesize or #dynamic
don't implement any custom getter and setter
the code above will just create an iVar named _name and a getter and setter that looks exactly like the one in the first example.
This means that the the first two and this sections are equivalent, because they produce the same code.
Write custom getters and setters
This is what the term "dynamic property" really means. For example, you may want the name to be always uppercase. So you may write a property like this.
#interface MyClass : UIView {
NSString *_name;
}
#property (copy, nonatomic) NSString *name;
#end
#implementation MyClass
- (NSString *)name {
return _name;
}
- (void)setName:(NSString *)name {
_name = [name uppercaseString];
}
#end
(in the code above, I changed strong to copy - don't worry, this is just a comment anyways. And it's a true one, because the uppercaseString will never be the same, it will always be a copy of the original.)
This is maybe the only really interesting case! For example, this kind of property is what UIKit uses all the time, e.g. the text property of UILabel is a dynamic property like that. It doesn't just set some iVar, but it also makes sure that the visible text on the screen changes too.
#dynamic properties
they are really tricky to get right, and most of the time they are not worth the hassle IMHO.
Note: I simplified some things and left out details which are only detectable when using objc runtime inspection APIs
This StackOverflow answer: https://stackoverflow.com/a/1160545/7833793 does a good job of explaining what the differences between #synthesize and #dynamic are. Typically you use #dynamic if you're delegating the task of implementing the accessors (get, set). It seems to me like you would want to use #synthesize here. But with modern objective c, you shouldn't even need to specify and the iVar will be created for you automatically.
i.e.:
MyClass.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyClass : NSObject
#property (strong, nonatomic) NSString *name;
#end
NS_ASSUME_NONNULL_END
MyClass.m
#import "MyClass.h"
#implementation MyClass
- (void)setName:(NSString *)name {
_name = name;
}
#end
Your solution leads to recursion, you are getting crash since you are not using ivar in setter, try this instead:
-(void)setName:(NSString *)name{
_name = name;
}
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.
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.
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.
I have a singleton I implement in this way:
PhotoViewController* sharedSingleton = [PhotoViewController sharedManager];
I know that to launch a method I have to do [sharedSingleton method];
but what if I want to change an integer declared in the PhotoViewController.h file as NSInteger* tagNumber, hoe can I do that? I tried this:
[sharedSingleton.tagNumber = 1];
but it doesn't work!
EDIT:
error: property tagNUmber not found on object of type photoViewController
#interface PhotoViewController : UIViewController{
BOOL newMedia;
UIPopoverController *popoverController;
DBRestClient *restClient;
NSInteger* tagNumber;
}
+ (PhotoViewController *) sharedManager;
#end
Singletons are regular objects. The only difference is that only one instance will be created from the class.
If you aren't able to set the tagNumber it is likely that some other type of coding error is happening... perhaps the tagNumber property was declared in a class extension, making the accessor/mutator methods private?
If you edit your question with how the tagNumber is declared, and also include the error message you are getting, I'll be able to edit this answer and give you more specific advice.
EDIT: ...and yes, definitely double check to make sure you didn't declare the NSInteger to be a pointer... an NSInteger is a scalar type (so it takes a direct value, and doesn't use the dereference '*' operator).
I suggest using properties instead of accessing the instance variables directly:
#interface PhotoViewController : UIViewController
#property (nonatomic, assign) BOOL newMedia;
#property (nonatomic, strong) UIPopoverController *popoverController;
#property (nonatomic, strong) DBRestClient *restClient;
#property (nonatomic, assign) NSInteger tagNumber;
+ (PhotoViewController *) sharedManager;
#end
Then set the variable without the brackets as:
sharedSingleton.tagNumber = 1;