今天修复Bug时候发现的一个小细节,记录下。
问题描述
事情是这样的:我在A视图(UITableView)注册了一个通知,当接收到此通知时,就重新读取数据并调用[tableView reloadData]
。但是视图有时刷新后的显示的内容不对,再重新切换下视图又正常了。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//A视图在初始化时注册通知
-
(
void
)
viewDidLoad
{
//...
[
[
NSNotificationCenter
defaultCenter
]
addObserver
:self
selector
:
@selector
(
reloadFavDBData
:
)
name
:
@"refreshMyFavortieData"
object
:nil
]
;
}
//接收通知后的被调函数
-
(
void
)
reloadFavDBData
:
(
NSNotification
*
)
sender
{
[
_dataArray
release
]
;
_dataArray
=
[
MusicCollectedDataOperate
getSongCollectedInfoWithKeyword
:nil
]
;
//重新获取数据
[
tableView
reloadData
]
;
}
//调用者在一个线程中运行,调用通知告诉A视图刷新数据
[
[
NSNotificationCenter
defaultCenter
]
postNotificationName
:
@"refreshMyFavortieData"
object
:nil
]
;
|
分析 & 解决
经过一番调试,在被调函数reloadFavDBData打了断点后意外发现,它并不是在主线程运行的!而我们在这里做了与UI相关的[tableView reloadData]
操作——在非主线程做UI刷新操作,导致了显示异常的问题。
解决办法也很简单,在被调函数中切换到主线程再做操作就行啦。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-
(
void
)
reloadFavDBData
:
(
NSNotification
*
)
sender
{
@synchronized
(
self
)
//保证线程安全
{
//获取新数据,请注意这里和上述代码的区别,由于获取数据操作耗时相对较长,
//原实现方式可能导致TableView获取数据时崩溃
NSArray
*tmpArray
=
[
MusicCollectedDataOperate
getSongCollectedInfoWithKeyword
:nil
]
;
//切换到主线程
dispatch_async
(
dispatch_get_main_queue
(
)
,
^
{
[
_dataArray
release
]
;
_dataArray
=
[
tmpArray
retain
]
;
[
self
.
refreshTableView
.
myTableView
reloadData
]
;
}
)
;
}
}
|
WHY?
测试得来的结论还是不够完整。翻了翻官方文档,在NSNotificationCenter部分看到这样一段话:
In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.
在多线程程序中,通知总是在发送通知者的线程中调用(delivered,可以这么理解么?),结合上述所遇到的情况,我的理解是,通知观察者的被调函数总是运行在发送通知者的线程中,如下图所示:

这个结论告诉我们,通知的被调函数不一定运行在主线程中,如果需要做UI相关操作,需要手动切换到主线程。
有空再尝试看看通知的实现原理,应该会有更透彻的理解。