Implementing NSCopying and copyWithZone I've read some resources on implementing NSCopying and copyWithZone, but things are still not very clear. I see many implementations with allocWithZone, which seems to be deprecated, and with autorelease, which seems to be unnecessary with ARC.

I was wondering if I can get away with something more simple. Something like this:

MTSound.h

#import &lt;Foundation/Foundation.h&gt;

@interface MTSound : NSObject &lt;NSCopying&gt;

@property (nonatomic) float frequency;
@property (nonatomic) float amplitude;
@property (nonatomic) float duration;

// Designated Initializer
- (instancetype)initWithFrequency:(float)freq
                        amplitude:(float)amp
                         duration:(float)dur;

+ (instancetype)soundWithFrequency:(float)freq
                         amplitude:(float)amp
                          duration:(float)dur;

@end


MTSound.m

#import "MTSound.h"

@implementation MTSound

// Initializers

- (instancetype)initWithFrequency:(float)freq amplitude:(float)amp duration:(float)dur {
    if (self = [super init]) {
        _frequency = freq;
        _amplitude = amp;
        _duration = dur;
    }
    return self;
}

// Class factory methods

+ (instancetype) soundWithFrequency:(float)freq amplitude:(float)amp duration:(float)dur {
    return [[self alloc] initWithFrequency:freq amplitude:amp duration:dur];
}

// NSCopying protocol

- (id)copyWithZone:(NSZone *)zone {
    return [[self class] soundWithFrequency:self.frequency
                                  amplitude:self.amplitude
                                   duration:self.duration];
}

@end


Is there something wrong with this approach? If so, what's the correct implementation?
Your implementation looks good to me.  From the NSCopying reference:

Your options for implementing this protocol are as follows:
Implement NSCopying using alloc and init... in classes that don’t inherit copyWithZone:.
Implement NSCopying by invoking the superclass’s copyWithZone: when NSCopying behavior is inherited. If the superclass implementation might use the NSCopyObject function, make explicit assignments to pointer instance variables for retained objects.
Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable.
If a subclass inherits NSCopying from its superclass and declares additional instance variables, the subclass has to override copyWithZone: to properly handle its own instance variables, invoking the superclass’s implementation first.

So you just need to know that if you are going to have classes that inherit from your MTSound, they will need to call [super copyWithZone] first, and then initialize their own instance variables after that.
You may also want to consider the first option of simply using alloc and init.  If it is okay with Apple, then it should be alright for you to do it too, if you want.
Looking at the reference examples, it also seems perfectly fine to ignore the NSZone argument that is passed into copyWithZone.  It seems to be common practice to ignore that argument.  This Stack Overflow answer talks more about NSZone if you are interested.
I have just a couple more comments about the code.  It seems to me that these comments:
// Initializers
// Class factory methods
// NSCopying protocol

Would be more useful as pragmas, or could be removed completely.  If you do this instead:
#pragma mark - Initializer

Then you will get a bold heading in the method browser in Xcode, which can be useful for finding specific methods in a class.  This is definitely a matter of preference, but simple comments like you have are not that useful and are a bit redundant.
Is there a reason that your properties such as @property (nonatomic) float frequency; are not readonly?  From reading the code, I cannot tell if they will be modified later, or if the values will always stay the same.  If they will always stay the same, I think readonly would be appropriate.
Overall the code is very clean and easy to understand.