IOS菜鸟的所感所思(七)——获取网络图片并添加

来源:互联网 发布:python rsa 私钥加密 编辑:程序博客网 时间:2024/06/05 09:21

目标:对网络图片的获取和利用线程进行图片的添加与更新

之前我们完成的搜索界面中的歌曲图片都是一样的,但在解析单个歌曲的时候会发现其中有歌曲的自己的图片,于是我们可以把解析歌曲时去获取歌曲图片的地址,从而在搜索界面可以实行图片的加载。

当然第一步是要获得歌曲的图片地址,这就要在获取网络数据的方法中

songID = [[[itemDictionary objectForKey:@"songs"]firstObject]objectForKey:@"mp3Url"] ;

musicLogo = [[[[itemDictionaryobjectForKey:@"songs"]firstObject]objectForKey:@"album"]objectForKey:@"blurPicUrl"];


这里有两种方法去实现图片的加载,个人推荐使用第二中,而第一种作为了解下怎样使用线程。

分析:
1.我们想要在点击搜索按钮的时候,在获得歌曲的同时获得图片的地址。
2.获取图片的地址的前提是首先获得一个歌曲对象,并把该对象作为参数传进去。
那么我们需要在SearchSongVC.m中的方法去实现:

- (void)getData:(NSString *)name page:(NSInteger)pageIndex

3.也就是获得了歌曲的数组之后,再用一个for循环语句获得歌曲数组中的单个对象,作为参数传进去,并且用回调函数获得歌曲图片的地址,并将它放入一个可变数组中,这样for循环完成时我们就获得了所有歌曲图片的地址。

4.再只需要创建一个方法,将该歌曲的对象和该歌曲的图片地址作为参数传入到cell的初始化。


问题:

- (void)getData:(NSString *)name page:(NSInteger)pageIndex{

    [FetchDataFromNetfetchMusicData:namepage: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:musicDatacallback:^(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:imageUrlplaceholderImage:[UIImageimageNamed:@"surf.jpg"]];

        });

        

    });

}

这些代码看起来没什么问题,但真正运行起来就会发现并不是和自己预期的一样。

原因:程序在执行for语句的时候,[self getMusicImage:obj];这个方法是异步的线程,所以程序不会等着for语句完成后再去执行后面的程序。而是会在for语句的同时执行

[self.collectionView reloadData];去重新刷新界面,但这时图片数组中并没有数据,所以程序会崩溃。


解决办法:

[self getMusicImage:obj]里面的异步线程改为同步线程。

- (void)getMusicImage:(MusicData *)musicData{

    //调用同步线程的方法

    [FetchDataFromNetfetchSongDetailInfoBySynch:musicDatacallback:^(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:requestreturningResponse:nilerror:nil];

   NSString *songID;

   NSString *musicLogo;

   @try {

        NSDictionary *itemDictionary = [NSJSONSerializationJSONObjectWithData:dataoptions: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:namepage: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:musicLogoforKey:@"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:musicDatacallback:^(id item,NSString *musicImage, NSError *error) {

            [self.logoImagesd_setImageWithURL:[NSURLURLWithString:musicImage]placeholderImage:[UIImageimageNamed:@"surf.jpg"]];

        }];

    }];

}

当然这里[item setValue:musicLogo forKey:@"logoname"];没有起到什么作用。

OperationQueue实质上也就是数组管理,对添加进去的operation进行管理、创建线程等,具体的请参考:点击打开链接

这样你再运行程序的话,就会发现图片只会显示当前在屏幕上的cell中的图片。



当然你们的没有后面的加号按钮,这个在后面会我们会再添加。

这样我们就获得了歌曲的图片,并且可以播放。

接下来需要的是添加加号按钮,想要实现的功能是点击加号,可以将这首歌收入收藏列表中,另一个视图中,并且点击的同时把这个歌曲给下载到本地。

涉及的知识会有coredata,以及数据向文件中的写入。













0 0
原创粉丝点击