/magic-property

Objective-C data records made easy

Primary LanguagePythonBSD 3-Clause "New" or "Revised" LicenseBSD-3-Clause

magic-property

Objective-C data records made easy.

magic-property generates immutable Objective-C classes with init, copy, isEqual, hash, and description methods from a simple header file with just a list of properties.

Features

  • Automatically generates immutable Objective-C classes
  • correct isEqual: and hash implementation
  • init, new and copy methods for/with all properties
  • description method with human-readable output
  • all property attributes (e.g. nonatomic, strong) supported
  • ARC support
  • NSCopying, NSCoding and NSSecureCoding support
  • support for extra method implementations and custom property setters and accessors
  • support for custom typedefs and enums
  • support for generating correct isEqual:, hash, and description for handwritten classes
  • no special compiler features needed

Usage

Sample Header

A simple header like this is enough to generate the immutable Objective-C class:

#import <Foundation/Foundation.h>

#import "Address.h"

@interface PhonebookEntry : NSObject
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *email;
@property (nonatomic, strong, readonly) NSArray *phoneNumbers;
@property (nonatomic, strong, readonly) NSString *birthday;
@property (nonatomic, strong, readonly) Address *address;
@end

Just process the header file with the magic-property script and the actual class header and implementation file will be generated by the script.

Generated Code

magic-property expands the interface to the code shown below. The generated code in the .m file also contains meaningful implementations of isEqual:, hash, and description.

                                     // added support for NSCopying and NSCoding
@interface PhonebookEntry : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong, readonly) NSString *name;
@property (nonatomic, strong, readonly) NSString *email;
@property (nonatomic, strong, readonly) NSArray *phoneNumbers;
@property (nonatomic, strong, readonly) NSString *birthday;
@property (nonatomic, strong, readonly) Address *address;

// added init and new methods
- (id)initWithName:(NSString *)name 
             email:(NSString *)email 
      phoneNumbers:(NSArray *)phoneNumbers 
          birthday:(NSString *)birthday 
           address:(Address *)address;
+ (PhonebookEntry *)newWithName:(NSString *)name 
                          email:(NSString *)email 
                   phoneNumbers:(NSArray *)phoneNumbers 
                       birthday:(NSString *)birthday 
                        address:(Address *)address;

// copy methods for all properties.
- (PhonebookEntry *)copyWithName:(NSString *)name;
- (PhonebookEntry *)copyWithEmail:(NSString *)email;
- (PhonebookEntry *)copyWithPhoneNumbers:(NSArray *)phoneNumbers;
- (PhonebookEntry *)copyWithBirthday:(NSString *)birthday;
- (PhonebookEntry *)copyWithAddress:(Address *)address;

// meaningful implementations of isEqual:, hash, and description
// are placed in the .m file
@end

Commandline Options

The magic-property script supports the following commandline options, see below for Xcode integration.

usage: magic-property [-h] -o DIR [--no-arc] [--force] [--custom-ints FILE]
                      [--enums FILE] [--only-eq-hash-description]
                      [--eq-hash-description-outfile FILE]
                      [--eq-hash-macro-base-name NAME]
                      [--description-macro-base-name NAME]
                      [--eq-hash-description-macro-base-name NAME]
                      [INPUT_FILE [INPUT_FILE ...]]

Objective-C data records made easy

positional arguments:
  INPUT_FILE            input files for case-class generation (.h and .m
                        files)

optional arguments:
  -h, --help            show this help message and exit
  -o DIR, --output-dir DIR
                        output directory
  --no-arc              generate code without ARC support
  --force               force code generation
  --custom-ints FILE    file with custom integer types (newline separated)
  --enums FILE          file with enum definitions
  --only-eq-hash-description
                        generate only the IsEqualHashDescription macros
  --eq-hash-description-outfile FILE
                        output file for the IsEqualHashDescription macros,
                        default: MPEqHashDescription.h
  --eq-hash-macro-base-name NAME
                        basename for the IsEqualHash macro, default:
                        MPGenerateIsEqualHash
  --description-macro-base-name NAME
                        basename for the Description macro, default:
                        MPGenerateDescription
  --eq-hash-description-macro-base-name NAME
                        basename for the IsEqualHashDescription macro,
                        default: MPGenerateIsEqualHashDescription

Custom Methods

magic-property can use custom and extra methods to override the default behavior. Simply add the methods to override or to add in an m-file. There is no need to manually add any of the generated methods.

@implementation PhonebookEntry
+ (PhonebookEntry *)parseFromString:(NSString *)
{
   ... // the method is added to the final class by the script
}

- (NSString *)serializeToString
{
    ... // this becomes a method of the immutable class
}
@end

Custom Integers

TODO

Enumerations

magic-property also simplifies definition of enumeration types. To define an enumeration type NetworkState with two states Offline and Online, just place the following line in some file and pass the file to magic-property via the --enums flag.

NetworkState: Offline Online

magic-property then generates the following .h file and a suitable .m file:

// the definition of the enumeration
typedef enum {
  NetworkStateOffline = 1,
  NetworkStateOnline = 2,
} NetworkState;
// a function for converting enum values to strings
NSString *stringFromNetworkState(NetworkState x);
// support for meaningful description methods if the enumeration type is used
// in a property in classes processed by magic-property (see below under "Custom Integers")
@interface MPIntegerNetworkState : MPInteger
+ (NSString *)descriptionOf:(NSInteger)i;
@end

isEqual:, hash, description for Handwritten Classes

TODO

XCode Integration

magic-property can be used as standalone script but XCode integration can be setup with these simple steps:

Step 1: Copy magic-property script

Copy the scripts folder to your project.

Step 2: Create folder and property files

Create a folder where the generated code will be placed (e.g. gen) and a folder where all your property header files will go (e.g. property-classes). Place the property header files (*.h) you want to use to this folder.

Step 3: Add the script as a build phase

Add a "Run script" build phase to XCode. Add build target

Make sure the script build phase is before compiling sources and add the following code:

scripts/magic-property -o gen property-classes/*.h property-classes/*.m

Make sure to change the paths to the correct location you chose for the magic-property script, generated code folder and input folder. Add the property-classes/*.m argument only if you have at least one .m file in property-classes.

Script

Step 4: Add generated files

Add the files from the objective-c folder to your project. Build your project once. The build will generate all the necessary files from your property class headers. Add the generated headers and implementation files to your XCode project. Note that you have to add the files again for each new property class header file you create, but not if you change the contents of a property header file.

Add files