一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

ios异步加载表格数据及内容不能及时显示的问题

时间:2016-02-22 编辑:简简单单 来源:一聚教程网

异步事件,就是说这一个代码或者代码块,并不会阻塞程序的运行,程序会立即执行下一条语句,而这条语句,会在相应的方法调用结束之后,执行它自身的回调函数发送一些信号,来表明这个异步事件完成。就像你约会提前1小时到见面地点,先去买点东西踩点什么的(……),等GF/BF到了之后短信通知你,你就立即回来。而不是一直在原地等到对方过来(……)

最早使用异步开发,是在使用JavaScript来开发Web前端的时候,XMLHttpRequest或者jQuery的$.ajax中,都会用到回调函数,来指明成功或者失败之后的处理方法。当对应的网络请求得到响应之后,会调用响应的成功或者失败的回调函数,然后执行里面相应的方法,这大大提升了前端的效率,不会在网络请求时整个页面卡住,而且也不需要一次次轮询看是否有响应,简化了代码的复杂性。

这点Node.js中更为常见,不过也更能表现中滥用异步事件编程的问题。新人使用Node.js总会发现基本任何东西都是异步的,数据库是异步的,IO文件操作是异步的,Session读写是异步的,甚至获得Request对象都是异步的。这就导致很多人一直在嵌套回调函数,导致了著名的Callback Hell

在Node.js中,解决方案有非常成熟的Async,更有号称能用同步思维写异步的Promises,都是非常棒的解决方案。前者的本质就是一个自动生成回调的封装……,后者则是一个真正意义上的全新的解决方案。

而在Swift和iOS开发中,也有必须用到异步事件编程的地方。除了View层的简单UI和Controller之间的交互以外(这部分一般不需要手写代码处理异步交互或者顺序),其他很多地方需要这些知识。例如网络请求的异步调用,请求队列的处理(虽然可以一个网络请求就是一个线程,但这种方法的效率不高,而且容易导致线程间冲突),SQLite数据库大量数据的读写,本地存储的大量数据读写,复杂UI的渲染顺序等等……这些都是需要进行异步编程的,而不能让同步的代码阻塞住整个应用或者UI。

举个例子,这里是一个UI顺序加载的动画……

func schoolLifeClicked()
{
    var mydrawerController = self.mm_drawerController //一个用TableView实现的应用侧边栏抽屉View
    let schoolLifeViewController:SchoolLifeViewController = SchoolLifeViewController(nibName: "SchoolLifeViewController", bundle: nil)
    let navSchoolLifeViewController = CommonNavViewController(rootViewController: schoolLifeViewController)

    self.mm_drawerController.toggleDrawerSide(MMDrawerSide.Left, animated: true, completion:{(complete) in
        if complete{//如果成功拉出抽屉
            mydrawerController.setCenterViewController(navSchoolLifeViewController, withCloseAnimation: true, completion: nil)//设置主视图
            mydrawerController.closeDrawerAnimated(true, completion:nil)//关闭抽屉
        }
    })//一个闭包,成功后调用
}

可以看到,Swift很多时候也可以依靠回调函数,把一个闭包扔进去当参数,然后执行,从而控制这种异步事件的流程……

但是,这种方法写起来,就会回到和JS那种匿名函数闭包扔进去当参数一样,小范围用还可以,一旦你要进行复杂的流程控制,比如一系列异步事件,AB同时执行,AB同时完成后执行C,C执行完成后执行D……这种控制下写出来的代码和JavaScript的callback hell是一样的,难以维护。

怎么办呢?其实自己实现一个语法糖或者函数队列来执行也不难,不过这里可以推荐一下GitHub上非常厉害的库,大家有怎么使用呢?参考人家的Readme,用语法糖可以很简单的使用:

Async.userInitiated {
    println("start")
}.main {
    println("1")
}.background {
    println("2")
}.background {
    println("2 all the same")
}.main {
    println("stop")
}由于异步事件的特点,所以整个输出可能就会是

start
1
2
stop
2 all the same不要大惊小怪哦。利用这个就可以从繁重的callback中解放出来,简单的处理异步事件的顺序,并且获得很高的性能,这也是网络请求和数据库访问等必须要考虑的地方……


ios异步加载表格数据,内容不能及时显示的问题


1,问题描述

我们使用 tableView 的时候,又是表格内容是异步加载的。比如从网络获取数据显示、或是开启个线程队列定时刷新加载表格数据。

(1)比如我们要加载的数据如下:
[
    {
        "name": "hangge",
        "age": 100,
    },
    {
        "name": "big boss",
        "age": 1,
    },
    {
        "name": "batman",
        "age": 12,
    }
]

(2)使用 NSURLSession 获取远程数据后,调用 tableView的reloadData() 方法重新加载数据。

import UIKit
 
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    var ctrlnames:NSArray = []
    var tableView:UITableView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表视图
        self.tableView = UITableView(frame: self.view.frame, style:UITableViewStyle.Plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.registerClass(UITableViewCell.self,
            forCellReuseIdentifier: "SwiftCell")
        self.view.addSubview(self.tableView!)
        
        //创建NSURL对象
        let urlString:String="http://www.hangge.com/code/test.php"
        let url:NSURL! = NSURL(string:urlString)
        //创建请求对象
        let request:NSURLRequest = NSURLRequest(URL: url)      
        let session = NSURLSession.sharedSession()
        
        let dataTask = session.dataTaskWithRequest(request,
            completionHandler: {(data, response, error) -> Void in
                if error != nil{
                    print(error?.code)
                    print(error?.description)
                }else{
                    self.ctrlnames = try! NSJSONSerialization.JSONObjectWithData(data!,
                        options: NSJSONReadingOptions.MutableContainers) as! NSArray
                    self.tableView?.reloadData()
                }
        }) as NSURLSessionTask
        
        //使用resume方法启动任务
        dataTask.resume()
    }
    
    //在本例中,只有一个分区
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1;
    }
    
    //返回表格行数(也就是返回控件数)
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.ctrlnames.count
    }
    
    //创建各单元显示内容(创建参数indexPath指定的单元)
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
        -> UITableViewCell
    {
        //为了提供表格显示性能,已创建完成的单元需重复使用
        let identify:String = "SwiftCell"
        //同一形式的单元格重复使用,在声明时已注册
        let cell = tableView.dequeueReusableCellWithIdentifier(identify,
            forIndexPath: indexPath) as UITableViewCell
        cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
        let item = self.ctrlnames[indexPath.row] as! NSDictionary
        cell.textLabel?.text = item.objectForKey("name") as? String
        return cell
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

(3)但会发现数据加载完毕后表格还是空白的,拖动一点点表格数据就显示出来了。
      原文:Swift - 异步加载表格数据,内容不能及时显示的问题解决      原文:Swift - 异步加载表格数据,内容不能及时显示的问题解决

2,解决办法

reloadData() 方法需要在主线程中调用,这样表格数据就能及时更新。(代码高亮出为修改的地方)

import UIKit
 
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    var ctrlnames:NSArray = []
    var tableView:UITableView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表视图
        self.tableView = UITableView(frame: self.view.frame, style:UITableViewStyle.Plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.registerClass(UITableViewCell.self,
            forCellReuseIdentifier: "SwiftCell")
        self.view.addSubview(self.tableView!)
        
        //创建NSURL对象
        let urlString:String="http://www.hangge.com/code/test.php"
        let url:NSURL! = NSURL(string:urlString)
        //创建请求对象
        let request:NSURLRequest = NSURLRequest(URL: url)      
        let session = NSURLSession.sharedSession()
        
        let dataTask = session.dataTaskWithRequest(request,
            completionHandler: {(data, response, error) -> Void in
                if error != nil{
                    print(error?.code)
                    print(error?.description)
                }else{
                    self.ctrlnames = try! NSJSONSerialization.JSONObjectWithData(data!,
                        options: NSJSONReadingOptions.MutableContainers) as! NSArray
 
                    dispatch_async(dispatch_get_main_queue(), {
                        self.tableView?.reloadData()
                        return
                    })
                }
        }) as NSURLSessionTask
        
        //使用resume方法启动任务
        dataTask.resume()
    }
    
    //在本例中,只有一个分区
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1;
    }
    
    //返回表格行数(也就是返回控件数)
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.ctrlnames.count
    }
    
    //创建各单元显示内容(创建参数indexPath指定的单元)
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
        -> UITableViewCell
    {
        //为了提供表格显示性能,已创建完成的单元需重复使用
        let identify:String = "SwiftCell"
        //同一形式的单元格重复使用,在声明时已注册
        let cell = tableView.dequeueReusableCellWithIdentifier(identify,
            forIndexPath: indexPath) as UITableViewCell
        cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
        let item = self.ctrlnames[indexPath.row] as! NSDictionary
        cell.textLabel?.text = item.objectForKey("name") as? String
        return cell
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

热门栏目