Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protocols proposal + dataSource sample implementation #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CodeChallenge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
8AF0EBB4206F7E6800AF374C /* NSDictionary+Networking.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AF0EBB3206F7E6800AF374C /* NSDictionary+Networking.m */; };
8AF0EBB9206F875300AF374C /* Avatar.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AF0EBB5206F875200AF374C /* Avatar.m */; };
8AF0EBBA206F875300AF374C /* _Avatar.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AF0EBB6206F875200AF374C /* _Avatar.m */; };
8AF0EBC4206F9DD500AF374C /* RepositoriesDataProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AF0EBC3206F9DD500AF374C /* RepositoriesDataProvider.m */; };
8B616C5B24EB85483A33F7B5 /* libPods-CodeChallenge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BE5C9355D5670BBC457EF045 /* libPods-CodeChallenge.a */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -60,6 +61,12 @@
8AF0EBB6206F875200AF374C /* _Avatar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _Avatar.m; sourceTree = "<group>"; };
8AF0EBB7206F875300AF374C /* Avatar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Avatar.h; sourceTree = "<group>"; };
8AF0EBB8206F875300AF374C /* _Avatar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _Avatar.h; sourceTree = "<group>"; };
8AF0EBBB206F94DE00AF374C /* NetworkManagerProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NetworkManagerProtocol.h; sourceTree = "<group>"; };
8AF0EBBD206F973600AF374C /* DataProviderProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataProviderProtocol.h; sourceTree = "<group>"; };
8AF0EBBE206F992400AF374C /* BusinessLogicControllerProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BusinessLogicControllerProtocol.h; sourceTree = "<group>"; };
8AF0EBC0206F99EF00AF374C /* PersistencyManagerProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersistencyManagerProtocol.h; sourceTree = "<group>"; };
8AF0EBC2206F9DD500AF374C /* RepositoriesDataProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RepositoriesDataProvider.h; sourceTree = "<group>"; };
8AF0EBC3206F9DD500AF374C /* RepositoriesDataProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RepositoriesDataProvider.m; sourceTree = "<group>"; };
A33A15A295127B1F83B1C5AA /* Pods-CodeChallenge.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CodeChallenge.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CodeChallenge/Pods-CodeChallenge.debug.xcconfig"; sourceTree = "<group>"; };
BA308E849395E49A61DB7D9A /* Pods-CodeChallenge.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CodeChallenge.release.xcconfig"; path = "Pods/Target Support Files/Pods-CodeChallenge/Pods-CodeChallenge.release.xcconfig"; sourceTree = "<group>"; };
BE5C9355D5670BBC457EF045 /* libPods-CodeChallenge.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CodeChallenge.a"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -98,6 +105,7 @@
6B5FAB5F206F883500371E40 /* Protocols */ = {
isa = PBXGroup;
children = (
8AF0EBC1206F9C0E00AF374C /* Proposal */,
6B5FAB5A206F7E2C00371E40 /* FeedProtocol.h */,
6B5FAB5E206F870D00371E40 /* FetchDelegate.h */,
);
Expand Down Expand Up @@ -173,6 +181,19 @@
path = Categories;
sourceTree = "<group>";
};
8AF0EBC1206F9C0E00AF374C /* Proposal */ = {
isa = PBXGroup;
children = (
8AF0EBBE206F992400AF374C /* BusinessLogicControllerProtocol.h */,
8AF0EBBD206F973600AF374C /* DataProviderProtocol.h */,
8AF0EBBB206F94DE00AF374C /* NetworkManagerProtocol.h */,
8AF0EBC0206F99EF00AF374C /* PersistencyManagerProtocol.h */,
8AF0EBC2206F9DD500AF374C /* RepositoriesDataProvider.h */,
8AF0EBC3206F9DD500AF374C /* RepositoriesDataProvider.m */,
);
path = Proposal;
sourceTree = "<group>";
};
8E9A3E0E896FD7902B4985B0 /* Pods */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -313,6 +334,7 @@
8AF0EBAC206F7C7300AF374C /* Commit.m in Sources */,
8AF0EBBA206F875300AF374C /* _Avatar.m in Sources */,
8AC26785206EC0550083E45A /* ViewController.m in Sources */,
8AF0EBC4206F9DD500AF374C /* RepositoriesDataProvider.m in Sources */,
8AC26790206EC0550083E45A /* main.m in Sources */,
8AF0EBAE206F7C7300AF374C /* _Commit.m in Sources */,
8AC26782206EC0550083E45A /* AppDelegate.m in Sources */,
Expand Down
20 changes: 20 additions & 0 deletions CodeChallenge/Protocols/Proposal/BusinessLogicControllerProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// BusinessLogicControllerProtocol.h
// CodeChallenge
//
// Created by alexey novikov on 31.03.2018.
//

#import <Foundation/Foundation.h>
#import "Repository.h"

// This is the top-level abstraction protocol
// Will be implemented by a business logic controller, which will be accessible from the view controllers.
// Implementation of such BL controller will incorporate the logic for making network requests (JSON+image data)
// and persistency.

@protocol BusinessLogicControllerProtocol <NSObject>
@optional
- (void)loadRepositoriesWithOffset:(NSUInteger)offset limit:(NSUInteger)limit;
- (void)loadCommitsForRepository:(Repository *)repository limit:(NSUInteger)limit;
@end
38 changes: 38 additions & 0 deletions CodeChallenge/Protocols/Proposal/DataProviderProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// DataProviderProtocol.h
// CodeChallenge
//
// Created by alexey novikov on 31.03.2018.
//

#import <Foundation/Foundation.h>

@protocol DataConsumer;


// for each of two of our UIViewControllers there will be two different objects implementing this protocol.
// UIViewControllers will have strong references to those objects, and provide a callback reference to themselves via
// the `dataConsumer` property.
// Each object that will implement this method will have an NSFetchedResultsController instance inside of it,
// and will be the delegate for that NSFetchedResultsController. These implementation details are never exposed,
// thus providing abstraction for the data layer.
@protocol DataProviderProtocol <NSObject>
@optional
- (id)cellModelAtRow:(NSUInteger)row;

@property (nonatomic, readonly) NSUInteger numberOfRows;

// our UIViewController. will be notified in two different cases:
// 1. when the incorporated NSFetchedResultsController fires controllerDidChangeContent: or
// 2. an image has been downloaded for a row.
@property (weak, nonatomic) id<DataConsumer> dataConsumer;
@end


// UIViewControllers will implement this and decide whether or not to reload the data for the entire table,
// or for just one row, in case of the image download
@protocol DataConsumer <NSObject>
- (void)dataProvider:(id<DataProviderProtocol>)dataProvider didChangeImageForRowAtIndex:(NSUInteger)index;
- (void)dataProviderDidChangeData:(id<DataProviderProtocol>)dataProvider
@end

34 changes: 34 additions & 0 deletions CodeChallenge/Protocols/Proposal/NetworkManagerProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// NetworkManagerProtocol.h
// CodeChallenge
//
// Created by alexey novikov on 31.03.2018.
//

#import <Foundation/Foundation.h>

// this protocol describes the low-level network interaction interface that is never exposed to view controllers,
// but only to the business logic controller.
// object implementing it will know how to create NSURLSessionTasks, will be aware of the base URL, and will form the request parameters.

typedef void(^DataSuccessBlock)(NSData *imageData)
typedef void(^SuccessBlock)(NSDictionary *payload);
typedef void(^FailureBlock)(NSError *error);

@protocol NetworkManagerProtocol <NSObject>
- (void)fetchRepositoriesWithOffset:(NSUInteger)offset
limit:(NSUInteger)limit
successBlock:(SuccessBlock)success
failureBlock:(FailureBlock)failure;

- (void)fetchCommitsForRepository:(NSString *)repository
limit:(NSUInteger)limit
successBlock:(SuccessBlock)success
failureBlock:(FailureBlock)failure;

- (void)fetchAvatarForAuthor:(NSString *)author
successBlock:(DataSuccessBlock)success
failureBlock:(FailureBlock)failure;

- (void)cancelAllRequests;
@end
19 changes: 19 additions & 0 deletions CodeChallenge/Protocols/Proposal/PersistencyManagerProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// PersistencyManagerProtocol.h
// CodeChallenge
//
// Created by alexey novikov on 31.03.2018.
//

#import <Foundation/Foundation.h>

// this interface describes the serializer.
// object implementing it will hide the details of how exactly the JSON is turned into model objects and persisted (=created and/or updated),
// be it CoreData or plain text files on the disk, thus providing abstraction on this layer.

@protocol PersistencyManagerProtocol <NSObject>
- (void)createRepositoryWithPayload:(NSDictionary *)payload;
- (void)createCommitWithPayload:(NSDictionary *)payload;
- (void)createAvatarWithData:(NSData *)data;
- (void)resetStorage;
@end
13 changes: 13 additions & 0 deletions CodeChallenge/Protocols/Proposal/RepositoriesDataProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// RepositoriesDataProvider.h
// CodeChallenge
//
// Created by alexey novikov on 31.03.2018.
//

#import <Foundation/Foundation.h>
#import "DataProviderProtocol.h"

@interface RepositoriesDataProvider : NSObject<DataProviderProtocol>

@end
81 changes: 81 additions & 0 deletions CodeChallenge/Protocols/Proposal/RepositoriesDataProvider.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// RepositoriesDataProvider.m
// CodeChallenge
//
// Created by alexey novikov on 31.03.2018.
//

@class RepositoriesCellModel;

#import <CoreData/CoreData.h>
#import <MagicalRecord/MagicalRecord.h>

#import "Repository.h"
#import "RepositoriesDataProvider.h"

@interface RepositoriesDataProvider ()<NSFetchedResultsControllerDelegate>
@property (strong, nonatomic) NSFetchedResultsController *fetchController;
@end

@implementation RepositoriesDataProvider

- (instancetype)init {
self = [super init];

if (self != nil) {

}

return self;
}

#pragma mark - DataProviderProtocol
- (NSUInteger)numberOfRows {
return self.fetchController.fetchedObjects.count;
}

- (id)cellModelAtRow:(NSUInteger)row {
if (row >= self.fetchController.fetchedObjects.count) {
return nil;
}

Repository *r = [self.fetchController.fetchedObjects objectAtIndex:row];

RepositoriesCellModel *cellModel = [[RepositoriesCellModel alloc] initWithRepository:r];
return cellModel;
}

#pragma mark - NSFRC delegate methods
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self notifyDataConsumerAboutDataChange];
}

#pragma mark - private subroutines
- (void)notifyDataConsumerAboutDataChange {
if (self.dataConsumer != nil && [self.dataConsumer respondsToSelector:@selector(dataProviderDidChangeData:)]) {
[self.dataConsumer dataProviderDidChangeData:self];
}
}

- (void)notifyDataConsumerAboutImageDownload {

}

#pragma mark - init subroutines
- (void)initFetchedResultsController {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@""];

self.fetchController = [Repository MR_fetchAllSortedBy:@"rank"
ascending:YES
withPredicate:predicate
groupBy:nil
delegate:self];
NSError *error;
[self.fetchController performFetch:&error];

if (error != nil) {
DLog(@"Atata, error: %@", error.localizedDescription);
}
}

@end