一面复盘

113 阅读5分钟

1. UITableView的刷新机制

常用的刷新方法

//刷新指定分组
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation API_AVAILABLE(ios(3.0));
//刷新指定分组和行
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation API_AVAILABLE(ios(3.0));
//重新加载所有内容,重新显示可见的行
- (void)reloadData;
//删除时刷新指定的行数据
- (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
//添加时刷新指定的行数据
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
// 重新加载section的索引栏
- (void)reloadSectionIndexTitles API_AVAILABLE(ios(3.0));

刷新方法底层其实也就是重新去执行数据源方法(重新加载数据)

在一个页面有多个tableView的刷新

在有多个TableView的时候我们就不能直接去用

[self.tableVIew reloadData]

因为这样只是刷新所有TableView中的一个

所以我们要用 循环刷新所有的TableVIew 或者 用Tag值取到要刷新的TableView 再去刷新

PS:
如果是网络请求数据再进行tableView的刷新,我们可以在子线程中更新完数据,在去主线程中刷新UI
注意,有关UI操作都必须在主线程中执行

2. UITableView的搜索功能

搜索的原理很简单:修改模型、刷新表格

3. UITableView的高度设置函数是什么时候执行?

//(每行高度可以不一样)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

以上设置高度函数和数据源方法的执行顺序是怎么样的呢? numberOfRowsInSectioncellForRowAtIndexPath(和numberOfSectionsInTableView

  1. 执行numberOfRowsInSection
  2. 执行cellForRowAtIndexPath
  3. 执行heightForRowAtIndexPath
  4. 重复2、3,直到数据被加载完毕

PS:

  • 数据源方法是在viewDidLoad之后执行的
  • Apple有一个滚动条,有时长,有时短,其实内部就是通过确认了cell的高度计算得到的 image.png

我的cell的行数是8,发现重复2、3也是8次 也就意味着你有多少个cell,程序启动后、显示界面前就会调用多少次heightForRowAtIndexPath这个方法,这会非常消耗性能(比如如果你有1W个cell,那程序启动就要调用该方法1W次)

那该怎么办呢?

  • 解决方法:使用estimatedRowHeight(行的预估高度)
//该属性在UITableView的定义
@property (nonatomic) CGFloat estimatedRowHeight API_AVAILABLE(ios(7.0)); // default is UITableViewAutomaticDimension, set to 0 to disable
  • 有了预估高度的好处?

    • 就会在用到的时候再调用heightForRowAtIndexPath方法,而不会在程序一启动的时候就调用N次heightForRowAtIndexPath导致消耗性能
    • 也可以正常显示滚动条
  • 使用方法

- (void)viewDidLoad {
[super viewDidLoad];
// 告诉tableView所有cell的估算高度
self.tableView.estimatedRowHeight = 200;
}

可以看到预估高度在viewDidLoad执行,即预估高度在调用heightForRowAtIndexPath之前就已经设置好了

  • 为什么设置预估高度后,heightForRowAtIndexPath的调用次数发生变化?
    • 设置预估高度后,系统会根据预估高度来确认"滚动条"的长短,而不会去算实际真实的cell的高度
    • 所以是根据estimatedRowHeight值的大小来确定程序启动时调用heightForRowAtIndexPath方法的次数
    • estimatedRowHeight值越大,调用heightForRowAtIndexPath方法的次数就越少,"滚动条"就越长
    • 可以理解为:手机屏幕固定就是那么大,cell高度越大,屏幕放入的cell就越少,也就是调用heightForRowAtIndexPath方法的次数越少,响应的滚动条长度也就越长了

参考博客

#有关UITableView的其他拓展问题#

1. MVC模式

通过UITableView的学习相信大家对于iOS的MVC已经有一个大致的了解,这里简单的分析一下iOS中MVC模式的设计方式

  • 在iOS中多数数据源视图控件(View)都有一个dataSource属性用于和控制器(Controller)交互,而数据来源我们一般会以数据模型(Model)的形式进行定义,View不直接和模型交互,而是通过Controller间接读取数据

  • 以联系人应用举例,UITableView作为视图(View)并不能直接访问模型Contact,它要显示联系人信息只能通过控制器(Controller)来提供数据源方法

  • 同样的控制器本身就拥有视图控件,可以操作视图,也就是说视图和控制器之间可以互相访问,而模型既不能访问视图也不能访问控制器。

4. atomic和线程安全的关系

@property (atomic) NSObject *atomicObj;

- (void)setAtomicObj:(NSObject *)atomicObj{

    @synchronized(self) {

        if (_atomicObj != atomicObj) {

            [_atomicObj release];

            _atomicObj = [atomicObj retain];
        }
    }
}

- (NSObject *)atomicObj{

    @synchronized(self) {

        return _atomicObj;
    }
}

苹果开发文档已经明确指出:Atomic不能保证对象多线程的安全

  • 所以Atomic不能保证对象多线程的安全
  • 它只是能保证你访问的时候给你返回一个完好无损的Value而已

线程安全的概念

多条线程同时工作的情况下,通过运用线程锁,原子性等方法避免多条线程因为同时访问同一块内存造成的数据错误或冲突

多线程数据为什么不安全

每条线程都有自己独立的栈空间,但是他们公用了堆,所以他们可能同时访问同一块内存空间,因此造成数据冲突


当使用nonatomic的时候,属性的setter,getter操作是非原子性的,所以当多个线程同时对某一属性读和写操作时,属性的最终结果是不能预测的

当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:

当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待
当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值

这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃

所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。

atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。

常用的线程锁方式:

  1. NSLock
  2. 使用synchronized关键字构建的锁
  3. 使用C语言的pthread_mutex_t实现的锁
  4. 使用GCD来实现的”锁”(其实是信号量)
  5. 使用自旋锁OSSpinLock来实现的”锁” 我就没有仔细去看了