Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 类和结构闭包中的Swift可变结构的行为不同_Ios_Swift_Mvvm_Mutable_Swift Structs - Fatal编程技术网

Ios 类和结构闭包中的Swift可变结构的行为不同

Ios 类和结构闭包中的Swift可变结构的行为不同,ios,swift,mvvm,mutable,swift-structs,Ios,Swift,Mvvm,Mutable,Swift Structs,我有一个类(a),它有一个结构变量(S)。在这个类的一个函数中,我调用结构变量上的一个变异函数,这个函数接受闭包。此闭包的主体检查结构变量的name属性 结构的变异函数依次调用某个类(B)的函数。这个类的函数再次接受闭包。在这个闭包的主体中,修改结构,即更改name属性,并调用由第一个类提供的闭包 当我们在检查结构的name属性的地方调用第一个类(A)闭包时,它从未更改 但是在步骤2中,如果我使用结构(C)而不是类B,我发现类a内部的闭包结构实际上发生了变化。代码如下: class Networ

我有一个类(a),它有一个结构变量(S)。在这个类的一个函数中,我调用结构变量上的一个变异函数,这个函数接受闭包。此闭包的主体检查结构变量的name属性

结构的变异函数依次调用某个类(B)的函数。这个类的函数再次接受闭包。在这个闭包的主体中,修改结构,即更改name属性,并调用由第一个类提供的闭包

当我们在检查结构的name属性的地方调用第一个类(A)闭包时,它从未更改

但是在步骤2中,如果我使用结构(C)而不是类B,我发现类a内部的闭包结构实际上发生了变化。代码如下:

class NetworkingClass {
  func fetchDataOverNetwork(completion:()->()) {
    // Fetch Data from netwrok and finally call the closure
    completion()
  }
}

struct NetworkingStruct {
  func fetchDataOverNetwork(completion:()->()) {
    // Fetch Data from netwrok and finally call the closure
    completion()
  }
}

struct ViewModelStruct {

  /// Initial value
  var data: String = "A"

  /// Mutate itself in a closure called from a struct
  mutating func changeFromStruct(completion:()->()) {
    let networkingStruct = NetworkingStruct()
    networkingStruct.fetchDataOverNetwork {
      self.data = "B"
      completion()
    }
  }

  /// Mutate itself in a closure called from a class
  mutating func changeFromClass(completion:()->()) {
    let networkingClass = NetworkingClass()
    networkingClass.fetchDataOverNetwork {
      self.data = "C"
      completion()
    }
  }
}

class ViewController {
  var viewModel: ViewModelStruct = ViewModelStruct()

  func changeViewModelStruct() {
    print(viewModel.data)

    /// This never changes self.viewModel inside closure, Why Not?
    viewModel.changeFromClass {
      print(self.viewModel.data)
    }

    /// This changes self.viewModel inside/outside closure, Why?
    viewModel.changeFromStruct {
      print(self.viewModel.data)
    }
  }
}

var c = ViewController()
c.changeViewModelStruct()

为什么会有这种不同的行为。我认为区别因素应该是我是为viewModel使用结构还是使用类。但在这里,这取决于网络是一个类还是一个结构,它独立于任何ViewController或ViewModel。有人能帮我理解这一点吗?

这不是一个解决方案,但通过这段代码,我们可以看到
ViewController的
viewModel.data
已为类和结构案例正确设置。不同的是,
viewModel.changeFromClass
闭包捕获了一个过时的
self.viewModel.data
。请特别注意,只有类的“3 self”打印错误。不是包裹它的“2个自我”和“4个自我”图案


我想我对我们在原始问题中的行为有所了解。我的理解来自于闭包中inout参数的行为

简短回答:

它与捕获值类型的闭包是转义还是非转义有关。要使此代码正常工作,请执行以下操作

class NetworkingClass {
  func fetchDataOverNetwork(@nonescaping completion:()->()) {
    // Fetch Data from netwrok and finally call the closure
    completion()
  }
}
长答案:

让我先介绍一下情况

inout参数用于更改函数范围外的值,如以下代码所示:

func changeOutsideValue(inout x: Int) {
  closure = {x}
  closure()
}
var x = 22
changeOutsideValue(&x)
print(x) // => 23
func changeOutsideValue(inout x: Int)->() -> () {
  closure = {x}
  return closure
}
var x = 22
let c= changeOutsideValue(&x)
print(x) // => 22
c()
print(x) // => 22
这里x作为inout参数传递给函数。此函数更改闭包中x的值,因此它在其范围之外更改。现在x的值是23。当我们使用引用类型时,我们都知道这种行为。但对于值类型,inout参数是按值传递的。这里x是函数中的传递值,标记为inout。在将x传递到此函数之前,将创建并传递x的副本。因此,在changeOutsideValue中,此副本被修改,而不是原始x。现在,当这个函数返回时,这个修改后的x拷贝会复制回原来的x。因此,我们看到x仅在函数返回时才在外部修改。实际上,它看到在更改inout参数之后,函数是否返回,即捕获x的闭包是转义类还是非转义类

当闭包是转义类型时,即它只捕获复制的值,但在函数返回之前,不会调用它。请看下面的代码:

func changeOutsideValue(inout x: Int) {
  closure = {x}
  closure()
}
var x = 22
changeOutsideValue(&x)
print(x) // => 23
func changeOutsideValue(inout x: Int)->() -> () {
  closure = {x}
  return closure
}
var x = 22
let c= changeOutsideValue(&x)
print(x) // => 22
c()
print(x) // => 22
此处函数在转义闭包中捕获x的副本,以供将来使用,并返回该闭包。因此,当函数返回时,它会将x的未更改副本写回x(值为22)。如果打印x,它仍然是22。如果调用返回的闭包,它会更改闭包内部的本地副本,并且永远不会将其复制到x外部,因此x外部仍然是22

因此,这完全取决于更改inout参数的闭包是转义类型还是非转义类型。如果它是不可逃避的,变化就在外面,如果它是逃避的,变化就不在外面。

回到我们最初的例子。这就是流程:

  • ViewController调用viewModel上的viewModel.changeFromClass函数 结构,self是viewController类实例的引用, 因此,它与我们使用
    var c=ViewController()创建的相同,
    所以它和c一样
  • 在ViewModel中的变异

    func changeFromClass(completion:()->())
    
    我们创建了一个网络类 实例并将闭包传递给fetchDataOverNetwork函数。通知 这里是changeFromClass函数的闭包 fetchDataOverNetwork采用的是转义类型,因为 changeFromClass不假设闭包传入 在changeFromClass之前是否调用fetchDataOverNetwork 返回

  • 在中捕获的viewModel自身 fetchDataOverNetwork的关闭实际上是viewModel self的副本。 因此self.data=“C”实际上是在更改viewModel的副本,而不是 与viewController保持的实例相同

  • 如果将所有代码放入swift文件并发出SIL,则可以验证这一点 (Swift中间语言)。这方面的步骤在本文末尾 回答。很明显,在中捕获viewModel self fetchDataOverNetwork关闭会阻止viewModel自身 优化到堆栈。这意味着不使用alloc_堆栈, viewModel自变量是使用alloc_框分配的:

    %3=alloc_box$ViewModelStruct,var,名称“self”,argno 2//用户: %4, %11%、13%、16%、17%

  • 在changeFromClass闭包中打印self.viewModel.data时,打印的是viewController保存的viewModel数据,而不是由fetchDataOverNetwork闭包更改的副本。而且,由于fetchDataOverNetwork闭包是转义类型,并且在changeFromClass函数返回之前使用(打印)了viewModel的数据,因此更改后的viewModel不会复制到原始viewModel(viewController)

  • 现在,只要changeFromClass方法返回更改后的viewModel就会被复制回原始viewModel,因此,如果您在changeFromClass调用之后执行“打印(self.viewModel.data)”,您就会看到值已更改。(这是因为尽管fetchDataOverNetwork被假定为转义类型,但在运行时它实际上是非转义类型)

  • 正如@san在评论中指出的,“如果您添加此
    /// ViewModelStruct
    mutating func changeFromClass(completion:(ViewModelStruct)->()){
    let networkingClass = NetworkingClass()
    networkingClass.fetchDataOverNetwork {
      self.data = "C"
      self = ViewModelStruct(self.data)
      completion(self)
    }
    }
    
    /// ViewController
    func changeViewModelStruct() {
        viewModel.changeFromClass { changedViewModel in
          self.viewModel = changedViewModel
          print(self.viewModel.data)
        }
    }
    
    /// ViewModelStruct
    var viewModelChanged: ((ViewModelStruct) -> Void)?
    
    mutating func changeFromClass(completion:()->()) {
    let networkingClass = NetworkingClass()
    networkingClass.fetchDataOverNetwork {
      self.data = "C"
      viewModelChanged(self)
      completion(self)
    }
    }
    
    /// ViewController
    func viewDidLoad() {
        viewModel = ViewModelStruct()
        viewModel.viewModelChanged = { changedViewModel in
          self.viewModel = changedViewModel
        }
    }
    
    func changeViewModelStruct() {
        viewModel.changeFromClass {
          print(self.viewModel.data)
        }
    }
    
    import Foundation
    import XCPlayground
    
    
    protocol ViewModel {
      var delegate: ViewModelDelegate? { get set }
    }
    
    protocol ViewModelDelegate {
      func viewModelDidUpdated(model: ViewModel)
    }
    
    struct ViewModelStruct: ViewModel {
      var data: Int = 0
      var delegate: ViewModelDelegate?
    
      init() {
      }
    
      mutating func fetchData() {
        XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
        NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.com")!) {
           result in
          self.data = 20
          self.delegate?.viewModelDidUpdated(self)
          print("viewModel.data in fetchResponse : \(self.data)")
    
          XCPlaygroundPage.currentPage.finishExecution()
          }.resume()
      }
    }
    
    protocol ViewModeling {
      associatedtype Type
      var viewModel: Type { get }
    }
    
    typealias ViewModelProvide = protocol<ViewModeling, ViewModelDelegate>
    
    class ViewController: ViewModelProvide {
      var viewModel = ViewModelStruct() {
        didSet {
          viewModel.delegate = self
          print("ViewModel in didSet \(viewModel)")
        }
      }
    
      func viewDidLoad() {
        viewModel = ViewModelStruct()
      }
    
      func changeViewModelStruct() {
        print(viewModel)
        viewModel.fetchData()
      }
    }
    
    extension ViewModelDelegate where Self: ViewController {
      func viewModelDidUpdated(viewModel: ViewModel) {
        self.viewModel = viewModel as! ViewModelStruct
      }
    }
    
    var c = ViewController()
    c.viewDidLoad()
    c.changeViewModelStruct()