UIViewController之间传值的7种方法

来源:互联网 发布:长城宽带 端口转发 编辑:程序博客网 时间:2024/06/05 23:04

        当父UIViewController打开一个新的子UIViewController时, 二者之间可能需要双向传值即父传子、子回传父, 或者单向传值的父传子、子传父。 

 我整理了一下,大概分为7种方法(前3种方法较常用):

1、UIStoryBoardSegue, 前置条件是要使用storyboard做界面。

2、闭包, 即在父窗口实现闭包, 在子窗口保存闭包的引用。 

3、协议, 跟闭包原理一样, 即在父窗口实现协议接口, 在子窗口保存协议引用。

4、单例模式, 即父/子界面访问同一个单例。

5、文件持久化, 例如Core Data、UserDefaults或其他三方数据库; 原理跟单例模式一样, 区别是单例模式读写的是内存。

6、网络接口, 即父/子窗口与网络服务器同步数据, 原理跟单例模式、文件持久化一样, 区别是将数据保存到网络。

7、 观察者模式, 即在UIViewController里注册监听对应的Notification; 例如父窗口观察, 子窗口修改值。


一、 使用UIStoryBoard画界面是比较基础的行为,一般被认为是iOS开发入门级。 在讲述值传递前先看一下UIViewController的生命周期。

父UIViewController打开子UIViewController:

父界面prepare父窗口viewWillDisappear子界面viewDidLoad子窗口viewWillAppear父窗口viewDidDisappear子窗口viewDidAppear


子UIViewController退出时:

子界面 prepareunwindToMain子窗口viewWillDisappear父窗口viewWillAppear子窗口viewDidDisappear父窗口viewDidAppear

可以看出当UIViewController不再是活动窗口时,最新执行prepare函数, 所以要在prepare函数里做业务逻辑!

    /**     *  父窗口向子窗口传值     *  segue, 判断下一级UIViewController类型     *  sender, 触发切换ViewController的地方,在这里是按钮控件     */    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        print("父界面prepare")                //方法一  通过目标UIViewController类型判断        if let vc = segue.destination as? SecondeViewController {            vc.value = "第二级界面111"            //print("显示SecondViewController")        }                //方法二  通过点击的控件判断        if sender as? UIButton == btnNext {            //print("点击按钮显示二级界面")        }    }
       上面代码是在显示子窗口SecondViewController时, 通过segue参数的destination属性为子窗口赋值, 即实现了父传子(类似于Android startActivity时在intent里携带参数)。


        子窗口SecondViewController在退出后需要传值给父窗口ViewController, 实现方法是在父窗口实现一个unwindToMain函数且在uistoryboard里将exit指向该函数。 具体如下:

    //从子ViewController返回, 作用类似于Android的onAcitivityResult函数    @IBAction func unwindToMain(_ segue: UIStoryboardSegue) {        print("unwindToMain")                if let vc = segue.source as? SecondeViewController {            print(fatherName)            print("\(vc.age)")    //子窗口给父窗口的传值, 在父窗口里能拿到子窗口的引用!        }    }


        子UIViewController在退出前可以给父ViewController赋值, 或者将控件值缓存到变量里。

    //退出当前界面时 保存值到变量里    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        //退出该界面时执行        print("子界面 prepare")                if let vc = segue.destination as? ViewController {            print("返回上一级界面")            vc.fatherName = "子窗口修改父窗口值"   //子ViewController也有父Controller的引用,能够修改父controller的值,但不建议这样做!!!                        //将本界面控件的值保存到变量里            value = "二级界面返回值"            age = 100        }            }

 注意: UIStoryboardSegue可以修改父或子UIViewController的值(即双向的)!!!  但实际编码中建议单向传值, 即父UIViewController对子UIViewController取值或赋值。
       

二、 闭包, 在这里应该是逃逸闭包。  在子界面里保存闭包的引用, 在父窗口实现闭包。 下面的写法有点像Java偷笑, 其实可以声明个构造函数,将闭包作为构造函数的一个参数。

class SecondeViewController: UIViewController {.......        var callback: (_ name: String?, _ age: Int?) -> Void? = {(name, age) in        print("默认闭包")        return nil    }        //设置闭包    func setCallBack(callback: @escaping (String?, Int?) -> Void?) {        self.callback = callback    }...}
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        //退出该界面时执行        print("子界面 prepare")        callback("张三", 100)  //通过闭包回调通知    }

在父窗口实现闭包:

   if let vc = segue.destination as? SecondeViewController {            vc.value = "第二级界面111"            vc.setCallBack(callback: { (name, age) in                 print("name: \(name), age: \(age)")    //子界面的返回值            })        }

输出:

子界面 prepare

name: Optional("张三"), age: Optional(100)

unwindToMain


PS: 上面的写法太像Java了, 实际上新声明个init函数并将闭包作为一个参数更好。

三、 协议, 跟Java的interface一模一样, 父类实现接口函数,子类保存接口的引用。

protocol SecondDelegate {    func respond(name: String?, age: Int?)}
在父类里实现protocol:

class ViewController: UIViewController, SecondDelegate {    。。。    /**     *  父窗口向子窗口传值     *  segue, 判断下一级UIViewController类型     *  sender, 触发切换ViewController的地方,在这里是按钮控件     */    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        if let vc = segue.destination as? SecondeViewController {            vc.value = "第二级界面111"            vc.delegate = self        }    }        func respond(name: String?, age: Int?) {        print("接口返回: \(name), \(age)")    }    。。。}

在退出子类时调用接口函数

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        //退出该界面时执行        print("子界面 prepare")        delegate?.respond(name: "李四", age: 200)            }

四、单例模式, 即进程里只有一个实例。  在父/子窗口读写该同一个实例时, 即可实现数据共享。 --- 不推荐这种做法

class SingleTon {    static let instance = SingleTon()        var name: String?    var age: Int?}


五、 持久化, 即从文件里读写数据。 包括iOS提供的Core Data, UserDefaults、文件或其他三方框架等。  不同界面读写同一个文件, 因为文件IO比内存读写更浪费资源也更慢, 要注意使用的时机。


六、 网络接口, 这个很好理解,就是数据存储在服务器上。 现在的互联网app都会联网, 那么A界面上传到服务器的数据, B界面在使用时可以从服务器下载。 PS: 网络交互会遇到各种情况, 要跟进业务逻辑使用这种方式。 原则是以服务器的数据为准!!! 避免发生数据不同步的问题。


七、 观察者模式, 就是使用iOS的NotificationCenter注册监听, 当数据变化时接收到回调。  注意iOS和Android的区别: Android一般会在onStop函数里unregister监听、onResume函数里注册监听, 即只有当前活跃窗口保持监听; 而iOS是只要界面还存在, 那么就保留监听,直接界面销毁时才取消监听。  PS: 原因是Android的内存管理机制,后台的activity很可能被gc回收,如果不在onStop函数里释放资源就可能内存泄漏; 而iOS的内存管理更强大,一般不会出现内存溢出,这就是Swift的强大之处!


          iOS没提供类似于Android startActivityForResult的功能, 日常开发中一般使用代码画界面, 所以UIViewConroller之间的数据交换一般用闭包或协议实现。

             

参考代码:http://download.csdn.net/detail/brycegao321/9916397









原创粉丝点击