I'm having trouble understanding a finer point of Writing and calling Classes. It's probably
easlier to grasp in Swift but it bothers me to start that study without
getting it right in obj_c first. Currently I do everything in the
ViewControllers with iVars and Globals. With two apps 18 months in the App
store its overdue to put them right.
I've formed a notion that properties are the Object's State, and any methods
within determine the Objects Behaviour but so far no-one is able to tell me.
here be a typical Class header:
#interface Math : NSObject
#property (nonatomic, assign) int a;
#property (nonatomic, assign) int b;
#property (nonatomic, assign) int c;
-(int)mathemagic:(int)a adding:(int)b;
#end
and the corresponding Class implementation:
#implementation Math
#synthesize a = _a;
#synthesize b = _b;
#synthesize c = _c;
- (instancetype)init {
self = [super init];
if (self) {
_a = 0;
_b = 0;
_c = 0;
}
return self;
}
-(int)mathemagic:(int)a adding:(int)b {
_c = (a + b);
return _c;
}
#end
and finally in the appropriate places in my ViewController
#import "Math"
- (void)viewDidLoad {
[super viewDidLoad];
Math *theMath = [Math alloc]; // makes no difference if I init[]
theMath.a = 10;
theMath.b = 20;
NSLog (#" answer is %i",theMath.c);
// but still outputs to:
// answer is 0
}
Now I know can make an iVar and do it this way,
int d = [self.theMath mathemagic:theMath.a adding:theMath.b];
NSLog (#" sum: %i",d);
But i shouldn't have to. Stanford CS193P seems to always make the Class a property of the ViewController, but then everything is again expressed as self.theMath.whatever and the Data Model is no longer encapsulated away from the VC ? Maybe Stanford leaves advanced distractions to Java graduates till later.
Well for this person who's read David Flanagan's "Java in A Nutshell" ,
and Niemeyer-Knudsen's "Learning Java", It's later Now.
I shouldn't have to touch theMath.c, just by assigning values to [ theMath.a ] and [ theMath.b ] should be enough.
Where am I wrong?
I think that is because you are setting a and b = 0 in alloc init . and you are not calling [self mathemagic:a adding:b] anywhere.
I think im Math.m you should change -(instancetype)init to
- (instancetype)initWith:(int)a andb:(int)b {
self = [super init];
if (self) {
_c = [self mathemagic:a adding:b];
}
return self;
}
and in viewDidLoad use
Math *theMath = [[Math alloc]initWith:10 andb:20];
Hope this helps :)
I think you have a misconception of how Objective-C classes work.
First of all, it takes two steps to create an object in Objective-C. You must both:
Dynamically allocate memory for the new object
Initialize the newly allocated memory to appropriate values
So your Math instance initialization should look like this:
Math *theMath = [[Math alloc] init];
Just calling alloc zeroes out all instance variables of the object. Although in your case it makes no difference using [Math alloc] or [[Math alloc] init], it's not good programming style.
Second, if by "automatically display behaviour" you mean logging the result of mathemagic:adding: method, then you should pass it as an argument to NSLog function instead of theMath.c
NSLog(#" should show the sum being %i", [theMath mathemagic:theMath.a adding:theMath.b]);
Related
Very simple question. Is it possible to create a class which is a list by it self? I mean:
I do
taskList *taskList1 = [[taskList alloc] init];
And than simply:
taskList1 addObject:[task1]
May seem stupid, but I'm totally new to O-C syntax
I'd need two methods:
-(instancetype) init;
which just initialize as an empty list
+(instancetype)taskList;
to allocate taskList instance
and last thing:
In interface i use:
#interface taskList : NSObject
or
#interface taskList : NSMuttableArray
I got stuck on something specific, didn't I? I'm sorry that I bother you with my programming level.
Alright, I gave up, just last question, because I have to finish it very soon.
I changed my approach I added
#property NSMutableArray *list;
Why does this:
taskList *TL1 =[taskList initTaskList];
task *task1 = [[task alloc] init];
task *task2 = [[task alloc] init];
TL1.list addObject:[task1];
doesn't work, I have "Expected identifier" Error
If you read the subclassing notes on NSArray / NSMutableArray you'll see that Apple recommend against subclassing them because they are a class cluster. (i.e. what you really get when you ask for one is an undocumented subclass, and the initialiser decides which undocumented subclass to return to you based on some undocumented qualifiers..
So just make an NSObject subclass which owns a (private) strong property of type NSMutableArray, and publish an api to access that array..
eg
#import "modelList.h"
//dont worry header is empty, its up to you to do that.. this is a subclass on NSObject
#interface modelList()
#property (strong, nonatomic) NSMutableArray *backingArray;
#end
#implementation modelList
#synthesize backingArray = _backingArray;
-(instancetype )init{
if (self = [super init]) {
[self setBackingArray:[[NSMutableArray alloc]init]];
}
return self;
}
//public API (the stuff that is prototyped in the header..)
-(id)objectAtIndex:(NSUInteger )index{
return [self.backingArray objectAtIndex:index];
}
-(BOOL )containsObject:(id)object{
return [self.backingArray containsObject:object];
}
-(void )addObject:(id)object{
//example application, qualifying object..
if ([object conformsToProtocol:#protocol(NSCoding)]) {
[self.backingArray addObject:object];
}
}
-(NSUInteger )count{
return [self.backingArray count];
}
//etc etc declare publicly the things you need to get the job done
#end
so far this is just a face for a mutable array obviously, but it gives you a place for whatever other model logic you need. good luck
I started transferring my java knowlege into objective c quite recently and got into making apps with xcode too. I do have a few things I'm confused about though. First of all in java when I'm making top down games and need to shoot a projectile, I would do that somewhat like:
public class Bullet{
int x,y;
public bullet(double x, double y){
this.x = x;
this.y = y;
}
public void tick(){
//logic goes in here to move bullet
}
}
then I'd have a class with an arraylist:
public class MainClass{
ArrayList<Bullet> bulletlist;
public main(){
//create an arraylist that takes Bullet objects
bulletlist = new ArrayList<Bullet>();
//add a new bullet at the coordinate (100,100)
bulletlist.add(new Bullet(100,100));
}
//gameloop(we'll pretend that this gets called every millisecond or so)
public void gameloop(){
//loop through list
for(int i = 0; i < bulletlist.size(); i++){
//run the method tick() at the current index
bulletlist.get(i).tick();
}
}
}
so... my question is how would I translate this code into objective c. Or in other terms, how do I create an arraylist similar to the one in the example that creates an object of a class , then finally loop through this and call a loop method or whatever method I create inside.
Objective-C has no generics, unlike Java. It wouldn't make much sense, since Objective-C is dynamically typed (for the most part). Instead, NSMutableArray and NSArray store instances of NSObject or subtypes thereof (similar to ArrayList<Object> in Java).
Something like this should get you started.
#interface MainClass()
#property(nonatomic, strong) NSMutableArray *bullets;
#end
#implementation MainClass
- (id)init {
if (self = [super init]) {
self.bullets = [NSMutableArray array];
[self.bullets addObject:[[Bullet alloc] initAtX:100 y:100]];
}
return self;
}
- (void)gameLoop {
[self.bullets makeObjectsPerformSelector:#selector(tick)];
}
#end
you create a new file inherited by NSObject with xcode, declare your header and your body, and your ArrayList<Bullet> can be a simple NSMutableArray. Your loop can look like this
NSMutableArray *bulletlist = [[NSMutableArray alloc] init];
// fill the array
for(Bullet *bullet in bulletlist) // or a simple for loop
{
[bullet tick];
}
for steps how to create and declera a class, just check some apple tutorials :)
Your "main class" should be a UIViewController. It should create a UIView which is full screen on the phone. Then it adds a "CALayer" object as a sublayer of that view's layer.
There are various ways to draw into a CALayer, the best in this case would be to create a "BulletLayer" subclass of CALayer and do your drawing in that.
A view is a rectangle of the screen that the user can see and touch. A layer is a set of pixels stored in the video card's [very fast] memory that can be manipulated by your code. For best performance you will only draw the pixels once, and then move them around the screen (which can be done with animation, etc).
If you want specific answers about any particular part of that, you should ask a new question for each after checking if it's already answered somewhere.
It would look like this:
// bullet.h
#interface Bullet : NSObject
#property(assign, nonatomic) CGPoint position;
- (id)initWithPosition:(CGPoint)position;
#end
// bullet.m
#implementation Bullet
- (id)initWithPosition:(CGPoint)position {
self = [self init];
if (self) {
_position = position;
}
return self;
}
#end
Then, in some other class that uses bullets (like a view controller), declare an array to keep the bullets...
#import "Bullet.h"
// put this in the class's interface
#property(nonatomic, strong) NSMutableArray *bullets;
... and add a new bullet like this:
[self.bullets addObject:[[Bullet alloc] initWithPosition:CGPointMake(100,100}];
... and iterate bullets like this:
for (Bullet *bullet in self.bullets) {
NSLog(#"bullet position is %#", NSStringFromCGPoint(bullet.position));
}
I was given a main by my professor that I have to make the .h and .m files for in Objective-C using Xcode. I have it almost done but have two warnings that I can't figure out how to get rid of, "Expression result unused". The errors have to do with my init method for making an array of size 100. Here is where the main creates an object and then calls the method I have coded improperly, this part is given from my teacher and I can't change it:
// main.m
// ChutesAndLadders
#import <Foundation/Foundation.h>
#import "ChutesAndLadders.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
ChutesAndLadders *cl = [[ChutesAndLadders alloc]init];
[cl initBoard];
[cl makeChutes:10];
[cl makeLadders:10];
int chutes=0;
int ladders=0;
for(int i=0;i<cl.board.count;i++){
NSString * cell = (NSString *)[cl.board objectAtIndex:i];
int additionalSpaces = (int)[cl addToMove:cell];
if(additionalSpaces>0)
ladders++;
else if (additionalSpaces<0)
chutes++;
}
[cl printBoard];
}
return 0;
}
Here is relevant parts of my method:The error is from the "initBoard" method above, and "initWithCapacity" line below. I know that the two warnings are describing the same error. The code below is mine so I can change it, unlike above.
#synthesize board = _board;
-(id) initBoard
if((self = [super init]){
[board intiWithCapacity: 100];
for(int i = 0; i < 100; i++){
[board addObject:#""];
}
}
return self;
}
and here is the relevant code from my declaration of "board" in the .h file:
#interface ChutesAndLadders : NSObject{
#private
NSMutableArray * board;
}
#property (readwrite, retain) NSMutableArray *board;
-(id) initBoard;
I have tried a lot of small changes in syntax but to no avail. Please don't tell me the solution directly, but a nudge or a shove in the right direction I think is all I need and I will post the solution once I figure get it.
Pseudo solution:
#synthesize board=board;
-(void) initBoard{
_board = [board initWithCapacity: 100];
for(int i =0; i < 100; i++){
[_board addObject:#""];
}
}
This eliminated one of the warnings, but my program is not running properly so I still have work to do in other methods. This specific question as been addressed though and I have a better understanding of how this is working now.
Actual Solution
#synthesize board=_board;
-(void) initBoard{
_board = [NSMutableArray arrayWithCapacity:100];
for(int i =0; i < 100; i++){
[_board addObject:#" "];
}
}
This is the one that finally worked per Hermann. I swear that I tried this once and it didn't work, so I kept playing around and changing things(messing it up more) and then eventually got back to this and it of course worked. Xcode lies sometimes I think to irritate me.
Edit because at first I overlooked something:
Simply use:
ChutesAndLadders *cl = [[ChutesAndLadders alloc]initBoard];
because within initBoard you are calling [super init] anyway.
Does your professor try to trick you?
It is difficult to say without having a look at all of the code.
You are synthesizing a property board which has the iVar _board.
Meaning you can access it's getter or setter respectively by self.board or [self board] or [self setBoard: ...].
But within the initBoard method it is accessed differently:
[board intiWithCapacity: 100];
Despite the fact that we do not see where board is actually allocated and that the result of the initWithCapacity method is not used, I am asking myself what board actually is. It is not the property (that would be self.board etc) nor is it the iVar (that would be _board).
Apparently just board refers to the private iVar that you declared. But
#synthesize board = _board;
creates an iVar with the name _board of the same type (NSMutableArray*).
Therefore in your method, board is different from _board.
Althoug I am editing this for the 3rd time now, I am still not getting it. The .m file was given by the teacher and you have to create a matching .h file, right? In that case you should go for
#private
NSMutableArray * _board;
Well, that is done automatically anyway but writing it with "_" would avoid the creation of a similar named iVar and therefore avoid likely errors.
Background AFAIK: Theoretically or even practically init may create a different instance than the one that alloc created. Therefore you should always use the return value of init.
In general - independent from the strange objective-c notation around object initialization - when ever a method returns a value, then
[object method];
is syntactically correct, runs fine and everything but the return value is unused. And that is what your warning message is all about. Even if you assign it to a dummy object like
SomeClass *someObject = [object method];
you would get the warning that `someClass' is an unused variable.
Last edit:
You are nearly there. However, this is a shortcut for current versions of Obj-C, not for older ones:
.h file:
#interface ChutesAndLadders : NSObject
#property (readwrite, retain) NSMutableArray *board;
-(void) initBoard;
#end
.m file:
// omitting #synthesize forces the compiler to autosynthesize. In that case it is named "_board"
-(void) initBoard
_board = [NSMutableArray arrayWithCapacity: 100]; // this implies the alloc
for(int i = 0; i < 100; i++){
[_board addObject:#""];
}
}
You're returning something that you're not using. Also read up on writing proper constructors/initializing objects in Objective-C.
As Hermann said you should do
ChutesAndLadders *cl = [[ChutesAndLadders alloc]initBoard];
and then in your initBoard method
self.board = [NSMutableArray alloc] intiWithCapacity: 100];
You are not following standard Objective-C conventions, and that gets you trapped.
You have an instance variable board. Objective-C convention is that all instance variables should start with an underscore character.
You have a property board, and #synthesize board = _board. The synthesize creates an instance variable with an underscore character, named _board. Now you have two instance variables: board and _board. One of them, _board, is the instance variable backing the board property. The other, board, has nothing to do with board property at all.
And then somewhere you have a harmless looking but fatal statement board = whatever. Which assigns to that totally useless instance variable board. Not _board, but board. If you wrote _board then you would be following Objective-C conventions: Assignment to instance variable using highly visible name. or self.board which would be the property.
And [board initWithCapacity:100] doesn't do anything. First, because the board instance variable (totally unrelated to board property or _board instance variable) hasn't been allocated. It's nil. Sending initWithCapacity to nil does nothing. Sending addObject to nil does nothing either.
I'm new to Objective-C, but I am curious about something that I haven't really seen addressed anywhere else.
Could anyone tell me what is the difference between a private variable that is declared at the #interface block versus a variable that is declared within the #implementation block outside of the class methods, i.e:
#interface Someclass : NSObject {
NSString *forExample;
}
#end
vs.
#implementation Someclass
NSString *anotherExample;
-(void)methodsAndSuch {}
#end
It seems both variables ( forExample, anotherExample ) are equally accessible throughout the class and I can't really find a difference in their behaviour. Is the second form also called an instance variable?
The latter is not defining an instance variable. Rather, it is defining a global variable in the .m file. Such a variable is not unique to or part of any object instance.
Such globals have their uses (roughly equivalent C++ static members; e.g. storing a singleton instance), but normally you would define them at the top of the file before the #implementation directive.
They're very different! The one in #implementation is a global variable not unique to each instance. Imagine there were accessors for both variables, written in the obvious way. Then the difference in behavior is shown here:
Someclass* firstObject = [[Someclass alloc] init];
Someclass* secondObject = [[Someclass alloc] init];
//forExample is an instance variable, and is unique to each instance.
[firstObject setForExample:#"One"];
[secondObject setForExample:#"Two"];
NSLog(#"%#",[firstObject forExample]); //Result: "One"
NSLog(#"%#",[secondObject forExample]); //Result: "Two"
//anotherExample is a global variable, and is NOT unique to each instance.
[firstObject setAnotherExample:#"One"];
[secondObject setAnotherExample:#"Two"];
NSLog(#"%#",[firstObject anotherExample]); //Result: "Two" (!)
NSLog(#"%#",[secondObject anotherExample]); //Result: "Two"
//Both instances return "Two" because there is only ONE variable this time.
//When secondObject set it, it replaced the value that firstObject set.
If you are looking for this sort of behavior, you might be better off using a class variable, like this:
static NSString* yetAnotherExample = nil;
Then you can use class methods to interact with the variable, and it's clearly class-specific (as opposed to instance-specific or global).
If you declare a variable inside the #implementation section, you're actually creating a global variable, visible everywhere (in every method in your application).
Member variables can only be declared in the #interface section. They are only accessible in the class itself.
The private block declared inside the #implementation block is kind of dangerous, seems to me, comparing with other OOP concept e.g. Java. Its look like member variable but kinda static.
Novice programmer can easily fooled with it. I write a test program and surprised with the behaviour.
#interface SomeClass : NSObject
{
NSString *forExample;
}
- (void) set:(NSString *)one another:(NSString *)another;
- (void)print;
#end
Implementation:
#import "SomeClass.h"
#implementation SomeClass
NSString *anotherExample;
- (void) set:(NSString *)one another:(NSString *)another
{
forExample = one;
anotherExample = another;
}
- (void)print{
NSLog(#"One = %#, another = %#", forExample, anotherExample);
}
#end
Test:
- (void)testClass {
SomeClass * s1 = [SomeClass new];
[s1 set:#"one one" another:#"one another"];
SomeClass *s2 = [SomeClass new];
[s2 set:#"two one" another:#"two another"];
[s1 print];
[s2 print];
}
And the output is,
One = one one, another = two another
One = two one, another = two another
Use a code snippet to tell the difference between a member variable and a global variable:
#implementation MyClass {
// It is an ivar, or called member variable
// Can NOT be initialized when defined.
// Can be accessed with `self->_i`
int _i;
}
- (instancetype)init {
if (self = [super init]) {
_i = 2; // should be initialized before being used.
}
return self;
}
int i = 9; // Global variable, and can be initialized when defined.
- (void)myFun {
NSLog(#"%i, %i", self->_i, i);
}
#end
// Another file
extern int i;
NSLog(#"%i", i);
Just to be clear, never ever ever declare an IBOutlet as a global var (in the implementation) if you are using it for localized nibs/xibs.
I spent a few hours figuring why the outlet is connectable only in one of the localized nibs at any given time.
Thanks for this question and the answers!
thing.h
#interface Thing : NSObject
{
float stuff[30];
}
#property float stuff;
#end
thing.m
#implementation Thing
#synthesize stuff;
#end
I get error: type of property 'stuff' does not match type of ivar 'stuff'
I don't want to use an NSArray because I'd have to make the floats into NSNumbers (right?) and that's a pain to do math with.
Update: I've noticed similar answers had guesses and trial answers. While I appreciate the attempts by non-Objective-C folks, I'm hoping for a definitive answer whether it's possible or not.
OK, I have compiled up the following code at it works as expected.
FloatHolder.h
#interface FloatHolder : NSObject {
int _count;
float* _values;
}
- (id) initWithCount:(int)count;
// possibly look into this for making access shorter
// http://vgable.com/blog/2009/05/15/concise-nsdictionary-and-nsarray-lookup/
- (float)getValueAtIndex:(int)index;
- (void)setValue:(float)value atIndex:(int)index;
#property(readonly) int count;
#property(readonly) float* values; // allows direct unsafe access to the values
#end
FloatHolder.m
#import "FloatHolder.h"
#implementation FloatHolder
#synthesize count = _count;
#synthesize values = _values;
- (id) initWithCount:(int)count {
self = [super init];
if (self != nil) {
_count = count;
_values = malloc(sizeof(float)*count);
}
return self;
}
- (void) dealloc
{
free(_values);
[super dealloc];
}
- (float)getValueAtIndex:(int)index {
if(index<0 || index>=_count) {
#throw [NSException exceptionWithName: #"Exception" reason: #"Index out of bounds" userInfo: nil];
}
return _values[index];
}
- (void)setValue:(float)value atIndex:(int)index {
if(index<0 || index>=_count) {
#throw [NSException exceptionWithName: #"Exception" reason: #"Index out of bounds" userInfo: nil];
}
_values[index] = value;
}
#end
then in your other application code you can do something like the following:
** FloatTestCode.h **
#import <Cocoa/Cocoa.h>
#import "FloatHolder.h"
#interface FloatTestCode : NSObject {
FloatHolder* holder;
}
- (void) doIt:(id)sender;
#end
** FloatTestCode.m **
#import "FloatTestCode.h"
#implementation FloatTestCode
- (id) init
{
self = [super init];
if (self != nil) {
holder = [[[FloatHolder alloc] initWithCount: 10] retain];
}
return self;
}
- (void) dealloc
{
[holder release];
[super dealloc];
}
- (void) doIt:(id)sender {
holder.values[1] = 10;
}
The type of the property must match the type of the instance variable it will be stored in, so you could do something like
#interface Thing : NSObject
{
float stuff[30];
}
#property float[30] stuff;
#end
and it should work. I wouldn't recommend it though.
I'm guessing you're looking for something like indexed properties from Delphi. The closest you'll get is something like the following.
#interface Thing : NSObject
{
float stuff[30];
}
- (void) setStuff:(float)value atIndex:(int)index;
- (float) getStuffAtIndex:(int)index;
#end
You can't do it the way you want to do it. You can jump through some hoops and get something similar, e.g. using Daniel's solution, but it's not quite the same thing. The reason you can't do it is that arrays are not lvalues in C. An lvalue is something that can appear on the left-hand side of an assignment. The following code is invalid C:
float stuff1[30], stuff2[30];
stuff1 = stuff2; // ERROR: arrays are not lvalues
As a consequence, you can't declare properties whose types are not lvalues.
Daniel's FloatHolder answer has a major bug (edit: he's now fixed it). It only allocates memory for one float and not for the whole array.
The line:
_values = malloc(sizeof(float));
Should be:
_values = malloc(sizeof(float) * count);
Otherwise it seems to be a good answer. Sorry couldn't work out how to reply directly. (edit: I didn't have the necessary privilege on stackoverflow then.)
Even if you could get that to compile, it wouldn't behave well. 'stuff' would return a float*, and the client would have no idea how long the array way; 'setStuff:' would just change the pointer, and you'd either be pointing to stack-allocated data that would vanish out from under you or heap-allocated data that would leak because it wouldn't know to free it.
I'm not well-versed in Objective-C 2.0, but I'm guessing that the issue might be caused by the fact that a C array is essentially just a pointer to the first element of the array, meaning that the type of float stuff[30] is actually float *, not merely a float.