国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

FunctionalReactivePixels基礎

2018-08-01 16:17 更新

FunctionReactivePixels將會是一個簡單的觀看'500px'中最受歡迎的照片的應用。一旦我們完成這一節(jié),應用的主界面將會像下面這樣:

app_main_page

當然我們也可以像下圖一樣觀看全屏模式下的圖片。

ReactiveCocoa實踐之FunctionalReactivePixels基礎

這個App將使用Collection Views。如果你沒有太多這方面的經(jīng)驗,也不需要太過擔心---他們(CollectionView)就像TableView一樣,使用起來非常簡單。如果你對UICollectionView感興趣,可以閱讀我的另一本書.

我們將使用CocoaPods來管理我們的依賴,現(xiàn)在創(chuàng)建一個新的工程。我喜歡使用空模版以便我可以完全控制viewController層級。

ReactiveCocoa實踐之FunctionalReactivePixels基礎

首先、我們將創(chuàng)建一個UICollectionViewController的子類FRPGalleryViewController.同時我們創(chuàng)建一個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.這個flowlayout子類的實現(xiàn)非常簡單,只需要設置一些屬性就可以了。

@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)在屏幕上。為了實現(xiàn)這個,我們首先要在應用的application delegate的application: didFinishLaunchingWithOptions:方法。我們想要將collectionview Controller置于一個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)在運行,我們將看到一個空視圖。

app_main_emptypage

我們來填充一些內(nèi)容。創(chuàng)建一個Podfile文件,并填寫如下內(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

下一章,我們將添加一些測試?,F(xiàn)在運行pod install,然后打開Xcode通用的workspace文件。打開與編譯頭文件FRP-Prefix.pch(Xcode6之后,新建工程默認不加載pch文件,需要自己添加,Apple的最佳實踐中已經(jīng)不推薦使用全局的預編譯pch文件),然后添加下面的內(nèi)容。這些語義會自動加載到項目的所有文件中。

//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])

對于這樣使用AppDelegate單例的用法,Saul Mora說:“每次看到你這么做,我家的狗都想死”。 但是這不是一本關于設計模式的書---這是一本關于ReactiveCocoa的書,所以我們可能要害死一些狗狗。。。

創(chuàng)建一個AppDelegate的屬性來hold住500px API客戶端

@property (nonatomic, readonly) PXAPIHelper * apiHelper;

application:didFinishLaunchingWithOptions:方法中實例化這個變量。

self.apiHelper = [[PXAPIHelper alloc]
                    initWithHost:nil
                    consumerKey:@"DC2To2BS0ic1ChKDK15d44M42YHf9gbUJgdFoF0m"
                    consumerSecret:@"i8WL4chWoZ4kw9fh3jzHK7XzTer1y5tUNvsTFNnB"];

我提供了一對一次性消費的密鑰---請不要瘋到你也使用這對密鑰,你可以申請自己的。

好了,我們差不多也該建立數(shù)據(jù)的加載了。我們需要一個數(shù)據(jù)模型來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)容,相反,這部分邏輯將被抽象到另一個類中。創(chuàng)建一個名為FRPPhotoImporter的類。

到現(xiàn)在為止沒有一處代碼是關于函數(shù)式的。別擔心,我們就要這么做了!這個FRPPhotoImporter將不會真正返回一個FRPPhotoModel對象,相反他會返回一些隨身攜帶API最新的請求結果的信號。

@interface FRPPhotoImporter : NSObject
+ (RACSignal *)importPhotos;

@end

FRPPhotoImporterimportPhotos方法返回一個從API發(fā)送最新結果的RACSignal。這個RACSignal實際上是一個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)容太多,我們慢慢來整理一下:

  • 首先我們創(chuàng)建了一個新的RACReplaySubject實例(這將是我們要返回的對象)。
  • 其次我們創(chuàng)建了一個NSURLRequest來獲取500px上熱門的FRPPhotoModel數(shù)據(jù)。
  • 隨后我們發(fā)送一個網(wǎng)絡的異步請求,并立即返回RACSubject對象。

這個直接返回的結果值得我們關注。

這個RACSubject對象被異步網(wǎng)絡請求的回調(diào)block捕獲,當API接口返回數(shù)據(jù)時回調(diào)block就會被調(diào)用,然后RACSubject對象會將結果傳送出來,這些值將被我們的訂閱了RACSubject信號的接收者所接受。

這是你看到的異步操作中,一個非常普通的模式。

  1. 創(chuàng)建一個RACSubject.
  2. 從異步調(diào)用的完成block中向RACSubject傳送結果值。
  3. 立即返回這個RACSubject對象

重要的是,要注意一個普通的RASSubject及其子類RACReplaySubject之間的區(qū)別。RACReplaySubject可以確保他背后的Subject只會被訂閱一次,避免執(zhí)行重復的操作(就像上面這種網(wǎng)絡活動的情況),RACReplaySubject將會緩存這個訂閱的值,并將其轉發(fā)給新的訂閱者們--- 對我們的需求來說這非常完美。就像ReactiveCocoa的開發(fā)者Justin Spahr-Summers所指出的,這也能夠避免可能的競爭狀況。

我們發(fā)送了一個完整的數(shù)據(jù)集而不是單個隨時間變化的流。如果我們連環(huán)地發(fā)送一個個單獨的FRPPhotoModel流,這將'更加Reactive',也有助于實現(xiàn)分頁的需求,但是我們不打算采用這種方式,因為他有點點‘高級’了。你可以下載octokit:一個類似這種方式的例子。

URL請求的構造方法看起來應該是這樣的:

+ (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];
}

測試是否有數(shù)據(jù)返回時,可以說這不是一個很好的錯誤條件檢測的方法,但這是一個教學的例子。如果數(shù)據(jù)為nil,我們會發(fā)送一個errorValue,否則我們會反序列化JSON數(shù)據(jù)并處理它。這不太容易很快就看清楚是怎么做到的,讓我們來仔細看看。

[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ā)送一個值,隨著subject擼過去,第一個表達式結構相當簡潔(但是場景很典型)。這個值是photos的值,然后轉化為一個序列(sequence),然后做映射,最后轉化為一個數(shù)組。這是上一章介紹的非常簡單的map技術。

這個map(映射)非常有意思。序列中的每一個元素,都會創(chuàng)建一個新的FRPPhotoModel對象、設置它然后返回它。為每一個results[ @"photos" ]的數(shù)組元素創(chuàng)建了一個FRPPhotoModel數(shù)組。這個數(shù)組就是隨著subject發(fā)送過來的值。最后我們發(fā)送一個完成值completedValue好讓訂閱者們知道任務完成了。

value_photoModel_map

注意在信號上手動附送值的能力是非典型的,這是RACSubject實例的專屬能力。

configurePhotoModel:withDictionary:方法,看起來應該像下面這樣:

+ (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的屬性設置,都是最基本的東西。依靠其他的方法來從500px的API中返回的圖片列表中提取正確的url信息。500px API返回的數(shù)據(jù)結構是下面這樣的格式:

(
    {
        size = size;
        url = ...;
    }
)

這是一個字典數(shù)組,每一個字典中包含一個size字段和一個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];
}

這里有一些隱含的錯誤處理,如果序列為空,NSArrayfirstObject方法默認返回nil.

  • 第一步,我們過濾掉那些size字段不匹配要求的字典。
  • 然后,將這些符合要求的字典做一次映射來提取字典中url字段的內(nèi)容。
  • 最后,我們獲得一個NSString 對象的序列,把它轉化為數(shù)組,然后返回firstObject.

error_handling

在ReactiveCocoa中類似上面的鏈式調(diào)用非常常見。值從rac_sequence推送到filter:方法中,最后推送到map:方法里。最后調(diào)用序列rac_sequencearray方法,將序列的結果轉化為array.

最后,我們的downloadThumbnailForPhotoModel:方法,看起來應該是下面這樣:

+ (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;
    }];
}

這個方法里面沒有任何的關于Reactive的部分---僅僅是下載thumbnail的url,然后在完成塊中適當?shù)卦O置相關屬性。

我們幾乎做完了這個畫廊所需要的所有基礎的事情,接下來,我們看看viewController.在實現(xiàn)文件里定義下面的的私有屬性。

@interface FRPGalleryViewController ()
@property (nonatomic , strong) NSArray *photoArray;

@end

來看下viewDidLoad中的實現(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設置了一個title并且為collectionView注冊了一個類,collectionView將會在他的cells中復用這個類的實例。這里我引用了一個不存在的UICollectionViewCell的子類,我們很快會創(chuàng)建她。

在'Reactive Stuff'注釋之下,你會發(fā)現(xiàn)一些奇怪的語法。

@weakify(self);
[RACObserver(self, photosArray) subscribeNext:^(id x){
    @strongify(self);
    [self.collectionView reloadData];
}];

RACObserver是一個C的宏定義,帶兩個參數(shù):對象及對象某個屬性的keyPath(關鍵路徑)。他會返回一個帶屬性值的信號,無論這個屬性的值怎么變都會及時地通過該信號反饋出來。在這里當self結束分配的時候會發(fā)送一個completion Value的值。訂閱這個信號的目的是無論我們的photosArray中的元素屬性怎么變,我們都能夠在collectionView重新加載的時候實時獲取反饋。

在Objective-C的ARC條件下@weakify/@strongify這個雙人舞是非常常見的。@weakify創(chuàng)建一個新的self的弱引用weakself,@strongify創(chuàng)建這個weakself的強引用,并在@strongify的作用域中起作用。strongify的這種做法,一般稱為“影子變量”,那是因為這個新的強引用的變量就叫self,替代了原本強引用的self.

一般而言,subscribeNext:的block將捕獲其詞法范圍內(nèi)的self,造成self和block之間的循環(huán)引用。block被subscribeNext:的返回值,一個RACSubscriber實例,強引用,然后被RACObserver宏捕獲。解除分配時,RACOberver會自動解除第一個參數(shù)的分配,這樣的話self就應該被解除分配,但self被block強引用,self要得以解除分配的唯一條件即引用計數(shù)為0,這樣的話就必須先解除block的分配,而前面的分析我們知道block被RACSubscriber實例引用,而該實例默認被self強引用,因此,如果不調(diào)用weakify/strongify,self就永遠也不可能解除分配。

最后,我們實際來調(diào)用loadPopularPhotos(他的實現(xiàn)如下)

- (void)loadPopularPhotos{
    [[FRPPhotoImporter importPhotos] subscribeNext:^(id x){
        self.photosArray = x;
    } error:^(NSError * error){
        NSLog(@"Couldn't fetch photofrom 500px: %@",error);
    }];
}

這個方法實際上負責調(diào)用FRPPhotoImporterimportPhotos方法(現(xiàn)在請加上他的頭文件),他訂閱了我們私有成員屬性的結果。由于UICollectionViewDataSource協(xié)議的架構,我們不得不把這些狀態(tài)引入進來。

現(xiàn)在讓我們來看一下這些協(xié)議方法,有兩個是必須的,實現(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;
}

第一個方法簡單地返回了collectionView中的cell的數(shù)量,在這里,準確地講是photosArray屬性的cell數(shù)量。接下來的這個方法從collectionView列表中獲得了一個cell實例,并調(diào)用其上的setPhotoModel:方法(這個我們還沒有實現(xiàn),但別擔心)。這些代碼應該看起來非常熟悉,如果你曾經(jīng)處理過UITableViewDataSource的方法的話。

這就是我們ViewController完整的實現(xiàn)?,F(xiàn)在我們來創(chuàng)建UICollectionViewCell的子類,命名為FRPCell,像下面這樣來修改他的頭文件。

@class FRPPhotoModel;

@interface FRPCell : UICollectionViewCell
- (void)setPhotoModel:(FRPPhotoModel *)photoModel;
@end

在實現(xiàn)文件中添加下面的私有擴展:

#import "FRPPhotoModel.h"
@interface FRPCell ()
@property (nonatomic , weak ) UIImageView * imageView;
@property (nonatomic , strong ) RACDisposeable *subscription;

@end

這里有兩個屬性:一個圖片視圖和一個訂閱者。圖片視圖是弱引用,因為它屬于父視圖(這是UICollectionViewCell的一個標準的用法),我們將實例化并賦值給imageView。接下來的屬性是一個訂閱,當使用ReactiveCocoa來設置圖像視圖的圖像屬性時,我們將接觸到它。注意它必須是強引用而非弱引用否則你會得到一個運行時的異常。

- (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;
}

標準的UICollectionView子類的模版會創(chuàng)建并分配imageView屬性。注意,我們必須有一個(被self)強引用的本地變量作為中介來存儲imageView,這樣就不會在賦值給self的imageView屬性的時候,imageView被立即解除分配。否則會有編譯錯誤。

完成我們的500px畫廊,我們還需要實現(xiàn)兩個方法,第一個就是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];
}

這種方法來給訂閱的屬性賦值,我們老早就知道了。它把setKeyPath:OnObject:的返回值賦給了self.subscription.實踐中這種方法根本不使用,我們使用RAC的C語法宏來代替,不久之后我們就會涉及這方面的知識。

兩個原因導致訂閱是必要的:

1. 當它沒有接受一個新的值時,我們想延遲處理。
2. 信號的訂閱通常是冷信號,除非有人訂閱他(信號),否則信號不會起作用。

setKeyPath:onObject:RACSignal的一個方法:綁定最新的信號的值給對象的關鍵路徑。在這里我們在一個級聯(lián)的信號上調(diào)用了這個方法,讓我們來仔細看看:

[[RACObserver (photoModel, thumbnailData)
    filter:^BOOL (id value){
        return value != nil;
    }] map:^ id (id value){
        return [UIImage imageWithData:value];
    }];

chained_signal

信號由RACObserver這個C的宏生成,這個宏簡單地返回一個監(jiān)控目標對象關鍵路徑值變化的信號。在我們這個例子中,我們的目標對象是photoModel,關鍵路徑為thumbnailData屬性。我們過濾掉所有的nil值,然后對過濾后的值做映射:把NSData實例轉為UIImage對象。

注意,把NSData實例轉化為UIImage的這個映射僅在小圖上可以很好地運行,如果頻繁地做這個映射或者作用到大圖上會引起性能問題。理想的情況下,我們會緩存這些已經(jīng)解壓的圖像以避免每一次都重復計算。這個技術不是本書所討論的范疇,但我們將使用另一個通過ReactiveCocoa來實現(xiàn)的方法。

thumbnailData屬性根本不需要在這里設置,他可以在稍后的某個時間在應用的其他部分來完成設置,然后cell的圖像就會像魔術一般更新。

可以讓我們稍微突破一下Model-View-Controller模式好嗎?只是一點點的不守規(guī)矩。幸運的是,下一章我們將看到無處不在的MVC模式的困境,所以我們不必擔心這一點點的突破,一點點的改進。

上面提到的setKeyPath:onObject:方法中,一旦onObject:對象被釋放,他的訂閱也會被自動取消。我們的cell實例是被collectionView所復用的,因此在復用的時候,我們需要取消cell上各組件的訂閱。我們可以通過重寫UICollectionViewCell的下列方法達成:

- (void)perpareForReuse {
    [super prepareForReuse];

    [self.subscription dispose], self.subscription = nil;
}

這個方法在Cell被復用之前調(diào)用。如果現(xiàn)在運行我的應用,我們可以看到下面的結果:

disposing_subscription_works

太好了!我們可以通過滾動視圖來證實我們手動處理訂閱的有效性。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號