无法使用变异成员...因为附加?

时间:2019-01-16 13:05:20

标签: swift

对于为什么会出现此错误(Swift 4.2.1),我感到困惑。

// next, select only entries in range
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
    .map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? timeSeriesDataFromAppData(data) : nil
    }.append(contentsOf: locationsData.map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? timeSeriesDataFromLocationData(data) : nil
    })

这产生

  

不能对不可变值使用变异成员:函数调用返回不可变值

在第三行。

但这不是:

// next, select only entries in range
let filteredDataOpt: [AppData?] = filteredByApps
    .map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? data : nil
}
let filteredData: [AppData] = filteredDataOpt.compactMap { $0 }

我的困惑源于我使用append操纵序列而不是先将其分配给常量然后append对其进行操纵的事实。为什么我的序列是只读的?

编辑:显然,地图总是(至少乍一看,很奇怪)返回一个常数。总的来说,我的解决方案是:

var filteredDataOpt: [TimeSeriesEntry?] = filteredApps
    .map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? self.timeSeriesData(appData: data) : nil
}
filteredDataOpt.append(contentsOf: self.locationsData.map { data in
    let isInDate = dates.contains { date in
        guard let d = date else {
            return false
        }
        return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
    }
    return isInDate ? self.timeSeriesData(locationData: data) : nil
})
let filteredData = filteredDataOpt.compactMap { $0 }

但是,还有其他人感到不满意吗?我坚持:

  • 中间变量
  • 我只需要一个常量的变量

4 个答案:

答案 0 :(得分:5)

您的问题可以减少为以下问题:

let data = [1, 2, 3]
let data2 = [4, 5, 6]

let filteredData: [Int] = data
    .map { $0 }
    .append(contentsOf: data2.map { $0 })

解决方案是使用串联而不是append

let data = [1, 2, 3]
let data2 = [4, 5, 6]

let filteredData: [Int] = data
    .map { $0 }
    + data2.map { $0 }

作为解释,这类似于:

let a: Int = 0
let b = a += 1 // this is append
let c = (a + 1) += 1 // this is append with a temporary expression

(您将添加到立即丢弃的内容中,并且该值不会存储到c中)。

显然应该这样做

let a: Int = 0
let b = a + 1

请注意,即使您可以append获得临时返回值,append也没有返回值,并且分配给filteredDataOpt的结果将是Void

临时表达式为常量(不可变)的原因是为了防止您发生类似的错误。

答案 1 :(得分:3)

不是您的问题的答案,但这会起作用

var filteredDataOpt: [TimeSeriesEntry?] = filteredApps
        .map { data in
            let isInDate = dates.contains { date in
                guard let d = date else {
                    return false
                }
                return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
            }
            return isInDate ? timeSeriesDataFromAppData(data) : nil
        }
filteredDataOpt.append(contentsOf: locationsData.map { data in
            let isInDate = dates.contains { date in
                guard let d = date else {
                    return false
                }
                return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
            }
            return isInDate ? timeSeriesDataFromLocationData(data) : nil})

答案 2 :(得分:2)

问题是方法append(contentsOf:)正在变异,并且默认情况下swift中任何函数的返回元素都是不可变的。

这就是为什么您无法在append(contentsOf:)方法返回的数组上调用方法map的原因。

最好为代码使用非变异方法appending(contentsOf:)

所以您的代码将是:

// next, select only entries in range
let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
    .map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? timeSeriesDataFromAppData(data) : nil
    }.appending(contentsOf: locationsData.map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? timeSeriesDataFromLocationData(data) : nil
    })

答案 3 :(得分:1)

函数返回不可变值。这就是Swift中的方式。如果要使其可变,则必须首先将其存储在var中。

但是,您可以使用+Array与任何Sequence连接起来。因此,如果filteredAppsArray,则应该可以:

    let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
        .map { data in
            let isInDate = dates.contains { date in
                guard let d = date else {
                    return false
                }
                return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
            }
            return isInDate ? self.timeSeriesData(appData: data) : nil
    } + self.locationsData.map { data in
        let isInDate = dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: data.date, toGranularity: Calendar.Component.day)
        }
        return isInDate ? self.timeSeriesData(locationData: data) : nil
    }
    let filteredData = filteredDataOpt.compactMap { $0 }

我们还可以做其他几件事来清理此代码。我们可以排除日期测试:

    func isValid(_ candidate: Date) -> Bool {
        return dates.contains { date in
            guard let d = date else {
                return false
            }
            return Calendar.current.isDate(d, equalTo: candidate, toGranularity: Calendar.Component.day)
        }
    }

    let filteredDataOpt: [TimeSeriesEntry?] = filteredApps
        .map { data in
            return isValid(data.date) ? self.timeSeriesData(appData: data) : nil
    } + self.locationsData.map { data in
        return isValid(data.date) ? self.timeSeriesData(locationData: data) : nil
    }
    let filteredData = filteredDataOpt.compactMap { $0 }

根据您的数据,最好预先计算有效的Date范围:

    let calendar = Calendar.current
    let dayRanges: [Range<Date>] = dates.lazy.compactMap({ $0 }).map({ date in
        let start = calendar.startOfDay(for: date)
        let end = calendar.date(byAdding: .day, value: 1, to: start)!
        return start ..< end
    })

    func isValid(_ candidate: Date) -> Bool {
        return dayRanges.contains(where: { $0.contains(candidate) })
    }

我们还可以将过滤与转换分开。这样就可以消除对compactMap的使用:

    let filteredData = Array(filteredApps.lazy.filter({ isValid($0.date) }).map(self.timeSeriesData))
        + locationsData.lazy.filter({ isValid($0.date) }).map(self.timeSeriesData)

或者我们可以两次使用compactMap

    let filteredData = filteredApps.compactMap({ isValid($0.date) ? self.timeSeriesData(appData: $0) : nil })
        + locationsData.compactMap({ isValid($0.date) ? self.timeSeriesData(locationData: $0) : nil })