異步事件,就是說這一個代碼或者代碼塊,並不會阻塞程序的運行,程序會立即執行下一條語句,而這條語句,會在相應的方法調用結束之後,執行它自身的回調函數發送一些信號,來表明這個異步事件完成。就像你約會提前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()
}
}