In swift 2.2, We could mutate a struct or enum within a closure, when it was inside a mutating function. But in swift 3.0 its no longer possible. I get the following error
closure cannot implicitly captured a mutating self parameter
Here is a code snippet,
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
test { (a) -> Void in
// Get the Error in the below line.
self.x = Double(a)
}
}
mutating func test(myClosure: #escaping (_ a: Double) -> Void) {
myClosure(3)
}
}
I get that value types are not supposed to be mutable. I have cases, where I do have to modify one variable in the struct within one of the functions, when I receive the API response. (In the completion closure)
Is what I was doing in swift 2.2, impossible or is there way to accomplish this?
The problem is that #escaping closures can be stored for later execution:
Escaping Closures
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. ...
One way that a closure can escape is by being stored in a variable that is defined outside the function....
Since the closure can be stored and live outside the scope of the function, the struct/enum inside the closure (self) will be copied (it is a value) as a parameter of the closure. And, if it was allowed to mutate, the closure could have an old copy of it, causing unwanted results.
So, in answer to your question, you cannot; unless you are able to remove "#escaping" (not your case because it's a 3rd party API)
Yeah, you can do something like this.
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
test { (a) -> Void in
self.x = Double(a)
}
}
mutating func test(myClosure: (_ a: Double) -> Void) {
myClosure(3)
}
}
Struct is value type. So when use as Model or ModelView, you can make up a closure with new Value to VC.
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
test { [x, y](a) -> Point in
// Get the Error in the below line.
return Point(x: Double(a), y: y)
}
}
mutating func test(myClosure: #escaping (_ a: Double) -> Point) {
self = myClosure(3)
}
}
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I ran into such a problem, and I can't figure out what are the ways to solve this problem.
First, let's say I have the following function
func summ(x: Int, y: Int, completionHandler: #escaping (Int) -> ()) {
let result: Int = x + y
completionHandler(result)
}
Next, in another function, we want to somehow process the result of the above function and return the processed value.
func summ(x: Int, y: Int, completionHandler: #escaping (Int) -> ()) {
let result: Int = x + y
completionHandler(result)
}
func getResult(x: Int, y: Int) -> (String) {
let resultString: String = ""
summ(x, y) { result in
resultString = "Result: \(String(result))"
}
return resultString
}
But when I call let resultString = getResult(x = 15, y = 10) I just get an empty string. When trying to find an error, I realized that in this method it creates let resultString: String = "" and then immediately returns this variable return resultString, and only After that completionHandler starts working
MARK - The solution below does not suit me, because the methods that I indicated above are just an example, in a real project, I need to return the correct value from the function in order to use it further.
let resultString: String = ""
func summ(x: Int, y: Int, completionHandler: #escaping (Int) -> ()) {
let result: Int = x + y
completionHandler(result)
}
func getResult(x: Int, y: Int) {
summ(x, y) { result in
resultString = "Result: \(String(result))"
self.resultString = resultString
}
}
So it is returning "" because the sum func takes time to complete. In the getResult func since the sum func takes time to finished u will always return "" in the getResult func. So instead the getResult should look something like this.
func getResult(x: Int, y: Int, completion: (String) -> Void) {
let resultString: String = ""
summ(x, y) { result in
resultString = "Result: \(String(result))"
completion(resultString)
}
}
extension UIScrollView {
func scrollPositionY(view:UIView) -> CGFloat {
if let origin = view.superview {
// Get the Y position of your child view
let childStartPoint = origin.convert(view.frame.origin, to: self)
let this = childStartPoint.y
return this
}
}
}
and then
let theYvalue = theScrollView.scrollPositionY
theYvalue is of type (UIView) -> CGFloat
Can I convert this to a CGFloat?
Thank you!
You didn't call the function. Functions are called using (), and because you have parameters, arguments need to be passed in, like (view: someViewPassedIn) where someViewPassedIn must be of type UIView.
In Swift, the difference between functions and closures is very small. Variables can be set as a closure, which is what (UIView) -> CGFloat is. It is a closure that has not been called like a function.
It can be quite hard to wrap your head around, but basically closures contain the block of code between the curly brackets/braces, {}, and the () runs that block of code.
Instead of:
let theYvalue = theScrollView.scrollPositionY
Try:
let theYvalue = theScrollView.scrollPositionY(view: someViewPassedIn)
let theYvalue = theScrollView.scrollPositionY
Just assigns the function to theYvalue
I think what you want is
let theYvalue = theScrollView.scrollPositionY()
which evaluates the function and should be a CGFloat, which the function returns.
I can't continue with my app and I can't test run it because something is wrong in my code that I don't know how to fix!
Here is the code:
import Foundation
extension Array {
mutating func shuffle() {
if count < 2 { return }
for i in 0..<(count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
customSwap(a: &self[i], b: &self[j])
}
}
}
func customSwap<T>(a: inout T, b: inout T) {
let temp = a
a = b
b = temp
}
The problem is that an array is a value type, and when you modify one element you change the whole array. So your call to customSwap() is passing in two references to the whole array which leads to the overlapping accesses to self error.
Instead, you could write customSwap() to take one copy of the array and the indices you want to swap:
func customSwap<T>(_ array: inout [T], _ a: Int, _ b: Int) {
let temp = array[a]
array[a] = array[b]
array[b] = temp
}
and then call it like this:
customSwap(&self, i, j)
But you don't have to do that, because Array has a built-in swapAt(_:_) defined like this:
mutating func swapAt(_ i: Int, _ j: Int)
So you could replace your customSwap call with:
self.swapAt(i, j)
But Array has a built-in shuffle() that you can just call instead of implementing it yourself.
I have code like this:
func newContent(jsonData: [String:AnyObject]) {
if let userContent: (User, String) = Helper.createUserFromWritingContent(jsonData) {
newContent?(userContent) // here is the warning: Passing 2 arguments to a callee as a single tuple value is deprecated
}
}
How can I solve this warning?
This warning is specified in here
Swift 3 will deprecate option of sending tuple as a argument. Try this one
func newAnswerWritingContent(jsonData: [String:AnyObject]) {
if let userContent: (BRSLegacyUser, String) = CometHelper.createBRSAnwerFromWritingContent(jsonData) {
let user = userContent.0
let content = userContent.1
newAnswerWritingContent?(user,content)
}
}
Swift used to let you do this:
func sum(x: Int, y: Int) -> Int {
return x + y
}
let params = (1,1)
sum(params) // <<== This is deprecated
Function sum takes two arguments, yet you call it with a single parameter - a tuple carrying both arguments.
This behavior is deprecated in Swift 2 for reasons outlined here.
Since the only tuple in your code is (User, String), this should fix the problem:
newContent?(userContent.0, userContent.1)
I just ran into this problem because I wanted two different sets of default parameters while keeping my code readable. I solved it by creating a typealias for my parameters.
typealias exampleParams = (param1: CGFloat, param2: CGFloat)
var exampleDefaultOpen: exampleParams = (param1: 0, param2: 20)
var exampleDefaultClose: exampleParams = (param1: 1, param2: 0)
func example(_ params: exampleParams) {
// do something with params.param1 and params.param2
}
This allows for setting to default:
example(exampleDefaultOpen)
but also for custom params like this:
example((param1: 2, param2: 50))
i have an array, var hoursPlayed = String
they are in a tableView and are all numbers, how would i add the numbers in that array together to get the average of hours played????? in Swift 2
You could use reduce:
let sum= hoursPlayed.reduce(0.0,combine:{$0+Float($1)!})
Basically you are iterating through the array and accumulating all the values. Since it is an array of strings,for simplicity I've force unwrapped to a Float, but you must check for the optional. The reduce function takes a closure as argument with 2 parameters. The dollar sign means take the first and the second and sum them.
Now you can easily divide to the number of elements in the array to have an avergae.
If you are in objC world it would be nice use key value coding and the #avg operator.
[UPDATE]
As Darko posted out the first version won't compile. The error was converting the first argument to a Float, since reduce takes an initial value and I put it as Float there is no need for further conversion.
let array = ["10.0", "30.0"]
if array.count > 0 {
let average = array.reduce(0.0, combine: {$0 + (Double($1) ?? 0.0)}) / Double(array.count)
print(average) // 20.0
}
$0 does not need to be converted because it is guaranteed that it's always Double. $0 is inferred from the initial value, which is declared as 0.0: Double.
array.count has to be checked to guard against a division thru 0.
I'd use a combination of flatMap to convert the strings to Doubles and reduce to add them up:
let doubles = array.flatMap { Double($0) }
let average = doubles.reduce(0.0, combine:+) / Double(doubles.count)
Using flatMap protects you from entries in array that can't be converted to Double If you know they all convert you can simplify it to:
let average = array.map({ Double($0)! }) / Double(array.count)
One final option is to extend Array with an average function if that seems like something you'll be more generally using, and use it in combination with flatMap and/or map:
protocol ArithmeticType {
static func zero() -> Self
func +(lhs:Self, rhs:Self) -> Self
func -(lhs:Self, rhs:Self) -> Self
func /(lhs:Self, rhs:Self) -> Self
func *(lhs:Self, rhs:Self) -> Self
init(_ number:Int)
}
extension Double : ArithmeticType {
static func zero() -> Double {
return 0.0
}
}
extension Array where Element : ArithmeticType {
func average() -> Element {
return reduce(Element.zero(), combine:+) / Element(count)
}
}
let avg = array.flatMap { Double($0) }.average()
Modified Darko's approach, which take in account if String is convertible to Double, or not. For an empty array it returns 0.0
let array = ["10.0", "31.2", "unknown", ""]
func avg(arr: [String])->Double {
let arr = array.flatMap(Double.init)
var avg = 0.0
if arr.count > 0 {
avg = arr.reduce(0.0, combine: + ) / Double(arr.count)
}
return avg
}
let a = avg(array)
print(a) // 20.6