1

I converted the Objective-C app Dropit from the Stanford CS193P course to Swift. The original code is located at:

http://web.stanford.edu/class/cs193p/cgi-bin/drupal/

There are multiple uses of lazy instantiation that perform additional initialization or cause some other side effect. My conversion to Swift is working, but I'm not sure that it is the proper solution or whether this kind of Objective-C coding should be avoided entirely? The first examples just set some properties as part of the lazy instantiation.

- (UIGravityBehavior *)gravity
{
    if (!_gravity) {
        _gravity = [[UIGravityBehavior alloc] init];
        _gravity.magnitude = 0.9;
    }
    return _gravity;
}

- (UICollisionBehavior *)collider
{
    if (!_collider) {
        _collider = [[UICollisionBehavior alloc] init];
        _collider.translatesReferenceBoundsIntoBoundary = YES;
    }
    return _collider;
}

My Swift version:

@lazy var gravity: UIGravityBehavior = {
    var tempGravity = UIGravityBehavior()
    tempGravity.magnitude = 0.9
    return tempGravity
    }()
@lazy var collider: UICollisionBehavior = {
    var tempCollider = UICollisionBehavior()
    tempCollider.translatesReferenceBoundsIntoBoundary = true
    return tempCollider
    }()

Is there a cleaner way of doing that initialization in Swift?

The ViewController of the Objective-C for the second example is a bit more involved:

@interface DropitViewController () <UIDynamicAnimatorDelegate>
@property (weak, nonatomic) IBOutlet UIView *gameView;
@property (strong, nonatomic) UIDynamicAnimator *animator;
@property (strong, nonatomic) DropitBehavior *dropitBehavior;
@end

@implementation DropitViewController

static const CGSize DROP_SIZE = { 40, 40 };

- (DropitBehavior *)dropitBehavior
{
    if (!_dropitBehavior) {
        _dropitBehavior = [[DropitBehavior alloc] init];
        [self.animator addBehavior:_dropitBehavior];
    }
    return _dropitBehavior;
}

- (UIDynamicAnimator *)animator
{
    if (!_animator) {
        _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.gameView];
        _animator.delegate = self;
    }
    return _animator;
}

In Swift:

class ViewController: UIViewController, UIDynamicAnimatorDelegate {
    @IBOutlet var gameView: UIView

    let DROP_SIZE = CGSize(width: 40.0, height: 40.0)

    // lazy instantiation with closures
    @lazy var animator: UIDynamicAnimator = {
        var tempAnimator = UIDynamicAnimator(referenceView: self.gameView)
        tempAnimator.delegate = self
        return tempAnimator
    }()
    @lazy var dropitBehavior: DropitBehavior = {
        var tempBehavior = DropitBehavior()
        // side effect...
        self.animator.addBehavior(tempBehavior)
        return tempBehavior
    }()

The way the program was designed, during the lazy initialization of dropitBehavior is when animator is created for the first time and then dropitBehavior is added to the animator. This can be verified with println, in the debugger, etc.

Note that there is a related question: Lazy instantiating a UIDynamicAnimator with referenceView - Swift

However, the suggested solution appears to be avoid @lazy, use optionals and move the work into viewDidLoad which doesn't seem that great either.

There is a lot of Objective-C code out there like this to translate, so I would like to learn the proper way to do this in Swift.

Community
  • 1
  • 1
kasplat
  • 1,177
  • 3
  • 13
  • 16
  • 1
    I would be in favor of `@lazy` as it has more semantic value. The code you wrote looks alright IMO, but if you don't like the way it works then you should [file a bug](http://bugreport.apple.com). – jtbandes Jul 19 '14 at 07:03
  • In general, if those classes had initializers that took all relevant arguments, rather than having to set properties after initialization, it might be cleaner. Specifically in the case of trying to pass `referenceView: self.gameView`, you'll run across the problem in the question you linked, but this might be solvable pretty easily if you make gameView optional. – jtbandes Jul 19 '14 at 07:06

0 Answers0