Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I'm unsure how I should initialise the various properties in an objective-C class. Please assume I'm a very new user to Objective-C in your answers...
I have the following classes:
Test class
#interface Test : NSObject
#property (nonatomic, strong) NSString *name;
#end
TestManager class
#interface TestManager : NSObject
#property (nonatomic, strong) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
Controller class
#interface TestController : NSObject
#property (nonatomic, strong) TestManager *aManager;
-(void)initManager;
-(void)doSomething;
#end
I want to have an method like initManager called:
-(void)initManager
{
// how can I init the aManager which will have an array of Test objects
}
which will automatically allocate an array of objects to be stored inside the manager class so I can do things like:
-(void)doSomething
{
NSString *name = ((Test *)[self.aManager.tests objectAtIndex:0]).name;
}
I'm not even sure that initManager is the correct method to use - is there something built in that always gets called?
Firstly, let's look at the way we can initialize your Test class objects.
You can also write some initialization method for your Test class so instead of this:
Test example = [[Test alloc] init];
example.name = #"s";
you can write something like this:
Test example = [[Test alloc] initWithName:#"s"];
Please note that this is very common for initialization method to return newly created object, hence the initialization method usually returns 'id' type (not void).
This is the implementation for your test class which will be used in examples below.
.h file:
- (id)initWithName:(NSString *)aName;
.m file:
- (id)initWithName:(NSString *)aName
{
self = [super init];
if (self) {
_name = aName;
}
return self;
}
You can initialize your TestController class this way:
.h file:
- (id)initManager;
.m file:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSMutableArray array];
[_tests addObject:[[Test alloc] initWithName:#"s"]];
[_tests addObject:[[Test alloc] initWithName:#"l"]];
}
return self;
}
Or something like this:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSArray arrayWithObjects:[[Test alloc] initWithName:#"s"], [[Test alloc] initWithName:#"l"]];
}
return self;
}
Like the #Andrew said it is better to use alloc + init. Here are some examples of this syntax:
CGRect rect = CGRectMake(0, 0, 100, 100);
[[UIView alloc] initWithFrame:rect];
[[NSArray alloc] init]
This is the common way to initialize objects. Despite having this mechanism there are also some additional methods (which are in fact static functions) which give the programmer the nice way to initialize objects. Using them u don't have to write keyword 'alloc' so that the code is shorter and easier to read.
[NSArray array] //creates and returns empty array
[NSMutableArray array] //creates and return empty mutable array
[UIButton buttonWithType:UIButtonTypeContactAdd]; //creates and return button
first import header files of test, and test manager class, into controller class
#import Test.h
#import TestManager.h
then in controller class
-(void)initManager
{
TestManager *aTestManager = [TestManager new];
Test *test1 = [Test new];
Test *test2 = [Test new];
[aTestManager.tests addObject:test1];
[aTestManager.tests addObject:test2];
}
Let's start at the top. You probably can and should make the name readonly.
(Demos assume ARC is enabled)
#interface Test : NSObject
#property (nonatomic, readonly) NSString *name;
// and then simply initialize name:
- (instancetype)initWithName:(NSString *)pName;
#end
NSString properties should be copied:
#implementation Test
- (instancetype)initWithName:(NSString *)pName
{
self = [super init];
if (nil == self) return nil;
// copy the NSString:
// don't use getters/setters in initializers or -dealloc
_name = pName.copy;
return self;
}
#end
Similarly readonly:
#interface TestManager : NSObject
#property (nonatomic, strong, readonly) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
#implementation TestManager
- (id)init
{
self = [super init];
if (nil == self) return nil;
// just initialize readonly tests:
_tests = NSMutableArray.new;
return self;
}
#end
Then TestController could probably use a readonly TestManager and borrow the form used above. Otherwise, it can be readwrite, if needed.
// don't declare/implement an instance method
// which has the prefix -init*, like initManager. renamed.
- (void)resetManager
{
// if readonly is ok, then just create it in the initializer.
// otherwise, if you need the ability to set the manager in the controller,
// then declare the property readwrite and:
self.testManager = TestManager.new;
// note: aManager is not a good name. renamed to testManager.
}
- (void)doSomething
{
assert(self.testManager && "did you forget to init the manager?");
Test * test = [self.testManager.tests objectAtIndex:0];
NSString * name = test.name;
...
}
This is far from covering all initialization cases in ObjC, but it is a start.
Related
Question
I'm trying to unit test my implementation of a protocol's method that takes an immutable object that's not in my project as a parameter. This object doesn't have any initializers exposed that set its properties. What's the best way to unit test in this scenario?
What I tried
I tried subclassing and casting the properties to readwrite, but you only have access to a super-class' ivars if they're explicitly marked as #protected. Since the class isn't within my project, I can't re-assign a new scope to the ivar.
I also can't mock a setter that doesn't exist, and ivars are not directly writable through mock objects.
Eventually, I decided to add an additional method that the method in question would call, passing only objects whose properties I can set, and I covered that additional method in unit tests.
Is there a better way to unit test in this scenario?
Specifics of my scenario
I'm trying to unit test my implementation of userContentController:didReceiveScriptMessage: from WebKit's WKScriptMessageHandler protocol and can't find a way to pass an instance of WKScriptMessage or of WKUserContentController with any properties set.
To unit test this, I created a new method that takes a parameter of type 'id' and called it from userContentController:didReceiveScriptMessage:, passing WKScriptMessage's body property.
Does this help you?
TestMessage.h
#import <WebKit/WebKit.h>
#interface TestMessage : WKScriptMessage
- (id) initWithBody:(id)body
frameInfo:(WKFrameInfo*)frameInfo
name:(NSString*)name
webView:(WKWebView*)webView;
#end
TestMessage.m
#import "TestMessage.h"
#interface TestMessage()
#property (nonatomic, strong) id messageBody;
#property (nonatomic, strong) WKFrameInfo* messageFrameInfo;
#property (nonatomic, strong) NSString* messageName;
#property (nonatomic, strong) WKWebView* messageWebView;
#end
#implementation TestMessage
- (id) initWithBody:(id)body
frameInfo:(WKFrameInfo*)frameInfo
name:(NSString*)name
webView:(WKWebView*)webView
{
self = [super init];
if (self)
{
_messageBody = body;
_messageFrameInfo = frameInfo;
_messageName = name;
_messageWebView = webView;
}
return self;
}
- (id) body
{
return self.messageBody;
}
- (WKFrameInfo *)frameInfo
{
return self.frameInfo;
}
- (NSString *)name
{
return self.messageName;
}
- (WKWebView *)webView
{
return self.messageWebView;
}
#end
So you're basically creating a subclass with matching properties and then overriding the getters for the superclass properties to return values for the ones you have full access to.
This worked for me with the simple test (I'm sure your scenario is more involved):
- (void) testMessageHandler
{
WKWebView* webView = [[WKWebView alloc] init];
WKUserContentController* controller = [[WKUserContentController alloc] init];
WKFrameInfo* info = [[WKFrameInfo alloc] init];
NSDictionary* testBody = #{#"testKey" : #"testVal" };
TestMessage* testMessage = [[TestMessage alloc] initWithBody:testBody
frameInfo:info
name:#"Message Name"
webView:webView];
// some object conforming to the WKScriptMessageHandler protocol
[self userContentController:controller
didReceiveScriptMessage:testMessage];
}
- (void)userContentController:(nonnull WKUserContentController *)userContentController
didReceiveScriptMessage:(nonnull WKScriptMessage *)message
{
NSLog(#"Received Script Message... (%#)", message.body);
}
"error: expected ';' at end of declaration list" objective c
When I am trying to declare global variable
#implementation CachingManager{
NSMutableArray*object = [[NSMutableArray alloc] init];
}
You can create instance variables here but cannot initialize those instance variables here like you do. They are all initialized to nil or zeroes. So compiler expect a semicolon when you are writing an equal sign.
You can initialize them in init method or other method where your class initialization takes place in order to make them global variables. e.g.
Interface block for instance variable inside .m file:
#interface CachingManager ()
{
// instance variables initialized to nil or zeroes
NSMutableArray *object; // global ivar
}
#end
Implementation part same .m file:
#implementation CachingManager
- (void)viewDidLoad {
[super viewDidLoad];
object = [[NSMutableArray alloc] init]; // initialization takes place
}
One way to implement global variables, and to manage their lifetime (i.e. that they are initialised) and even to provide global methods is to implement a class exposing those variables/methods and to use the singleton pattern:
yourFile.h:
#import <Foundation/Foundation.h>
#interface GlobalVars : NSObject
{
NSMutableArray *_truckBoxes;
NSMutableArray *_farmerlist;
NSString *_farmerCardNumber;
NSString *_fName;
}
+ (GlobalVars *)sharedInstance;
#property(strong, nonatomic, readwrite) NSMutableArray *truckBoxes;
#property(strong, nonatomic, readwrite) NSMutableArray *farmerList;
#property(strong, nonatomic, readwrite) NSString *farmerCardNumber;
#property(strong, nonatomic, readwrite) NSString *fName;
#end
yourFile.m:
#import "GlobalVars.h"
#implementation GlobalVars
#synthesize truckBoxes = _truckBoxes;
#synthesize farmerList = _farmerList;
#synthesize frameCardNumber = _frameCardNumber;
#synthesize fName = _fName;
+ (GlobalVars *)sharedInstance {
static dispatch_once_t onceToken;
static GlobalVars *instance = nil;
dispatch_once(&onceToken, ^{
instance = [[GlobalVars alloc] init];
});
return instance;
}
- (id)init {
self = [super init];
if (self) {
_truckBoxes = [[NSMutableArray alloc] init];
_farmerlist = [[NSMutableArray alloc] init];
// Note these aren't allocated as [[NSString alloc] init] doesn't provide a useful object
_farmerCardNumber = nil;
_fName = nil;
}
return self;
}
You can then use these global variables like this, for example:
GlobalVars *globals = [GlobalVars sharedInstance];
globals.fName = #"HelloWorld.txt";
[globals.farmerList addObject:#"Old Macdonald"];
[self processList:[globals farmerList]];
However, please consider:
You don't need to use global variables like this; you should be able to create a model object which is created as necessary and reference to it passed to the views. This is MVC.
You also posted a stack trace of an unrelated issue which is extremely common with Objective-C; only you can fix this error, once you realise what it is.
That sin't a global variable. That would be an instance variable and that particular syntax wasn't commonly used after 2005 (really, it wasn't terribly common after the mid 90s).
If you want a global variable, do:
NSMutableArray *myGlobal;
Somewhere at the top level -- at the same level with the #implementation -- of your source.
You'll have to initialize the global variable elsewhere, though. Typically, in the +initialize or +load method of the class.
I have a iPad application where I'm attempting to use a singleton. This is the code in the .h file:
//-------------------------------------------
//-- singleton: timeFormat
#interface SingletonTimeFormat : NSObject {
}
#property (nonatomic, retain) NSNumber *timeFormat;
+ (id)sharedTimeFormat;
#end
This is the code from the .m file:
//-------------------------------------------
//-- SingletonTimeFormat
#implementation SingletonTimeFormat {
}
#synthesize timeFormat;
//-- sharedColorScheme --
+ (id)sharedTimeFormat {
static dispatch_once_t dispatchOncePredicate = 0;
__strong static id _sharedObject = nil;
dispatch_once(&dispatchOncePredicate, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
-id) init {
self = [super init];
if (self) {
timeFormat = [[NSNumber alloc] init];
}
return self;
}
#end
I load the value (either 12 or 24) in AppDelegate - didFinishLaunchingWithOptions, then when I want to get the value of timeFormat I use this:
SingletonTimeFormat *stf = [[SingletonTimeFormat alloc]init];
if([stf.timeFormat isEqualToNumber: [NSNumber numberWithInt:12]]) {
which returns 0 (it was set correctly in AppDelegate, but apparently when I do the alloc in another class, it loses it's value. So obviously it's not working! (I have several other singletons that have the same pattern, but so far they appear to be working.
What's wrong here and how do I fix it?
You don't want to call your singleton using alloc init. With this singleton, all references to it should be through its sharedTimeFormat method, which will init the object if necessary, and will return the singleton instance otherwise.
In other words, it doesn't appear that you're referencing the instance of the object stored in the static sharedObject variable, which means that it's stored value will not necessarily be the same.
I've encountered a stupid problem, and I've tried almost everything (bought 3 books, went through the whole google :)) but nothing helped. And it seems to me like the solution should be extremely simple...
I need to declare a singleton in Objective-C (for an iOS app, if that matters), and it should have some properties that I need to update from other classes. But I can't do that - the properties just won't update, they have the same values set in the "init" method.
I've created a simple app to test out this problem. That's what I've done:
First, I've declared a sample class and its subclass that I'm going to use as a singleton's property:
#interface Entity : NSObject
#property (nonatomic, strong, readwrite) NSMutableString * name;
#end
#implementation Entity
#synthesize name;
#end
#interface Company : Entity
#property (nonatomic, strong, readwrite) NSMutableString * boss;
#property (nonatomic) int rating;
#end
#implementation Company
#synthesize boss, rating;
#end
Then I declare the singleton itself based on the method described in the "iOS Programming Guide by Big Nerd Ranch" book. I'm using both my custom class and a standard NSMutableString as properties, just for clarity's sake:
#class Company;
#interface CompanyStore : NSObject
{
NSMutableString * someName;
}
#property (nonatomic, strong, readwrite) Company * someCompany;
#property (nonatomic, strong, readwrite) NSMutableString * someName;
+ (CompanyStore *) store;
- (void) modifyCompanyProperties;
#end
#implementation CompanyStore
#synthesize someCompany, someName;
// Declaring the shared instance
+ (CompanyStore *) store
{
static CompanyStore * storeVar = nil;
if (!storeVar) storeVar = [[super allocWithZone:nil] init];
return storeVar;
}
// Replacing the standard allocWithZone method
+ (id) allocWithZone:(NSZone *)zone
{
return [self store];
}
Then I initialize all the properties with initial values:
- (id) init
{
self = [super init];
if (self) {
someCompany = [[Company alloc] init];
[someCompany setBoss:[NSMutableString stringWithFormat:#"John Smith"]];
[someCompany setName:[NSMutableString stringWithFormat:#"Megasoft"]];
[someCompany setRating:50];
someName = [[NSMutableString alloc] initWithString:#"Bobby"];
}
return self;
}
And from another class (view controller that displays the contents in a view):
1. I get the values of the singleton's properties. Everything's okay - I get "John Smith", "Megasoft", "Bobby" and 50 for my int value. The values from my init method.
2. I change the singleton's properties from that view controller (using several ways - I'm not sure now which one is right):
- (IBAction)modify2Button:(id)sender {
CompanyStore * cst = [CompanyStore store];
NSMutableString * name = [[NSMutableString alloc] initWithString:#"Microcompany"];
NSMutableString * boss = [[NSMutableString alloc] initWithString:#"Larry"];
[[[CompanyStore store] someCompany] setName:name];
cst.someCompany.boss = boss;
NSMutableString * strng = [[NSMutableString alloc] initWithString:#"Johnny"];
[cst setSomeName:strng];
}
... and then I'm trying to get the values again. I'm still getting the old set - "John Smith", "Megasoft" etc. even though when I set a breakpoint at one of the strings, I can see that singleton's name property is "Microcompany" and not "Megasoft" at the time of the break... But it doesn't seem to be assigned.
3. Then I'm trying another thing - I'm calling from the view controller a singleton's private method, which assigns another set of values to the properties. This is that method in the singleton:
- (void) modifyCompanyProperties
{
NSMutableString * boss = [[NSMutableString alloc] initWithString:#"George"];
NSMutableString * name = [[NSMutableString alloc] initWithString:#"Georgeland"];
[someCompany setBoss:boss];
[someCompany setName:name];
[someCompany setRating:100000];
[someName setString:#"Nicholas"];
}
4. I'm trying to get the updated property values from the view controller again... and still get those "John Smith", "Megasoft"... Nothing changes.
It seems like the properties of the singleton are set only once and then I can't change them, even though their attributes are declared as "readwrite".
It looks like I don't understand something simple.
If someone could explain how to correctly declare and update properties in singletons, I would be very grateful.
First thing I noticed was that you are declaring "storeVar" in the body of the store method. And this looks like terribly wrong to me because every time you call this you'll re-initialize the singleton. You should declare the variable like this:
static CompanyStore * storeVar = nil;
#implementation CompanyStore
#synthesize someCompany, someName;
// Declaring the shared instance
+ (CompanyStore *) store
{
if (!storeVar) storeVar = [[super allocWithZone:nil] init];
return storeVar;
}
Also your init method is not exactly complete because you don't want to call init again after the singleton has been initialized so you have to check this and if it has been initialized you should simply return it:
- (id) init
{
if (storeVar!=nil) {
return storeVar;
}
self = [super init];
if (self) {
someCompany = [[Company alloc] init];
[someCompany setBoss:[NSMutableString stringWithFormat:#"John Smith"]];
[someCompany setName:[NSMutableString stringWithFormat:#"Megasoft"]];
[someCompany setRating:50];
someName = [[NSMutableString alloc] initWithString:#"Bobby"];
}
return self;
}
Also, this is not a mistake, just a mere suggestion - you can ditch #synthesize because since ios 6 because the compiler generates it automatically. But again, not a mistake to use it. Hope it helps
I have a Class that runs the following method (a getter):
// the interface
#interface MyClass : NSObject{
NSNumber *myFloatValue;
}
- (double)myFloatValue;
- (void)setMyFloatValue:(float)floatInput;
#end
// the implementation
#implementation
- (MyClass *)init{
if (self = [super init]){
myFloatValue = [[NSNumber alloc] initWithFloat:3.14];
}
return self;
}
// I understand that NSNumbers are non-mutable objects and can't be
// used like variables.
// Hence I decided to make make the getter's implementation like this
- (double)myFloatValue{
return [myFloatValue floatValue];
}
- (void)setMyFloatValue:(float)floatInput{
if ([self myFloatValue] != floatInput){
[myFloatValue release];
myFloatValue = [[NSNumber alloc] initWithFloat:floatInput;
}
#end
When I mouse over the myFloatValue object during debugging, it does not contain a value. Instead it says: "out of scope".
I would like to be able to make this work without using #property, using something other than NSNumbers, or other major changes since I just want to understand the concepts first. Most importantly, I would like to know what mistake I've apparently made.
I can see a couple of mistakes:
The line #implementation should read #implementation MyClass
The function setMyFloatValue is missing a closing ] and } —it should read:
- (void)setMyFloatValue:(float)floatInput{
if ([self myFloatValue] != floatInput){
[myFloatValue release];
myFloatValue = [[NSNumber alloc] initWithFloat:floatInput];
}
}
I've just tested it in Xcode and it works for me with these changes.
Why not just set property in interface and synthesize accessors in implementation?
#interface MyClass : NSObject {
float *myFloat
}
#property (assign) float myFloat;
#end
#implementation MyClass
#synthesize myFloat;
#end