目标:对网络图片的获取和利用线程进行图片的添加与更新
songID = [[[itemDictionary objectForKey:@"songs"]firstObject]objectForKey:@"mp3Url"] ;
musicLogo = [[[[itemDictionaryobjectForKey:@"songs"]firstObject]objectForKey:@"album"]objectForKey:@"blurPicUrl"];
这里有两种方法去实现图片的加载,个人推荐使用第二中,而第一种作为了解下怎样使用线程。
- (void)getData:(NSString *)name page:(NSInteger)pageIndex
3.也就是获得了歌曲的数组之后,再用一个for循环语句获得歌曲数组中的单个对象,作为参数传进去,并且用回调函数获得歌曲图片的地址,并将它放入一个可变数组中,这样for循环完成时我们就获得了所有歌曲图片的地址。
4.再只需要创建一个方法,将该歌曲的对象和该歌曲的图片地址作为参数传入到cell的初始化。
问题:
- (void)getData:(NSString *)name page:(NSInteger)pageIndex{
[FetchDataFromNetfetchMusicData:name page:pageIndex callback:^(NSArray *array,NSInteger page, NSError *error){
if (error) {
NSLog(@"error = %@",error);
}else{
self.getDataArray = array;
//数组的循环,去获得每一个对象的图片地址,并添加到本地数组中去
[_getDataArrayenumerateObjectsUsingBlock:^(id obj,NSUInteger idx, BOOL *stop) {
NSLog(@"======%ld",idx);
[selfgetMusicImage:obj];
}];
[self.collectionViewreloadData];
}
}];
}
- (void)getMusicImage:(MusicData *)musicData{
[FetchDataFromNetfetchSongDetailInfo:musicData callback:^(id item, NSString *musicImage, NSError *error) {
NSLog(@"======%@",musicImage);
[_imageArrayaddObject:musicImage];
}];
}
//cell的初始化工作
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionViewdequeueReusableCellWithReuseIdentifier:reuseIdentifierforIndexPath:indexPath];
[cellsetInfo:self.getDataArray[indexPath.row]andMusicImage:self.imageArray[indexPath.row]];
return cell;
}
//CollectionViewCell.m文件中的共有方法
- (void)setInfo:(MusicData *)musicData andMusicImage:(NSString *)image{
self.songName.text = musicData.trackname;
self.albumName.text = musicData.albumname;
//加载图片是耗时操作,需要在异步线程中
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
NSURL *imageUrl = [NSURLURLWithString:image];
//更新界面,需要在主线程中实现
dispatch_async(dispatch_get_main_queue(), ^{
[self.logoImagesd_setImageWithURL:imageUrl placeholderImage:[UIImageimageNamed:@"surf.jpg"]];
});
});
}
这些代码看起来没什么问题,但真正运行起来就会发现并不是和自己预期的一样。原因:程序在执行for语句的时候,[self getMusicImage:obj];这个方法是异步的线程,所以程序不会等着for语句完成后再去执行后面的程序。而是会在for语句的同时执行
[ self . collectionView reloadData ];去重新刷新界面,但这时图片数组中并没有数据,所以程序会崩溃。
解决办法:
将[self getMusicImage:obj]里面的异步线程改为同步线程。
- (void)getMusicImage:(MusicData *)musicData{
//调用同步线程的方法
[FetchDataFromNetfetchSongDetailInfoBySynch:musicData callback:^(id item, NSString *musicImage, NSError *error) {
NSLog(@"======%@",musicImage);
[_imageArrayaddObject:musicImage];
}];
}
//FetchDataFromNet.m文件中:(当然回调函数还是不变,item还是musicData的对象)
+ (void)fetchSongDetailInfoBySynch:(id)item callback:(fetchDetailSongInfoCallback)callback{
NSURL *songURL = [NSURLURLWithString:[NSStringstringWithFormat:@"http://music.163.com/api/song/detail?ids=[%@]",[itemvalueForKey:@"trackIdentifier"]]];
NSMutableURLRequest *request = [NSMutableURLRequestrequestWithURL:songURL];
[request setValue:@"deflate,gzip"forHTTPHeaderField:@"Accept-Encoding"];
[request setValue:@"http://music.163.com/"forHTTPHeaderField:@"Referer"];
[request setValue:@"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"forHTTPHeaderField:@"User-Agent"];
[requestsetHTTPMethod:@"GET"];
//发送同步请求拿到数据
NSData *data = [NSURLConnectionsendSynchronousRequest:request returningResponse:nilerror:nil];
NSString *songID;
NSString *musicLogo;
@try {
NSDictionary *itemDictionary = [NSJSONSerializationJSONObjectWithData:data options:kNilOptionserror:nil];
NSLog(@"%@",itemDictionary);
musicLogo = [[[[itemDictionaryobjectForKey:@"songs"]firstObject]objectForKey:@"album"]objectForKey:@"blurPicUrl"];
}
@catch (NSException *exception) {
}
@finally {
callback(songID,musicLogo,nil);
}
}
这样可以显示搜索后的界面,但不是很好。
原因:
由于同步必须将所以的图片地址或得到后,才会显示界面,比如说有100行数据,每行数据会耗时0.1s,总耗时也会有10s,这个就不是很好的解决办法。
改进:
- (void)getData:(NSString *)name page:(NSInteger)pageIndex{
[FetchDataFromNetfetchMusicData:name page:pageIndex callback:^(NSArray *array,NSInteger page, NSError *error){
if (error) {
NSLog(@"error = %@",error);
}else{
self.getDataArray = array;
//创建一个队列,并设置优先级
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
//dispatch_group_create创建一个调度任务组
dispatch_group_t group = dispatch_group_create();
[_getDataArrayenumerateObjectsUsingBlock:^(id obj,NSUInteger idx, BOOL *stop) {
NSLog(@"======%ld",idx);
//开启一个异步线程,dispatch_group_async把一个任务异步提交到任务组里
dispatch_group_async(group, queue, ^{
[selfgetMusicImage:obj];
});
}];
//上面线程执行完后,才执行更新界面
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
[self.collectionViewreloadData];
});
}
}];
}
参数: group 提交到的任务组,这个任务组的对象会一直持续到任务组执行完毕queue 提交到的队列,任务组里不同任务的队列可以不同block 提交的任务。
这样做处理后界面出现的时间明显就快。
缺点:
上面方法,是将所有的图片都显示出来(滑出界面外的cell的图片),这样就比较浪费内存空间,因为滑出界面外的cell是没有人会看的。
所以我们只需要关心正在当前界面的cell的图片即可。
另外,就是我们不需要再创建一个图片数组,只需要在解析图片地址的时候,设置一下图片地址在该对象中的key即可。
在异步获取数据的方法中+ (void)fetchSongDetailInfo:(id)item callback:(fetchDetailSongInfoCallback)callback。
songID = [[[itemDictionary objectForKey:@"songs"]firstObject]objectForKey:@"mp3Url"] ;
musicLogo = [[[[itemDictionaryobjectForKey:@"songs"]firstObject]objectForKey:@"album"]objectForKey:@"blurPicUrl"];
//设置图片地址在该对象中的key,而MusicData中有这个logoname属性
[itemsetValue:musicLogo forKey:@"logoname"];
这样的好处是我们传参数的时候只需要传一个MusicData的对象即可所以我们只需要在方法中:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
[cellsetInfo:_getDataArray[indexPath.row]];
调用这个方法即可。CollectionViewCell.m文件中:
@property (nonatomic,strong) NSOperationQueue *operationQueue;
- (void)setInfo:(MusicData *)musicData{
_musicData = musicData;
self.songName.text = musicData.trackname;
self.albumName.text = musicData.albumname;
if (!_operationQueue) {
_operationQueue = [[NSOperationQueuealloc] init];
}
[_operationQueuecancelAllOperations];
[_operationQueueaddOperationWithBlock:^{
[FetchDataFromNetfetchSongDetailInfo:musicData callback:^(id item, NSString *musicImage, NSError *error) {
[self.logoImagesd_setImageWithURL:[NSURLURLWithString:musicImage] placeholderImage:[UIImage imageNamed:@"surf.jpg"]];
}];
}];
}
当然这里[item setValue:musicLogo forKey:@"logoname"];没有起到什么作用。
OperationQueue实质上也就是数组管理,对添加进去的operation进行管理、创建线程等,具体的请参考:点击打开链接
这样你再运行程序的话,就会发现图片只会显示当前在屏幕上的cell中的图片。
当然你们的没有后面的加号按钮,这个在后面会我们会再添加。
这样我们就获得了歌曲的图片,并且可以播放。
接下来需要的是添加加号按钮,想要实现的功能是点击加号,可以将这首歌收入收藏列表中,另一个视图中,并且点击的同时把这个歌曲给下载到本地。
涉及的知识会有coredata,以及数据向文件中的写入。