5
\$\begingroup\$

When the app is run on an iPhone or simulator, tapping the screen spawns a dog class and displays an image of the dog.

I'd like comments, issues, whatever. Is this a decent first Objective-C project, and can others learn from it?

The code is meant to demonstrate Objective-C, Cocoa and UIKit.

Primary files in the project:

Protocols

GKLiving.h

//
//  GKLiving.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

#pragma mark - Defines


#pragma mark - The Living protocol of the GK namespace

@protocol GKLiving <NSObject>

#pragma mark - Mandatory methods

#pragma mark - Optional methods

@optional


@end

GKAnimalia.h

//
//  GKAnimalia.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

#import "GKLiving.h"

#pragma mark - Defines


#pragma mark - The Animalia protocol of the GK namespace

@protocol GKAnimalia <GKLiving>

#pragma mark - Mandatory methods

  + (NSString *)stringRepresentationOfBreed;

#pragma mark - Optional methods

@optional


@end

GKCanidae.h

//
//  GKCanidae.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

#pragma mark - Defines

#define GKCANIDAE_UNKNOWN_BREED @"Mutt"


#pragma mark - The Canidae protocol of the GK namespace

@protocol GKCanidae <GKAnimalia>

#pragma mark - Mandatory methods

  // Note that the "+" makes this a class-level method (it is
  // not an instance method).
  + (NSString *)says;

#pragma mark - Optional methods

@optional



@end

Dog Classes

GKDog.h

//
//  GKDog.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

// Protocols
#import "GKAnimalia.h"
#import "GKCanidae.h"

#pragma mark - Defines

#define GKDOG_PRIVATE_THOUGHTS @"I love you"
#define GKDOG_SOUND @"Arf"
#define GKDOG_PLACEHOLDER_PATH \
  [[NSBundle mainBundle] pathForResource:@"how-to-draw-a-dog-4" ofType:@"jpg"]


#pragma mark - The Dog class of the GK namespace

// 1. No namespaces in Objective-C, so class prefixes are used instead.
// The "NS" prefix (for NextStep, I assume) is pretty common.
// 2. This class conforms to the GKCanidae protocol.
// For a truer take on protocol naming conventions, see this article:
// http://stackoverflow.com/questions/7483813/protocol-naming-in-objective-c
//
@interface GKDog : NSObject <GKCanidae>

#pragma mark - Properties with getters/setters

  @property (strong, nonatomic) NSString *name;

  @property (strong, nonatomic) NSString *breed;

  @property (assign, nonatomic) NSInteger age;

  // A cute picture of the dog
  @property (strong, nonatomic) NSString *pictureUrlString;

#pragma mark - Instance methods

  - (void)becomeMutt;

@end

GKYorkshireTerrier.h

//
//  GKYorkshireTerrier.h
//  SampleApp
//

#import "GKDog.h"

#pragma mark - Defines

#define GKYORKSHIRETERRIER_BREED @"Yorkie"
#define GKYORKSHIRETERRIER_PICTURE_URL @"http://www.acuteaday.com/blog/wp-content/uploads/2010/12/yorkie-puppy-091210.jpg"


#pragma mark - The YorkshireTerrier class of the GK namespace

@interface GKYorkshireTerrier : GKDog


@end

GKYorkshireTerrier.m

//
//  GKYorkshireTerrier.m
//  SampleApp
//

#import "GKYorkshireTerrier.h"


@implementation GKYorkshireTerrier

#pragma mark - Class methods from GKAnimalia

+ (NSString *)stringRepresentationOfBreed
{
  return GKYORKSHIRETERRIER_BREED;
}

#pragma mark - Initializers

- (instancetype)init
{
  self = [super init];
  if (self)
  {
    self.breed = [GKYorkshireTerrier stringRepresentationOfBreed];
    self.pictureUrlString = GKYORKSHIRETERRIER_PICTURE_URL;
  }
  return self;
}

- (instancetype)initWithName:(NSString *)name
{
  self = [self init];
  if (self)
  {
    self.name = name;
  }
  return self;
}

- (instancetype)initWithName:(NSString *)name andAge:(NSInteger)age
{
  self = [self initWithName:name];
  if (self)
  {
    self.age = age;
  }
  return self;
}


@end

Main Dog Spawning

GKDogSpawner.h

//
//  GKDogSpawner.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

#pragma mark - Defines

#define GKDOGSPAWNER_IMAGE_MIN_X 0.0f
#define GKDOGSPAWNER_IMAGE_MAX_X ([UIScreen mainScreen].bounds.size.width - GKDOGSPAWNER_IMAGE_WIDTH)
#define GKDOGSPAWNER_IMAGE_MIN_Y 0.0f
#define GKDOGSPAWNER_IMAGE_MAX_Y ([UIScreen mainScreen].bounds.size.height - GKDOGSPAWNER_IMAGE_HEIGHT)
#define GKDOGSPAWNER_IMAGE_WIDTH 64.0f
#define GKDOGSPAWNER_IMAGE_HEIGHT 64.0f


#pragma mark - The DogSpawner class of the GK namespace

@interface GKDogSpawner : NSObject

#pragma mark - Instance methods

  - (void)spawnDogWithClassNamed:(NSString *)className onView:(UIView *)targetView;

@end

GKDogSpawner.m

//
//  GKDogSpawner.m
//  SampleApp
//

#import "GKDogSpawner.h"

#import "GKDog.h"

#import "GKYorkshireTerrier.h"

#import "GKRandomNumber.h"

// AFNetworking
// https://github.com/AFNetworking/AFNetworking
#import "UIImageView+AFNetworking.h"


@implementation GKDogSpawner

#pragma mark - Instance methods

- (void)spawnDogWithClassNamed:(NSString *)className onView:(UIView *)targetView
{
  // Create a dog instance of the specified class
  Class dogMetaClass = NSClassFromString(className);
  GKDog *newDog = [[dogMetaClass alloc] init];

  // Just for fun...
  if ([newDog isKindOfClass:[GKYorkshireTerrier class]])
  {
    NSLog(@"Whoa, you spawned a %@! They typically say %@ regularly.", [newDog breed], [dogMetaClass says]);
  }

  // Create a UIImage from the placeholder's local path
  UIImage *placeholderImage = [UIImage imageWithContentsOfFile:GKDOG_PLACEHOLDER_PATH];

  // Create an NSURL object from the picture's URL string
  NSURL *pictureUrl = [NSURL URLWithString:[newDog pictureUrlString]];

  // -- Create a UIImageView and populate its image from the picture URL.
  // -- While it's loading, the placeholder will be shown.
  // -- The -setImageWithURL:placeholderImage: method is from the "UIImageView+AFNetworking"
  // category on UIImageView; it provides plugin functionality that is not native to the base
  // UIImageView class.
  CGFloat x = [GKRandomNumber floatFrom:GKDOGSPAWNER_IMAGE_MIN_X to:GKDOGSPAWNER_IMAGE_MAX_X];
  CGFloat y = [GKRandomNumber floatFrom:GKDOGSPAWNER_IMAGE_MIN_Y to:GKDOGSPAWNER_IMAGE_MAX_Y];
  CGRect dogImageFrame = CGRectMake(x, y, GKDOGSPAWNER_IMAGE_WIDTH, GKDOGSPAWNER_IMAGE_HEIGHT);
  UIImageView *dogImageView = [[UIImageView alloc] initWithFrame:dogImageFrame];
  [dogImageView setImageWithURL:pictureUrl placeholderImage:placeholderImage];

  // Add the dog image view as a child of the target view. This will make the image visible.
  [targetView addSubview:dogImageView];
}

@end

Gesture Recognizers

GKTapHandler.h

//
//  GKTapHandler.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

#pragma mark - Defines


#pragma mark - The TapHandler class of the GK namespace

@interface GKTapHandler : NSObject <UIGestureRecognizerDelegate>

#pragma mark - Instance methods

  // Use "IBAction" to link methods to the Storyboard
  - (IBAction)handleTouch:(id)sender;

@end

GKTapHandler.m

//
//  GKTapHandler.m
//  SampleApp
//


#import "GKTapHandler.h"

#import "GKDogSpawner.h"

#import "GKRandomNumber.h"


@implementation GKTapHandler
#pragma mark - Instance variables
{
  GKDogSpawner *_dogSpawner;
  NSArray *_dogTypes;
}

#pragma mark - Initializers

- (instancetype)init
{
  self = [super init];
  if (self)
  {
    // Initialize the dog spawner
    self->_dogSpawner = [[GKDogSpawner alloc] init];
    // Initialize and populate the dog types. Note the nil guard value.
    self->_dogTypes = [NSArray arrayWithObjects:
      @"GKLhasaApso",
      @"GKYorkshireTerrier",
      nil];
  }
  return self;
}

#pragma mark - Instance methods

- (IBAction)handleTouch:(id)sender
{
  NSUInteger totalDogTypes = self->_dogTypes.count;

  NSUInteger randomDogTypeIndex = [GKRandomNumber unsignedIntFrom:0 to:totalDogTypes];

  NSString *dogType = [self->_dogTypes objectAtIndex:randomDogTypeIndex];

  id applicationDelegate = [[UIApplication sharedApplication] delegate];
  UIView *targetView = [applicationDelegate window].rootViewController.view;

  [self->_dogSpawner spawnDogWithClassNamed:dogType onView:targetView];
}

@end

Utility Classes

GKRandomNumber.h

//
//  GKRandomNumber.h
//  SampleApp
//

// Cocoa foundation classes
#import <Foundation/Foundation.h>

#pragma mark - Defines


#pragma mark - The RandomNumber class of the GK namespace

@interface GKRandomNumber : NSObject

#pragma mark - Class methods

  + (NSUInteger)unsignedIntFrom:(NSUInteger)min to:(NSUInteger)max;

  + (float)floatFrom:(NSUInteger)min to:(NSUInteger)max;

@end

GKRandomNumber.m

//
//  GKRandomNumber.m
//  SampleApp
//


#import "GKRandomNumber.h"

// Required for arc4random_uniform
#include <stdlib.h>


@implementation GKRandomNumber

#pragma mark - Class methods

+ (NSUInteger)unsignedIntFrom:(NSUInteger)min to:(NSUInteger)max
{
  NSUInteger range = max - min;
  u_int32_t generatedInteger = arc4random_uniform((u_int32_t)range);
  NSUInteger result = generatedInteger + min;
  return result;
}

+ (float)floatFrom:(NSUInteger)min to:(NSUInteger)max
{
  return (float)[self unsignedIntFrom:min to:max];
}

@end

View Controllers

GKViewController.h

//
//  GKViewController.h
//  SampleApp
//

// Main library for the iOS user interface
#import <UIKit/UIKit.h>

#pragma mark - Defines


#pragma mark - The ViewController class of the GK namespace

@interface GKViewController : UIViewController

  // Use "IBOutlet" to link properties to the Storyboard
  @property (weak, nonatomic) IBOutlet UITapGestureRecognizer *tapRecognizer;

@end
\$\endgroup\$
13
\$\begingroup\$

Because this question is so, so large, I won't get into all the specifics of everything I see. I will point out some things, and provide some examples from one file or another, and as you work through my answer, you should work through your project to find all the other instances of an example I point out.

I will start with a simple answer to the topic question:

Is this a decent first Objective-C project, and can others learn from it?

Simply, yes. I haven't built and compiled and tried running this, but I assume you have. Certainly anyone whose Objective-C knowledge is below that of this project would be able to learn from it. And it's fairly well organized, clean, etc, so I'll pass it as decent as well.

But in the first few paragraphs, you talk about wanting for a project to serve as an example of Objective-C "standard practice". A perfectly fine want--but let's be clear, this project isn't that.

Also, while I did say that someone could learn from this project, I think the audience of people who would take the time to learn from this is fairly narrow. At least in my personal experience as a programmer, I learn best when adding small chunks of knowledge at a time. To me, learning programming is like having a rubber band ball. It's really hard to get that ball started. But once you do, all you have to do to keep it going is snap one more rubber band on here and there.

Your project is, in my opinion, a bit too complex for Objective-C and iOS beginners and covers far too much ground. And the project contains some problems that if a more advanced Objective-C user "learned" as the way to do it, he wouldn't have benefited all that much.

So, now that that's out of the way, I'll start at the top, and work my way down.

It is likely that this answer my be broken into multiple answers or will receive future edits. Basically, treat this as an answer in progress, as there's quite a few things here I want to address, and I won't be able to address it all in one sitting--you didn't write this whole project in one sitting, did you?


First of all, some of the problems that are consistent throughout the entirety of the project:

EDIT: As a note here, I'm pointing to sections of your code you've marked off with a #pragma mark. I wanted to come back and edit in some clarity. The #pragma mark themselves are for the most part okay. These are basically just comments with some special features, and using #pragma marks to mark off sections is great. I'm mostly discussing the content in each of these sections.


#pragma mark - Namespace ...

// 1. No namespaces in Objective-C, so class prefixes are used instead.
// The "NS" prefix (for NextStep, I assume) is pretty common.

Correct and correct. "NS" does stand for NextStep. In iOS development, most of the Foundation classes you'll use are prefixed with NS or UI. You'll run into some other common ones too like CG, AF, etc.

But more importantly, you're correct about the fact that there are no namespaces in Objective-C. As such, a project hoping to serve as the "standard practice" for Objective-C shouldn't have a #pragma mark - Namespace in every single header file in its project. True Objective-C projects should have this no where. Best case scenario, you might see it in an Objective-C++ project.

It is perfectly fine for a programmer coming from a non-Objective-C background to do something like this if it helps the organization of the project because he's used to thinking of it in some other way, but it's definitely not "standard practice".


#pragma mark - Defines

You've included this line in most of the files. The fact of the matter is, you shouldn't be using #define so often that it's a regularly occurring section in your code.

Certainly, you shouldn't be using #define simply for constant values. That's what the const keyword is for. And if you need to declare a constant value in a .h file so it can be seen by multiple files, well, that's what the extern keyword is for.

So for example this:

#define GKDOGSPAWNER_IMAGE_MIN_X 0.0f

Should be replaced with this:

extern CGFloat const DKDOGSPAWNER_IMAGE_MIN_X;

in the .h, and then define it in the .m of the same file:

CGFloat const DKDOGSPAWNER_IMAGE_MIN_X = 0.0f;

Applying this thinking to all of your defines eliminates all but 3 of the #defines.

The last three could also potentially be eliminated via functions. For example:

#define GKDOGSPAWNER_IMAGE_MAX_X ([UIScreen mainScreen].bounds.size.width - GKDOGSPAWNER_IMAGE_WIDTH)

could be replaced with:

CGFloat GKDOGSPAWNER_IMAGE_MAX_X();

in the .h, then define the function in the .m:

CGFloat GKDOGSPAWNER_IMAGE_MAX_X() {
    return ([UIScreen mainScreen].bounds.size.width - GKDOGSPAWNER_IMAGE_WIDTH);
}

In this case though, the #define doesn't bother me as much.

These points on #define aren't really even Objective-C specific. They can apply across all of the C-Based languages that support preprocessor statements (though the syntax may vary slightly from language to language).


#pragma mark - Mandatory methods

#pragma mark - Optional methods

It is fine to have these #pragma marks in, particularly in large protocols or just large files. But having these marks does not excuse not marking the methods with code that actually makes methods required or optional.

This is something that doesn't really translate unless you're in an IDE, but a #pragma mark and other codes show up in different colors. In my current Xcode theme, all precompiler code (which includes #pragma mark) shows up as brown. Things like @protocol, @required, @optional, and @end show up as pink.

So if I were to load up your code in Xcode, a quick glance at your protocols would show me the keyword @required never shows up. It'd be a bit confusing. Now, all methods in a protocol are by default @required (unless marked @optional), but if we're going to talk about standard practice, I am certainly of the opinion that a section of @required methods should be explicitly marked with the @required keyword. It is my personal habit to individually mark every method in a protocol so there is no mistaking the intent of any of the protocol methods. This may be a bit overboard for a "standard practice", but I certainly think the @required keyword belongs, for clarity.


GKRandomNumber.h / GKRandomNumber.m

There are a few problems I have with this class.

First, it's not really a class, is it? No one should ever instantiate an object of this class, and yet they could.

There are two fixes. Fix one is to use preprocessor statements to prevent instantiation and subclassing. I don't really like this fix, but it is a possibility. Preventing instantiation looks like this in the .h file:

+ (id)alloc __attribute__((unavailable("GKRandomNumber cannot be instantiated")));
- (id)init __attribute__((unavailable("GKRandomNumber cannot be instantiated")));

But this is a little silly. Especially when the two methods in this class are so simple. We don't need a class because we don't need Objective-C style methods. We can simply use C-style functions:

NSUInteger randomUnsignedInt(NSUInteger min, NSUInteger max);

My second big problem with these files is that the method names don't say "random" in them--they should. The name of the file or class isn't enough. Consider NSArray or NSString, for example. Look at all their methods. If you're using an NSArray, you know you're dealing with an NSArray, but Apple says this isn't enough, and as such, all the methods look like this:

  • array
  • arrayWithArray:
  • arrayWithContentsOfFile:
  • arrayWithContentsOfURL:
  • arrayWithObject:
  • arrayWithObjects:
  • arrayWithObjects:count:

My third big problem with this pair of files is that you have a method for grabbing an integer in a range... but require only unsigned integers. If I want to assure a positive number, I can just assure my minimum number is greater than zero, right? Why are we dealing only with unsigned integers?

Fourth, your method has no way of dealing with when the value sent for max is larger than the value sent for min. Given we're dealing with UNSIGNED INTEGERS, this will be a big, big problem. If you try to find a random number between 10 and 5, you won't get 6, 7, 8, or 9. When you find the range (5-10) using unsigned ints, you'll get underflow, and it will wrap around to some number about 5 less than whatever the max for NSUInteger is (which will depend on system). So your range will be almost the entire spectrum of unsigned ints. When you add your min back (10, not 5), you will definitely be outside the range of 5 to 10. The answer to this problem is to do things the way Apple has done. Rather than taking a starting point and an ending point, you take a starting point and a range.

Fifth, you're doing a lot of unhelpful and misleading castings. The float version of your random number method doesn't really return a random float... it returns a random NSUInteger cast as a float. And your NSUInteger doesn't really return a random NSUInteger in the given range... it returns a u_int32_t cast as an NSUInteger.

u_int32_t is a typedef from C that is a 32-bit integer, no matter the operating system.

NSInteger is a typedef from Objective-C. On 32-bit systems it's a 32-bit integer. On 64-bit systems, it's a 64-bit integer.

float is a floating point number and not an integer.

Again, I'm not super familiar with arc4random_uniform, and using u_int32_t may be a limitation of this function. But if that's the case, then your Objective-C method should reflect this. If you're using a u_int32_t range and getting the result from the random function as a u_int32_t, then the method should take u_int32_t arguments and return a u_int32_t value. If the end user of this method needs to cast it as something else, then let them, but if the internals of the method do all the actual work in some particular data type, you should probably use that data type as the return type and the argument type.

And this logic applies to the float version of the method. To me, a method that says it returns a random float is a method that will return more than just an integer cast to a float. If I want a random whole number to use as an argument to some method that expects a floating point number, then I need to use a method to generate a random int and cast it to a float myself or generate a random float and come up with some method for zeroing out the decimals. Either way, I shouldn't be mislead by a method that claims to return a float and only returns integers cast as floats.

\$\endgroup\$
  • \$\begingroup\$ Excellent feedback; I'll take in your advice. \$\endgroup\$ – Greg M. Krsak Apr 7 '14 at 21:05
  • \$\begingroup\$ Found something interesting about const NSString values: stackoverflow.com/questions/6828831/… \$\endgroup\$ – Greg M. Krsak Apr 23 '14 at 14:50
  • \$\begingroup\$ Correct. But you don't have any NSString's that I recommended consting, but NSString * const someString would be the correct way to do it. \$\endgroup\$ – nhgrif Apr 23 '14 at 23:26
8
\$\begingroup\$

Now, given that basically all Objective-C is written to make use of Cocoa frameworks, and we're talking about programs to run on OSX or iOS, we have to discuss Apple. Apple is pretty consistent when their method naming conventions, and their way of doing things. So any conversation about Objective-C standard practice would be incomplete without a very serious conversation for the "Apple way". So, let's go over your project's inconsistencies with the "Apple way".


First of all, your initializers. You're accessing properties through their accessor methods in your initializer methods. This is an absolute no-no in Objective-C, and Apple explicitly states not to do this. The short answer as to why is basically because it is an absolute maintenance nightmare.

Basically, in init, dealloc, and the accessor methods themselves (should you choose to override them), you need to access the instance variable directly.


And while we're on the topic of instance variables, can we discuss the instance variables you declare in the .m and access elsewhere via self->_iVar? I don't understand why you're doing this. I mean, I understand what this is, but I'm confused as to your inconsistency. To me, anything "standard practice" should certainly be consistent.

The only reason to self-> an instance variable is when a scoping issue is overshadowing the instance variable, and we're talking only about an instance variable that's not declare as a @property.

If your intention is to make private properties for your classes, you can do this with a class category in the .m file:

@interface YourClass()

@property (nonatomic,strong) NSString *yourIvar;

@end

@implementation YourClass
// stuff

This is the same as declaring a property in the interface of the .h file, except the variable is private. Now, you can access the variable within the class via self.yourIvar instead of needed to do self->_yourIvar.

You really shouldn't ever need to use the arrow operator in Objective-C really. It's fine to have non-property instance variables, but these should be so clearly name that you're not going to run into any scoping issues. It may be common practice to use self in front of every instance variable in other languages, but it's just not really done in Objective-C--it's not standard practice.

And the biggest problem you'd run into is that Objective-C programmers that don't come from a background where they've used the dereferencing arrow will have to stop and figure out what that is and what that means... because as I've said, it's just not typically used in Objective-C.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the heads-up on not using property accessors in initializers. \$\endgroup\$ – Greg M. Krsak Apr 7 '14 at 21:06
  • \$\begingroup\$ Again with regards to using accessors in initializers, would that rule only apply to base classes within an inheritance chain? \$\endgroup\$ – Greg M. Krsak Apr 23 '14 at 15:28
  • \$\begingroup\$ No. There is no concept of a Final class in Objective-C, as such, any class can technically be inherited from. The only place it would possibly be even remotely save to use an accessor would be in an init method with a return type defined as whatever the class is instead of id or instancetype, but I'd recommend both against using a return type other than instancetype and using the accessors even if you did change the return type. \$\endgroup\$ – nhgrif Apr 23 '14 at 23:17

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.