FunctionReactivePixels將會(huì)是一個(gè)簡(jiǎn)單的觀看'500px'中最受歡迎的照片的應(yīng)用。一旦我們完成這一節(jié),應(yīng)用的主界面將會(huì)像下面這樣:
當(dāng)然我們也可以像下圖一樣觀看全屏模式下的圖片。
這個(gè)App將使用Collection Views。如果你沒(méi)有太多這方面的經(jīng)驗(yàn),也不需要太過(guò)擔(dān)心---他們(CollectionView)就像TableView一樣,使用起來(lái)非常簡(jiǎn)單。如果你對(duì)UICollectionView感興趣,可以閱讀我的另一本書(shū).
我們將使用CocoaPods來(lái)管理我們的依賴,現(xiàn)在創(chuàng)建一個(gè)新的工程。我喜歡使用空模版以便我可以完全控制viewController層級(jí)。
首先、我們將創(chuàng)建一個(gè)UICollectionViewController的子類FRPGalleryViewController.同時(shí)我們創(chuàng)建一個(gè)UICollectionViewFlowLayout的子類FRPGalleryFlowLayout.
#import the new flow layout's header in the view controller's implementation file and
#then override FRPGalleryViewController's init method
- (id)init{
FRPGalleryFlowLayout *flowLayout = [[FRPGalleryFlowLayout alloc] init];
self = [self initWithCollectionViewLayout:flowLayout];
if(!self) return nil;
return self;
}
這將初始化collection View的layout為我們自己的layout.這個(gè)flowlayout子類的實(shí)現(xiàn)非常簡(jiǎn)單,只需要設(shè)置一些屬性就可以了。
@implementation FRPGalleryFlowLayout
- (instancetype)init{
if (!(self = [super init])) return nil;
self.itemSize = CGSizeMake(145,145);
self.minimumInteritemSpacing = 10;
self.minimumLineSpacing = 10;
self.sectionInset = UIEdgeInsetsMake(10,10,10,10);
return self;
}
@end
很棒!下一步,我們需要把Viewcontroller展現(xiàn)在屏幕上。為了實(shí)現(xiàn)這個(gè),我們首先要在應(yīng)用的application delegate的application: didFinishLaunchingWithOptions:
方法。我們想要將collectionview Controller置于一個(gè)navigationController容器中:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[FRPGalleryViewController alloc] init]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
很好!如果我們現(xiàn)在運(yùn)行,我們將看到一個(gè)空視圖。
我們來(lái)填充一些內(nèi)容。創(chuàng)建一個(gè)Podfile文件,并填寫(xiě)如下內(nèi)容:
platform :ios, "7.0"
target "FRP" do
pod 'ReactiveCocoa', '~> 2.1.4'
pod 'libextobjc', '~> 0.3'
pod '500-iOS-api', '~> 1.0.4'
pod 'SVProgressHUD', '~> 0.9'
end
target "FRPTests" do
end
下一章,我們將添加一些測(cè)試?,F(xiàn)在運(yùn)行pod install
,然后打開(kāi)Xcode通用的workspace
文件。打開(kāi)與編譯頭文件FRP-Prefix.pch
(Xcode6之后,新建工程默認(rèn)不加載pch文件,需要自己添加,Apple的最佳實(shí)踐中已經(jīng)不推薦使用全局的預(yù)編譯pch文件),然后添加下面的內(nèi)容。這些語(yǔ)義會(huì)自動(dòng)加載到項(xiàng)目的所有文件中。
//Pods
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <500px-iOS-api/PXAPI.h>
#import <libextobjc/EXTScope.h>
//App Delegate
#import "FRPAppDelegate.h"
#define AppDelegate ((FRPAppDelegate *)[[UIApplication sharedApplication] delegate])
對(duì)于這樣使用AppDelegate單例的用法,Saul Mora說(shuō):“每次看到你這么做,我家的狗都想死”。 但是這不是一本關(guān)于設(shè)計(jì)模式的書(shū)---這是一本關(guān)于ReactiveCocoa的書(shū),所以我們可能要害死一些狗狗。。。
創(chuàng)建一個(gè)AppDelegate的屬性來(lái)hold住500px API客戶端
@property (nonatomic, readonly) PXAPIHelper * apiHelper;
在application:didFinishLaunchingWithOptions:
方法中實(shí)例化這個(gè)變量。
self.apiHelper = [[PXAPIHelper alloc]
initWithHost:nil
consumerKey:@"DC2To2BS0ic1ChKDK15d44M42YHf9gbUJgdFoF0m"
consumerSecret:@"i8WL4chWoZ4kw9fh3jzHK7XzTer1y5tUNvsTFNnB"];
我提供了一對(duì)一次性消費(fèi)的密鑰---請(qǐng)不要瘋到你也使用這對(duì)密鑰,你可以申請(qǐng)自己的。
好了,我們差不多也該建立數(shù)據(jù)的加載了。我們需要一個(gè)數(shù)據(jù)模型來(lái)hold住我們的信息。我創(chuàng)建了下面的FRPPhotoModel
。
@interface FRPPhotoModel : NSObject
@property (nonatomic, strong) NSString *photoName;
@property (nonatomic, Strong) NSNumber *identifier;
@property (nonatomic, strong) NSString *photographerName;
@property (nonatomic, strong) NSNumber *rating;
@property (nonatomic, strong) NSString *thumbnailURL;
@property (nonatomic, strong) NSData *thumbnailData;
@property (nonatomic, strong) NSString *fullsizedURL;
@property (nonatomic, strong) NSData * fullsizedData;
@end
@implementation FRPPhotoModel
@end
非常好,到這里,我們將不直接在ViewController中加載內(nèi)容,相反,這部分邏輯將被抽象到另一個(gè)類中。創(chuàng)建一個(gè)名為FRPPhotoImporter
的類。
到現(xiàn)在為止沒(méi)有一處代碼是關(guān)于函數(shù)式的。別擔(dān)心,我們就要這么做了!這個(gè)FRPPhotoImporter
將不會(huì)真正返回一個(gè)FRPPhotoModel
對(duì)象,相反他會(huì)返回一些隨身攜帶API最新的請(qǐng)求結(jié)果的信號(hào)。
@interface FRPPhotoImporter : NSObject
+ (RACSignal *)importPhotos;
@end
FRPPhotoImporter
的importPhotos
方法返回一個(gè)從API發(fā)送最新結(jié)果的RACSignal。這個(gè)RACSignal實(shí)際上是一個(gè)RACReplaySubject.但是由于ReactiveCocoa編程指南中不建議使用RACSubjects,我們申明的公共接口的返回類型為RACSignal而非RACSubject.現(xiàn)在讓我們繼續(xù)往下看:
+ (RACSignal *)importPhotos{
RACReplaySubject * subject = [RACReplaySubject subject];
NSURLRequest * request = [self popularURLRequest];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
if (data) {
id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[subject sendNext:[[[results[@"photos"] rac_sequence] map:^id(NSDictionary *photoDictionary){
FRPPhotoModel * model = [FRPPhotoModel new];
[self configurePhotoModel:model withDictionary:photoDictionary];
[self downloadThumbnailForPhotoModel:model];
return model;
}] array]];
[subject sendCompleted];
}
else{
[subject sendError:connectionError];
}
}];
return subject;
}
這里面包含的內(nèi)容太多,我們慢慢來(lái)整理一下:
RACReplaySubject
實(shí)例(這將是我們要返回的對(duì)象)。NSURLRequest
來(lái)獲取500px上熱門(mén)的FRPPhotoModel
數(shù)據(jù)。這個(gè)直接返回的結(jié)果值得我們關(guān)注。
這個(gè)RACSubject對(duì)象被異步網(wǎng)絡(luò)請(qǐng)求的回調(diào)block捕獲,當(dāng)API接口返回?cái)?shù)據(jù)時(shí)回調(diào)block就會(huì)被調(diào)用,然后RACSubject對(duì)象會(huì)將結(jié)果傳送出來(lái),這些值將被我們的訂閱了RACSubject信號(hào)的接收者所接受。
這是你看到的異步操作中,一個(gè)非常普通的模式。
重要的是,要注意一個(gè)普通的RASSubject及其子類RACReplaySubject之間的區(qū)別。RACReplaySubject可以確保他背后的Subject只會(huì)被訂閱一次,避免執(zhí)行重復(fù)的操作(就像上面這種網(wǎng)絡(luò)活動(dòng)的情況),RACReplaySubject將會(huì)緩存這個(gè)訂閱的值,并將其轉(zhuǎn)發(fā)給新的訂閱者們--- 對(duì)我們的需求來(lái)說(shuō)這非常完美。就像ReactiveCocoa的開(kāi)發(fā)者Justin Spahr-Summers所指出的,這也能夠避免可能的競(jìng)爭(zhēng)狀況。
我們發(fā)送了一個(gè)完整的數(shù)據(jù)集而不是單個(gè)隨時(shí)間變化的流。如果我們連環(huán)地發(fā)送一個(gè)個(gè)單獨(dú)的FRPPhotoModel
流,這將'更加Reactive',也有助于實(shí)現(xiàn)分頁(yè)的需求,但是我們不打算采用這種方式,因?yàn)樗悬c(diǎn)點(diǎn)‘高級(jí)’了。你可以下載octokit:一個(gè)類似這種方式的例子。
URL請(qǐng)求的構(gòu)造方法看起來(lái)應(yīng)該是這樣的:
+ (NSURLRequest *)popularURLRequest {
return [AppDelegate.apiHelper urlRequestForPhotoFeature:PXAPIHelperPhotoFeaturePopular
resultsPerPage:100 page:0
photoSize:PXPhotoModelSizeThumbnail
sortOrder:PXAPIHelperSortOrderRating
except:PXPhotoModelCategoryNude];
}
subject發(fā)送什么,完全看不到好嗎?呃。這取決于回調(diào)block.
if(data){
id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[subject sendNext:[[[results[@"photos"] rac_sequence] map:^id (NSDictionary *photoDictionary){
FRPPhotoModel *model = [FRPPhotoModel new];
[self donwloadThumbnailForPhotoModel:model];
return model;
}] array]];
[subject sendCompleted];
}
else{
[subject sendError:connectionError];
}
測(cè)試是否有數(shù)據(jù)返回時(shí),可以說(shuō)這不是一個(gè)很好的錯(cuò)誤條件檢測(cè)的方法,但這是一個(gè)教學(xué)的例子。如果數(shù)據(jù)為nil
,我們會(huì)發(fā)送一個(gè)errorValue
,否則我們會(huì)反序列化JSON
數(shù)據(jù)并處理它。這不太容易很快就看清楚是怎么做到的,讓我們來(lái)仔細(xì)看看。
[subject sendNext:[[[results[@"photos"] rac_sequence] map:^id (NSDictionary *photoDictionary){
FRPPhotoModel * model = [FRPPhotoModel new];
[self configurePhotoModel:model withDictionary:photoDictionary];
[self downloadThumbnailForPhotoModel:model];
return model;
}] array]];
[subject sendCompleted];
發(fā)送一個(gè)值,隨著subject擼過(guò)去,第一個(gè)表達(dá)式結(jié)構(gòu)相當(dāng)簡(jiǎn)潔(但是場(chǎng)景很典型)。這個(gè)值是photos
的值,然后轉(zhuǎn)化為一個(gè)序列(sequence),然后做映射,最后轉(zhuǎn)化為一個(gè)數(shù)組。這是上一章介紹的非常簡(jiǎn)單的map
技術(shù)。
這個(gè)map
(映射)非常有意思。序列中的每一個(gè)元素,都會(huì)創(chuàng)建一個(gè)新的FRPPhotoModel
對(duì)象、設(shè)置它然后返回它。為每一個(gè)results[ @"photos" ]
的數(shù)組元素創(chuàng)建了一個(gè)FRPPhotoModel
數(shù)組。這個(gè)數(shù)組就是隨著subject發(fā)送過(guò)來(lái)的值。最后我們發(fā)送一個(gè)完成值completedValue
好讓訂閱者們知道任務(wù)完成了。
注意在信號(hào)上手動(dòng)附送值的能力是非典型的,這是RACSubject實(shí)例的專屬能力。
configurePhotoModel:withDictionary:
方法,看起來(lái)應(yīng)該像下面這樣:
+ (void)configurePhotoModel:(FRPPhotoModel *)photomodel withDictionary:(NSDictionary *)dictionary{
//Basic details fetched with the first, basic request
photomodel.photoname = dictionary[@"name"];
photomodel.identifier = dictionary[@"id"];
photomodel.photographerName = dictionary[@"user"][@"username"];
photomodel.rating = dictionary[@"rating"];
photomodel.thumbnailURL = [self urlForImageSize:3 inArray:dictionary[@"images"]];
//Extended attributes fetched with subsequent request
if (dictionary[@"comments_count"]){
photomodel.fullsizedURL = [self urlForImageSize:4 inArray:dictionary[@"images"]];
}
}
除了URL的屬性設(shè)置,都是最基本的東西。依靠其他的方法來(lái)從500px的API中返回的圖片列表中提取正確的url信息。500px API返回的數(shù)據(jù)結(jié)構(gòu)是下面這樣的格式:
(
{
size = size;
url = ...;
}
)
這是一個(gè)字典數(shù)組,每一個(gè)字典中包含一個(gè)size
字段和一個(gè)url
字段。我們讀取這樣字段的方法如下:
+ (NSString *)urlForImageSize:(NSInteger)size inDictionary:(NSArray *)array{
return [[[[[array rac_sequence] filter:^ BOOL (NSDictionary * value){
return [value[@"size"] integerValue] == size;
}] map:^id (id value){
return value[@"url"];
}] array] firstObject];
}
這里有一些隱含的錯(cuò)誤處理,如果序列為空,NSArray
的firstObject
方法默認(rèn)返回nil.
size
字段不匹配要求的字典。url
字段的內(nèi)容。firstObject
.在ReactiveCocoa中類似上面的鏈?zhǔn)秸{(diào)用非常常見(jiàn)。值從rac_sequence
推送到filter:
方法中,最后推送到map:
方法里。最后調(diào)用序列rac_sequence
的array
方法,將序列的結(jié)果轉(zhuǎn)化為array
.
最后,我們的downloadThumbnailForPhotoModel:
方法,看起來(lái)應(yīng)該是下面這樣:
+ (void)downloadThumbnailForPhotoModel:(FRPPhotoModel *)photoModel{
NSAssert(photoModel.thumbnailURL, @"Thumbnail URL must not be nil");
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:photoModel.ThumbnailURL]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError * connectionError){
photoModel.thumbnailData = data;
}];
}
這個(gè)方法里面沒(méi)有任何的關(guān)于Reactive
的部分---僅僅是下載thumbnail的url,然后在完成塊中適當(dāng)?shù)卦O(shè)置相關(guān)屬性。
我們幾乎做完了這個(gè)畫(huà)廊所需要的所有基礎(chǔ)的事情,接下來(lái),我們看看viewController
.在實(shí)現(xiàn)文件里定義下面的的私有屬性。
@interface FRPGalleryViewController ()
@property (nonatomic , strong) NSArray *photoArray;
@end
來(lái)看下viewDidLoad中的實(shí)現(xiàn)。
static NSString * CellIdentifier = @"Cell";
- (void)viewDidLoad{
[super ViewDidLoad];
//Configure self
self.title = @"Popular on 500px";
//Configure View
[self.collectionView registerClass:[FRPCell class] forCellWithReuseIdentifier:CellIdentifier];
//Reactive Stuff
@weakify(self);
[RACObserver(self, photosArray) subscribeNext:^(id x){
@strongify(self);
[self.collectionView reloadData];
}];
//Load data
[self loadPopularPhotos];
}
我們?yōu)関iewController設(shè)置了一個(gè)title并且為collectionView注冊(cè)了一個(gè)類,collectionView將會(huì)在他的cells中復(fù)用這個(gè)類的實(shí)例。這里我引用了一個(gè)不存在的UICollectionViewCell的子類,我們很快會(huì)創(chuàng)建她。
在'Reactive Stuff'注釋之下,你會(huì)發(fā)現(xiàn)一些奇怪的語(yǔ)法。
@weakify(self);
[RACObserver(self, photosArray) subscribeNext:^(id x){
@strongify(self);
[self.collectionView reloadData];
}];
RACObserver
是一個(gè)C的宏定義,帶兩個(gè)參數(shù):對(duì)象及對(duì)象某個(gè)屬性的keyPath
(關(guān)鍵路徑)。他會(huì)返回一個(gè)帶屬性值的信號(hào),無(wú)論這個(gè)屬性的值怎么變都會(huì)及時(shí)地通過(guò)該信號(hào)反饋出來(lái)。在這里當(dāng)self結(jié)束分配的時(shí)候會(huì)發(fā)送一個(gè)completion Value
的值。訂閱這個(gè)信號(hào)的目的是無(wú)論我們的photosArray中的元素屬性怎么變,我們都能夠在collectionView重新加載的時(shí)候?qū)崟r(shí)獲取反饋。
在Objective-C的ARC條件下@weakify/@strongify這個(gè)雙人舞是非常常見(jiàn)的。@weakify創(chuàng)建一個(gè)新的self的弱引用weakself,@strongify創(chuàng)建這個(gè)weakself的強(qiáng)引用,并在@strongify的作用域中起作用。strongify的這種做法,一般稱為“影子變量”,那是因?yàn)檫@個(gè)新的強(qiáng)引用的變量就叫self
,替代了原本強(qiáng)引用的self.
一般而言,subscribeNext:
的block將捕獲其詞法范圍內(nèi)的self,造成self和block之間的循環(huán)引用。block被subscribeNext:
的返回值,一個(gè)RACSubscriber實(shí)例,強(qiáng)引用,然后被RACObserver宏捕獲。解除分配時(shí),RACOberver會(huì)自動(dòng)解除第一個(gè)參數(shù)的分配,這樣的話self就應(yīng)該被解除分配,但self被block強(qiáng)引用,self要得以解除分配的唯一條件即引用計(jì)數(shù)為0,這樣的話就必須先解除block的分配,而前面的分析我們知道block被RACSubscriber實(shí)例引用,而該實(shí)例默認(rèn)被self強(qiáng)引用,因此,如果不調(diào)用weakify/strongify,self就永遠(yuǎn)也不可能解除分配。
最后,我們實(shí)際來(lái)調(diào)用loadPopularPhotos
(他的實(shí)現(xiàn)如下)
- (void)loadPopularPhotos{
[[FRPPhotoImporter importPhotos] subscribeNext:^(id x){
self.photosArray = x;
} error:^(NSError * error){
NSLog(@"Couldn't fetch photofrom 500px: %@",error);
}];
}
這個(gè)方法實(shí)際上負(fù)責(zé)調(diào)用FRPPhotoImporter
的importPhotos
方法(現(xiàn)在請(qǐng)加上他的頭文件),他訂閱了我們私有成員屬性的結(jié)果。由于UICollectionViewDataSource協(xié)議的架構(gòu),我們不得不把這些狀態(tài)引入進(jìn)來(lái)。
現(xiàn)在讓我們來(lái)看一下這些協(xié)議方法,有兩個(gè)是必須的,實(shí)現(xiàn)如下:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return self.photosArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
FRPCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
[cell setPhotoModel:self.photosArray[indexPath.row]];
return cell;
}
第一個(gè)方法簡(jiǎn)單地返回了collectionView中的cell的數(shù)量,在這里,準(zhǔn)確地講是photosArray屬性的cell數(shù)量。接下來(lái)的這個(gè)方法從collectionView列表中獲得了一個(gè)cell實(shí)例,并調(diào)用其上的setPhotoModel:
方法(這個(gè)我們還沒(méi)有實(shí)現(xiàn),但別擔(dān)心)。這些代碼應(yīng)該看起來(lái)非常熟悉,如果你曾經(jīng)處理過(guò)UITableViewDataSource的方法的話。
這就是我們ViewController
完整的實(shí)現(xiàn)。現(xiàn)在我們來(lái)創(chuàng)建UICollectionViewCell的子類,命名為FRPCell
,像下面這樣來(lái)修改他的頭文件。
@class FRPPhotoModel;
@interface FRPCell : UICollectionViewCell
- (void)setPhotoModel:(FRPPhotoModel *)photoModel;
@end
在實(shí)現(xiàn)文件中添加下面的私有擴(kuò)展:
#import "FRPPhotoModel.h"
@interface FRPCell ()
@property (nonatomic , weak ) UIImageView * imageView;
@property (nonatomic , strong ) RACDisposeable *subscription;
@end
這里有兩個(gè)屬性:一個(gè)圖片視圖和一個(gè)訂閱者。圖片視圖是弱引用,因?yàn)樗鼘儆诟敢晥D(這是UICollectionViewCell的一個(gè)標(biāo)準(zhǔn)的用法),我們將實(shí)例化并賦值給imageView。接下來(lái)的屬性是一個(gè)訂閱,當(dāng)使用ReactiveCocoa來(lái)設(shè)置圖像視圖的圖像屬性時(shí),我們將接觸到它。注意它必須是強(qiáng)引用而非弱引用否則你會(huì)得到一個(gè)運(yùn)行時(shí)的異常。
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(!self) return nil;
//Configure self
self.backgroundColor = []UIColor darkGrayColor];
//Configure subviews
UIImageView * imageView = [[UIImageView alloc] initWithFrame:self.bounds];
imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.contentView addsubView:imageView];
self.imageView = imageView;
return self;
}
標(biāo)準(zhǔn)的UICollectionView子類的模版會(huì)創(chuàng)建并分配imageView屬性。注意,我們必須有一個(gè)(被self)強(qiáng)引用的本地變量作為中介來(lái)存儲(chǔ)imageView,這樣就不會(huì)在賦值給self的imageView屬性的時(shí)候,imageView被立即解除分配。否則會(huì)有編譯錯(cuò)誤。
完成我們的500px畫(huà)廊,我們還需要實(shí)現(xiàn)兩個(gè)方法,第一個(gè)就是setPhotoModel:
方法
- (void)setPhotoModel:(FRPPhotoModel *)photoModel{
self.subscription = [[[RACObserver(photoModel, thumbnailData)
filter:^ BOOL (id value){
return value != nil;
}] map:^id (id value){
return [UIImage imageWithData:value];
}] setKeyPath:@keypath(self.imageView, image) onObject:self.imageView];
}
這種方法來(lái)給訂閱的屬性賦值,我們老早就知道了。它把setKeyPath:OnObject:
的返回值賦給了self.subscription
.實(shí)踐中這種方法根本不使用,我們使用RAC的C語(yǔ)法宏來(lái)代替,不久之后我們就會(huì)涉及這方面的知識(shí)。
兩個(gè)原因?qū)е掠嗛喪潜匾模?/p>
1. 當(dāng)它沒(méi)有接受一個(gè)新的值時(shí),我們想延遲處理。
2. 信號(hào)的訂閱通常是冷信號(hào),除非有人訂閱他(信號(hào)),否則信號(hào)不會(huì)起作用。
setKeyPath:onObject:
是RACSignal
的一個(gè)方法:綁定最新的信號(hào)的值給對(duì)象的關(guān)鍵路徑。在這里我們?cè)谝粋€(gè)級(jí)聯(lián)的信號(hào)上調(diào)用了這個(gè)方法,讓我們來(lái)仔細(xì)看看:
[[RACObserver (photoModel, thumbnailData)
filter:^BOOL (id value){
return value != nil;
}] map:^ id (id value){
return [UIImage imageWithData:value];
}];
信號(hào)由RACObserver
這個(gè)C的宏生成,這個(gè)宏簡(jiǎn)單地返回一個(gè)監(jiān)控目標(biāo)對(duì)象關(guān)鍵路徑值變化的信號(hào)。在我們這個(gè)例子中,我們的目標(biāo)對(duì)象是photoModel
,關(guān)鍵路徑為thumbnailData
屬性。我們過(guò)濾掉所有的nil值,然后對(duì)過(guò)濾后的值做映射:把NSData實(shí)例轉(zhuǎn)為UIImage對(duì)象。
注意,把NSData實(shí)例轉(zhuǎn)化為UIImage的這個(gè)映射僅在小圖上可以很好地運(yùn)行,如果頻繁地做這個(gè)映射或者作用到大圖上會(huì)引起性能問(wèn)題。理想的情況下,我們會(huì)緩存這些已經(jīng)解壓的圖像以避免每一次都重復(fù)計(jì)算。這個(gè)技術(shù)不是本書(shū)所討論的范疇,但我們將使用另一個(gè)通過(guò)ReactiveCocoa來(lái)實(shí)現(xiàn)的方法。
thumbnailData屬性根本不需要在這里設(shè)置,他可以在稍后的某個(gè)時(shí)間在應(yīng)用的其他部分來(lái)完成設(shè)置,然后cell的圖像就會(huì)像魔術(shù)一般更新。
可以讓我們稍微突破一下Model-View-Controller模式好嗎?只是一點(diǎn)點(diǎn)的不守規(guī)矩。幸運(yùn)的是,下一章我們將看到無(wú)處不在的MVC模式的困境,所以我們不必?fù)?dān)心這一點(diǎn)點(diǎn)的突破,一點(diǎn)點(diǎn)的改進(jìn)。
上面提到的setKeyPath:onObject:
方法中,一旦onObject:
對(duì)象被釋放,他的訂閱也會(huì)被自動(dòng)取消。我們的cell實(shí)例是被collectionView所復(fù)用的,因此在復(fù)用的時(shí)候,我們需要取消cell上各組件的訂閱。我們可以通過(guò)重寫(xiě)UICollectionViewCell
的下列方法達(dá)成:
- (void)perpareForReuse {
[super prepareForReuse];
[self.subscription dispose], self.subscription = nil;
}
這個(gè)方法在Cell被復(fù)用之前調(diào)用。如果現(xiàn)在運(yùn)行我的應(yīng)用,我們可以看到下面的結(jié)果:
太好了!我們可以通過(guò)滾動(dòng)視圖來(lái)證實(shí)我們手動(dòng)處理訂閱的有效性。
更多建議: