I have a superclass called SuperClass a read-only property. That looks like this:
#property (nonatomic, strong, readonly) NSArray *arrayProperty;
In a subclass I need an initializer that takes a instance of SuperClass as a parameter:
- (instancetype)initWithSuperClass:(SuperClass *)superClass
I created a GitHub sample project that shows what the problem is: https://github.com/marosoaie/Objc-test-project
I cannot do _arrayProperty = superClass.arrayProperty in the initializer.
I want to keep the property read-only in SubClass as well.
Any ideas on how this could be solved?
I know I could declare the property as readwrite in a class extension inside the SubClass implementation file, but I'm hoping that there's a better solutions than this.
Edit:
SuperClass.h
#import <Foundation/Foundation.h>
#interface SuperClass : NSObject
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
#property (nonatomic, strong, readonly) NSString *stringProperty;
#property (nonatomic, strong, readonly) NSArray *arrayProperty;
#end
SuperClass.m
#import "SuperClass.h"
#implementation SuperClass
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
_arrayProperty = dictionary[#"array"];
_stringProperty = dictionary[#"string"];
}
return self;
}
#end
SubClass.h:
#import <Foundation/Foundation.h>
#import "SuperClass.h"
#interface SubClass : SuperClass
#property (nonatomic, strong, readonly) NSString *additionalStringProperty;
- (instancetype)initWithSuperClass:(SuperClass *)superClass;
#end
SubClass.m:
#import "SubClass.h"
#implementation SubClass
#synthesize additionalStringProperty = _additionalStringProperty;
- (NSString *)additionalStringProperty
{
if (!_additionalStringProperty) {
NSMutableString *mutableString = [[NSMutableString alloc] init];
for (NSString *string in self.arrayProperty) {
[mutableString appendString:string];
}
_additionalStringProperty = [mutableString copy];
}
return _additionalStringProperty;
}
- (instancetype)initWithSuperClass:(SuperClass *)superClass
{
self = [super init];
if (self) {
// Doesn't work
// _stringProperty = superClass.stringProperty;
// _arrayProperty = superClass.arrayProperty;
}
return self;
}
#end
You already exposed an initializer, that writes to that readonly property -initWithDictionary:. Call that in your SubClass, instead [super init]:
- (instancetype)initWithSuperClass:(SuperClass *)superClass {
NSDictionary *dict = #{
#"array": superClass.arrayProperty,
#"string": superClass.stringProperty,
};
self = [super initWithDictionary:dict];
if (self) {
// Nothing here.
}
return self;
}
It’s quite common to have an initializer for readonly properties, although using dictionary is not that good solution. Typically, I would create:
- (instancetype)initWithArray:(NSArray *)array string:(NSString *)string;
First of all, there is a bug in your test setup: Your key in - (instancetype)initWithDictionary:(NSDictionary *)dictionary is #"array", where the array contains #"arrayProperty".
Regarding your problem:
//...
#interface SuperClass : NSObject
{
#protected // this is what you want: a protected class property, accessible in subclasses, but no where else
NSString *_stringProperty;
NSArray *_arrayProperty;
}
#property (nonatomic, strong, readonly) NSString *stringProperty;
#property (nonatomic, strong, readonly) NSArray *arrayProperty;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
#end
// SubClass.m
//...
#implementation SuperClass
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
_arrayProperty = dictionary[#"arrayProperty"]; // this was #"array", so could not work
_stringProperty = dictionary[#"stringProperty"]; // same here
}
return self;
}
#end
Then it works. In addition, I would write
#interface SubClass ()
#property (nonatomic, strong, readwrite) NSString *additionalStringProperty;
#end
#implementation SubClass
- (instancetype)initWithSuperClass:(SuperClass *)superClass
{
self = [super init];
if (self) {
_stringProperty = superClass.stringProperty;
_arrayProperty = superClass.arrayProperty;
}
return self;
}
because I prefer the readwrite property in a class extension over the #synthesize magic. But this is a personal opinion.
One main issue regarding to class design still holds: What happens if (similar to your test setup) the dictionary of the superclass does not contain the key? Then it won't be initialized, which is not a good idea, because you expect them to be initialized. So you should check in the subclass if superclass.stringProperty is not nil and add a standard constructor for the superclass to avoid that the two dictionaries are uninitialized.
In your SuperClass.m:
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
// these were always nil, check your dictionary keys
_arrayProperty = dictionary[#"arrayProperty"];
_stringProperty = dictionary[#"stringProperty"];
}
return self;
}
In your SubClass.m:
#interface SubClass ()
#property (strong, nonatomic) NSString * additionalStringProperty;
#property (strong, nonatomic) NSString * subClassString;
#property (strong, nonatomic) NSArray * subClassArray;
#end
#implementation SubClass
- (instancetype)initWithSuperClass:(SuperClass *)superClass
{
self = [super init];
if (self) {
_subClassString = superClass.stringProperty;
_subClassArray = superClass.arrayProperty;
}
return self;
}
I tried the answers here to no avail. What ended up working for me was this answer which mentions that you should directly access the member variable (after declaring it as protected) like so:
self->_stringProperty = #"some string";
Related
I'm trying to hide a UILabel in every object (UIView) of the same class in my app. I tried something with a static class method but I'm not able to access to the instance variable.
MyView.h
#interface MyView: UIView
{
UILabel *titleLabel;
UILabel *subTitleLabel;
}
+(void)hideLabel;
#end
MyView.m
#import "MyView.h"
#implementation TempNodeView
+(void)hideLabel
{
[titleLabel setHidden:YES];
}
#end
What is the best (proper) solution in this kind of situation?
Thank you very much
For your case I suggest you to have references to all of this objects. This means you will need to add the object into some static array in its constructor.
The problem then occurs that the views will be retained by the array so you need another object that will be a container for a weak reference to your object so you avoid memory leak.
Try to build something like the following:
static NSMutableArray *__containersPool = nil;
#interface MyViewContainer : NSObject
#property (nonatomic, weak) MyView *view;
#end
#implementation MyViewContainer
#end
#interface MyView : UIView
#property (nonatomic, readonly) UILabel *labelToHide;
#end
#implementation MyView
+ (NSMutableArray *)containersPool {
if(__containersPool == nil) {
__containersPool = [[NSMutableArray alloc] init];
}
return __containersPool;
}
// TODO: override other constructors as well
- (instancetype)initWithFrame:(CGRect)frame {
if((self = [super initWithFrame:frame])) {
MyViewContainer *container = [[MyViewContainer alloc] init];
container.view = self;
[[MyView containersPool] addObject:container];
}
return self;
}
+ (void)setAllLabelsHidden:(BOOL)hidden {
for(MyViewContainer *container in [[self containersPool] copy]) {
if(container.view == nil) {
[[self containersPool] removeObject:container]; // It has been released so remove the container as well
}
else {
container.view.labelToHide.hidden = hidden;
}
}
}
#end
How to get associated object from another class
My code is :
#import <UIKit/UIKit.h>
static char NUMBER ='a';
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
#implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
NSNumber *num=#10;
objc_setAssociatedObject(self, &NUMBER, num, OBJC_ASSOCIATION_RETAIN);
}
return self;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p=[[Person alloc]init];
NSNumber *num=objc_getAssociatedObject(p, &NUMBER);
NSLog(#"%#",num);
}
#end
NSLog(#"%#",num) is null.
Why can't I get the associated object from the above code. Can't we get the associated object from another class ? Thank you!
Problem is in your key. You probably define this classes in different files. Do not use static keyword, static variables can only be accessed within a single translation unit. This means you have new copy of NUMBER for each file. Remove static keyword and add extern declaration in Person.h header:
Person.h:
extern const char NUMBER;
#interface Person : NSObject
#end
Person.m:
#import "Person.h"
#import "objc/runtime.h"
const char NUMBER ='a';
#implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
NSNumber *num = #10;
objc_setAssociatedObject(self, &NUMBER, num, OBJC_ASSOCIATION_RETAIN);
}
return self;
}
#end
ViewController.m:
#import "Person.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
NSNumber *num = objc_getAssociatedObject(p, &NUMBER);
NSLog(#"%#",num);
}
#end
I've searched all over google, and stack overflow for a solution, but I wasn't able to find an answer that solved my problem. Sorry for the long post, as I'm trying to give as much information as I can. I'm new to iOS and Objective- c, but not programming in general due to being asked to switch over from Android by my company, so any help is appreciated.
I'm trying to assign a value to an NSString in one class from a TextField in another, but I get the error:
**-[ViewController name:]: unrecognized selector sent to instance 0x78712fe0**
when I run the app in the simulator.
Relevant code:
//UserInfo.h
#import <Foundation/Foundation.h>
#interface UserInfo : NSObject <NSCoding>
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *age;
#property (nonatomic, strong) NSString *address;
#end
//UserInfo.m
#import "UserInfo.h"
static NSString *nameKey = #"userName";
static NSString *ageKey = #"userAge";
static NSString *addressKey = #"userAddress";
static NSString *userInfoKey = #"userInfoKey";
#implementation UserInfo
#synthesize name;
#synthesize age;
#synthesize address;
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.name = [coder decodeObjectForKey:nameKey];
self.age = [coder decodeObjectForKey:ageKey];
self.address = [coder decodeObjectForKey:addressKey];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.name forKey:nameKey];
[coder encodeObject:self.age forKey:ageKey];
[coder encodeObject:self.address forKey:addressKey];
}
#end
//ViewController.h
#import <UIKit/UIKit.h>
#import "UserInfo.h"
#interface ViewController : UIViewController <UITextFieldDelegate, UITextViewDelegate, NSCoding>
#property (nonatomic, strong) UserInfo *userInfoObject;
#property (nonatomic, weak) IBOutlet UILabel *titleLabel;
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic, weak) IBOutlet UILabel *ageLabel;
#property (nonatomic, weak) IBOutlet UILabel *addressLabel;
#property (nonatomic, weak) IBOutlet UITextField *nameText;
#property (nonatomic, weak) IBOutlet UITextField *ageText;
#property (nonatomic, weak) IBOutlet UITextField *addressText;
#property (nonatomic, weak) IBOutlet UIButton *saveBtn;
- (IBAction)saveBtnTouched:(id)sender;
- (void) saveUserInfo;
- (void) loadUserInfo;
- (void) setUserInterfaceValues;
- (IBAction)nameText:(id)sender;
- (IBAction)ageText:(id)sender;
- (IBAction)addressText:(id)sender;
#end
//ViewController.m
#import "ViewController.h"
#import "UserInfo.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize titleLabel;
#synthesize nameLabel;
#synthesize ageLabel;
#synthesize addressLabel;
#synthesize nameText;
#synthesize ageText;
#synthesize addressText;
#synthesize saveBtn;
static NSString *userInfoKey = #"userInfoKey";
- (void)viewDidLoad {
[super viewDidLoad];
[self loadUserInfo];
if(!self.userInfoObject)
{
self.userInfoObject = [[UserInfo alloc] init];
}
[self setUserInterfaceValues];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
self.saveBtn.enabled = YES;
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField*)textField
{
return YES;
}
- (BOOL) textFieldShouldEndEditing:(UITextField *)textField
{
[self.nameText resignFirstResponder];
[self.ageText resignFirstResponder];
[self.addressText resignFirstResponder];
return YES;
}
- (IBAction)saveBtnTouched:(id)sender
{
NSLog(#"%# was entered into the name field", self.nameText.text);
NSLog(#"%# was entered into the age field", self.ageText.text);
NSLog(#"%# was entered into the address field", self.addressText.text);
[self textFieldShouldEndEditing:self.nameText];
[self textFieldShouldEndEditing:self.ageText];
[self textFieldShouldEndEditing:self.addressText];
self.userInfoObject.name = self.nameText.text;
self.userInfoObject.age = self.ageText.text;
self.userInfoObject.address = self.addressText.text;
[self saveUserInfo];
self.saveBtn.enabled = NO;
}
- (void)saveUserInfo
{
NSData *userInfoData = [NSKeyedArchiver archivedDataWithRootObject:self.userInfoObject];
[[NSUserDefaults standardUserDefaults] setObject:userInfoData forKey:userInfoKey];
}
- (void)loadUserInfo
{
NSData *userInfoData = [[NSUserDefaults standardUserDefaults] objectForKey:userInfoKey];
if(userInfoData)
{
self.userInfoObject = [NSKeyedUnarchiver unarchiveObjectWithData:userInfoData];
}
}
- (void) setUserInterfaceValues
{
self.nameText.text = self.userInfoObject.name;
self.ageText.text = self.userInfoObject.age;
self.addressText.text = self.userInfoObject.address;
}
- (IBAction)nameText:(id)sender {
}
- (IBAction)ageText:(id)sender {
}
- (IBAction)addressText:(id)sender {
}
#end
//AppDelegate.h
#import <UIKit/UIKit.h>
#import "ViewController.h"
#import "UserInfo.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) ViewController *viewController;
#end
//AppDelegate.m
#import "AppDelegate.h"
#import "ViewController.h"
#import "UserInfo.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
//all the other generated methods. Taken out due to space.
#end
Three breakpoints are set that are supposidly the source of the problem:
From the ViewController.m, in - (void)setUserInterfaceValues
self.nameText.text = self.userInfoObject.name;
(I assume that this applies to the other two lines below it also)
Also from the ViewController.m, in - (void)viewDidLoad
[self setUserInterfaceValues];
And finally from the AppDelegate.m, in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
self.window.rootViewController = self.viewController;
From what I understand, and have learned from searching this issue, the app is trying to send data to something that doesn't exist, the NSString name being the culprit. Others have suggested to make sure that my .xib file is connected to the ViewController, and I have verified that it is.
As another bit of information, I'm not using a storyboard for this app, and instead am using the interface builder. I'm aware that there are advantages to storyboards, and I would like to be using them, but my company uses the interface builder and does a lot of things programmatically, so I'm learning to develop without.
[EDIT]: Issue solved thanks to Ian.
I have a class
#interface AppRecord : NSObject
#property (nonatomic, retain) NSString * urlSingle;
#property (nonatomic, retain) NSArray * image_url;
#end
It is included in another class
#class AppRecord;
#interface IconDownloader : NSObject
#property (nonatomic, strong) AppRecord *appRecord;
#end
This is my root view controller
#import "IconDownloader.h"
#implementation RootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageDownloadsInProgress = [NSMutableDictionary dictionary];
}
- (void)startIconDownload:(AppRecord *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
IconDownloader *iconDownloader = [self.imageDownloadsInProgress objectForKey:indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[IconDownloader alloc] init];
int imgArrCount=[appRecord.image_url count];
NSLog(#"Image array is********************** %#",appRecord.image_url);
for(int i=0;i<imgArrCount;i++)
{
iconDownloader.appRecord.urlSingle=[appRecord.image_url objectAtIndex:i];
NSLog(#"iconDownloader.appRecord.urlSingle---------------------%#",iconDownloader.appRecord.urlSingle);
}
}
}
#end
Can i assign iconDownloader.appRecord.urlSingle here, I am having null value.Please help.
This has nothing to do with forward declaration. When you forward declare a class, you should #import the .h file before using any of the class properties/methods.
The problem is property appRecord in iconDownloader is not created yet and hence is nil. In your code you should do this.
- (void)startIconDownload:(AppRecord *)appRecord forIndexPath:(NSIndexPath *)indexPath
//...
for(int i=0;i<imgArrCount;i++)
{
// First assign to the property so that it is not nil
iconDownloader.appRecord = appRecord;
// If required then make this assignment
iconDownloader.appRecord.urlSingle=[appRecord.image_url objectAtIndex:i];
}
//...
}
Alternately, you can also override the init in IconDownloader class and create the appRecord property inside it, so that it is not nil when you are assigning values.
Hope that helps!
You didnt initializing the appRecord object. thats why you get null value. Just initialize appRecord in your init method like:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
appRecord = [[AppRecord alloc]init];
}
return self;
}
Similiarly you have to initialize the urlSingle variable inside the init definition:
-(id)init
{
self = [super init];
if (self) {
urlSingle = URL_STRING_HERE;
}
return self;
}
Now you try
I have a a class I created to generate UIButton's I add to my UIView. This worked great until my conversion to ARC yesterday, not I get the following error:
-[OrderTypeButton performSelector:withObject:withObject:]: message sent to deallocated instance 0x12449f70
Here is the code to add the button to my UIView (actually a subview in my main UIView):
OrderTypeButton *btn = [[OrderTypeButton alloc]initWithOrderType:#"All Orders" withOrderCount:[NSString stringWithFormat:#"%i",[self.ordersPlacedList count]] hasOpenOrder:NO];
btn.view.tag = 6969;
btn.delegate = self;
[btn.view setFrame:CGRectMake((col * width)+ colspacer, rowHeight + (row * height), frameWidth, frameHeight)];
[self.statsView addSubview:btn.view];
And here is my class header:
#import <UIKit/UIKit.h>
#protocol OrderTypeButtonDelegate
-(void) tapped:(id)sender withOrderType:(NSString*) orderType;
#end
#interface OrderTypeButton : UIViewController {
id<OrderTypeButtonDelegate> __unsafe_unretained delegate;
IBOutlet UILabel *lblOrderType;
IBOutlet UILabel *lblOrderCount;
NSString *orderType;
NSString *orderCount;
BOOL hasOpenOrder;
}
#property (nonatomic, strong) IBOutlet UIButton *orderButton;
#property (nonatomic, strong) IBOutlet UILabel *lblOrderType;
#property (nonatomic, strong) IBOutlet UILabel *lblOrderCount;
#property (nonatomic, strong) NSString *orderType;
#property (nonatomic, strong) NSString *orderCount;
#property (nonatomic, assign) BOOL hasOpenOrder;
#property (nonatomic, unsafe_unretained) id<OrderTypeButtonDelegate> delegate;
-(id) initWithOrderType: (NSString *) anOrderType withOrderCount: (NSString *) anOrderCount hasOpenOrder: (BOOL) openOrder;
-(IBAction)btnTapped:(id)sender;
#end
Implementation:
#import "OrderTypeButton.h"
#implementation OrderTypeButton
#synthesize orderButton;
#synthesize lblOrderType, lblOrderCount, orderType, orderCount, hasOpenOrder, delegate;
-(id) initWithOrderType: (NSString *) anOrderType withOrderCount: (NSString *) anOrderCount hasOpenOrder: (BOOL) openOrder {
if ((self = [super init])) {
self.orderType = anOrderType;
self.orderCount = anOrderCount;
self.hasOpenOrder = openOrder;
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.lblOrderType.text =[NSString stringWithFormat:#"%#", self.orderType];
self.lblOrderCount.text = [NSString stringWithFormat:#"%#", self.orderCount];
if (self.hasOpenOrder) {
[self.orderButton setBackgroundImage:[UIImage imageNamed:#"background-order-btn-red.png"] forState:UIControlStateNormal];
self.lblOrderType.textColor = [UIColor whiteColor];
self.lblOrderCount.textColor = [UIColor whiteColor];
}
}
-(IBAction)btnTapped:(id)sender {
NSLog(#"TAPPED");
if ([self delegate] ) {
[delegate tapped:sender withOrderType:self.orderType];
}
}
- (void)viewDidUnload
{
[self setOrderButton:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
This seems fairly simple what I am doing here, not sure what changed with ARC that is causing me problems.
Maybe ARC autorelease created button, try to store created buttons in Array
//.h file
#property (nonatomic, strong) NSArray *buttonsArray
//.m file
#synthesize buttonsArray
...
- (void)viewDidLoad {
buttonsArray = [NSArray array];
...
OrderTypeButton *btn = [[OrderTypeButton alloc]initWithOrderType:#"All Orders"
withOrderCount:[NSString stringWithFormat:#"%i",[self.ordersPlacedList count]]
hasOpenOrder:NO];
btn.view.tag = 6969;
btn.delegate = self;
[btn.view setFrame:CGRectMake((col * width)+ colspacer, rowHeight + (row * height), frameWidth, frameHeight)];
[self.statsView addSubview:btn.view];
//Add button to array
[buttonsArray addObject:btn];
Also this approach will help if you want to change buttons, or remove some specific button from view