I'm in the process of introducing Swift to an Objective-C app.
I have a UIViewController subclass where I want to hide a button if a certain property is nil. My Swift code looks like this, using optional binding:
if let optionalVar = modelObject.propertyThatCouldBeNil {
button.setTitle(...)
} else {
button.hidden = true
}
My hope was that when propertyThatCouldBeNil was nil, the if wasn't satisfied and control would continue to the else. But that's not what's happening. In fact if I set a breakpoint, what I see is...
(lldb) po optionalVar
nil
(lldb) po modelObject.propertyThatCouldBeNil
▿ Optional<Optional<String>>
- some : nil
That latter one is what's tripping me up a bit. I think it should be Optional, not nested, correct?
Some more info...
Since I'm using modelObject throughout this class, for convenience it's defined as an implicitly unwrapped optional...
var modelObject : ModelObjectClass!
ModelObjectClass is actually an Objective-C class, and the property is read only and declared as such...
#property (readonly, nullable) NSString *propertyThatCouldBeNil;
In it's implementation, it actually acts as a sort of proxy for another ready only property...
- (NSString*) propertyThatCouldBeNil {
return self.someOtherProperty;
}
And the someOtherProperty is nullable as well...
#property (nullable, nonatomic, retain) NSString *someOtherProperty;
Any insight as to why the optional binding isn't working as I expect?
Seems to be working with me
#interface ModelObjectClass : NSObject
#property (readonly, nullable) NSString *propertyThatCouldBeNil;
#property (nullable, nonatomic, retain) NSString *someOtherProperty;
- (nullable NSString*) propertyThatCouldBeNil;
#end
#implementation ModelObjectClass
- (nullable NSString*) propertyThatCouldBeNil {
return self.someOtherProperty;
}
#end
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 just noticed that, for some reason, I don't seem to have automatically created underscore iVars in my iOS 7 project, and I wonder why that is. My setup:
MyClass.h
#property (readonly) NSNumber *aNumber;
MyClass.m
#interface MyClass ()
#property (readwrite, strong) NSNumber *aNumber;
#end
#implementation MyClass
(...)
- (NSNumber *)aNumber {
return _aNumber;
}
- (void)setANumber:(NSNumber *)aNumber {
_aNumber = aNumber;
}
#end
This results in Use of undeclared identifier: '_aNumber'.
Why is that so? I thought that underscore iVars are always automatically synthesized? Is it because of the class extension I use? If I put in #synthesize aNumber = _aNumber; it (obviously) works.
There is one exception to the automatic synthesize rule.
If you override both the getter and the setter of a property then you will have to manually synthesize the property.
This has been the case ever since auto synthesis came in.
Just add the #synthesize line and it will be fine.
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'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.
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;