Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift for in - get entries as reference

Is there a way to make the for .. in loop return references to the entries of a collection instead of copies?

Say I have an array points of CGPoint objects and I want to loop over them and pass each point to a function adjustPoint that can modify the point using an inout parameter.

Now doing the following doesn't work, since the for .. in loop returns the points as immutable / mutable (depending on whether or not I use var) copies of the actual points in the array:

for var point in points {
    adjustPoint(point: &point)  // This function only changes the copy
}

Currently, the only way I see to do this is to loop over the index:

for i in 0..<points.count {
    adjustPoint(point: &points[i])
}

Is this really the only way or is it also possible with a for .. in loop?

Note: I've read this question which is from quite some time ago (Swift 1 I believe) so I thought maybe they've changed something in the meantime: turn for in loops local variables into mutable variables

like image 331
Keiwan Avatar asked Sep 26 '16 16:09

Keiwan


People also ask

How do you pass a variable as a reference in Swift?

To pass parameter by reference to a Swift function, define this parameter with inout keyword, and use preface the parameter in function call with ampersand (&).

Are arrays passed by reference or value Swift?

Structs in Swift are passed by value, but you can use the inout modifier to modify your array (see answers below). Classes are passed by reference.

How to specify array type in Swift?

Array Type Shorthand Syntax The type of a Swift array is written in full as Array<Element> , where Element is the type of values the array is allowed to store. You can also write the type of an array in shorthand form as [Element] .

How to break an if statement Swift?

In Swift, the break statement is used to immediately end the execution of a loop, a switch statement, an if statement of a do statement. As such, the break statement consists of a single break keyword followed optionally by a (you guessed it) statement label!


1 Answers

So the basic answer for your original for loop question is: no. The for...in is designed to give you copies of value types. It's a forced-functional programming style as you said yourself in the comments.

To mutate an array you must say array[index] in some fashion or another and now you're referring to the original value and can mutate it. The trick is finding an expressive way that prevents common errors. The four techniques I'm advocating below are:

  1. Make a powerful abstraction as an extension so you DRY throughout the code
  2. Use indices not a manual range which is also error prone (... vs. ..<)
  3. Avoid ugly throwbacks to C-language constructs like & (see #1)
  4. Consider keeping around the mutating version and the non-mutating version

This is probably most in keeping with the spirit of Swift, that is, quirky, verbose, and more nettlesome than you'd want, but ultimately very expressive and powerful with the proper layers in place:

import Foundation
import CoreGraphics

protocol Pointy {
    var x: CGFloat { get set }
    var y: CGFloat { get set }
    func adjustBy(amount: CGFloat) -> CGPoint
    mutating func adjustInPlace(amount: CGFloat) -> Void
}

extension CGPoint: Pointy {
    func adjustBy(amount: CGFloat) -> CGPoint {
        return CGPoint(x: self.x + amount, y: self.y + amount)
    }

    mutating func adjustInPlace(amount: CGFloat) -> Void {
        x += amount
        y += amount
    }
}

extension Array where Element: Pointy {
    func adjustBy(amount: CGFloat) -> Array<Pointy> {
        return self.map { $0.adjustBy(amount: amount) }
    }

    mutating func adjustInPlace(amount: CGFloat) {
        for index in self.indices {
            // mysterious chunk of type calculus: need  "as! Element" -- https://forums.developer.apple.com/thread/62164
            self[index].adjustInPlace(amount: amount) // or self[index] = (self[index].adjustBy(amount: amount)) as! Element 
       }
    }
}


// Hide the above in a Util.swift that noone ever sees.

// AND NOW the true power shows
var points = [ CGPoint(x: 3.0, y: 4.0) ]
points.adjustInPlace(amount: 7.5)
points.forEach { print($0) }
// outputs (10.5, 11.5)
let adjustedPoints = points.adjustBy(amount: 7.5) // Original unchanged
like image 133
BaseZen Avatar answered Oct 24 '22 17:10

BaseZen