iOS KVO 设计模式的应用

#import "ActivityListViewController.h"

#import "ActivityListViewCell.h"

#import "Activity.h"

@interface ActivityListViewController ()

// 存放activity的数组

@property (nonatomic,strong) NSMutableArray *modelArray;

@end

@implementation ActivityListViewController

// 请求网络数据

- (void)setNetWorkData

{

    // 活动网址: http://project.lanou3g.com/teacher/yihuiyun/lanouproject/activitylist.php

    

    // 1. URL

    NSURL *url = [NSURL URLWithString:@"http://project.lanou3g.com/teacher/yihuiyun/lanouproject/activitylist.php"];

    

    // 2. request

    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];

    

    // 3. connection

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {

        

        

        // 4. 解析数据

        NSDictionary *rootDic = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

        

        // events

        NSArray *array = [rootDic objectForKey:@"events"];

        

        //  开辟空间

        self.modelArray = [NSMutableArray array];

        

        // kvc赋值

        for (NSDictionary *dic in array) {

            Activity *activity = [[Activity alloc] init];

            [activity setValuesForKeysWithDictionary:dic];

            [self.modelArray addObject:activity];

            

        }

        

        

        // 5. 刷新单元格数据

        // 原因: 网络数据请求需要时间,因此 单元格创建会在数据请求完成之前执行。所以,我们在数据请求完成之后,再次给单元格赋值。

        [self.tableView reloadData];

        

    }];

    

}

- (void)viewDidLoad {

    [super viewDidLoad];

    

    // 调用请求网络数据

    [self setNetWorkData];

    

    

    // Uncomment the following line to preserve selection between presentations.

    // self.clearsSelectionOnViewWillAppear = NO;

    

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.

    // self.navigationItem.rightBarButtonItem = self.editButtonItem;

}

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

#warning Incomplete implementation, return the number of sections

    return 1;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

#warning Incomplete implementation, return the number of rows

    return [self.modelArray count];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    ActivityListViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell1" forIndexPath:indexPath];

    // Configure the cell...

    

    Activity *activity = self.modelArray[indexPath.row];

    

    // 查看activity中,是否已经下载图片,如果没有,则下载

    if (activity.picture == nil && activity.isDownLoading == NO) {

        

        [activity loadImage];

        

        

        // 下载图片后,给activity添加观察者

        // 1. 添加观察者

        [activity addObserver:self forKeyPath:@"picture" options:(NSKeyValueObservingOptionNew) context:(__bridge void * _Nullable)(indexPath)];

        // arc 需要 桥接,(__bridge void *)indexPath

        // mrc 里需要进行retain [indexPath retain],防止野指针

        

        

        

        

    }

    

    // 此时存在问题,当单元格第一次加载的时候,没有图片,因为,第一次加载时可能图片没有下载下来,赋值时就没有值。 当再次加载单元格时才有数据。

    

    // 原因: 数据请求下来后,没有被及时添加到单元格上。 数据有了,但是没有赋值。

    

    // 解决方案:使用 KVO 观察者模式,观察 activity,观察activity 的pICTure属性, tableViewController 去观察

    // 1. 被观察者 activity ,

    // 2. 被观察属性 picture

    // 3. 观察者 self (tableViewController)

    // 4. 执行操作  当图片下载下来后,执行观察方法,我们把下载下来的图片赋值给单元格。

    cell.activity = activity;

    

    return cell;

}

// 2. 实现kvo方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context

{

    

    // 1. 获取改变的值--获取下载后的图片

    UIImage *newImage = (UIImage *)[change objectForKey:@"new"];

    

    // 2. 获取传递过来的数据 indexPath

    // 注意: 也需要强制转换,如果是arc,需要桥接

    NSIndexPath *indexPath = (__bridge NSIndexPath *)context;

    

    // 如果下载好的单元格正在被显示,我们才把图片赋值给它,否则就没有意义。如果一个没有正在显示的单元格数据,(重用队列里面的单元格)就会出现,单元格加载出来时,有别的数据。类似于,拿了一个脏盘子给你。

    

    // 3. 获取正在显示的单元格下标数组

    NSArray *array = [self.tableView indexPathsForVisibleRows];

    

    // 4. 判断indexPath 是否正在显示,即是否在数组中

    if ([array containsObject:indexPath]) {

        

        // 说明正在显示,把下载好的图片赋值给单元格

        // 获取单元格

        ActivityListViewCell *cell = (ActivityListViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];

        

        // 给单元格赋值

        cell.pictureView.image = newImage;

        

    }

    

    // 三、 触发观察者方法

    // ......

    

    // 四、移除观察者

    [object removeObserver:self forKeyPath:@"picture"];

    

    // 补充: mrc 如果想要观察者一直观察,又不想出现内容泄露,那么,可以把移除写在 dealloc 里面。

}

原文地址:https://www.cnblogs.com/xlsn0w/p/4978844.html