-2
\$\begingroup\$

I am trying to learn Objective C and want to make sure I am following the proper practices/convention.

I would very much appreciate advice on what to change/improve

I also have some questions:

  • when do I use entity.position vs [entity position]?
  • when do I use NSNumber vs double
  • when do I use int vs NSInteger vs NSNumber
  • can I hide methods from super class as RotationalVector can do [RotationalVector vector3] but I would like for it to be only able to use [RotationalVector rotationalVector]
  • within an instance method whats the difference between _x, [self x] and self.x
  • would it be a good idea to only include init methods in the .m file?
  • am I using proper return types for init and factory method?

Vector3.h:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Vector3 : NSObject

@property NSNumber* x;
@property NSNumber* y;
@property NSNumber* z;

- (id)init;
- (id)initWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

+ (instancetype)vector3;
+ (instancetype)vector3WithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

- (void)addX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;
- (void)setX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

@end

NS_ASSUME_NONNULL_END

Vector3.m

#import "Vector3.h"

@implementation Vector3


- (id)init{
    if (self = [super init]){
        _x = [NSNumber numberWithDouble:0];
        _y = [NSNumber numberWithDouble:0];
        _z = [NSNumber numberWithDouble:0];
    }
    return self;
}

- (id)initWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    if (self = [super init]){
        [self setX:x Y:y Z:z];
    }
    return self;
}

+ (instancetype)vector3{
    return [[self alloc] init];
}

+ (instancetype)vector3WithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    return [[self alloc] initWithX:x Y:y Z:z];
}

- (NSString*)description{
    return [NSString stringWithFormat:@"(%@, %@, %@)",_x,_y,_z];
}


- (void)addX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    _x = [NSNumber numberWithDouble:[_x doubleValue] + [x doubleValue]];
    _y = [NSNumber numberWithDouble:[_y doubleValue] + [y doubleValue]];
    _z = [NSNumber numberWithDouble:[_z doubleValue] + [z doubleValue]];
}

- (void)setX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    _x = x;
    _y = y;
    _z = z;
}


@end

RotationVector.h

#import "Vector3.h"

NS_ASSUME_NONNULL_BEGIN

@interface RotationVector : Vector3

- (id)init;
- (id)initWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

+ (instancetype)rotationVector;
+ (instancetype)rotationVectorWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

- (void)normalize;
- (void)addX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;
- (void)setX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

@end

NS_ASSUME_NONNULL_END

RotationVector.m

#import "RotationVector.h"

@implementation RotationVector

- (id)init{
    self = [super init];
    return self;
}

- (id)initWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    if (self = [super initWithX:x Y:y Z:y]){
        [self normalize];
    }
    return self;
}

+ (instancetype)rotationVector{
    return [[self alloc] init];
}
+ (instancetype)rotationVectorWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    return [[self alloc] initWithX:x Y:y Z:z];
}

- (void)addX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    [super addX:x Y:y Z:z];
    [self normalize];
}

- (void)setX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    [super setX:x Y:y Z:z];
    [self normalize];
}

- (void)normalize{
    if ([[self x] doubleValue] >= 360.0 || [[self x] doubleValue] < 0.0){
        int rotations = fmod([[self x] doubleValue], 360.0);
        self.x = [NSNumber numberWithDouble:[[self x] doubleValue] - rotations * 360.0];
    }
    if ([[self y] doubleValue] >= 360.0 || [[self y] doubleValue] < 0.0){
        int rotations = fmod([[self x] doubleValue], 360.0);
        self.y = [NSNumber numberWithDouble:[[self y] doubleValue] - rotations * 360.0];
    }
    if ([[self y] doubleValue] >= 360.0 || [[self y] doubleValue] < 0.0){
        int rotations = fmod([[self y] doubleValue], 360.0);
        self.y = [NSNumber numberWithDouble:[[self y] doubleValue] - rotations * 360.0];
    }
}

@end

BaseEntity.h

#import <Foundation/Foundation.h>
#import "Vector3.h"
#import "RotationVector.h"

NS_ASSUME_NONNULL_BEGIN

@interface BaseEntity : NSObject

@property Vector3* position;
@property RotationVector* rotation;


- (id)init;
- (id)initWithPositionX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

+ (instancetype)baseEntity;
+ (instancetype)baseEntityWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

- (void)moveX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;
- (void)moveToX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;
- (void)rotateX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;
- (void)rotateToX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z;

@end

NS_ASSUME_NONNULL_END

BaseEntity.m

#import "BaseEntity.h"

@implementation BaseEntity


- (id)init{
    if (self = [super init]){
        _position = [Vector3 vector3];
        _rotation = [RotationVector rotationVector];
    }
    return self;
}
- (id)initWithPositionX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    if (self = [super init]){
        _position = [Vector3 vector3WithX:x Y:y Z:z];
        _rotation = [RotationVector rotationVector];
    }
    return self;
}

+ (instancetype)baseEntity{
    return [[self alloc] init];
}
+ (instancetype)baseEntityWithX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    return [[self alloc] initWithPositionX:x Y:y Z:z];
}

- (NSString*)description{
    return [NSString stringWithFormat:@"Entity with pos: (%@, %@, %@) rot:(%@, %@, %@)",
            [[self position] x],
            [[self position] y],
            [[self position] z],
            [[self rotation] x],
            [[self rotation] y],
            [[self rotation] z]];
}

- (void)moveX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    [[self position] addX:x Y:y Z:z];
}
- (void)moveToX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    [[self position] setX:x Y:y Z:z];
}
- (void)rotateX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    [[self rotation] addX:x Y:y Z:z];
}
- (void)rotateToX:(NSNumber*)x Y:(NSNumber*)y Z:(NSNumber*)z{
    [[self rotation] setX:x Y:y Z:z];
}

@end

main.m

#import <Foundation/Foundation.h>
#import "BaseEntity.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");

        BaseEntity* entity1 = [BaseEntity baseEntity];
        BaseEntity* entity2 = [BaseEntity baseEntityWithX:[NSNumber numberWithDouble:10.0]
                                                        Y:[NSNumber numberWithDouble:10.0]
                                                        Z:[NSNumber numberWithDouble:10.0]];

        NSLog(@"Entity #1: %@",entity1);
        NSLog(@"Entity #2: %@",entity2);

        [entity1 moveToX:[NSNumber numberWithDouble:5.0]
                       Y:[NSNumber numberWithDouble:6.0]
                       Z:[NSNumber numberWithDouble:7.0]];

        [entity1 rotateToX:[NSNumber numberWithDouble:3.0]
                         Y:[NSNumber numberWithDouble:2.0]
                         Z:[NSNumber numberWithDouble:1.0]];

        [entity2 rotateToX:[NSNumber numberWithDouble:3.0]
                         Y:[NSNumber numberWithDouble:2.0]
                         Z:[NSNumber numberWithDouble:1.0]];

        [entity2 moveX:[NSNumber numberWithDouble:5.0]
                     Y:[NSNumber numberWithDouble:6.0]
                     Z:[NSNumber numberWithDouble:7.0]];

        [entity2 rotateX:[NSNumber numberWithDouble:3.0]
                       Y:[NSNumber numberWithDouble:2.0]
                       Z:[NSNumber numberWithDouble:1.0]];

        NSLog(@"Entity #1: %@",entity1);
        NSLog(@"Entity #2: %@",entity2);
    }
    return 0;
}

Everything works fine and produces the output:

2019-06-07 16:49:51.525097-0600 ObjCProj[57486:829658] Hello, World!
2019-06-07 16:49:51.525371-0600 ObjCProj[57486:829658] Entity #1: Entity with pos: (0, 0, 0) rot:(0, 0, 0)
2019-06-07 16:49:51.525404-0600 ObjCProj[57486:829658] Entity #2: Entity with pos: (10, 10, 10) rot:(0, 0, 0)
2019-06-07 16:49:51.525456-0600 ObjCProj[57486:829658] Entity #1: Entity with pos: (5, 6, 7) rot:(3, 2, 1)
2019-06-07 16:49:51.525479-0600 ObjCProj[57486:829658] Entity #2: Entity with pos: (15, 16, 17) rot:(6, 4, 2)
Program ended with exit code: 0
\$\endgroup\$
  • 1
    \$\begingroup\$ The current question title, which states your concerns about the code, is too general to be useful here. The site standard is for the title to simply state the task accomplished by the code. Please see How to Ask for examples, and revise the title accordingly. \$\endgroup\$ – Jamal Jun 8 at 3:24
  • \$\begingroup\$ If you are just coming into IOS programming it might be better to learn Swift rather than Objective-C. \$\endgroup\$ – pacmaninbw Jun 8 at 13:32
  • 1
    \$\begingroup\$ @pacmaninbw I already am fluent in swift. This is just for fun and learning \$\endgroup\$ – Mumphus Jun 8 at 16:39
0
\$\begingroup\$

You asked:

when do I use entity.position vs [entity position]?

The former is syntactic sugar for the latter, used with properties. I personally use . notation when dealing with @property, and otherwise use [...] syntax. But this is a matter of opinion, so use your own good judgment. I’d only suggest consistency throughout your project.

when do I use NSNumber vs double

Use NSNumber when you need an object. A few cases:

  • if the values might go into a NSArray or other collection;
  • if you you want to use with NSNumberFormatter;
  • where the numeric value is not required and you’d like to distinguish between nil and NSNumber and you want to avoid magical “sentinel” values in your code.

If you don’t need object behaviors, then feel free to use double, especially where doing something computationally intensive.

when do I use int vs NSInteger ...

Generally prefer NSInteger over int, unless there’s some reason that you need to specify the int type explicitly. See https://stackoverflow.com/a/4445199/1271826.

... vs NSNumber

See double/NSNumber discussion.

can I hide methods from super class as RotationalVector can do [RotationalVector vector3] but I would like for it to be only able to use [RotationalVector rotationalVector]

In general, we tend to want to follow the “Liskov substitution principle”, (LSP) where anywhere you used the base class you should be able to use a subclass. The notion of “hiding” some behavior of the parent is contrary to this notion.

And before you go down the road of “well, instead of hiding, I can just change the behavior of the parent’s method,” that would violate the “Open-closed principle”, another guiding design principle.

within an instance method whats the difference between _x, [self x] and self.x

self.x is simply syntactic sugar for [self x]. As discussed above, you’d tend to use the former for properties.

Re _x, that is bypassing the accessor methods (getters/setters) and interacting with the instance variable (ivar) directly. That’s in contrast to self.x (and [self x]), which are interacting with the accessor methods (getters and setters), which is a higher level of abstraction. In general, the self.x pattern offers better future-proofing of your code (e.g. if you decide to implement custom getters and setters at some future date, it saves you from pouring through your code for when you bypassed the accessor methods and interacted directly with the ivar).

You just want to avoid using self.x pattern inside the getters/setters for x (or else you can end up with infinite recursion). And, arguably, you should avoid it inside your initializer method, too.

would it be a good idea to only include init methods in the .m file?

The implementation of the init methods (as do the implementations of all methods) belong in .m file. The declaration of the init methods belongs in the .h file if you choose to expose that initializer to other classes. Think of the .h as your “public interface” for your class and the .m is for implementations (and any private interfaces).

am I using proper return types for init and factory method?

I’d suggest using instancetype in both cases for consistency’s sake, though the compiler is smart enough to infer instancetype when the method starts with init. But definitely use instancetype for your class methods, especially if you might subclass this class in the future.

\$\endgroup\$
  • \$\begingroup\$ This was exactly what I was hoping for, thank you for explaining so thoroughly! I hope this will help others as well. One follow up question, is the _x only because I used the @property(Macro?)? Or do all instance variables prepend an underscore for when you are directly accessing? Once again thanks, this was a great help! \$\endgroup\$ – Mumphus Jun 10 at 18:07
  • \$\begingroup\$ Also is there any modern Obj-C books you would recommend or should I continue learning off Apple Developer documentation and online tutorials? \$\endgroup\$ – Mumphus Jun 10 at 18:14
  • \$\begingroup\$ When you declare a @property it “synthesizes” an ivar for your with the leading _ underscore. If you manually declare your own ivars (e.g. for something that is not a property), it will not prepend anything, using exactly what you specified. \$\endgroup\$ – Rob Jun 10 at 18:19
  • \$\begingroup\$ Re resources, that’s off topic for this forum, but Apple’s documentation and WWDC videos are a great place to start. I’m sure there are good books out there, but make sure you get the most contemporary rendition as older resources show old patterns that are now discouraged and/or are obsolete. Also, Stack Overflow has tons of information for Objective-C (e.g. all of the above has been covered there extensively). \$\endgroup\$ – Rob Jun 10 at 18:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.