diff --git a/.travis.sh b/.travis.sh index c4dc7ad..27f1470 100755 --- a/.travis.sh +++ b/.travis.sh @@ -1,13 +1,35 @@ -#! /usr/bin/env sh +#! /bin/zsh -set -eo pipefail -# set -euxo pipefail +set -euo pipefail -echo $TRAVIS_COMMIT_MESSAGE -echo "RFCI_TASK = $RFCI_TASK" -readonly RFWorkspace="RFAPI.xcworkspace" -readonly RFSTAGE="$1" -echo "RFSTAGE = $RFSTAGE" +logInfo () { + echo "\033[32m$1\033[0m" >&2 +} + +logWarning () { + echo "\033[33m$1\033[0m" >&2 +} + +logError () { + echo "\033[31m$1\033[0m" >&2 +} + +# Make sure all parameters are set correctly. +logInfo "RFCI_PRODUCT_NAME = $RFCI_PRODUCT_NAME" + +readonly RFCI_TASK="${RFCI_TASK:? is not set.}" +logInfo "RFCI_TASK = $RFCI_TASK" + +readonly RFCI_STAGE="${1:?STAGE is not set.}" +logInfo "RFCI_STAGE = $RFCI_STAGE" + +readonly RFWorkspace=${RFWorkspace:="$RFCI_PRODUCT_NAME.xcworkspace"} +logInfo "RFWorkspace = $RFWorkspace" + +TRAVIS_COMMIT_MESSAGE=${TRAVIS_COMMIT_MESSAGE:="$(logWarning 'TRAVIS_COMMIT_MESSAGE is not set, leave it blank.')"} +TRAVIS_BRANCH=${TRAVIS_BRANCH:="$(logWarning 'TRAVIS_BRANCH is not set, leave it blank.')"} + +echo "" # Run test # $1 scheme @@ -23,32 +45,60 @@ XC_TestMac() { # Run watchOS test XC_TestWatch() { - xcodebuild build -workspace "$RFWorkspace" -scheme Target-watchOS ONLY_ACTIVE_ARCH=NO | xcpretty + xcodebuild build -workspace "$RFWorkspace" -scheme "Target-watchOS" ONLY_ACTIVE_ARCH=NO | xcpretty +} + +# Run tests on iOS Simulator. +# The destinations are the first and last available destination that are automatically detected. +# $1 scheme +XC_TestAutoIOS() { + logInfo "Detecting destinations..." + destList=$(xcodebuild -showdestinations -workspace "$RFWorkspace" -scheme "$1" | grep "iOS Simulator") + destCount=$(echo "$destList" | wc -l) + destFirst=$(echo "$destList" | head -1) + destLast=$(echo "$destList" | tail -2 | head -1) + destFirstID=$(echo "$destFirst" | awk 'match($0,/id\:[0-9A-F-]+/){ print substr($0,RSTART+3,RLENGTH-3) }') + destLastID=$(echo "$destLast" | awk 'match($0,/id\:[0-9A-F-]+/){ print substr($0,RSTART+3,RLENGTH-3) }') + + logWarning "Test on simulator (id: $destFirstID)." + XC_Test "$1" "platform=iOS Simulator,id=$destFirstID" + + logWarning "Test on simulator (id: $destLastID)." + XC_Test "$1" "platform=iOS Simulator,id=$destLastID" +} + +STAGE_SETUP() { + gem install cocoapods --no-document } STAGE_MAIN() { if [ "$RFCI_TASK" = "POD_LINT" ]; then if [[ "$TRAVIS_COMMIT_MESSAGE" = *"[skip lint]"* ]]; then - echo "Skip pod lint" + logWarning "Skip pod lint" else - echo "TRAVIS_BRANCH = $TRAVIS_BRANCH" - gem install cocoapods --no-rdoc --no-ri --no-document --quiet - # Always allow warnings as third-party dependencies generate unavoidable warnings. - pod lib lint --allow-warnings + if [[ "$TRAVIS_BRANCH" =~ ^[0-9]+\.[0-9]+ ]]; then + logWarning "Release the podspec." + pod trunk push "$RFCI_PRODUCT_NAME.podspec" + elif [ "$TRAVIS_BRANCH" = "master" ]; then + logInfo "Lint the podspec." + pod lib lint --fail-fast + else + logInfo "Lint the podspec." + pod lib lint --fail-fast --allow-warnings + fi fi - elif [ "$RFCI_TASK" = "Xcode9" ]; then + elif [ "$RFCI_TASK" = "Xcode10" ]; then pod install XC_TestMac - XC_Test "Example-iOS" "platform=iOS Simulator,name=iPhone 6,OS=11.2" - # XC_Test "Example-iOS" "platform=iOS Simulator,name=X1,OS=11.3" + XC_TestAutoIOS "Test-iOS" else - echo "Unexpected CI task: $RFCI_TASK" + logError "Unexpected CI task: $RFCI_TASK" fi } STAGE_SUCCESS() { - if [ "$RFCI_TASK" = "Xcode9" ]; then + if [ "${RFCI_COVERAGE-}" = "1" ]; then curl -s https://codecov.io/bash | bash -s fi } @@ -59,4 +109,4 @@ STAGE_FAILURE() { fi } -"STAGE_$RFSTAGE" +"STAGE_$RFCI_STAGE" diff --git a/.travis.yml b/.travis.yml index c01f48c..919f999 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,14 +6,17 @@ env: - LC_CTYPE=en_US.UTF-8 - LANG=en_US.UTF-8 - LANGUAGE=en_US.UTF-8 + - RFCI_PRODUCT_NAME="RFAPI" + - RFWorkspace="RFAPI.xcworkspace" matrix: include: - - osx_image: xcode9.3 + - osx_image: xcode11.3 env: RFCI_TASK="POD_LINT" - - osx_image: xcode9.3 - env: RFCI_TASK="Xcode9" -before_install: - - pod repo update master --silent -script: ./.travis.sh MAIN -after_success: ./.travis.sh SUCCESS -after_failure: ./.travis.sh FAILURE + - osx_image: xcode10 + env: + - RFCI_TASK="Xcode10" + - RFCI_COVERAGE=1 +before_install: ./.travis.sh SETUP +script: ./.travis.sh MAIN +after_success: ./.travis.sh SUCCESS +after_failure: ./.travis.sh FAILURE diff --git a/Documents/design.md b/Documents/design.md new file mode 100644 index 0000000..cb3dd22 --- /dev/null +++ b/Documents/design.md @@ -0,0 +1,4 @@ +# RFAPI 背后的设计 + +好的设计应该是不会过时的 + diff --git a/Documents/migration_guide_v2.md b/Documents/migration_guide_v2.md new file mode 100644 index 0000000..8a6e90f --- /dev/null +++ b/Documents/migration_guide_v2.md @@ -0,0 +1,48 @@ +# RFAPI v2 升级指南 + +> *Because there should be no non-Chinese developers using this library before, this guide is not available in English at this time.* + +v1 到 v2 几乎全部重写,内部变化很大,但是实际项目需要调整的地方应该不多。 + +如果只是用到请求发送、取消这样的基本功能,甚至无需修改。 + +## 主要类型变化 + +`RFAPI` 的父类由 `NSOperationQueue` 变为 `NSObject`。如果之前用到了 NSOperationQueue 的方法,只能都移除了。`maxConcurrentOperationCount` 可以改用 NSURLSessionConfiguration 的 `HTTPMaximumConnectionsPerHost` 属性设置。 + +请求方法返回的请求对象类型从 `AFHTTPRequestOperation` 变为 `RFAPITask`,暴露的属性有减少。 + +`RFAPIControl` 被移除,取而代之的类是 `RFAPIRequestConext`,必须修改的地方并不多: + +* `message` 属性更名为 `activityMessage`; +* 移除的属性都没用到,`RFAPIRequestConext` 新增的属性也无需修改; +* 不再支持从字典创建,这个正常用得极少。 + +缓存管理移除了,但这个系统只在 iOS 7 之前工作,正常的项目应该影响不到。 + +## Define 和 DefineManager + +`RFAPIDefine` 的 `responseClass` 类型由 class 改为 string,其他在外部看来没有变化。 + +`RFAPIDefineManager` 现在直接使用 define 对象,不再使用字典作为存储。`defaultRule` 更名为 `defaultDefine`,修改立即生效,无需再手动调用 `setNeedsUpdateDefaultRule` 方法。之前对 define 字段进行修改的方法被移除,直接对 define 对象进行修改即可。`setDefinesWithRulesInfo:` 现在支持分组。 + +在 DEBUG 环境(准确的说是 RFDEBUG 为真且 NSAssert 启用)下编译的 RFAPI,在 define 处理时会进行一些额外检查,帮助你正确使用,Release 环境这些检查不会执行。 + +## 请求创建 + +v1 请求有两个方法,正常请求和表单上传请求,正常请求有兼容实现,可无需修改(但是推荐改成新的方法)。 + +表单上传只能用新的请求方法,不再需要 `RFHTTPRequestFormData`,直接设置 request context 的 formData 即可。 + +## responseProcessingQueue + +变为 `processingQueue`,默认的队列由主线程队列变为私有的并行队列。 + +## Swift + +如果之前在 Swift 中子类了 `RFAPI`,可能需要调整方法名,需要重写的方法有了更合适的命名。 + +## 国际化 + +// todo + diff --git a/Example/Shared/Example-iOS.entitlements b/Example/Shared/Example-iOS.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/Example/Shared/Example-iOS.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Example/iOS-Swift/RFDTestEntity.h b/Example/Shared/Models/RFDTestEntity.h similarity index 59% rename from Example/iOS-Swift/RFDTestEntity.h rename to Example/Shared/Models/RFDTestEntity.h index 0a6a797..b814d39 100644 --- a/Example/iOS-Swift/RFDTestEntity.h +++ b/Example/Shared/Models/RFDTestEntity.h @@ -9,9 +9,6 @@ #import "JSONModel.h" @interface RFDTestEntity : JSONModel -@property (assign, nonatomic) int uid; -@property (strong, nonatomic) NSString *name; -@end - -@protocol RFDTestEntity +@property int uid; +@property NSString *name; @end diff --git a/Example/iOS-Swift/RFDTestEntity.m b/Example/Shared/Models/RFDTestEntity.m similarity index 91% rename from Example/iOS-Swift/RFDTestEntity.m rename to Example/Shared/Models/RFDTestEntity.m index be956ff..d885443 100644 --- a/Example/iOS-Swift/RFDTestEntity.m +++ b/Example/Shared/Models/RFDTestEntity.m @@ -1,6 +1,6 @@ #import "RFDTestEntity.h" -#import "RFRuntime.h" +#import @implementation RFDTestEntity diff --git a/Example/Shared/OCBridging-Header.h b/Example/Shared/OCBridging-Header.h index 153789b..bbc799d 100644 --- a/Example/Shared/OCBridging-Header.h +++ b/Example/Shared/OCBridging-Header.h @@ -2,5 +2,9 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // +#import +#import #import +#import +#import #import diff --git a/Example/iOS-Swift/AppDelegate.swift b/Example/iOS-Swift/AppDelegate.swift index d51bfc0..39d0a62 100644 --- a/Example/iOS-Swift/AppDelegate.swift +++ b/Example/iOS-Swift/AppDelegate.swift @@ -10,7 +10,8 @@ import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { return true } } diff --git a/Example/iOS-Swift/Main.storyboard b/Example/iOS-Swift/Main.storyboard index ee540db..bc244a6 100644 --- a/Example/iOS-Swift/Main.storyboard +++ b/Example/iOS-Swift/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -14,7 +12,7 @@ - + @@ -23,12 +21,12 @@ - + - + @@ -38,14 +36,14 @@ - + - + - - + + @@ -54,14 +52,13 @@ - + @@ -74,16 +71,16 @@ - + - - + + - + @@ -114,7 +111,7 @@ - + @@ -125,13 +122,13 @@ - - + + - + diff --git a/Example/iOS-Swift/RFDAPITestViewController.h b/Example/iOS-Swift/RFDAPITestViewController.h deleted file mode 100644 index 4dba7fe..0000000 --- a/Example/iOS-Swift/RFDAPITestViewController.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// RFDAPITestViewController.h -// RFDemo -// -// Created by BB9z on 3/29/16. -// Copyright © 2016 RFUI. All rights reserved. -// - -#import - -@interface RFDAPITestViewController : UIViewController < - UITableViewDelegate, - UITableViewDataSource -> -@property (nonatomic, nullable, weak) IBOutlet UITableView *operationList; -@property (nonatomic, nullable, weak) IBOutlet UITextView *responseTextView; -@end - - -@interface RFDAPITestRequestObject : NSObject -@property (nonatomic, nonnull, strong) NSString *title; -@property (nonatomic, nonnull, strong) NSString *APIName; -@property (nonatomic, nullable, strong) NSString *message; -@property (nonatomic) BOOL modal; -@end diff --git a/Example/iOS-Swift/RFDAPITestViewController.m b/Example/iOS-Swift/RFDAPITestViewController.m deleted file mode 100644 index f55bdd2..0000000 --- a/Example/iOS-Swift/RFDAPITestViewController.m +++ /dev/null @@ -1,78 +0,0 @@ - -#import "RFDAPITestViewController.h" -#import "RFDTestAPI.h" - -@interface RFDAPITestViewController () -@property (nonatomic, strong) NSArray *items; -@end - -@implementation RFDAPITestViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - RFDAPITestRequestObject *r1 = [RFDAPITestRequestObject new]; - r1.title = @"Null"; - r1.APIName = @"NullTest"; - r1.message = @"Request: Null"; - - RFDAPITestRequestObject *r2 = [RFDAPITestRequestObject new]; - r2.title = @"An object"; - r2.APIName = @"ObjSample"; - r2.message = @""; - - RFDAPITestRequestObject *r3 = [RFDAPITestRequestObject new]; - r3.title = @"Objects"; - r3.APIName = @"ObjArraySample"; - r3.message = @"Loadding..."; - r3.modal = YES; - - RFDAPITestRequestObject *r4 = [RFDAPITestRequestObject new]; - r4.title = @"Empty object"; - r4.APIName = @"ObjEmpty"; - // r4 no progress - - RFDAPITestRequestObject *r5 = [RFDAPITestRequestObject new]; - r5.title = @"Fail request"; - r5.APIName = @"NotFound"; - - self.items = @[ r1, r2, r3, r4, r5 ]; -} - -#pragma mark - List - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return self.items.count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - RFDAPITestRequestObject *requestDefine = self.items[indexPath.row]; - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; - cell.textLabel.text = requestDefine.title; - return cell; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - RFDAPITestRequestObject *requestDefine = self.items[indexPath.row]; - @weakify(self); - [RFDTestAPI requestWithName:requestDefine.APIName parameters:nil viewController:self forceLoad:NO loadingMessage:requestDefine.message modal:requestDefine.modal success:^(AFHTTPRequestOperation *operation, id responseObject) { - @strongify(self); - [self displayResponse:responseObject error:nil]; - } failure:nil completion:nil]; -} - -- (void)displayResponse:(id)responseObject error:(NSError *)error { - if (error) { - self.responseTextView.text = [NSString stringWithFormat:@"%@", error]; - self.responseTextView.textColor = [UIColor redColor]; - return; - } - self.responseTextView.text = [NSString stringWithFormat:@"%@", responseObject]; - self.responseTextView.textColor = [UIColor darkTextColor]; -} - -@end - - -@implementation RFDAPITestRequestObject -@end diff --git a/Example/iOS-Swift/RFDTestAPI.h b/Example/iOS-Swift/RFDTestAPI.h deleted file mode 100644 index 22c6ec2..0000000 --- a/Example/iOS-Swift/RFDTestAPI.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// RFDTestAPI.h -// RFDemo -// -// Created by BB9z on 3/29/16. -// Copyright © 2016 RFUI. All rights reserved. -// - -#import "RFAPI.h" - -@interface RFDTestAPI : RFAPI - -+ (instancetype)sharedInstance; - -+ (AFHTTPRequestOperation *)requestWithName:(NSString *)APIName - parameters:(NSDictionary *)parameters - viewController:(UIViewController *)viewController - forceLoad:(BOOL)forceLoad - loadingMessage:(NSString *)message - modal:(BOOL)modal - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure - completion:(void (^)(AFHTTPRequestOperation *operation))completion; -@end - - -@interface UIViewController (APIControl) - -/** - 完善请求取消的控制,解决 view controller 嵌套不能正确取消子控制器中的请求 - 子控制器应该返回父控制器的 APIGroupIdentifier,返回 nil 时使用 receiver 的 class name - */ -@property (nonatomic, copy) NSString *APIGroupIdentifier; - -@end - - diff --git a/Example/iOS-Swift/RFDTestAPI.m b/Example/iOS-Swift/RFDTestAPI.m deleted file mode 100644 index b2b6467..0000000 --- a/Example/iOS-Swift/RFDTestAPI.m +++ /dev/null @@ -1,62 +0,0 @@ - -#import "RFDTestAPI.h" -#import "AFURLResponseSerialization.h" -#import -#import - -@interface RFDTestAPI () -@end - -@implementation RFDTestAPI - -+ (instancetype)sharedInstance { - static id sharedInstance = nil; - static dispatch_once_t oncePredicate; - dispatch_once(&oncePredicate, ^{ - sharedInstance = [self.alloc init]; - }); - return sharedInstance; -} - -- (void)onInit { - [super onInit]; - - NSString *configPath = [[NSBundle mainBundle] pathForResource:@"TestAPIDefine" ofType:@"plist"]; - NSDictionary *rules = [[NSDictionary alloc] initWithContentsOfFile:configPath]; - RFAPIDefineManager *dm = self.defineManager; - [dm setDefinesWithRulesInfo:rules]; - dm.defaultResponseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments]; - self.networkActivityIndicatorManager = [RFSVProgressMessageManager new]; -} - -+ (AFHTTPRequestOperation *)requestWithName:(NSString *)APIName parameters:(NSDictionary *)parameters viewController:(UIViewController *)viewController forceLoad:(BOOL)forceLoad loadingMessage:(NSString *)message modal:(BOOL)modal success:(void (^)(AFHTTPRequestOperation *, id))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure completion:(void (^)(AFHTTPRequestOperation *))completion { - RFAPIControl *cn = [[RFAPIControl alloc] init]; - if (message) { - cn.message = [[RFNetworkActivityMessage alloc] initWithIdentifier:APIName message:message status:RFNetworkActivityStatusLoading]; - cn.message.modal = modal; - } - cn.identifier = APIName; - cn.groupIdentifier = viewController.APIGroupIdentifier; - cn.forceLoad = forceLoad; - return [self.sharedInstance requestWithName:APIName parameters:parameters controlInfo:cn success:success failure:failure completion:completion]; -} - -@end - -#import - -static char UIViewController_APIControl_CateogryProperty; - -@implementation UIViewController (APIControl) - -- (NSString *)APIGroupIdentifier { - id value = objc_getAssociatedObject(self, &UIViewController_APIControl_CateogryProperty); - if (value) return value; - return NSStringFromClass(self.class); -} - -- (void)setAPIGroupIdentifier:(NSString *)APIGroupIdentifier { - objc_setAssociatedObject(self, &UIViewController_APIControl_CateogryProperty, APIGroupIdentifier, OBJC_ASSOCIATION_COPY_NONATOMIC); -} - -@end diff --git a/Example/iOS-Swift/TestAPI.swift b/Example/iOS-Swift/TestAPI.swift new file mode 100644 index 0000000..f662d79 --- /dev/null +++ b/Example/iOS-Swift/TestAPI.swift @@ -0,0 +1,40 @@ +// +// TestAPI.swift +// Example-iOS +// +// Created by BB9z on 2020/1/3. +// Copyright © 2020 RFUI. All rights reserved. +// + +class TestAPI: RFAPI { + override init() { + super.init() + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 5 + sessionConfiguration = config + guard let configPath = Bundle.main.path(forResource: "TestAPIDefine", ofType: "plist"), + let rules = NSDictionary(contentsOfFile: configPath) as? [String : [String : Any]] else { + fatalError() + } + defineManager.setDefinesWithRulesInfo(rules) + defineManager.defaultResponseSerializer = AFJSONResponseSerializer(readingOptions: .allowFragments) + networkActivityIndicatorManager = RFSVProgressMessageManager() + modelTransformer = RFAPIJSONModelTransformer() + } + + override func preprocessingRequest(parametersRef: UnsafeMutablePointer, httpHeadersRef: UnsafeMutablePointer, parameters: [AnyHashable : Any]?, define: RFAPIDefine, context: RFAPIRequestConext) { + super.preprocessingRequest(parametersRef: parametersRef, httpHeadersRef: httpHeadersRef, parameters: parameters, define: define, context: context) + } + + override func finalizeSerializedRequest(_ request: NSMutableURLRequest, define: RFAPIDefine, context: RFAPIRequestConext) -> NSMutableURLRequest { + return request + } + + override func generalHandlerForError(_ error: Error, define: RFAPIDefine, task: RFAPITask, failure: RFAPIRequestFailureCallback? = nil) -> Bool { + return true + } + + override func isSuccessResponse(_ responseObjectRef: UnsafeMutablePointer, error: NSErrorPointer) -> Bool { + return super.isSuccessResponse(responseObjectRef, error: error) + } +} diff --git a/Example/iOS-Swift/TestAPIDefine.plist b/Example/iOS-Swift/TestAPIDefine.plist index 39d25c4..79b6bb9 100644 --- a/Example/iOS-Swift/TestAPIDefine.plist +++ b/Example/iOS-Swift/TestAPIDefine.plist @@ -64,5 +64,37 @@ Response Type 0 + @ HTTPBin + + Status + + Path + https://httpbin.org/status/{code} + + Anything + + Path + https://httpbin.org/anything/{path} + Method + POST + + Upload + + Path + https://httpbin.org/anything + Method + POST + + 404 + + Path + https://httpbin.org/status/404 + + Timeout + + Path + https://httpbin.org/delay/10 + + diff --git a/Example/iOS-Swift/TestViewController.swift b/Example/iOS-Swift/TestViewController.swift new file mode 100644 index 0000000..60adf67 --- /dev/null +++ b/Example/iOS-Swift/TestViewController.swift @@ -0,0 +1,178 @@ +// +// TestViewController.swift +// Example-iOS +// +// Created by BB9z on 2020/1/3. +// Copyright © 2020 RFUI. All rights reserved. +// + +import UIKit + +class TestRequestObject { + var title = "" + var APIName = "" + var message: String? + var modal = false +} + +class TestViewController: UIViewController, + UITableViewDelegate, + UITableViewDataSource +{ + @IBOutlet weak var operationList: UITableView! + @IBOutlet weak var responseTextView: UITextView! + + override func viewDidLoad() { + super.viewDidLoad() + makeListItems() + } + + struct ListSection { + var title = "" + var objects = [TestRequestObject]() + } + var items = [ListSection]() + var uploadRequest: TestRequestObject? + func makeListItems() { + let r1 = TestRequestObject() + r1.title = "Null" + r1.APIName = "NullTest" + r1.message = "Request: Null" + + let r2 = TestRequestObject() + r2.title = "An object" + r2.APIName = "ObjSample" + r2.message = "" + + let r3 = TestRequestObject() + r3.title = "Objects" + r3.APIName = "ObjArraySample" + r3.message = "Loadding..." + r3.modal = true + + let r4 = TestRequestObject() + r4.title = "Empty object" + r4.APIName = "ObjEmpty" + // r4 no progress + + let r5 = TestRequestObject() + r5.title = "Fail request" + r5.APIName = "NotFound" + + let r6 = TestRequestObject() + r6.title = "big_json" + r6.APIName = "local" + + let r7 = TestRequestObject() + r7.title = "Time out" + r7.APIName = "Timeout" + r7.message = "Waiting..." + + let r8 = TestRequestObject() + r8.title = "Upload" + r8.APIName = "Upload" + r8.message = "Uploading..." + uploadRequest = r8 + + items = [ + ListSection(title: "Sample Request", objects: [r1, r2, r3, r4, r5]), + ListSection(title: "Local Files", objects: [r6]), + ListSection(title: "HTTPBin", objects: [r7, r8]), + ] + } + + lazy var API = TestAPI() + weak var lastTask: RFAPITask? + + func numberOfSections(in tableView: UITableView) -> Int { + return items.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return items[section].objects.count + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return items[section].title + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let item = items[indexPath.section].objects[indexPath.row] + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + cell.textLabel?.text = item.title + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let task = lastTask { + task.cancel() + lastTask = nil + } + let request = items[indexPath.section].objects[indexPath.row] + if indexPath.section == 1 { + let define = RFAPIDefine() + define.path = Bundle.main.url(forResource: request.title, withExtension: "data")?.absoluteString + define.name = RFAPIName(rawValue: request.APIName) + lastTask = API.request(define: define) { c in + c.success { [weak self] _, responseObject in + self?.display(response: responseObject) + } + c.failure { [weak self] _, error in + self?.display(error: error) + } + } + } + else { + lastTask = API.request(name: request.APIName) { c in + c.loadMessage = request.message + c.loadMessageShownModal = request.modal + c.success { [weak self] _, responseObject in + self?.display(response: responseObject) + } + c.failure { [weak self] _, error in + self?.display(error: error) + } + if request.APIName == "Timeout" { + c.timeoutInterval = 1 + } + if request === uploadRequest { + c.timeoutInterval = 60 + c.formData = { data in + try! data.appendPart(withFileURL: Bundle.main.executableURL!, name: "eXe") + data.throttleBandwidth(withPacketSize: 3000, delay: 0.1) + } + c.uploadProgress = { [weak self] task, progress in + guard let sf = self else { return } + DispatchQueue.main.async { + sf.display(response: String(format: "Uploading %.1f%%", progress.fractionCompleted * 100)) + } + } + c.downloadProgress = { [weak self] task, progress in + guard let sf = self else { return } + DispatchQueue.main.async { + sf.display(response: String(format: "Downloaing %.1f%%", progress.fractionCompleted * 100)) + } + } + } + } + } + } + + func display(response: Any?) { + if let rsp = response { + responseTextView.text = (rsp as AnyObject).debugDescription + } + else { + responseTextView.text = "" + } + if #available(iOS 13.0, *) { + responseTextView.textColor = .label + } else { + responseTextView.textColor = .darkText + } + } + func display(error: Error) { + responseTextView.text = (error as NSError).debugDescription + responseTextView.textColor = .red + } +} diff --git a/LICENSE b/LICENSE index e8d25b6..a50ceab 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2014-2016, 2018 RFUI. +Copyright (c) 2014-2016, 2018-2020 RFUI. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Podfile b/Podfile index 51bd6b6..e8f9468 100644 --- a/Podfile +++ b/Podfile @@ -1,14 +1,22 @@ +source 'https://github.com/PBPods/PBFlex.git' +source 'https://cdn.cocoapods.org/' + target 'Example-iOS' do platform :ios, '8.0' - pod 'RFMessageManager', :subspecs => ['SVProgressHUD'], :git => 'https://github.com/RFUI/RFMessageManager.git', :branch => 'develop' + + pod 'RFMessageManager', :subspecs => ['SVProgressHUD'] pod 'RFAPI', :path => '.' +# pod 'PBFlex', :configurations => ['Debug'] +end + +target 'Test-iOS' do + platform :ios, '12.0' - target 'Test-iOS' do - end + pod 'RFAPI', :path => '.' end target 'Test-macOS' do - platform :osx, '10.9' + platform :osx, '10.13' pod 'RFAPI', :path => '.' end diff --git a/README.md b/README.md index 009e297..0f9df6b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@ # RFAPI -[![Build Status](https://img.shields.io/travis/RFUI/RFAPI.svg?style=flat-square&colorA=333333&colorB=6600cc)](https://travis-ci.org/RFUI/RFAPI) +[![Build Status](https://img.shields.io/travis/RFUI/RFAPI.svg?style=flat-square&colorA=333333&colorB=6600cc)](https://travis-ci.com/RFUI/RFAPI) [![Codecov](https://img.shields.io/codecov/c/github/RFUI/RFAPI.svg?style=flat-square&colorA=333333&colorB=6600cc)](https://codecov.io/gh/RFUI/RFAPI) [![CocoaPods](https://img.shields.io/cocoapods/v/RFAPI.svg?style=flat-square&colorA=333333&colorB=6600cc)](https://cocoapods.org/pods/RFAPI) -v1 requires AFNetworking v2 and leaves here for legacy. RFAPI v2 will support AFNetworking v3. + + +RFAPI is a full-featured URL session wrapper designed for API requests. It's easy to use and powerfull. + +[RFAPI v2 Migration Guide](Documents/migration_guide_v2.md). The `v1` branch contains RFAPI v1 for legacy use, which requires AFNetworking v2. ## CocoaPods Install ```ruby + pod 'RFAPI' ``` diff --git a/RFAPI.h b/RFAPI.h deleted file mode 100644 index 916aac0..0000000 --- a/RFAPI.h +++ /dev/null @@ -1,225 +0,0 @@ -/*! - RFAPI - - Copyright (c) 2014-2016, 2018 BB9z - https://github.com/RFUI/RFAPI - - The MIT License (MIT) - http://www.opensource.org/licenses/mit-license.php - */ -#import -#import "RFAPIDefine.h" -#import "RFAPIDefineManager.h" -#import "RFAPICacheManager.h" - -@class AFNetworkReachabilityManager; -@class AFSecurityPolicy; -@protocol AFMultipartFormData; - -@class RFMessageManager, RFNetworkActivityMessage, AFHTTPRequestOperation; -@class RFAPIControl, RFHTTPRequestFormData; - - -@interface RFAPI : NSOperationQueue < - RFInitializing -> - -/** - Defult shared manager - */ -@property (nonnull, readonly) AFNetworkReachabilityManager *reachabilityManager; - -/** - @bug RFAPI cache management only works on iOS 7. - */ -@property (nonnull, readonly) RFAPICacheManager *cacheManager; - -#pragma mark - Define - -@property (nonnull, readonly) RFAPIDefineManager *defineManager; - -#pragma mark - Request management - -- (nonnull NSArray *)operationsWithIdentifier:(nullable NSString *)identifier; -- (nonnull NSArray *)operationsWithGroupIdentifier:(nullable NSString *)identifier; - -- (void)cancelOperationWithIdentifier:(nullable NSString *)identifier; -- (void)cancelOperationsWithGroupIdentifier:(nullable NSString *)identifier; - -#pragma mark - Activity Indicator - -@property (nullable, strong) RFMessageManager *networkActivityIndicatorManager; - -#pragma mark - Request - -// 如果传一个特殊请求,直接创建一个 AFHTTPRequestOperation 并加进来也许更合适 - -/** - Creat and send a HTTP request. - - @discussion 当请求取消时,success 和 failure 都不会被调用,只有 completion 会被调用。请求从缓存读取时,几个 block 回调中的 operation 参数会为空。 - - @param APIName 接口名 - @param parameters 请求的参数 - @param controlInfo 控制接口行为的结构体 - @param success 请求成功回调的 block,可为空 - @param failure 请求失败回调的 block,可为空。为空时将用默认的方法显示错误信息 - @param completion 请求完成回掉的 block,必定会被调用(即使请求创建失败),会在 success 和 failure 回调后执行。被设计用来执行通用的清理。可为空。 - */ -- (nullable AFHTTPRequestOperation *)requestWithName:(nonnull NSString *)APIName - parameters:(nullable NSDictionary *)parameters - controlInfo:(nullable RFAPIControl *)controlInfo - success:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation, id _Nullable responseObject))success - failure:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation, NSError *_Nonnull error))failure - completion:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation))completion; - -/** - 上传文件 - - @param arrayContainsFormDataObj 包含 RFHTTPRequestFormData 对象的数组 - */ -- (nullable AFHTTPRequestOperation *)requestWithName:(nonnull NSString *)APIName - parameters:(nullable NSDictionary *)parameters - formData:(nullable NSArray *)arrayContainsFormDataObj - controlInfo:(nullable RFAPIControl *)controlInfo - uploadProgress:(void (^_Nullable)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress - success:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation, id _Nullable responseObject))success - failure:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation, NSError *_Nonnull error))failure - completion:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation))completion; - -/** - Creat a mutable URLRequest with special info. - */ -- (nullable NSMutableURLRequest *)URLRequestWithDefine:(nonnull RFAPIDefine *)define parameters:(nullable NSDictionary *)parameters formData:(nullable NSArray *)RFFormData controlInfo:(nullable RFAPIControl *)controlInfo error:(NSError *_Nullable __autoreleasing *_Nullable)error; - -#pragma mark - Response - -/** - If `NULL` (default), the main queue will be used. - */ -@property (nonatomic, null_resettable, strong) dispatch_queue_t responseProcessingQueue; - -- (void)invalidateCacheWithName:(nullable NSString *)APIName parameters:(nullable NSDictionary *)parameters; - -#pragma mark - Methods for overwrite - -/** - Default implementation first add parameters from APIDefine then add parameters from define manager. - */ -- (void)preprocessingRequestParameters:(NSMutableDictionary *_Nullable *_Nonnull)requestParameters HTTPHeaders:(NSMutableDictionary *_Nullable *_Nonnull)requestHeaders withParameters:(nullable NSDictionary *)parameters define:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo; - -/** - Default implementation execute RFAPIControl’s requestCustomization. - */ -- (nullable NSMutableURLRequest *)finalizeSerializedRequest:(nonnull NSMutableURLRequest *)request withDefine:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo; - -/** - 默认实现返回 YES - - This method is called on the main queue. - - @return 返回 YES 将继续错误的处理继续交由请求的回调处理,NO 处理结束 - */ -- (BOOL)generalHandlerForError:(nonnull NSError *)error withDefine:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo requestOperation:(nullable AFHTTPRequestOperation *)operation operationFailureCallback:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable, NSError *_Nonnull))operationFailureCallback; - -/** - 判断响应是否是成功的结果 - - Default implementation just return YES. - - This method is called on responseProcessingQueue. - - @param responseObjectRef 可以用来修改返回值 - @param error 可选的错误信息 - */ -- (BOOL)isSuccessResponse:(id _Nullable __strong *_Nonnull)responseObjectRef error:(NSError *_Nullable __autoreleasing *_Nullable)error; - -#pragma mark - Credentials & Security - -/** - Whether request operations should consult the credential storage for authenticating the connection. `YES` by default. - - @see AFURLConnectionOperation -shouldUseCredentialStorage - */ -@property BOOL shouldUseCredentialStorage; - -/** - The credential used by request operations for authentication challenges. - - @see AFURLConnectionOperation -credential - */ -@property (nullable, strong) NSURLCredential *credential; - -/** - The security policy used by created request operations to evaluate server trust for secure connections. `RFAPI` uses the `defaultPolicy` unless otherwise specified. - */ -@property (nonnull, strong) AFSecurityPolicy *securityPolicy; - -@end - -extern NSString *_Nonnull const RFAPIRequestArrayParameterKey; -extern NSString *_Nonnull const RFAPIRequestForceQuryStringParametersKey; -extern NSString *_Nonnull const RFAPIErrorDomain; - -extern NSString *_Nonnull const RFAPIMessageControlKey; -extern NSString *_Nonnull const RFAPIIdentifierControlKey; -extern NSString *_Nonnull const RFAPIGroupIdentifierControlKey; -extern NSString *_Nonnull const RFAPIBackgroundTaskControlKey; -extern NSString *_Nonnull const RFAPIRequestCustomizationControlKey; - -@interface RFAPIControl : NSObject -/** Activity message. - 请求开始前,自动进入消息显示队列。结束时自动从队列中清除。 -*/ -@property (nullable, strong) RFNetworkActivityMessage *message; - -/// Identifier for request. -@property (nullable, copy) NSString *identifier; - -/// Group identifier for request. -@property (nullable, copy) NSString *groupIdentifier; - -// No implementation -@property BOOL backgroundTask; - -/// Ignore cache policy, force current request load from server. -@property BOOL forceLoad; - -/// Customization URL request object -@property (nullable, copy) NSMutableURLRequest *_Nullable (^requestCustomization)(NSMutableURLRequest *_Nonnull request); - -- (nonnull id)initWithDictionary:(nonnull NSDictionary *)info; -- (nonnull id)initWithIdentifier:(nonnull NSString *)identifier loadingMessage:(nullable NSString *)message; -@end - - -@interface RFHTTPRequestFormData : NSObject -/// The name to be associated with the specified data. This property must be set. -@property (nonnull, copy) NSString *name; - -// No implementation -@property (nullable, copy) NSString *fileName; - -// No implementation -@property (nullable, copy) NSString *mimeType; - -/// The URL corresponding to the form content -@property (nullable, copy) NSURL *fileURL; - -// No implementation -@property (nullable, strong) NSInputStream *inputStream; - -/// The data to be encoded and appended to the form data. -@property (nullable, strong) NSData *data; - -/** - @param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`. - @param name The name to be associated with the specified data. This parameter must not be `nil`. - */ -+ (nonnull instancetype)formDataWithFileURL:(nonnull NSURL *)fileURL name:(nonnull NSString *)name; - -+ (nonnull instancetype)formDataWithData:(nonnull NSData *)data name:(nonnull NSString *)name; -+ (nonnull instancetype)formDataWithData:(nonnull NSData *)data name:(nonnull NSString *)name fileName:(nullable NSString *)fileName mimeType:(nullable NSString *)mimeType; - -- (void)buildFormData:(nonnull id)formData error:(NSError *_Nullable __autoreleasing *_Nullable)error; -@end diff --git a/RFAPI.m b/RFAPI.m deleted file mode 100644 index a615f7e..0000000 --- a/RFAPI.m +++ /dev/null @@ -1,496 +0,0 @@ - -#import "RFRuntime.h" -#import "RFAPI.h" -#import "RFMessageManager+RFDisplay.h" -#import "RFAPIDefineManager.h" - -#import "AFHTTPRequestOperation.h" -#import "AFURLRequestSerialization.h" -#import "AFURLResponseSerialization.h" -#import "AFNetworkReachabilityManager.h" -#import "AFSecurityPolicy.h" -#import "JSONModel.h" -#import "NSFileManager+RFKit.h" - -RFDefineConstString(RFAPIErrorDomain); -static NSString *RFAPIOperationUIkControl = @"RFAPIOperationUIkControl"; -NSString *const RFAPIRequestArrayParameterKey = @"_RFArray_"; -NSString *const RFAPIRequestForceQuryStringParametersKey = @"RFAPIRequestForceQuryStringParametersKey"; - - -@interface RFAPI () -@property (strong, readwrite) AFNetworkReachabilityManager *reachabilityManager; -@property (strong, readwrite) RFAPIDefineManager *defineManager; -@property (strong, readwrite) RFAPICacheManager *cacheManager; -@end - -@implementation RFAPI -RFInitializingRootForNSObject - -- (void)onInit { - self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; - self.maxConcurrentOperationCount = 5; - self.defineManager = [[RFAPIDefineManager alloc] init]; - self.defineManager.master = self; - - self.securityPolicy = [AFSecurityPolicy defaultPolicy]; - self.shouldUseCredentialStorage = YES; - - // As most request are API reqest, we dont need too much space - self.cacheManager = [[RFAPICacheManager alloc] initWithMemoryCapacity:500 * 1000 diskCapacity:10 * 1000 * 1000 diskPath:@"com.github.RFUI.RFAPICache"]; - self.cacheManager.reachabilityManager = self.reachabilityManager; -} - -- (void)afterInit { - [self.reachabilityManager startMonitoring]; -} - -- (NSString *)debugDescription { - return [NSString stringWithFormat:@"<%@: %p, operations: %@>", self.class, (void *)self, self.operations]; -} - -#pragma mark - Request management - -- (void)cancelOperationWithIdentifier:(nullable NSString *)identifier { - for (AFHTTPRequestOperation *op in [self operationsWithIdentifier:identifier]) { - _dout_debug(@"Cancel HTTP request operation(%p) with identifier: %@", (void *)op, identifier); - [op cancel]; - } -} - -- (void)cancelOperationsWithGroupIdentifier:(nullable NSString *)identifier { - for (AFHTTPRequestOperation *op in [self operationsWithGroupIdentifier:identifier]) { - _dout_debug(@"Cancel HTTP request operation(%p) with group identifier: %@", (void *)op, identifier); - [op cancel]; - } -} - -- (nonnull NSArray *)operationsWithIdentifier:(nullable NSString *)identifier { - @autoreleasepool { - return [self.operations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K.%K.%K == %@", @keypathClassInstance(AFHTTPRequestOperation, userInfo), RFAPIOperationUIkControl, @keypathClassInstance(RFAPIControl, identifier), identifier]]; - } -} - -- (nonnull NSArray *)operationsWithGroupIdentifier:(nullable NSString *)identifier { - @autoreleasepool { - return [self.operations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K.%K.%K == %@", @keypathClassInstance(AFHTTPRequestOperation, userInfo), RFAPIOperationUIkControl, @keypathClassInstance(RFAPIControl, groupIdentifier), identifier]]; - } -} - -#pragma mark - Request - -#define RFAPICompletionCallback_(BLOCK, ...)\ - if (BLOCK) {\ - BLOCK(__VA_ARGS__);\ - } - -#if RFDEBUG -# define RFAPILogError_(DEBUG_ERROR, ...) dout_error(DEBUG_ERROR, __VA_ARGS__); -#else -# define RFAPILogError_(DEBUG_ERROR, ...) -#endif - -#define RFAPICompletionCallback_ProccessError(CONDITION, DEBUG_ERROR, DEBUG_ARG, ERROR_DESCRIPTION, ERROR_FAILUREREASON, ERROR_RECOVERYSUGGESTION)\ - if (CONDITION) {\ - RFAPILogError_(DEBUG_ERROR, DEBUG_ARG);\ - error = [NSError errorWithDomain:RFAPIErrorDomain code:0 userInfo:@{ NSLocalizedDescriptionKey: ERROR_DESCRIPTION, NSLocalizedFailureReasonErrorKey: ERROR_FAILUREREASON, NSLocalizedRecoverySuggestionErrorKey: ERROR_RECOVERYSUGGESTION }];\ - RFAPICompletionCallback_(operationFailure, op, error);\ - return;\ - } - -- (nullable AFHTTPRequestOperation *)requestWithName:(nonnull NSString *)APIName parameters:(NSDictionary *)parameters formData:(nullable NSArray *)arrayContainsFormDataObj controlInfo:(nullable RFAPIControl *)controlInfo uploadProgress:(void (^_Nullable)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress success:(void (^_Nullable )(AFHTTPRequestOperation *_Nullable operation, id _Nullable responseObject))success failure:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation, NSError *_Nonnull error))failure completion:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable operation))completion { - NSParameterAssert(APIName); - RFAPIDefine *define = [self.defineManager defineForName:APIName]; - RFAssert(define, @"Can not find an API with name: %@.", APIName); - if (!define) return nil; - - NSError __autoreleasing *e = nil; - NSMutableURLRequest *request = [self URLRequestWithDefine:define parameters:parameters formData:arrayContainsFormDataObj controlInfo:controlInfo error:&e]; - if (!request) { - RFAPILogError_(@"无法创建请求: %@", e); - NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:@{ - NSLocalizedDescriptionKey : @"内部错误,无法创建请求", - NSLocalizedFailureReasonErrorKey : @"很可能是应用 bug", - NSLocalizedRecoverySuggestionErrorKey : @"请再试一次,如果依旧请尝试重启应用。给您带来不便,敬请谅解" - }]; - - RFAPICompletionCallback_(failure, nil, error); - RFAPICompletionCallback_(completion, nil); - return nil; - } - - // Request object get ready. - // Build operation block. - RFNetworkActivityMessage *message = controlInfo.message; - void (^operationCompletion)(id) = ^(AFHTTPRequestOperation *blockOp) { - dispatch_async_on_main(^{ - NSString *mid = message.identifier; - if (mid) { - [self.networkActivityIndicatorManager hideMessage:message]; - } - - if (completion) { - completion(blockOp); - } - }); - }; - - void (^operationSuccess)(id, id) = ^(AFHTTPRequestOperation *blockOp, id blockResponse) { - dispatch_async_on_main(^{ - if (success) { - success(blockOp, blockResponse); - } - operationCompletion(blockOp); - }); - }; - - void (^operationFailure)(id, NSError*) = ^(AFHTTPRequestOperation *blockOp, NSError *blockError) { - dispatch_async_on_main(^{ - if (blockError.code == NSURLErrorCancelled && blockError.domain == NSURLErrorDomain) { - dout_info(@"A HTTP operation cancelled: %@", blockOp); - operationCompletion(blockOp); - return; - } - - if ([self generalHandlerForError:blockError withDefine:define controlInfo:controlInfo requestOperation:blockOp operationFailureCallback:failure]) { - if (failure) { - failure(blockOp, blockError); - } - else { - [self.networkActivityIndicatorManager alertError:blockError title:nil fallbackMessage:@"Request Failed"]; - } - }; - operationCompletion(blockOp); - }); - }; - - // Check cache - NSCachedURLResponse *cachedResponse = [self.cacheManager cachedResponseForRequest:request define:define control:controlInfo]; - if (cachedResponse) { - dout_debug(@"Cache(%@) vaild for request: %@", cachedResponse, request); - AFHTTPResponseSerializer *serializer = [self.defineManager responseSerializerForDefine:define]; - - NSError *error = nil; - id _Nullable responseObject = [serializer responseObjectForResponse:cachedResponse.response data:cachedResponse.data error:&error]; - if (error) { - dispatch_async(self.responseProcessingQueue, ^{ - operationFailure(nil, error); - }); - return nil; - } - - dispatch_async(self.responseProcessingQueue, ^{ - [self processingCompletionWithHTTPOperation:nil responseObject:responseObject define:define control:controlInfo success:operationSuccess failure:operationFailure]; - }); - return nil; - } - - // Setup HTTP operation - AFHTTPRequestOperation *operation = [self requestOperationWithRequest:request define:define controlInfo:controlInfo]; - operation.completionQueue = self.responseProcessingQueue; - [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *_Nonnull op, id _Nullable responseObject) { - @autoreleasepool { - dout_debug(@"HTTP request operation(%p) with info: %@ completed.", (void *)op, [op valueForKeyPath:@"userInfo.RFAPIOperationUIkControl"]); - - [self processingCompletionWithHTTPOperation:op responseObject:responseObject define:define control:controlInfo success:operationSuccess failure:operationFailure]; - [self.cacheManager storeCachedResponseForRequest:op.request response:op.response data:op.responseData define:define control:controlInfo]; - } - } failure:^(AFHTTPRequestOperation *_Nonnull op, NSError *_Nonnull error) { - operationFailure(op, error); - }]; - - if (progress) { - [operation setUploadProgressBlock:progress]; - } - - // Start request - if (message) { - dispatch_sync_on_main(^{ - [self.networkActivityIndicatorManager showMessage:message]; - }); - } - [self addOperation:operation]; - return operation; -} - -- (AFHTTPRequestOperation *)requestWithName:(nonnull NSString *)APIName parameters:(NSDictionary *)parameters controlInfo:(RFAPIControl *)controlInfo success:(void (^)(AFHTTPRequestOperation *, id))success failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure completion:(void (^)(AFHTTPRequestOperation *))completion { - return [self requestWithName:APIName parameters:parameters formData:nil controlInfo:controlInfo uploadProgress:nil success:success failure:failure completion:completion]; -} - -- (void)invalidateCacheWithName:(nullable NSString *)APIName parameters:(nullable NSDictionary *)parameters { - if (!APIName.length) return; - - RFAPIDefine *define = [self.defineManager defineForName:APIName]; - if (!define) return; - - NSError __autoreleasing *e = nil; - NSURLRequest *request = [self URLRequestWithDefine:define parameters:parameters formData:nil controlInfo:nil error:&e]; - if (e) dout_error(@"%@", e); - - [self.cacheManager removeCachedResponseForRequest:request]; -} - -#pragma mark - Build Request - -#define RFAPIMakeRequestError_(CONDITION)\ - if (CONDITION) {\ - if (error) {\ - *error = e;\ - }\ - return nil;\ - } - -- (nullable NSMutableURLRequest *)URLRequestWithDefine:(nonnull RFAPIDefine *)define parameters:(nullable NSDictionary *)parameters formData:(nullable NSArray *)RFFormData controlInfo:(nullable RFAPIControl *)controlInfo error:(NSError *_Nullable __autoreleasing *_Nullable)error { - NSParameterAssert(define); - - // Preprocessing arguments - NSMutableDictionary *requestParameters = [NSMutableDictionary new]; - NSMutableDictionary *requestHeaders = [NSMutableDictionary new]; - [self preprocessingRequestParameters:&requestParameters HTTPHeaders:&requestHeaders withParameters:(NSDictionary *)parameters define:define controlInfo:controlInfo]; - - // Creat URL - NSError __autoreleasing *e = nil; - NSURL *url = [self.defineManager requestURLForDefine:define parameters:requestParameters error:&e]; - RFAPIMakeRequestError_(!url); - - // Creat URLRequest - NSMutableURLRequest *r; - AFHTTPRequestSerializer *s = [self.defineManager requestSerializerForDefine:define]; - if (RFFormData.count) { - NSString *urlString = url.absoluteString; - r = [s multipartFormRequestWithMethod:define.method URLString:urlString parameters:requestParameters constructingBodyWithBlock:^(id formData) { - for (RFHTTPRequestFormData *file in RFFormData) { - NSError __autoreleasing *f_e = nil; - [file buildFormData:formData error:&f_e]; - if (f_e) dout_error(@"%@", f_e); - } - } error:&e]; - } - else { - NSURLRequestCachePolicy cachePolicy = [self.cacheManager cachePolicyWithDefine:define control:controlInfo]; - r = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:40]; - [r setHTTPMethod:define.method]; - NSArray *arrayParameter = requestParameters[RFAPIRequestArrayParameterKey]; - r = [[s requestBySerializingRequest:r withParameters:arrayParameter?: requestParameters error:&e] mutableCopy]; - } - RFAPIMakeRequestError_(!r); - - // Set header - [requestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *__unused stop) { - [r setValue:value forHTTPHeaderField:field]; - }]; - - // Finalization - r = [self finalizeSerializedRequest:r withDefine:define controlInfo:controlInfo]; - return r; -} - -- (void)preprocessingRequestParameters:(NSMutableDictionary *_Nullable *_Nonnull)requestParameters HTTPHeaders:(NSMutableDictionary *_Nullable *_Nonnull)requestHeaders withParameters:(nullable NSDictionary *)parameters define:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo { - BOOL needsAuthorization = define.needsAuthorization; - - [*requestParameters addEntriesFromDictionary:define.defaultParameters]; - if (needsAuthorization) { - [*requestParameters addEntriesFromDictionary:self.defineManager.authorizationParameters]; - } - if (parameters) { - [*requestParameters addEntriesFromDictionary:(NSDictionary *)parameters]; - } - - [*requestHeaders addEntriesFromDictionary:define.HTTPRequestHeaders]; - if (needsAuthorization) { - [*requestHeaders addEntriesFromDictionary:self.defineManager.authorizationHeader]; - } -} - -- (nullable NSMutableURLRequest *)finalizeSerializedRequest:(nonnull NSMutableURLRequest *)request withDefine:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo { - if (controlInfo.requestCustomization) { - return controlInfo.requestCustomization(request); - } - return request; -} - -- (nonnull AFHTTPRequestOperation *)requestOperationWithRequest:(nonnull NSURLRequest *)request define:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo { - AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; - operation.responseSerializer = [self.defineManager responseSerializerForDefine:define]; - operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage; - operation.credential = self.credential; - operation.securityPolicy = self.securityPolicy; - if (controlInfo) { - operation.userInfo = @{ RFAPIOperationUIkControl : controlInfo }; - } - return operation; -} - -#pragma mark - Handel Response - -- (dispatch_queue_t)responseProcessingQueue { - if (_responseProcessingQueue) return _responseProcessingQueue; - _responseProcessingQueue = dispatch_get_main_queue(); - return _responseProcessingQueue; -} - -- (void)processingCompletionWithHTTPOperation:(nullable AFHTTPRequestOperation *)op responseObject:(nullable id)responseObject define:(nonnull RFAPIDefine *)define control:(nullable RFAPIControl *)control success:(void (^_Nonnull)(AFHTTPRequestOperation *_Nullable, id _Nullable))operationSuccess failure:(void (^_Nonnull)(id _Nullable, NSError *_Nonnull))operationFailure { - - if ((!responseObject || responseObject == [NSNull null]) - && define.responseAcceptNull) { - operationSuccess(op, nil); - return; - } - Class expectClass = define.responseClass; - NSError *error = nil; - switch (define.responseExpectType) { - case RFAPIDefineResponseExpectObject: { - RFAPICompletionCallback_ProccessError(![responseObject isKindOfClass:[NSDictionary class]], @"期望的数据类型是字典,而实际是 %@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", [responseObject class], @"返回数据异常", @"可能服务器正在升级或者维护,也可能是应用bug", @"建议稍后重试,如果持续报告这个错误请检查AppStore是否有新版本"); - - NSError __autoreleasing *e = nil; - id JSONModelObject = [[expectClass alloc] initWithDictionary:responseObject error:&e]; - RFAPICompletionCallback_ProccessError(!JSONModelObject, @"不能将返回内容转换为Model:%@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", e, @"返回数据异常", @"可能服务器正在升级或者维护,也可能是应用bug", @"建议稍后重试,如果持续报告这个错误请检查AppStore是否有新版本"); - responseObject = JSONModelObject; - break; - } - case RFAPIDefineResponseExpectObjects: { - RFAPICompletionCallback_ProccessError(![responseObject isKindOfClass:[NSArray class]], @"期望的数据类型是数组,而实际是 %@\n", [responseObject class], @"返回数据异常", @"可能服务器正在升级或者维护,也可能是应用bug", @"建议稍后重试,如果持续报告这个错误请检查AppStore是否有新版本"); - - NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[responseObject count]]; - for (NSDictionary *info in responseObject) { - id obj = [[expectClass alloc] initWithDictionary:info error:&error]; - RFAPICompletionCallback_ProccessError(!obj, @"不能将数组中的元素转换为Model %@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", error, @"返回数据异常", @"可能服务器正在升级或者维护,也可能是应用bug", @"建议稍后重试,如果持续报告这个错误请检查AppStore是否有新版本") - else { - [objects addObject:obj]; - } - } - responseObject = objects; - break; - } - case RFAPIDefineResponseExpectSuccess: { - if (![self isSuccessResponse:&responseObject error:&error]) { - operationFailure(op, error); - return; - } - break; - } - case RFAPIDefineResponseExpectDefault: - default: - break; - } - _douto(responseObject) - operationSuccess(op, responseObject); -} - -- (BOOL)generalHandlerForError:(nonnull NSError *)error withDefine:(nonnull RFAPIDefine *)define controlInfo:(nullable RFAPIControl *)controlInfo requestOperation:(nullable AFHTTPRequestOperation *)operation operationFailureCallback:(void (^_Nullable)(AFHTTPRequestOperation *_Nullable, NSError *_Nonnull))operationFailureCallback { - return YES; -} - -- (BOOL)isSuccessResponse:(id _Nullable __strong *_Nonnull)responseObjectRef error:(NSError *_Nullable __autoreleasing *_Nullable)error { - return YES; -} - -@end - - -#pragma mark - RFAPIControl -NSString *const RFAPIMessageControlKey = @"_RFAPIMessageControl"; -NSString *const RFAPIIdentifierControlKey = @"_RFAPIIdentifierControl"; -NSString *const RFAPIGroupIdentifierControlKey = @"_RFAPIGroupIdentifierControl"; -NSString *const RFAPIBackgroundTaskControlKey = @"_RFAPIBackgroundTaskControl"; -NSString *const RFAPIRequestCustomizationControlKey = @"_RFAPIRequestCustomizationControl"; - -@implementation RFAPIControl - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p, identifier = %@, groupIdentifier = %@>", self.class, (void *)self, self.identifier, self.groupIdentifier]; -} - -- (nonnull id)initWithDictionary:(nonnull NSDictionary *)info { - self = [super init]; - if (self) { - _message = info[RFAPIMessageControlKey]; - _identifier = info[RFAPIIdentifierControlKey]; - _groupIdentifier = info[RFAPIGroupIdentifierControlKey]; - _backgroundTask = [info[RFAPIBackgroundTaskControlKey] boolValue]; - _requestCustomization = info[RFAPIRequestCustomizationControlKey]; - } - return self; -} - -- (nonnull id)initWithIdentifier:(nonnull NSString *)identifier loadingMessage:(nullable NSString *)message { - self = [super init]; - if (self) { - _identifier = identifier; - _message = [[RFNetworkActivityMessage alloc] initWithIdentifier:identifier message:message status:RFNetworkActivityStatusLoading]; - } - return self; -} - -@end - -#pragma mark - RFHTTPRequestFormData - -typedef NS_ENUM(short, RFHTTPRequestFormDataSourceType) { - RFHTTPRequestFormDataSourceTypeURL = 0, - RFHTTPRequestFormDataSourceTypeStream, - RFHTTPRequestFormDataSourceTypeData -}; - -@interface RFHTTPRequestFormData () -@property RFHTTPRequestFormDataSourceType type; -@end - -@implementation RFHTTPRequestFormData - -+ (nonnull instancetype)formDataWithFileURL:(nonnull NSURL *)fileURL name:(nonnull NSString *)name { - NSParameterAssert(fileURL); - NSParameterAssert(name); - RFHTTPRequestFormData *this = [RFHTTPRequestFormData new]; - this.fileURL = fileURL; - this.name = name; - this.type = RFHTTPRequestFormDataSourceTypeURL; - return this; -} - -+ (nonnull instancetype)formDataWithData:(nonnull NSData *)data name:(nonnull NSString *)name { - NSParameterAssert(data); - NSParameterAssert(name); - RFHTTPRequestFormData *this = [RFHTTPRequestFormData new]; - this.data = data; - this.name = name; - this.type = RFHTTPRequestFormDataSourceTypeData; - return this; -} - -+ (nonnull instancetype)formDataWithData:(nonnull NSData *)data name:(nonnull NSString *)name fileName:(nullable NSString *)fileName mimeType:(nullable NSString *)mimeType { - NSParameterAssert(data); - NSParameterAssert(name); - RFHTTPRequestFormData *this = [RFHTTPRequestFormData new]; - this.data = data; - this.name = name; - this.fileName = fileName; - this.mimeType = mimeType; - this.type = RFHTTPRequestFormDataSourceTypeData; - return this; -} - -- (void)buildFormData:(nonnull id)formData error:(NSError *_Nullable __autoreleasing *_Nullable)error { - switch (self.type) { - case RFHTTPRequestFormDataSourceTypeURL: { - NSURL *fileURL = self.fileURL; - [formData appendPartWithFileURL:fileURL name:self.name error:error]; - break; - } - case RFHTTPRequestFormDataSourceTypeData: { - NSData *data = self.data; - if (self.fileName - && self.mimeType) { - [formData appendPartWithFileData:data name:self.name fileName:(NSString *)self.fileName mimeType:(NSString *)self.mimeType]; - } - else { - [formData appendPartWithFormData:data name:self.name]; - } - break; - } - default: - break; - } -} - -@end diff --git a/RFAPI.podspec b/RFAPI.podspec index 0c0a880..80dbd53 100644 --- a/RFAPI.podspec +++ b/RFAPI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'RFAPI' - s.version = '1.2.0' + s.version = '2.0.0-beta.1' s.summary = 'API Manager.' s.homepage = 'https://github.com/RFUI/RFAPI' @@ -12,20 +12,26 @@ Pod::Spec.new do |s| } s.requires_arc = true - s.osx.deployment_target = '10.8' + s.osx.deployment_target = '10.10' s.ios.deployment_target = '8.0' - # s.tvos.deployment_target = '9.0' + s.tvos.deployment_target = '9.0' + # s.watchos.deployment_target = '2.0' s.dependency 'JSONModel' - s.dependency 'AFNetworking/NSURLConnection', '~> 2.0' + s.dependency 'AFNetworking/Serialization', '>= 2.3' + s.dependency 'AFNetworking/Security', '>= 2.3' + s.dependency 'AFNetworking/Reachability', '>= 2.3' s.dependency 'RFKit/Runtime', '> 1.7' - s.dependency 'RFKit/Category/NSDictionary' - s.dependency 'RFKit/Category/NSFileManager' s.dependency 'RFInitializing', '>= 1.1' s.dependency 'RFMessageManager/Manager', '>= 0.5' s.dependency 'RFMessageManager/RFNetworkActivityMessage' - s.source_files = ['*.{h,m}', 'RFAPIDefine/*.{h,m}'] - s.public_header_files = ['*.h', 'RFAPIDefine/*.h'] + s.source_files = ['Sources/**/*.{h,m}'] + s.public_header_files = [ + 'Sources/RFAPI/RFAPI.h', + 'Sources/RFAPI/Define/*.h', + 'Sources/RFAPI/ModelTransformer/*.h', + 'Sources/RFAPI/Compatible/*.h', + ] s.pod_target_xcconfig = { } diff --git a/RFAPI.xcodeproj/project.pbxproj b/RFAPI.xcodeproj/project.pbxproj index dee0749..0254212 100644 --- a/RFAPI.xcodeproj/project.pbxproj +++ b/RFAPI.xcodeproj/project.pbxproj @@ -8,38 +8,46 @@ /* Begin PBXBuildFile section */ 04DDA15841D7E3DB05160199 /* libPods-Example-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 219EFF170EACE3662191400C /* libPods-Example-iOS.a */; }; - B2D4F3D32A1923BBCC54A69A /* libPods-Test-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED60EEB15D01DD7BDCF30A /* libPods-Test-macOS.a */; }; - CEBE41E43EDF2DFA7DEA515E /* libPods-Example-iOS-Test-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A32EDE312F90DA4AF288ED6 /* libPods-Example-iOS-Test-iOS.a */; }; + 2A251C10B17C5D95EAABDBAE /* libPods-Test-macOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 65ED60EEB15D01DD7BDCF30A /* libPods-Test-macOS.a */; }; + A805072FFAC70498847A1922 /* libPods-Test-iOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 93B3187890A6DB746F9A8161 /* libPods-Test-iOS.a */; }; + D5091B1723BF2E3B00E52FF3 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5091B1623BF2E3B00E52FF3 /* TestViewController.swift */; }; D54183C3206B685100DD25CE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54183C2206B685100DD25CE /* AppDelegate.swift */; }; D54183CA206B685100DD25CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D54183C9206B685100DD25CE /* Assets.xcassets */; }; D54183CD206B685100DD25CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D54183CB206B685100DD25CE /* LaunchScreen.storyboard */; }; - D54183FC206CD13000DD25CE /* Test_DefineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54183FB206CD13000DD25CE /* Test_DefineManager.swift */; }; - D54183FD206CD13000DD25CE /* Test_DefineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54183FB206CD13000DD25CE /* Test_DefineManager.swift */; }; D5418408206CD45400DD25CE /* RFDTestEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = D54183FE206CD44F00DD25CE /* RFDTestEntity.m */; }; - D541840A206CD45400DD25CE /* RFDAPITestViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D5418400206CD44F00DD25CE /* RFDAPITestViewController.m */; }; - D541840B206CD45400DD25CE /* RFDTestAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = D5418401206CD45000DD25CE /* RFDTestAPI.m */; }; D541840C206CD45400DD25CE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D5418404206CD45100DD25CE /* Main.storyboard */; }; D541840D206CD45400DD25CE /* TestAPIDefine.plist in Resources */ = {isa = PBXBuildFile; fileRef = D5418405206CD45300DD25CE /* TestAPIDefine.plist */; }; + D55ADAF523BF4AFB00AD6DB2 /* TestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55ADAF423BF4AFB00AD6DB2 /* TestAPI.swift */; }; + D55ADB0F23C06C6600AD6DB2 /* TestDefineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54183FB206CD13000DD25CE /* TestDefineManager.swift */; }; + D55ADB1023C06C6700AD6DB2 /* TestDefineManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54183FB206CD13000DD25CE /* TestDefineManager.swift */; }; + D57C353D23C70F76000B6A3C /* test_defines.plist in Resources */ = {isa = PBXBuildFile; fileRef = D57C353B23C70A56000B6A3C /* test_defines.plist */; }; + D57C353E23C70F76000B6A3C /* test_defines.plist in Resources */ = {isa = PBXBuildFile; fileRef = D57C353B23C70A56000B6A3C /* test_defines.plist */; }; + D57C354623C7193C000B6A3C /* RTHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D57C354523C7193C000B6A3C /* RTHelper.m */; }; + D57C354723C7193C000B6A3C /* RTHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D57C354523C7193C000B6A3C /* RTHelper.m */; }; + D57C354A23C83B89000B6A3C /* TestRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57C354923C83B89000B6A3C /* TestRequest.swift */; }; + D57C354B23C83B89000B6A3C /* TestRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57C354923C83B89000B6A3C /* TestRequest.swift */; }; + D59FF65023D1E2D800206713 /* TestConvention.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59FF64F23D1E2D800206713 /* TestConvention.swift */; }; + D59FF65123D1E2D800206713 /* TestConvention.swift in Sources */ = {isa = PBXBuildFile; fileRef = D59FF64F23D1E2D800206713 /* TestConvention.swift */; }; + D5D61FF623C42A5300B26C33 /* TestDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D61FF523C42A5300B26C33 /* TestDefine.swift */; }; + D5D61FF723C42A5300B26C33 /* TestDefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D61FF523C42A5300B26C33 /* TestDefine.swift */; }; + D5D61FFC23C5E19800B26C33 /* archived_define_v1.plist in Resources */ = {isa = PBXBuildFile; fileRef = D5D61FFB23C5E19800B26C33 /* archived_define_v1.plist */; }; + D5D61FFD23C5E19800B26C33 /* archived_define_v1.plist in Resources */ = {isa = PBXBuildFile; fileRef = D5D61FFB23C5E19800B26C33 /* archived_define_v1.plist */; }; + D5F9245923D5BBCA00E9781D /* big_json.data in Resources */ = {isa = PBXBuildFile; fileRef = D5F9245823D5BBCA00E9781D /* big_json.data */; }; + D5F9245A23D5BBCA00E9781D /* big_json.data in Resources */ = {isa = PBXBuildFile; fileRef = D5F9245823D5BBCA00E9781D /* big_json.data */; }; + D5F9245B23D5BBCA00E9781D /* big_json.data in Resources */ = {isa = PBXBuildFile; fileRef = D5F9245823D5BBCA00E9781D /* big_json.data */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - D54183DE206BBA7B00DD25CE /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D54183B5206B66FA00DD25CE /* Project object */; - proxyType = 1; - remoteGlobalIDString = D54183BE206B685000DD25CE; - remoteInfo = "Example-iOS"; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ - 1A32EDE312F90DA4AF288ED6 /* libPods-Example-iOS-Test-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example-iOS-Test-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 219EFF170EACE3662191400C /* libPods-Example-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Example-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2DB51C46282E6DD0627B6D13 /* Pods-Example-iOS-Test-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS-Test-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS-Test-iOS/Pods-Example-iOS-Test-iOS.release.xcconfig"; sourceTree = ""; }; 387B55A79A2B1A8C07C03E12 /* Pods-Test-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Test-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Test-macOS/Pods-Test-macOS.debug.xcconfig"; sourceTree = ""; }; 65ED60EEB15D01DD7BDCF30A /* libPods-Test-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Test-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 735FA53B9EA138BDBFEA4922 /* Pods-Example-iOS-Test-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS-Test-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS-Test-iOS/Pods-Example-iOS-Test-iOS.debug.xcconfig"; sourceTree = ""; }; + 759E83620E24AB315EE017E4 /* Pods-Test-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Test-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Test-iOS/Pods-Test-iOS.debug.xcconfig"; sourceTree = ""; }; + 93B3187890A6DB746F9A8161 /* libPods-Test-iOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Test-iOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 97F2581ADA52229A84F7E000 /* Pods-Test-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Test-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Test-iOS/Pods-Test-iOS.release.xcconfig"; sourceTree = ""; }; 9C37EB0BE59A81EC90E0CDCC /* Pods-Example-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS/Pods-Example-iOS.release.xcconfig"; sourceTree = ""; }; + D5091B1623BF2E3B00E52FF3 /* TestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestViewController.swift; sourceTree = ""; }; D54183BF206B685000DD25CE /* Example-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; D54183C2206B685100DD25CE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D54183C9206B685100DD25CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -47,19 +55,26 @@ D54183CE206B685100DD25CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D54183D3206B806200DD25CE /* OCBridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OCBridging-Header.h"; sourceTree = ""; }; D54183D4206BB68400DD25CE /* RFUI-Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "RFUI-Config.xcconfig"; sourceTree = ""; }; - D54183D9206BBA7B00DD25CE /* Test-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D54183DD206BBA7B00DD25CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D54183EE206BDCFF00DD25CE /* Test-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D54183F2206BDCFF00DD25CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D54183FB206CD13000DD25CE /* Test_DefineManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test_DefineManager.swift; sourceTree = ""; }; + D54183FB206CD13000DD25CE /* TestDefineManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDefineManager.swift; sourceTree = ""; }; D54183FE206CD44F00DD25CE /* RFDTestEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFDTestEntity.m; sourceTree = ""; }; - D5418400206CD44F00DD25CE /* RFDAPITestViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFDAPITestViewController.m; sourceTree = ""; }; - D5418401206CD45000DD25CE /* RFDTestAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFDTestAPI.m; sourceTree = ""; }; - D5418402206CD45000DD25CE /* RFDTestAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFDTestAPI.h; sourceTree = ""; }; - D5418403206CD45100DD25CE /* RFDAPITestViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFDAPITestViewController.h; sourceTree = ""; }; D5418404206CD45100DD25CE /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; D5418405206CD45300DD25CE /* TestAPIDefine.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TestAPIDefine.plist; sourceTree = ""; }; D5418407206CD45400DD25CE /* RFDTestEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFDTestEntity.h; sourceTree = ""; }; + D55ADAF423BF4AFB00AD6DB2 /* TestAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAPI.swift; sourceTree = ""; }; + D55ADAFB23C06B7E00AD6DB2 /* Test-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + D55ADB0723C06C3B00AD6DB2 /* Test-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + D57C353A23C6EDAC000B6A3C /* Example-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Example-iOS.entitlements"; sourceTree = ""; }; + D57C353B23C70A56000B6A3C /* test_defines.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = test_defines.plist; sourceTree = ""; }; + D57C354423C7193B000B6A3C /* RTHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RTHelper.h; sourceTree = ""; }; + D57C354523C7193C000B6A3C /* RTHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RTHelper.m; sourceTree = ""; }; + D57C354823C7196B000B6A3C /* TestsBridgingHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestsBridgingHeader.h; sourceTree = ""; }; + D57C354923C83B89000B6A3C /* TestRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRequest.swift; sourceTree = ""; }; + D59FF64F23D1E2D800206713 /* TestConvention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConvention.swift; sourceTree = ""; }; + D5D61FF523C42A5300B26C33 /* TestDefine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDefine.swift; sourceTree = ""; }; + D5D61FFB23C5E19800B26C33 /* archived_define_v1.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = archived_define_v1.plist; sourceTree = ""; }; + D5F9245823D5BBCA00E9781D /* big_json.data */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = big_json.data; sourceTree = ""; }; DFF2285EDB18B106627B701F /* Pods-Example-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example-iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Example-iOS/Pods-Example-iOS.debug.xcconfig"; sourceTree = ""; }; E5FA99E5E4196E679EB5925D /* Pods-Test-macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Test-macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Test-macOS/Pods-Test-macOS.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -73,19 +88,19 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - D54183D6206BBA7B00DD25CE /* Frameworks */ = { + D55ADAF823C06B7E00AD6DB2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CEBE41E43EDF2DFA7DEA515E /* libPods-Example-iOS-Test-iOS.a in Frameworks */, + A805072FFAC70498847A1922 /* libPods-Test-iOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - D54183EB206BDCFF00DD25CE /* Frameworks */ = { + D55ADB0423C06C3B00AD6DB2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B2D4F3D32A1923BBCC54A69A /* libPods-Test-macOS.a in Frameworks */, + 2A251C10B17C5D95EAABDBAE /* libPods-Test-macOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,8 +110,8 @@ 03AE588ADB8E8D378064FCA9 /* Frameworks */ = { isa = PBXGroup; children = ( - 1A32EDE312F90DA4AF288ED6 /* libPods-Example-iOS-Test-iOS.a */, 219EFF170EACE3662191400C /* libPods-Example-iOS.a */, + 93B3187890A6DB746F9A8161 /* libPods-Test-iOS.a */, 65ED60EEB15D01DD7BDCF30A /* libPods-Test-macOS.a */, ); name = Frameworks; @@ -109,6 +124,8 @@ 2DB51C46282E6DD0627B6D13 /* Pods-Example-iOS-Test-iOS.release.xcconfig */, DFF2285EDB18B106627B701F /* Pods-Example-iOS.debug.xcconfig */, 9C37EB0BE59A81EC90E0CDCC /* Pods-Example-iOS.release.xcconfig */, + 759E83620E24AB315EE017E4 /* Pods-Test-iOS.debug.xcconfig */, + 97F2581ADA52229A84F7E000 /* Pods-Test-iOS.release.xcconfig */, 387B55A79A2B1A8C07C03E12 /* Pods-Test-macOS.debug.xcconfig */, E5FA99E5E4196E679EB5925D /* Pods-Test-macOS.release.xcconfig */, ); @@ -122,7 +139,7 @@ 03AE588ADB8E8D378064FCA9 /* Frameworks */, 8522E7E8B70EDBBE3150722F /* Pods */, D54183C0206B685000DD25CE /* Products */, - D54183E5206BBC0B00DD25CE /* Test */, + D54183E5206BBC0B00DD25CE /* Tests */, ); sourceTree = ""; }; @@ -130,8 +147,8 @@ isa = PBXGroup; children = ( D54183BF206B685000DD25CE /* Example-iOS.app */, - D54183D9206BBA7B00DD25CE /* Test-iOS.xctest */, - D54183EE206BDCFF00DD25CE /* Test-macOS.xctest */, + D55ADAFB23C06B7E00AD6DB2 /* Test-iOS.xctest */, + D55ADB0723C06C3B00AD6DB2 /* Test-macOS.xctest */, ); name = Products; sourceTree = ""; @@ -144,13 +161,9 @@ D54183CE206B685100DD25CE /* Info.plist */, D54183CB206B685100DD25CE /* LaunchScreen.storyboard */, D5418404206CD45100DD25CE /* Main.storyboard */, - D5418403206CD45100DD25CE /* RFDAPITestViewController.h */, - D5418400206CD44F00DD25CE /* RFDAPITestViewController.m */, - D5418402206CD45000DD25CE /* RFDTestAPI.h */, - D5418401206CD45000DD25CE /* RFDTestAPI.m */, - D5418407206CD45400DD25CE /* RFDTestEntity.h */, - D54183FE206CD44F00DD25CE /* RFDTestEntity.m */, + D55ADAF423BF4AFB00AD6DB2 /* TestAPI.swift */, D5418405206CD45300DD25CE /* TestAPIDefine.plist */, + D5091B1623BF2E3B00E52FF3 /* TestViewController.swift */, ); path = "iOS-Swift"; sourceTree = ""; @@ -158,6 +171,8 @@ D54183D2206B801E00DD25CE /* Shared */ = { isa = PBXGroup; children = ( + D55ADAF623BF4D1D00AD6DB2 /* Models */, + D57C353A23C6EDAC000B6A3C /* Example-iOS.entitlements */, D54183D3206B806200DD25CE /* OCBridging-Header.h */, D54183D4206BB68400DD25CE /* RFUI-Config.xcconfig */, ); @@ -175,19 +190,28 @@ D54183E3206BBAA100DD25CE /* Shared */ = { isa = PBXGroup; children = ( - D54183FB206CD13000DD25CE /* Test_DefineManager.swift */, + D5F9245723D5BBB000E9781D /* Data */, + D5D61FFB23C5E19800B26C33 /* archived_define_v1.plist */, + D57C354423C7193B000B6A3C /* RTHelper.h */, + D57C354523C7193C000B6A3C /* RTHelper.m */, + D57C353B23C70A56000B6A3C /* test_defines.plist */, + D59FF64F23D1E2D800206713 /* TestConvention.swift */, + D5D61FF523C42A5300B26C33 /* TestDefine.swift */, + D54183FB206CD13000DD25CE /* TestDefineManager.swift */, + D57C354923C83B89000B6A3C /* TestRequest.swift */, + D57C354823C7196B000B6A3C /* TestsBridgingHeader.h */, ); path = Shared; sourceTree = ""; }; - D54183E5206BBC0B00DD25CE /* Test */ = { + D54183E5206BBC0B00DD25CE /* Tests */ = { isa = PBXGroup; children = ( D54183DA206BBA7B00DD25CE /* iOS */, D54183EF206BDCFF00DD25CE /* macOS */, D54183E3206BBAA100DD25CE /* Shared */, ); - path = Test; + path = Tests; sourceTree = ""; }; D54183E6206BBC2900DD25CE /* Example */ = { @@ -207,6 +231,23 @@ path = macOS; sourceTree = ""; }; + D55ADAF623BF4D1D00AD6DB2 /* Models */ = { + isa = PBXGroup; + children = ( + D5418407206CD45400DD25CE /* RFDTestEntity.h */, + D54183FE206CD44F00DD25CE /* RFDTestEntity.m */, + ); + path = Models; + sourceTree = ""; + }; + D5F9245723D5BBB000E9781D /* Data */ = { + isa = PBXGroup; + children = ( + D5F9245823D5BBCA00E9781D /* big_json.data */, + ); + path = Data; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -229,34 +270,32 @@ productReference = D54183BF206B685000DD25CE /* Example-iOS.app */; productType = "com.apple.product-type.application"; }; - D54183D8206BBA7B00DD25CE /* Test-iOS */ = { + D55ADAFA23C06B7E00AD6DB2 /* Test-iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = D54183E2206BBA7B00DD25CE /* Build configuration list for PBXNativeTarget "Test-iOS" */; + buildConfigurationList = D55ADB0023C06B7E00AD6DB2 /* Build configuration list for PBXNativeTarget "Test-iOS" */; buildPhases = ( - 06062321C634390C9BACC0ED /* [CP] Check Pods Manifest.lock */, - D54183D5206BBA7B00DD25CE /* Sources */, - D54183D6206BBA7B00DD25CE /* Frameworks */, - D54183D7206BBA7B00DD25CE /* Resources */, - F9D875E7A4F92AF4A24AB29E /* [CP] Copy Pods Resources */, + A2A2AF6793561F6333AD17C3 /* [CP] Check Pods Manifest.lock */, + D55ADAF723C06B7E00AD6DB2 /* Sources */, + D55ADAF823C06B7E00AD6DB2 /* Frameworks */, + D55ADAF923C06B7E00AD6DB2 /* Resources */, ); buildRules = ( ); dependencies = ( - D54183DF206BBA7B00DD25CE /* PBXTargetDependency */, ); name = "Test-iOS"; productName = "Test-iOS"; - productReference = D54183D9206BBA7B00DD25CE /* Test-iOS.xctest */; + productReference = D55ADAFB23C06B7E00AD6DB2 /* Test-iOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - D54183ED206BDCFF00DD25CE /* Test-macOS */ = { + D55ADB0623C06C3B00AD6DB2 /* Test-macOS */ = { isa = PBXNativeTarget; - buildConfigurationList = D54183F3206BDCFF00DD25CE /* Build configuration list for PBXNativeTarget "Test-macOS" */; + buildConfigurationList = D55ADB0C23C06C3B00AD6DB2 /* Build configuration list for PBXNativeTarget "Test-macOS" */; buildPhases = ( - 59B0456C642D28AD86304270 /* [CP] Check Pods Manifest.lock */, - D54183EA206BDCFF00DD25CE /* Sources */, - D54183EB206BDCFF00DD25CE /* Frameworks */, - D54183EC206BDCFF00DD25CE /* Resources */, + 17A4BE8D47494A7D02BA16FC /* [CP] Check Pods Manifest.lock */, + D55ADB0323C06C3B00AD6DB2 /* Sources */, + D55ADB0423C06C3B00AD6DB2 /* Frameworks */, + D55ADB0523C06C3B00AD6DB2 /* Resources */, ); buildRules = ( ); @@ -264,7 +303,7 @@ ); name = "Test-macOS"; productName = "Test-macOS"; - productReference = D54183EE206BDCFF00DD25CE /* Test-macOS.xctest */; + productReference = D55ADB0723C06C3B00AD6DB2 /* Test-macOS.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -274,23 +313,22 @@ isa = PBXProject; attributes = { CLASSPREFIX = DM; - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0920; + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1130; ORGANIZATIONNAME = RFUI; TargetAttributes = { D54183BE206B685000DD25CE = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 0920; }; - D54183D8206BBA7B00DD25CE = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 0920; + D55ADAFA23C06B7E00AD6DB2 = { + CreatedOnToolsVersion = 11.3; + LastSwiftMigration = 1130; ProvisioningStyle = Automatic; - TestTargetID = D54183BE206B685000DD25CE; }; - D54183ED206BDCFF00DD25CE = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 0920; + D55ADB0623C06C3B00AD6DB2 = { + CreatedOnToolsVersion = 11.3; + LastSwiftMigration = 1130; ProvisioningStyle = Automatic; }; }; @@ -309,8 +347,8 @@ projectRoot = ""; targets = ( D54183BE206B685000DD25CE /* Example-iOS */, - D54183D8206BBA7B00DD25CE /* Test-iOS */, - D54183ED206BDCFF00DD25CE /* Test-macOS */, + D55ADAFA23C06B7E00AD6DB2 /* Test-iOS */, + D55ADB0623C06C3B00AD6DB2 /* Test-macOS */, ); }; /* End PBXProject section */ @@ -321,41 +359,52 @@ buildActionMask = 2147483647; files = ( D54183CA206B685100DD25CE /* Assets.xcassets in Resources */, + D5F9245923D5BBCA00E9781D /* big_json.data in Resources */, D54183CD206B685100DD25CE /* LaunchScreen.storyboard in Resources */, D541840C206CD45400DD25CE /* Main.storyboard in Resources */, D541840D206CD45400DD25CE /* TestAPIDefine.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - D54183D7206BBA7B00DD25CE /* Resources */ = { + D55ADAF923C06B7E00AD6DB2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5D61FFC23C5E19800B26C33 /* archived_define_v1.plist in Resources */, + D5F9245A23D5BBCA00E9781D /* big_json.data in Resources */, + D57C353D23C70F76000B6A3C /* test_defines.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; - D54183EC206BDCFF00DD25CE /* Resources */ = { + D55ADB0523C06C3B00AD6DB2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D5D61FFD23C5E19800B26C33 /* archived_define_v1.plist in Resources */, + D5F9245B23D5BBCA00E9781D /* big_json.data in Resources */, + D57C353E23C70F76000B6A3C /* test_defines.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 06062321C634390C9BACC0ED /* [CP] Check Pods Manifest.lock */ = { + 17A4BE8D47494A7D02BA16FC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Example-iOS-Test-iOS-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Test-macOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -380,18 +429,22 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example-iOS/Pods-Example-iOS-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 59B0456C642D28AD86304270 /* [CP] Check Pods Manifest.lock */ = { + A2A2AF6793561F6333AD17C3 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Test-macOS-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Test-iOS-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -416,24 +469,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F9D875E7A4F92AF4A24AB29E /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Example-iOS-Test-iOS/Pods-Example-iOS-Test-iOS-resources.sh", - "${PODS_ROOT}/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SVProgressHUD.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example-iOS-Test-iOS/Pods-Example-iOS-Test-iOS-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -442,38 +477,38 @@ buildActionMask = 2147483647; files = ( D54183C3206B685100DD25CE /* AppDelegate.swift in Sources */, - D541840A206CD45400DD25CE /* RFDAPITestViewController.m in Sources */, - D541840B206CD45400DD25CE /* RFDTestAPI.m in Sources */, D5418408206CD45400DD25CE /* RFDTestEntity.m in Sources */, + D55ADAF523BF4AFB00AD6DB2 /* TestAPI.swift in Sources */, + D5091B1723BF2E3B00E52FF3 /* TestViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - D54183D5206BBA7B00DD25CE /* Sources */ = { + D55ADAF723C06B7E00AD6DB2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D54183FC206CD13000DD25CE /* Test_DefineManager.swift in Sources */, + D57C354623C7193C000B6A3C /* RTHelper.m in Sources */, + D59FF65023D1E2D800206713 /* TestConvention.swift in Sources */, + D5D61FF623C42A5300B26C33 /* TestDefine.swift in Sources */, + D55ADB0F23C06C6600AD6DB2 /* TestDefineManager.swift in Sources */, + D57C354A23C83B89000B6A3C /* TestRequest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - D54183EA206BDCFF00DD25CE /* Sources */ = { + D55ADB0323C06C3B00AD6DB2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D54183FD206CD13000DD25CE /* Test_DefineManager.swift in Sources */, + D57C354723C7193C000B6A3C /* RTHelper.m in Sources */, + D59FF65123D1E2D800206713 /* TestConvention.swift in Sources */, + D5D61FF723C42A5300B26C33 /* TestDefine.swift in Sources */, + D55ADB1023C06C6700AD6DB2 /* TestDefineManager.swift in Sources */, + D57C354B23C83B89000B6A3C /* TestRequest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - D54183DF206BBA7B00DD25CE /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D54183BE206B685000DD25CE /* Example-iOS */; - targetProxy = D54183DE206BBA7B00DD25CE /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin PBXVariantGroup section */ D54183CB206B685100DD25CE /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; @@ -490,6 +525,33 @@ isa = XCBuildConfiguration; baseConfigurationReference = D54183D4206BB68400DD25CE /* RFUI-Config.xcconfig */; buildSettings = { + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + ONLY_ACTIVE_ARCH = YES; SWIFT_OBJC_BRIDGING_HEADER = "Example/Shared/OCBridging-Header.h"; }; name = Debug; @@ -498,6 +560,31 @@ isa = XCBuildConfiguration; baseConfigurationReference = D54183D4206BB68400DD25CE /* RFUI-Config.xcconfig */; buildSettings = { + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; SWIFT_OBJC_BRIDGING_HEADER = "Example/Shared/OCBridging-Header.h"; }; name = Release; @@ -533,8 +620,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "Example/Shared/Example-iOS.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -560,9 +650,10 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFAPI.Example-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -598,8 +689,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "Example/Shared/Example-iOS.entitlements"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = ""; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -618,29 +712,31 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFAPI.Example-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; - D54183E0206BBA7B00DD25CE /* Debug */ = { + D55ADB0123C06B7E00AD6DB2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 735FA53B9EA138BDBFEA4922 /* Pods-Example-iOS-Test-iOS.debug.xcconfig */; + baseConfigurationReference = 759E83620E24AB315EE017E4 /* Pods-Test-iOS.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -648,6 +744,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -656,7 +753,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; @@ -676,38 +773,40 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = Test/iOS/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + INFOPLIST_FILE = Tests/iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFMessageManager.Test-iOS"; + PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFAPI.Test-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = Tests/Shared/TestsBridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example-iOS.app/Example-iOS"; }; name = Debug; }; - D54183E1206BBA7B00DD25CE /* Release */ = { + D55ADB0223C06B7E00AD6DB2 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2DB51C46282E6DD0627B6D13 /* Pods-Example-iOS-Test-iOS.release.xcconfig */; + baseConfigurationReference = 97F2581ADA52229A84F7E000 /* Pods-Test-iOS.release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -715,6 +814,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -723,7 +823,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -737,22 +837,23 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = Test/iOS/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.2; + INFOPLIST_FILE = Tests/iOS/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFMessageManager.Test-iOS"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFAPI.Test-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + SWIFT_OBJC_BRIDGING_HEADER = Tests/Shared/TestsBridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example-iOS.app/Example-iOS"; VALIDATE_PRODUCT = YES; }; name = Release; }; - D54183F4206BDCFF00DD25CE /* Debug */ = { + D55ADB0D23C06C3B00AD6DB2 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 387B55A79A2B1A8C07C03E12 /* Pods-Test-macOS.debug.xcconfig */; buildSettings = { @@ -763,10 +864,12 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -774,6 +877,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -782,7 +886,6 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; @@ -803,21 +906,23 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = Test/macOS/Info.plist; + INFOPLIST_FILE = Tests/macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; - MTL_ENABLE_DEBUG_INFO = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFMessageManager.Test-macOS"; + PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFAPI.Test-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = Tests/Shared/TestsBridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; - D54183F5206BDCFF00DD25CE /* Release */ = { + D55ADB0E23C06C3B00AD6DB2 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = E5FA99E5E4196E679EB5925D /* Pods-Test-macOS.release.xcconfig */; buildSettings = { @@ -828,10 +933,12 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -839,6 +946,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -847,7 +955,6 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; @@ -862,15 +969,17 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = Test/macOS/Info.plist; + INFOPLIST_FILE = Tests/macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFMessageManager.Test-macOS"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.github.RFUI.RFAPI.Test-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_OBJC_BRIDGING_HEADER = Tests/Shared/TestsBridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -895,20 +1004,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D54183E2206BBA7B00DD25CE /* Build configuration list for PBXNativeTarget "Test-iOS" */ = { + D55ADB0023C06B7E00AD6DB2 /* Build configuration list for PBXNativeTarget "Test-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - D54183E0206BBA7B00DD25CE /* Debug */, - D54183E1206BBA7B00DD25CE /* Release */, + D55ADB0123C06B7E00AD6DB2 /* Debug */, + D55ADB0223C06B7E00AD6DB2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D54183F3206BDCFF00DD25CE /* Build configuration list for PBXNativeTarget "Test-macOS" */ = { + D55ADB0C23C06C3B00AD6DB2 /* Build configuration list for PBXNativeTarget "Test-macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - D54183F4206BDCFF00DD25CE /* Debug */, - D54183F5206BDCFF00DD25CE /* Release */, + D55ADB0D23C06C3B00AD6DB2 /* Debug */, + D55ADB0E23C06C3B00AD6DB2 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/RFAPI.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme b/RFAPI.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme index c54af66..291e105 100644 --- a/RFAPI.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme +++ b/RFAPI.xcodeproj/xcshareddata/xcschemes/Example-iOS.xcscheme @@ -1,6 +1,6 @@ + ReferencedContainer = "container:RFAPI.xcodeproj"> @@ -26,38 +26,14 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> - - - - - - - - - - - - + ReferencedContainer = "container:RFAPI.xcodeproj"> diff --git a/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-iOS.xcscheme b/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-iOS.xcscheme new file mode 100644 index 0000000..5a139a3 --- /dev/null +++ b/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-iOS.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-macOS.xcscheme b/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-macOS.xcscheme index 38fe458..79c226e 100644 --- a/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-macOS.xcscheme +++ b/RFAPI.xcodeproj/xcshareddata/xcschemes/Test-macOS.xcscheme @@ -1,16 +1,31 @@ + + + + + + @@ -18,29 +33,24 @@ skipped = "NO"> - - - - + + + + diff --git a/RFAPICacheManager.h b/RFAPICacheManager.h deleted file mode 100644 index 4e4a409..0000000 --- a/RFAPICacheManager.h +++ /dev/null @@ -1,27 +0,0 @@ -/*! - RFAPICacheManager - RFAPI - - Copyright (c) 2014, 2018 BB9z - https://github.com/RFUI/RFAPI - - The MIT License (MIT) - http://www.opensource.org/licenses/mit-license.php - */ -#import -#import - -@class RFAPIDefine, RFAPIControl, AFNetworkReachabilityManager; - -@interface RFAPICacheManager : NSURLCache -@property (weak, nonatomic) AFNetworkReachabilityManager *reachabilityManager; - -- (NSURLRequestCachePolicy)cachePolicyWithDefine:(RFAPIDefine *)define control:(RFAPIControl *)control; - -- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request define:(RFAPIDefine *)define control:(RFAPIControl *)control; -- (void)storeCachedResponseForRequest:(NSURLRequest *)request response:(NSHTTPURLResponse *)response data:(NSData *)responseData define:(RFAPIDefine *)define control:(RFAPIControl *)control; - -// TODO -// - (void)removeCachedResponseForDefine:(RFAPIDefine *)define; - -@end diff --git a/RFAPICacheManager.m b/RFAPICacheManager.m deleted file mode 100644 index 224b3dc..0000000 --- a/RFAPICacheManager.m +++ /dev/null @@ -1,114 +0,0 @@ - -#import "RFAPICacheManager.h" -#import "RFAPI.h" - -@interface RFAPICacheManager () -@end - -@implementation RFAPICacheManager - -- (NSURLRequestCachePolicy)cachePolicyWithDefine:(RFAPIDefine *)define control:(RFAPIControl *)control { - if (control.forceLoad) { - return NSURLRequestReloadIgnoringLocalCacheData; - } - - if (self.reachabilityManager.reachable) { - switch (define.cachePolicy) { - case RFAPICachePolicyAlways: - return NSURLRequestReturnCacheDataElseLoad; - - case RFAPICachePolicyNoCache: - return NSURLRequestReloadIgnoringLocalCacheData; - - case RFAPICachePolicyExpire: - default: - return NSURLRequestUseProtocolCachePolicy; - } - } - else { - switch (define.offlinePolicy) { - case RFAPIOfflinePolicyLoadCache: - return NSURLRequestReturnCacheDataElseLoad; - - default: - break; - } - } - return NSURLRequestUseProtocolCachePolicy; -} - -- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request define:(RFAPIDefine *)define control:(RFAPIControl *)control { - NSCachedURLResponse *cachedResponse = [self cachedResponseForRequest:request]; - if (!cachedResponse) return nil; - - if (control.forceLoad) { - return nil; - } - - if (self.reachabilityManager.reachable) { - switch (define.cachePolicy) { - case RFAPICachePolicyAlways: - return cachedResponse; - - case RFAPICachePolicyNoCache: - return nil; - - case RFAPICachePolicyExpire: { - NSTimeInterval expireTime = [cachedResponse.userInfo[RFAPIDefineExpireKey] doubleValue]; - if (expireTime > [NSDate timeIntervalSinceReferenceDate]) { - return cachedResponse; - } - else { - dout_debug(@"Cache expired.") - } - } - default: - return nil; - } - } - else { - switch (define.offlinePolicy) { - case RFAPIOfflinePolicyLoadCache: - return cachedResponse; - - default: - return nil; - } - } - return nil; -} - -- (void)storeCachedResponseForRequest:(NSURLRequest *)request response:(NSHTTPURLResponse *)response data:(NSData *)responseData define:(RFAPIDefine *)define control:(RFAPIControl *)control { - // No need to store cache - if (define.offlinePolicy == RFAPIOfflinePolicyDefault) { - if (define.cachePolicy == RFAPICachePolicyNoCache) { - return; - } - - // Cache controlled by server, ignore. - if (define.cachePolicy == RFAPICachePolicyDefault - || define.cachePolicy == RFAPICachePolicyProtocol) { - return; - } - } - - NSTimeInterval age = define.expire; - NSTimeInterval expire = age? age + [NSDate timeIntervalSinceReferenceDate] : 1; - - NSMutableDictionary *headers = [response.allHeaderFields mutableCopy]; - - // Rewrite cache headers to force NSURLCache store responses. - headers[@"Cache-Control"] = [NSString stringWithFormat:@"%@; max-age=%.0f", define.needsAuthorization? @"private" : @"public", fmax(age, 1)]; - [headers removeObjectForKey:@"Expires"]; - [headers removeObjectForKey:@"Pragma"]; - - // MARK: How to avoid hard-code HTTPVersion? - NSHTTPURLResponse *modifiedResponse = [[NSHTTPURLResponse alloc] initWithURL:(id)response.URL statusCode:response.statusCode HTTPVersion:@"HTTP/1.1" headerFields:headers]; - - NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:modifiedResponse data:responseData userInfo:@{ RFAPIDefineExpireKey : @(expire) } storagePolicy:NSURLCacheStorageAllowed]; - [self storeCachedResponse:cachedResponse forRequest:request]; - - _dout_debug(@"RFAPICache current usage: disk = %lu, memory = %lu", (unsigned long)self.currentDiskUsage, (unsigned long)self.currentMemoryUsage); -} - -@end diff --git a/RFAPIDefine/RFAPIDefine.m b/RFAPIDefine/RFAPIDefine.m deleted file mode 100644 index a39f39c..0000000 --- a/RFAPIDefine/RFAPIDefine.m +++ /dev/null @@ -1,152 +0,0 @@ - -#import "RFAPIDefine.h" -#import "dout.h" - -@implementation RFAPIDefine - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p, name = %@, path = %@>", self.class, (void *)self, self.name, self.path]; -} - -- (NSString *)debugDescription { - return [NSString stringWithFormat:@"<%@: %p,\n" - "\t name = %@,\n" - "\t baseURL = %@,\n" - "\t pathPrefix = %@,\n" - "\t path = %@,\n" - "\t method = %@,\n" - "\t HTTPRequestHeaders = %@,\n" - "\t defaultParameters = %@,\n" - "\t needsAuthorization = %@,\n" - "\t requestSerializerClass = %@,\n" - "\t cachePolicy = %d,\n" - "\t expire = %f,\n" - "\t offlinePolicy = %d,\n" - "\t responseSerializerClass = %@,\n" - "\t responseExpectType = %@,\n" - "\t responseAcceptNull = %@,\n" - "\t responseClass = %@,\n" - "\t userInfo = %@\n" - "\t notes = %@\n" - ">", self.class, (void *)self, self.name, - self.baseURL, self.pathPrefix, self.path, self.method, - self.HTTPRequestHeaders, self.defaultParameters, @(self.needsAuthorization), - self.responseSerializerClass, - self.cachePolicy, self.expire, self.offlinePolicy, - self.responseSerializerClass, @(self.responseExpectType), @(self.responseAcceptNull), self.responseClass, - self.userInfo, self.notes]; -} - -- (void)setBaseURL:(NSURL *)baseURL { - if (_baseURL != baseURL) { - // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected - if (baseURL.path.length && ![baseURL.absoluteString hasSuffix:@"/"]) { - baseURL = [baseURL URLByAppendingPathComponent:@""]; - } - - _baseURL = baseURL; - } -} - -- (void)setMethod:(NSString *)method { - if (!method) { - _method = nil; - return; - } - - RFAssert(method.length, @"Method can not be empty string."); - - if (_method != method) { - _method = [method uppercaseString]; - } -} - -#pragma mark - NSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - self = [self init]; - if (!self) { - return nil; - } - - self.name = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, name)]; - self.baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:@keypath(self, baseURL)]; - self.pathPrefix = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, pathPrefix)]; - self.path = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, path)]; - self.method = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, method)]; - self.HTTPRequestHeaders = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@keypath(self, HTTPRequestHeaders)]; - self.defaultParameters = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@keypath(self, defaultParameters)]; - self.needsAuthorization = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, needsAuthorization)] boolValue]; - self.requestSerializerClass = NSClassFromString((id)[decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, requestSerializerClass)]); - self.cachePolicy = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, cachePolicy)] shortValue]; - self.expire = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, expire)] doubleValue]; - self.offlinePolicy = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, offlinePolicy)] shortValue]; - self.responseSerializerClass = NSClassFromString((id)[decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, responseSerializerClass)]); - self.responseExpectType = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, responseExpectType)] shortValue]; - self.responseAcceptNull = [[decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, responseExpectType)] boolValue]; - self.responseClass = NSClassFromString((id)[decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, responseClass)]); - self.userInfo = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@keypath(self, userInfo)]; - self.notes = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, notes)]; - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.name forKey:@keypath(self, name)]; - [aCoder encodeObject:self.baseURL forKey:@keypath(self, baseURL)]; - [aCoder encodeObject:self.pathPrefix forKey:@keypath(self, pathPrefix)]; - [aCoder encodeObject:self.path forKey:@keypath(self, path)]; - [aCoder encodeObject:self.method forKey:@keypath(self, method)]; - [aCoder encodeObject:self.HTTPRequestHeaders forKey:@keypath(self, HTTPRequestHeaders)]; - [aCoder encodeObject:self.defaultParameters forKey:@keypath(self, defaultParameters)]; - [aCoder encodeObject:@(self.needsAuthorization) forKey:@keypath(self, needsAuthorization)]; - [aCoder encodeObject:NSStringFromClass(self.requestSerializerClass) forKey:@keypath(self, requestSerializerClass)]; - [aCoder encodeObject:@(self.cachePolicy) forKey:@keypath(self, cachePolicy)]; - [aCoder encodeObject:@(self.expire) forKey:@keypath(self, expire)]; - [aCoder encodeObject:@(self.offlinePolicy) forKey:@keypath(self, offlinePolicy)]; - [aCoder encodeObject:NSStringFromClass(self.responseSerializerClass) forKey:@keypath(self, responseSerializerClass)]; - [aCoder encodeObject:@(self.responseExpectType) forKey:@keypath(self, responseExpectType)]; - [aCoder encodeObject:@(self.responseAcceptNull) forKey:@keypath(self, responseAcceptNull)]; - [aCoder encodeObject:NSStringFromClass(self.responseClass) forKey:@keypath(self, responseClass)]; - [aCoder encodeObject:self.userInfo forKey:@keypath(self, userInfo)]; - [aCoder encodeObject:self.notes forKey:@keypath(self, notes)]; -} - - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone { - RFAPIDefine *clone = [(RFAPIDefine *)[self.class allocWithZone:zone] init]; - - clone.name = self.name; - - clone.baseURL = self.baseURL; - clone.pathPrefix = self.pathPrefix; - clone.path = self.path; - clone.method = self.method; - clone.HTTPRequestHeaders = self.HTTPRequestHeaders; - - clone.defaultParameters = self.defaultParameters; - clone.needsAuthorization = self.needsAuthorization; - clone.requestSerializerClass = self.requestSerializerClass; - - clone.cachePolicy = self.cachePolicy; - clone.expire = self.expire; - clone.offlinePolicy = self.offlinePolicy; - - clone.responseSerializerClass = self.responseSerializerClass; - clone.responseExpectType = self.responseExpectType; - clone.responseAcceptNull = self.responseAcceptNull; - clone.responseClass = self.responseClass; - - clone.userInfo = self.userInfo; - clone.notes = self.notes; - - return clone; -} - -@end diff --git a/RFAPIDefine/RFAPIDefineConfigFileKeys.h b/RFAPIDefine/RFAPIDefineConfigFileKeys.h deleted file mode 100644 index f751fba..0000000 --- a/RFAPIDefine/RFAPIDefineConfigFileKeys.h +++ /dev/null @@ -1,36 +0,0 @@ -/*! - RFAPIDefineConfigFileKeys - RFAPI - - Copyright (c) 2014, 2018 BB9z - https://github.com/RFUI/RFAPI - - The MIT License (MIT) - http://www.opensource.org/licenses/mit-license.php - */ -#import - -extern NSString *const RFAPIDefineDefaultKey; /// DEFAULT - -extern NSString *const RFAPIDefineNameKey; /// Name -extern NSString *const RFAPIDefineBaseKey; /// Base -extern NSString *const RFAPIDefinePathPrefixKey; /// Path Prefix -extern NSString *const RFAPIDefinePathKey; /// Path -extern NSString *const RFAPIDefineMethodKey; /// Method -extern NSString *const RFAPIDefineHeadersKey; /// Headers - -extern NSString *const RFAPIDefineParametersKey; /// Parameters -extern NSString *const RFAPIDefineAuthorizationKey; /// Authorization -extern NSString *const RFAPIDefineRequestSerializerKey; /// Serializer - -extern NSString *const RFAPIDefineCachePolicyKey; /// Cache Policy -extern NSString *const RFAPIDefineExpireKey; /// Expire -extern NSString *const RFAPIDefineOfflinePolicyKey; /// Offline Policy - -extern NSString *const RFAPIDefineResponseSerializerKey; /// Response Serializer -extern NSString *const RFAPIDefineResponseTypeKey; /// Response Type -extern NSString *const RFAPIDefineResponseAcceptNullKey; /// Response Accept Null -extern NSString *const RFAPIDefineResponseClassKey; /// Response Class - -extern NSString *const RFAPIDefineUserInfoKey; /// User Info -extern NSString *const RFAPIDefineNotesKey; /// Notes diff --git a/RFAPIDefine/RFAPIDefineConfigFileKeys.m b/RFAPIDefine/RFAPIDefineConfigFileKeys.m deleted file mode 100644 index ef9007c..0000000 --- a/RFAPIDefine/RFAPIDefineConfigFileKeys.m +++ /dev/null @@ -1,26 +0,0 @@ -#import "RFAPIDefineConfigFileKeys.h" - -NSString *const RFAPIDefineDefaultKey = @"DEFAULT"; - -NSString *const RFAPIDefineNameKey = @"Name"; -NSString *const RFAPIDefineBaseKey = @"Base"; -NSString *const RFAPIDefinePathPrefixKey = @"Path Prefix"; -NSString *const RFAPIDefinePathKey = @"Path"; -NSString *const RFAPIDefineMethodKey = @"Method"; -NSString *const RFAPIDefineHeadersKey = @"Headers"; - -NSString *const RFAPIDefineParametersKey = @"Parameters"; -NSString *const RFAPIDefineAuthorizationKey = @"Authorization"; -NSString *const RFAPIDefineRequestSerializerKey = @"Serializer"; - -NSString *const RFAPIDefineCachePolicyKey = @"Cache Policy"; -NSString *const RFAPIDefineExpireKey = @"Expire"; -NSString *const RFAPIDefineOfflinePolicyKey = @"Offline Policy"; - -NSString *const RFAPIDefineResponseSerializerKey = @"Response Serializer"; -NSString *const RFAPIDefineResponseTypeKey = @"Response Type"; -NSString *const RFAPIDefineResponseAcceptNullKey = @"Response Accept Null"; -NSString *const RFAPIDefineResponseClassKey = @"Response Class"; - -NSString *const RFAPIDefineUserInfoKey = @"User Info"; -NSString *const RFAPIDefineNotesKey = @"Notes"; diff --git a/RFAPIDefine/RFAPIDefineManager.h b/RFAPIDefine/RFAPIDefineManager.h deleted file mode 100644 index 25ff188..0000000 --- a/RFAPIDefine/RFAPIDefineManager.h +++ /dev/null @@ -1,72 +0,0 @@ -/*! - RFAPIDefineManager - RFAPI - - Copyright (c) 2014-2016, 2018 BB9z - https://github.com/RFUI/RFAPI - - The MIT License (MIT) - http://www.opensource.org/licenses/mit-license.php - */ -#import -#import "RFAPIDefine.h" - -@class RFAPI; -@protocol AFURLRequestSerialization; -@protocol AFURLResponseSerialization; - -@interface RFAPIDefineManager : NSObject -@property (weak, nonatomic) RFAPI *master; - -/** - You cannot get default rule with RFAPIDefineDefaultKey. - */ -@property (readonly, nonatomic) NSMutableDictionary *defaultRule; - -/** - If you make any change in the default rule, you should call this method to make these changes take effect. - */ -- (void)setNeedsUpdateDefaultRule; - -- (void)setDefinesWithRulesInfo:(NSDictionary *)rulesDictionary; - -/** - Returns the define object with the specified name. - - You cannot get default rule with this method. - - @return A define object with it’s name. - */ -- (RFAPIDefine *)defineForName:(NSString *)defineName; - -#pragma mark - Access raw rule values -// You cannot modify default rule with these methods. - -- (id)valueForRule:(NSString *)key defineName:(NSString *)defineName; - -- (void)setValue:(id)value forRule:(NSString *)key defineName:(NSString *)defineName; -- (void)removeRule:(NSString *)key withDefineName:(NSString *)defineName; - -#pragma mark - Authorization values - -@property (readonly, nonatomic) NSMutableDictionary *authorizationHeader; -@property (readonly, nonatomic) NSMutableDictionary *authorizationParameters; - - -#pragma mark - RFAPI Support - -@property (strong, nonatomic) id defaultRequestSerializer; - -@property (strong, nonatomic) id defaultResponseSerializer; - -- (NSURL *)requestURLForDefine:(RFAPIDefine *)define parameters:(NSMutableDictionary *)parameters error:(NSError *__autoreleasing *)error; - -- (id)requestSerializerForDefine:(RFAPIDefine *)define; -- (id)responseSerializerForDefine:(RFAPIDefine *)define; - -@end - -@interface RFAPIDefine (RFConfigFile) -- (instancetype)initWithRule:(NSDictionary *)rule name:(NSString *)name; - -@end diff --git a/RFAPIDefine/RFAPIDefineManager.m b/RFAPIDefine/RFAPIDefineManager.m deleted file mode 100644 index 8fe2995..0000000 --- a/RFAPIDefine/RFAPIDefineManager.m +++ /dev/null @@ -1,326 +0,0 @@ - -#import "RFAPIDefineManager.h" -#import "RFAPI.h" -#import "RFAPIDefineConfigFileKeys.h" -#import "NSDictionary+RFKit.h" - -#import "AFURLRequestSerialization.h" -#import "AFURLResponseSerialization.h" - -@interface RFAPIDefineManager () -@property (strong, nonatomic) NSCache *defineCache; - -@property (strong, nonatomic, readwrite) NSMutableDictionary *defaultRule; -@property (strong, nonatomic, readwrite) NSMutableDictionary *rawRules; - -@property (strong, nonatomic, readwrite) NSMutableDictionary *authorizationHeader; -@property (strong, nonatomic, readwrite) NSMutableDictionary *authorizationParameters; - -@end - -@implementation RFAPIDefineManager -RFInitializingRootForNSObject - -+ (NSRegularExpression *)cachedPathParameterRegularExpression { - static NSRegularExpression *sharedInstance = nil; - static dispatch_once_t oncePredicate; - dispatch_once(&oncePredicate, ^{ - sharedInstance = [[NSRegularExpression alloc] initWithPattern:@"\\{\\w+\\}" options:NSRegularExpressionAnchorsMatchLines error:nil]; - RFAssert(sharedInstance, @"Cannot create path parameter regular expression"); - }); - return sharedInstance; -} - -- (void)onInit { - _defineCache = [[NSCache alloc] init]; - _defineCache.name = @"RFAPIDefineCache"; - - _defaultRule = [[NSMutableDictionary alloc] initWithCapacity:20]; - _rawRules = [[NSMutableDictionary alloc] initWithCapacity:50]; - _authorizationHeader = [[NSMutableDictionary alloc] initWithCapacity:3]; - _authorizationParameters = [[NSMutableDictionary alloc] initWithCapacity:3]; -} -- (void)afterInit { -} - -- (void)setNeedsUpdateDefaultRule { - [self.defineCache removeAllObjects]; -} - -- (void)setDefinesWithRulesInfo:(NSDictionary *)rules { - [self.defineCache removeAllObjects]; - - // Check and add. - [rules enumerateKeysAndObjectsUsingBlock:^(NSString *name, NSDictionary *rule, BOOL *stop) { - RFAPIDefine *define = [[RFAPIDefine alloc] initWithRule:rule name:name]; - if (define) { - if ([name isEqualToString:RFAPIDefineDefaultKey]) { - [self.defaultRule setDictionary:rule]; - } - - (self.rawRules)[name] = rule; - } - else { - dout_warning(@"Bad rule(%@): %@", name, rule); - } - }]; -} - -- (NSDictionary *)mergedRuleForName:(NSString *)defineName { - NSDictionary *rule = self.rawRules[defineName]; - if (!rule) { - dout_warning(@"Can not find a rule with name: %@", defineName); - return nil; - } - - NSMutableDictionary *mergedRule = [self.defaultRule mutableCopy]; - [mergedRule addEntriesFromDictionary:rule]; - return mergedRule; -} - -- (RFAPIDefine *)defineForName:(NSString *)defineName { - NSParameterAssert(defineName.length); - - RFAPIDefine *define = [self.defineCache objectForKey:defineName]; - if (define) { - return define; - } - - NSDictionary *rule = [self mergedRuleForName:defineName]; - if (!rule) { - return nil; - } - - define = [[RFAPIDefine alloc] initWithRule:rule name:defineName]; - if (!define) { - return nil; - } - - [self.defineCache setObject:define forKey:defineName]; - return define; -} - -#pragma mark - Access raw rule values - -- (id)valueForRule:(NSString *)key defineName:(NSString *)defineName { - return self.rawRules[defineName][key]; -} - -- (void)setValue:(id)value forRule:(NSString *)key defineName:(NSString *)defineName { - NSMutableDictionary *rule = [self.rawRules[defineName] mutableCopy]; - [rule rf_setObject:value forKey:key]; - self.rawRules[defineName] = rule; -} - -- (void)removeRule:(NSString *)key withDefineName:(NSString *)defineName { - NSMutableDictionary *dict = [self.rawRules[defineName] mutableCopy]; - [dict removeObjectForKey:key]; - self.rawRules[defineName] = dict; -} - -#pragma mark - RFAPI Support - -- (NSURL *)requestURLForDefine:(RFAPIDefine *)define parameters:(NSMutableDictionary *)parameters error:(NSError *__autoreleasing *)error { - NSMutableString *path = [define.path mutableCopy]; - - // Replace {PARAMETER} in path - NSArray *matches = [[RFAPIDefineManager cachedPathParameterRegularExpression] matchesInString:path options:kNilOptions range:NSMakeRange(0, path.length)]; - - for (NSTextCheckingResult *match in matches.reverseObjectEnumerator) { - NSRange keyRange = match.range; - keyRange.location++; - keyRange.length -= 2; - NSString *key = [path substringWithRange:keyRange]; - - id parameter = parameters[key]; - if (parameter) { - NSString *encodedParameter = [[parameter description] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; - [path replaceCharactersInRange:match.range withString:encodedParameter]; - [parameters removeObjectForKey:key]; - } - else { - [path replaceCharactersInRange:match.range withString:@""]; - } - } - - NSURL *url; - if ([path hasPrefix:@"http://"] || [path hasPrefix:@"https://"]) { - url = [NSURL URLWithString:path]; - } - else { - NSString *URLString = define.pathPrefix? [define.pathPrefix stringByAppendingString:path] : path; - url = [NSURL URLWithString:URLString relativeToURL:define.baseURL]; - } - - if (!url) { -#if RFDEBUG - dout_error(@"无法拼接路径 %@ 到 %@\n请检查接口定义", path, define.baseURL); -#endif - if (error) { - *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:@{ - NSLocalizedDescriptionKey : @"内部错误,无法创建请求", - NSLocalizedFailureReasonErrorKey : @"很可能是应用 bug", - NSLocalizedRecoverySuggestionErrorKey : @"请再试一次,如果依旧请尝试重启应用。给您带来不便,敬请谅解" - }]; - } - return nil; - } - - if (parameters[RFAPIRequestForceQuryStringParametersKey]) { - NSDictionary *forceQuryStringParameters = parameters[RFAPIRequestForceQuryStringParametersKey]; - [parameters removeObjectForKey:RFAPIRequestForceQuryStringParametersKey]; - NSMutableArray *queryStringPair = [NSMutableArray array]; - for (NSString *key in forceQuryStringParameters.allKeys) { - [queryStringPair addObject:[NSString stringWithFormat:@"%@=%@", key, forceQuryStringParameters[key]]]; - } - NSString *query = [queryStringPair componentsJoinedByString:@"&"]; - url = [NSURL URLWithString:[url.absoluteString stringByAppendingFormat:url.query.length ? @"&%@" : @"?%@", query]]; - } - - return url; -} - -- (id)requestSerializerForDefine:(RFAPIDefine *)define { - if (define.requestSerializerClass) { - return [define.requestSerializerClass serializer]; - } - return self.defaultRequestSerializer; -} - -- (id)responseSerializerForDefine:(RFAPIDefine *)define { - if (define.responseSerializerClass) { - return [define.responseSerializerClass serializer]; - } - return self.defaultResponseSerializer; -} - -- (id)defaultRequestSerializer { - if (!_defaultRequestSerializer) { - _defaultRequestSerializer = [AFHTTPRequestSerializer serializer]; - } - return _defaultRequestSerializer; -} - -- (id)defaultResponseSerializer { - if (!_defaultResponseSerializer) { - _defaultResponseSerializer = [AFJSONResponseSerializer serializer]; - } - return _defaultResponseSerializer; -} - -@end - - -@implementation RFAPIDefine (RFConfigFile) - -- (instancetype)initWithRule:(NSDictionary *)rule name:(NSString *)name { - NSParameterAssert(name); - NSParameterAssert(rule); - self = [self init]; - if (self) { - self.name = name; - - id value; - -#define RFAPIDefineConfigFileValue_(KEY)\ - value = nil;\ - if ((value = rule[KEY])) - -#define RFAPIDefineConfigFileProperty_(PROPERTY, KEY)\ - RFAPIDefineConfigFileValue_(KEY) {\ - self.PROPERTY = value;\ - } - -#define RFAPIDefineConfigFileDictionaryProperty_(PROPERTY, KEY)\ - RFAPIDefineConfigFileValue_(KEY) {\ - if (![value isKindOfClass:[NSDictionary class]]) {\ - dout_error(@"%@ must be a dictionary.", KEY);\ - return nil;\ - }\ - self.PROPERTY = value;\ - } - -#define RFAPIDefineConfigFileClassProperty_(PROPERTY, KEY)\ - RFAPIDefineConfigFileValue_(KEY) {\ - Class aClass = NSClassFromString(value);\ - if (aClass) {\ - self.PROPERTY = aClass;\ - }\ - else {\ - dout_warning(@"Can not get class from name: %@", value);\ - }\ - } - -#define RFAPIDefineConfigFileEnumCase_(PROPERTY, ENUM)\ - case ENUM:\ - self.PROPERTY = ENUM;\ - break; - - RFAPIDefineConfigFileValue_(RFAPIDefineBaseKey) { - NSURL *url = [NSURL URLWithString:value]; - if (url) { - self.baseURL = url; - } - } - - RFAPIDefineConfigFileProperty_(pathPrefix, RFAPIDefinePathPrefixKey) - RFAPIDefineConfigFileProperty_(path, RFAPIDefinePathKey) - RFAPIDefineConfigFileProperty_(method, RFAPIDefineMethodKey) - RFAPIDefineConfigFileDictionaryProperty_(HTTPRequestHeaders, RFAPIDefineHeadersKey) - - RFAPIDefineConfigFileDictionaryProperty_(defaultParameters, RFAPIDefineParametersKey) - - RFAPIDefineConfigFileValue_(RFAPIDefineAuthorizationKey) { - self.needsAuthorization = [value boolValue]; - } - RFAPIDefineConfigFileClassProperty_(requestSerializerClass, RFAPIDefineRequestSerializerKey) - - RFAPIDefineConfigFileValue_(RFAPIDefineCachePolicyKey) { - switch ([value shortValue]) { - RFAPIDefineConfigFileEnumCase_(cachePolicy, RFAPICachePolicyDefault) - RFAPIDefineConfigFileEnumCase_(cachePolicy, RFAPICachePolicyProtocol) - RFAPIDefineConfigFileEnumCase_(cachePolicy, RFAPICachePolicyAlways) - RFAPIDefineConfigFileEnumCase_(cachePolicy, RFAPICachePolicyExpire) - RFAPIDefineConfigFileEnumCase_(cachePolicy, RFAPICachePolicyNoCache) - default: - dout_error(@"Unknow cache policy: %@", value) - return nil; - } - } - - RFAPIDefineConfigFileValue_(RFAPIDefineExpireKey) { - self.expire = [value intValue]; - } - - RFAPIDefineConfigFileValue_(RFAPIDefineOfflinePolicyKey) { - switch ([value shortValue]) { - RFAPIDefineConfigFileEnumCase_(offlinePolicy, RFAPIOfflinePolicyDefault) - RFAPIDefineConfigFileEnumCase_(offlinePolicy, RFAPIOfflinePolicyLoadCache) - default: - dout_error(@"Unknow offline policy: %@", value) - return nil; - } - } - - RFAPIDefineConfigFileValue_(RFAPIDefineResponseTypeKey) { - self.responseExpectType = [value intValue]; - } - RFAPIDefineConfigFileValue_(RFAPIDefineResponseAcceptNullKey) { - self.responseAcceptNull = [value boolValue]; - } - RFAPIDefineConfigFileClassProperty_(responseSerializerClass, RFAPIDefineResponseSerializerKey) - - RFAPIDefineConfigFileClassProperty_(responseClass, RFAPIDefineResponseClassKey) - RFAPIDefineConfigFileDictionaryProperty_(userInfo, RFAPIDefineUserInfoKey) - RFAPIDefineConfigFileProperty_(notes, RFAPIDefineNotesKey) - -#undef RFAPIDefineConfigFileValue_ -#undef RFAPIDefineConfigFileProperty_ -#undef RFAPIDefineConfigFileDictionaryProperty_ -#undef RFAPIDefineConfigFileClassProperty_ -#undef RFAPIDefineConfigFileEnumCase_ - } - return self; -} - -@end diff --git a/Sources/RFAPI/Compatible/RFAPICompatible.h b/Sources/RFAPI/Compatible/RFAPICompatible.h new file mode 100644 index 0000000..92b600b --- /dev/null +++ b/Sources/RFAPI/Compatible/RFAPICompatible.h @@ -0,0 +1,34 @@ +/* + RFAPICompatible + RFAPI + + Copyright © 2020 BB9z + https://github.com/RFUI/RFAPI + + The MIT License (MIT) + http://www.opensource.org/licenses/mit-license.php + */ + +#import "RFAPI.h" + +/// Map v1 RFAPIControl to RFAPIRequestConext +#ifndef RFAPIControl +#define RFAPIControl RFAPIRequestConext +#endif + +@interface RFAPI (V1Compatible) + +- (nullable id)requestWithName:(nonnull NSString *)APIName + parameters:(nullable NSDictionary *)parameters + controlInfo:(nullable RFAPIControl *)controlInfo + success:(void (^_Nullable)(id _Nullable operation, id _Nullable responseObject))success + failure:(void (^_Nullable)(id _Nullable operation, NSError *_Nonnull error))failure + completion:(void (^_Nullable)(id _Nullable operation))completion; + +@end + +@interface RFAPIControl (V1Compatible) + +- (nonnull id)initWithIdentifier:(nonnull NSString *)identifier loadingMessage:(nullable NSString *)message; + +@end diff --git a/Sources/RFAPI/Compatible/RFAPICompatible.m b/Sources/RFAPI/Compatible/RFAPICompatible.m new file mode 100644 index 0000000..593ab96 --- /dev/null +++ b/Sources/RFAPI/Compatible/RFAPICompatible.m @@ -0,0 +1,40 @@ + +#import "RFAPICompatible.h" + +@implementation RFAPI (V1Compatible) + +- (id)requestWithName:(NSString *)APIName parameters:(NSDictionary *)parameters controlInfo:(RFAPIRequestConext *)controlInfo success:(void (^)(id _Nullable, id _Nullable))success failure:(void (^)(id _Nullable, NSError * _Nonnull))failure completion:(void (^)(id _Nullable))completion { + return [self requestWithName:APIName context:^(RFAPIRequestConext *c) { + c.parameters = parameters; + if (controlInfo) { + c.activityMessage = controlInfo.activityMessage; + c.identifier = controlInfo.identifier; + c.groupIdentifier = controlInfo.groupIdentifier; + c.requestCustomization = controlInfo.requestCustomization; + } + c.success = success; + c.failure = failure; + if (completion) { + c.finished = ^(id _Nullable task, BOOL success) { + completion(task); + }; + } + }]; +} + +@end + + +#import + +@implementation RFAPIControl (V1Compatible) + +- (id)initWithIdentifier:(NSString *)identifier loadingMessage:(NSString *)message { + self = [super init]; + self.identifier = identifier; + RFNetworkActivityMessage *m = [[RFNetworkActivityMessage alloc] initWithIdentifier:identifier message:message status:RFNetworkActivityStatusLoading]; + self.activityMessage = m; + return self; +} + +@end diff --git a/RFAPIDefine/RFAPIDefine.h b/Sources/RFAPI/Define/RFAPIDefine.h similarity index 59% rename from RFAPIDefine/RFAPIDefine.h rename to Sources/RFAPI/Define/RFAPIDefine.h index 1f27ccb..5c38b4a 100644 --- a/RFAPIDefine/RFAPIDefine.h +++ b/Sources/RFAPI/Define/RFAPIDefine.h @@ -1,48 +1,59 @@ -/*! +/* RFAPIDefine RFAPI - Copyright (c) 2014, 2018 BB9z + Copyright © 2014, 2018-2020 BB9z https://github.com/RFUI/RFAPI The MIT License (MIT) http://www.opensource.org/licenses/mit-license.php */ #import -#import "RFAPIDefineConfigFileKeys.h" +typedef NSString * RFAPIName NS_EXTENSIBLE_STRING_ENUM; + +// todo: Default define + +/** + A define object is used to describe all aspects of an API: what the URL is, how to send the request sent, how to handle the response, and so on. + */ @interface RFAPIDefine : NSObject < NSCopying, NSSecureCoding > /// Used to get a deine from a RFAPIDefineManager -@property (copy, nonatomic) NSString *name; +@property (copy, nullable) RFAPIName name; -/// HTTP base URL -@property (copy, nonatomic) NSURL *baseURL; +/** + HTTP base URL + + If there are other paths behind the host, a terminal slash should added at the end of the url. + E.g: "http://example.com/base" is equal to "http://example.com", the correct one is "http://example.com/base/". + */ +@property (copy, nullable) NSURL *baseURL; /// -@property (copy, nonatomic) NSString *pathPrefix; +@property (copy, nullable) NSString *pathPrefix; /// -@property (copy, nonatomic) NSString *path; +@property (copy, nullable) NSString *path; /// HTTP Method -@property (copy, nonatomic) NSString *method; +@property (copy, nullable, nonatomic) NSString *method; #pragma mark - Request /// HTTP headers to append -@property (copy, nonatomic) NSDictionary *HTTPRequestHeaders; +@property (copy, nullable) NSDictionary *HTTPRequestHeaders; /// Default HTTP request parameters -@property (copy, nonatomic) NSDictionary *defaultParameters; +@property (copy, nullable) NSDictionary *defaultParameters; /// If send authorization HTTP header or parameters @property (nonatomic) BOOL needsAuthorization; /// AFURLRequestSerialization class -@property (strong, nonatomic) Class requestSerializerClass; +@property (nullable) Class requestSerializerClass; #pragma mark - Cache @@ -53,20 +64,23 @@ typedef NS_ENUM(short, RFAPIDefineCachePolicy) { RFAPICachePolicyExpire = 3, /// 一段时间内不再请求 RFAPICachePolicyNoCache = 5 /// 无缓存,总是请求新数据 }; +/// @warning unimplemented @property (nonatomic) RFAPIDefineCachePolicy cachePolicy; /// Gives the date/time after which the cache is considered stale +/// @warning unimplemented @property (nonatomic) NSTimeInterval expire; typedef NS_ENUM(short, RFAPIDefineOfflinePolicy) { RFAPIOfflinePolicyDefault = 0, /// 不特殊处理 RFAPIOfflinePolicyLoadCache = 1 /// 返回缓存数据 }; +/// @warning unimplemented @property (nonatomic) RFAPIDefineOfflinePolicy offlinePolicy; #pragma mark - Response -@property (strong, nonatomic) Class responseSerializerClass; +@property (nullable) Class responseSerializerClass; typedef NS_ENUM(short, RFAPIDefineResponseExpectType) { RFAPIDefineResponseExpectDefault = 0, /// 不特殊处理 @@ -80,14 +94,18 @@ typedef NS_ENUM(short, RFAPIDefineResponseExpectType) { /// Accept null response @property (nonatomic) BOOL responseAcceptNull; -/// Expect JSONModel class -@property (strong, nonatomic) Class responseClass; +/// Expect class name +/// The model transformer uses this property to create special kind of model instances. +@property (nullable) NSString *responseClass; #pragma mark - /// User info -@property (copy, nonatomic) NSDictionary *userInfo; +@property (copy, nullable) NSDictionary *userInfo; /// Comment -@property (copy, nonatomic) NSString *notes; +@property (copy, nullable) NSString *notes; + +- (nonnull RFAPIDefine *)newDefineMergedDefault:(nonnull RFAPIDefine *)defaultDefine; + @end diff --git a/Sources/RFAPI/Define/RFAPIDefine.m b/Sources/RFAPI/Define/RFAPIDefine.m new file mode 100644 index 0000000..0dcaa25 --- /dev/null +++ b/Sources/RFAPI/Define/RFAPIDefine.m @@ -0,0 +1,221 @@ + +#import "RFAPIPrivate.h" + +@implementation RFAPIDefine + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, name = %@, path = %@>", self.class, (void *)self, self.name, self.path]; +} + +- (NSString *)debugDescription { + return [NSString stringWithFormat:@"<%@: %p,\n" + "\t name = %@,\n" + "\t baseURL = %@,\n" + "\t pathPrefix = %@,\n" + "\t path = %@,\n" + "\t method = %@,\n" + "\t HTTPRequestHeaders = %@,\n" + "\t defaultParameters = %@,\n" + "\t needsAuthorization = %@,\n" + "\t requestSerializerClass = %@,\n" + "\t cachePolicy = %d,\n" + "\t expire = %f,\n" + "\t offlinePolicy = %d,\n" + "\t responseSerializerClass = %@,\n" + "\t responseExpectType = %@,\n" + "\t responseAcceptNull = %@,\n" + "\t responseClass = %@,\n" + "\t userInfo = %@\n" + "\t notes = %@\n" + ">", self.class, (void *)self, self.name, + self.baseURL, self.pathPrefix, self.path, self.method, + self.HTTPRequestHeaders, self.defaultParameters, @(self.needsAuthorization), + self.responseSerializerClass, + self.cachePolicy, self.expire, self.offlinePolicy, + self.responseSerializerClass, @(self.responseExpectType), @(self.responseAcceptNull), self.responseClass, + self.userInfo, self.notes]; +} + +- (void)setMethod:(NSString *)method { + if (!method) { + _method = nil; + return; + } + NSAssert(method.length, @"Method can not be empty string."); + + if (_method != method) { + _method = [method uppercaseString]; + } +} + +#pragma mark - Wrapper Properties + +- (BOOL)needsAuthorization { + return self.needsAuthorizationValue.boolValue; +} +- (void)setNeedsAuthorization:(BOOL)needsAuthorization { + self.needsAuthorizationValue = @(needsAuthorization); +} + +- (RFAPIDefineCachePolicy)cachePolicy { + return self.cachePolicyValue.shortValue; +} +- (void)setCachePolicy:(RFAPIDefineCachePolicy)cachePolicy { + self.cachePolicyValue = @(cachePolicy); +} + +- (NSTimeInterval)expire { + return self.cacheExpireValue.doubleValue; +} +- (void)setExpire:(NSTimeInterval)expire { + self.cacheExpireValue = @(expire); +} + +- (RFAPIDefineOfflinePolicy)offlinePolicy { + return self.offlinePolicyValue.shortValue; +} +- (void)setOfflinePolicy:(RFAPIDefineOfflinePolicy)offlinePolicy { + self.offlinePolicyValue = @(offlinePolicy); +} + +- (RFAPIDefineResponseExpectType)responseExpectType { + return self.responseExpectTypeValue.shortValue; +} +- (void)setResponseExpectType:(RFAPIDefineResponseExpectType)responseExpectType { + self.responseExpectTypeValue = @(responseExpectType); +} + +- (BOOL)responseAcceptNull { + return self.responseAcceptNullValue.boolValue; +} +- (void)setResponseAcceptNull:(BOOL)responseAcceptNull { + self.responseAcceptNullValue = @(responseAcceptNull); +} + +#pragma mark - + +- (RFAPIDefine *)newDefineMergedDefault:(RFAPIDefine *)defaultDefine { + RFAPIDefine *ret = defaultDefine.copy; +#define _transf(INDEX, CONTEXT, PROPERTY) \ + if (self.PROPERTY) ret.PROPERTY = self.PROPERTY; + +#define _transf_properties(...) \ + metamacro_foreach_cxt(_transf, , , __VA_ARGS__) + + _transf_properties(name, + baseURL, + pathPrefix, + path, + method, + HTTPRequestHeaders, + defaultParameters, + needsAuthorizationValue, + requestSerializerClass, + userInfo, + notes) + _transf_properties(responseSerializerClass, + responseExpectTypeValue, + responseAcceptNullValue, + responseClass) + _transf_properties(cachePolicyValue, + cacheExpireValue, + offlinePolicyValue) + return ret; +} + +#pragma mark - NSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)decoder { + self = [self init]; + if (!self) { + return nil; + } + + self.name = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, name)]; + self.baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:@keypath(self, baseURL)]; + self.pathPrefix = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, pathPrefix)]; + self.path = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, path)]; + self.method = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, method)]; + self.HTTPRequestHeaders = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@keypath(self, HTTPRequestHeaders)]; + self.defaultParameters = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@keypath(self, defaultParameters)]; + self.needsAuthorizationValue = [decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, needsAuthorization)]; + self.requestSerializerClass = NSClassFromString((id)[decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, requestSerializerClass)]); + self.cachePolicyValue = [decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, cachePolicy)]; + self.cacheExpireValue = [decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, expire)]; + self.offlinePolicyValue = [decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, offlinePolicy)]; + self.responseSerializerClass = NSClassFromString((id)[decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, responseSerializerClass)]); + self.responseExpectTypeValue = [decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, responseExpectType)]; + self.responseAcceptNullValue = [decoder decodeObjectOfClass:[NSNumber class] forKey:@keypath(self, responseExpectType)]; + self.responseClass = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, responseClass)]; + self.userInfo = [decoder decodeObjectOfClass:[NSDictionary class] forKey:@keypath(self, userInfo)]; + self.notes = [decoder decodeObjectOfClass:[NSString class] forKey:@keypath(self, notes)]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.name forKey:@keypath(self, name)]; + [aCoder encodeObject:self.baseURL forKey:@keypath(self, baseURL)]; + [aCoder encodeObject:self.pathPrefix forKey:@keypath(self, pathPrefix)]; + [aCoder encodeObject:self.path forKey:@keypath(self, path)]; + [aCoder encodeObject:self.method forKey:@keypath(self, method)]; + [aCoder encodeObject:self.HTTPRequestHeaders forKey:@keypath(self, HTTPRequestHeaders)]; + [aCoder encodeObject:self.defaultParameters forKey:@keypath(self, defaultParameters)]; + [aCoder encodeObject:self.needsAuthorizationValue forKey:@keypath(self, needsAuthorization)]; + [aCoder encodeObject:self.cachePolicyValue forKey:@keypath(self, cachePolicy)]; + [aCoder encodeObject:self.cacheExpireValue forKey:@keypath(self, expire)]; + [aCoder encodeObject:self.offlinePolicyValue forKey:@keypath(self, offlinePolicy)]; + [aCoder encodeObject:self.responseExpectTypeValue forKey:@keypath(self, responseExpectType)]; + [aCoder encodeObject:self.responseClass forKey:@keypath(self, responseClass)]; + [aCoder encodeObject:self.responseAcceptNullValue forKey:@keypath(self, responseAcceptNull)]; + [aCoder encodeObject:self.userInfo forKey:@keypath(self, userInfo)]; + [aCoder encodeObject:self.notes forKey:@keypath(self, notes)]; + + Class aClass; + aClass = self.requestSerializerClass; + if (aClass) { + [aCoder encodeObject:NSStringFromClass(aClass) forKey:@keypath(self, requestSerializerClass)]; + } + aClass = self.responseSerializerClass; + if (aClass) { + [aCoder encodeObject:NSStringFromClass(aClass) forKey:@keypath(self, responseSerializerClass)]; + } +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + RFAPIDefine *clone = [(RFAPIDefine *)[self.class allocWithZone:zone] init]; + + clone.name = self.name; + + clone.baseURL = self.baseURL; + clone.pathPrefix = self.pathPrefix; + clone.path = self.path; + clone.method = self.method; + clone.HTTPRequestHeaders = self.HTTPRequestHeaders; + + clone.defaultParameters = self.defaultParameters; + clone.needsAuthorizationValue = self.needsAuthorizationValue; + clone.requestSerializerClass = self.requestSerializerClass; + + clone.cachePolicyValue = self.cachePolicyValue; + clone.cacheExpireValue = self.cacheExpireValue; + clone.offlinePolicyValue = self.offlinePolicyValue; + + clone.responseSerializerClass = self.responseSerializerClass; + clone.responseExpectTypeValue = self.responseExpectTypeValue; + clone.responseAcceptNullValue = self.responseAcceptNullValue; + clone.responseClass = self.responseClass; + + clone.userInfo = self.userInfo; + clone.notes = self.notes; + + return clone; +} + +@end diff --git a/Sources/RFAPI/Define/RFAPIDefineConfigFile.h b/Sources/RFAPI/Define/RFAPIDefineConfigFile.h new file mode 100644 index 0000000..23a7497 --- /dev/null +++ b/Sources/RFAPI/Define/RFAPIDefineConfigFile.h @@ -0,0 +1,62 @@ +/* + RFAPIDefineConfigFile + RFAPI + + Copyright © 2014, 2018-2020 BB9z + https://github.com/RFUI/RFAPI + + The MIT License (MIT) + http://www.opensource.org/licenses/mit-license.php + */ + +#import "RFAPIDefine.h" +#import "RFAPIDefineManager.h" + +typedef NSString * RFAPIDefineKey; +typedef NSDictionary * RFAPIDefineRawConfig; + +NS_ASSUME_NONNULL_BEGIN + +FOUNDATION_EXTERN RFAPIName const RFAPIDefineDefaultKey; /// DEFAULT + +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineNameKey; /// Name +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineBaseKey; /// Base +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefinePathPrefixKey; /// Path Prefix +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefinePathKey; /// Path +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineMethodKey; /// Method +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineHeadersKey; /// Headers + +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineParametersKey; /// Parameters +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineAuthorizationKey; /// Authorization +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineRequestSerializerKey; /// Serializer + +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineCachePolicyKey; /// Cache Policy +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineExpireKey; /// Expire +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineOfflinePolicyKey; /// Offline Policy + +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineResponseSerializerKey; /// Response Serializer +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineResponseTypeKey; /// Response Type +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineResponseAcceptNullKey; /// Response Accept Null +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineResponseClassKey; /// Response Class + +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineUserInfoKey; /// User Info +FOUNDATION_EXTERN RFAPIDefineKey const RFAPIDefineNotesKey; /// Notes + +NS_ASSUME_NONNULL_END + +@interface RFAPIDefine (RFConfigFile) + +/** + + */ +- (nonnull instancetype)initWithRule:(nonnull NSDictionary *)rule name:(nonnull NSString *)name; +@end + +@interface RFAPIDefineManager (RFConfigFile) + +/** + + */ +- (void)setDefinesWithRulesInfo:(nonnull NSDictionary *> *)rules; + +@end diff --git a/Sources/RFAPI/Define/RFAPIDefineConfigFile.m b/Sources/RFAPI/Define/RFAPIDefineConfigFile.m new file mode 100644 index 0000000..07303b4 --- /dev/null +++ b/Sources/RFAPI/Define/RFAPIDefineConfigFile.m @@ -0,0 +1,134 @@ + +#import "RFAPIDefineConfigFile.h" +#import "RFAPIPrivate.h" + +NSString *const RFAPIDefineDefaultKey = @"DEFAULT"; + +RFAPIDefineKey const RFAPIDefineNameKey = @"Name"; +RFAPIDefineKey const RFAPIDefineBaseKey = @"Base"; +RFAPIDefineKey const RFAPIDefinePathPrefixKey = @"Path Prefix"; +RFAPIDefineKey const RFAPIDefinePathKey = @"Path"; +RFAPIDefineKey const RFAPIDefineMethodKey = @"Method"; +RFAPIDefineKey const RFAPIDefineHeadersKey = @"Headers"; + +RFAPIDefineKey const RFAPIDefineParametersKey = @"Parameters"; +RFAPIDefineKey const RFAPIDefineAuthorizationKey = @"Authorization"; +RFAPIDefineKey const RFAPIDefineRequestSerializerKey = @"Serializer"; + +RFAPIDefineKey const RFAPIDefineCachePolicyKey = @"Cache Policy"; +RFAPIDefineKey const RFAPIDefineExpireKey = @"Expire"; +RFAPIDefineKey const RFAPIDefineOfflinePolicyKey = @"Offline Policy"; + +RFAPIDefineKey const RFAPIDefineResponseSerializerKey = @"Response Serializer"; +RFAPIDefineKey const RFAPIDefineResponseTypeKey = @"Response Type"; +RFAPIDefineKey const RFAPIDefineResponseAcceptNullKey = @"Response Accept Null"; +RFAPIDefineKey const RFAPIDefineResponseClassKey = @"Response Class"; + +RFAPIDefineKey const RFAPIDefineUserInfoKey = @"User Info"; +RFAPIDefineKey const RFAPIDefineNotesKey = @"Notes"; + + +@implementation RFAPIDefine (RFConfigFile) + +static inline NSString *_ruleStrKey(NSDictionary *rule, RFAPIDefineKey key) { + NSString *v = rule[key]; + if (![v isKindOfClass:NSString.class]) return nil; + return v; +} + +static inline NSNumber *_ruleNumberKey(NSDictionary *rule, RFAPIDefineKey key) { + NSNumber *v = rule[key]; + if (![v isKindOfClass:NSNumber.class]) return nil; + return v; +} + +static inline NSDictionary *_ruleDicKey(NSDictionary *rule, RFAPIDefineKey key) { + NSDictionary *v = rule[key]; + if (![v isKindOfClass:NSDictionary.class]) return nil; + return v; +} + +- (instancetype)initWithRule:(NSDictionary *)rule name:(NSString *)name { + NSParameterAssert(name); + NSParameterAssert(rule); + self = [self init]; + self.name = name; + + NSString *baseURLString = _ruleStrKey(rule, RFAPIDefineBaseKey); + if (baseURLString) { + self.baseURL = [NSURL URLWithString:baseURLString]; + } + self.pathPrefix = _ruleStrKey(rule, RFAPIDefinePathPrefixKey); + self.path = _ruleStrKey(rule, RFAPIDefinePathKey); + self.method = _ruleStrKey(rule, RFAPIDefineMethodKey); + + self.HTTPRequestHeaders = _ruleDicKey(rule, RFAPIDefineHeadersKey); + self.defaultParameters = _ruleDicKey(rule, RFAPIDefineParametersKey); + + self.needsAuthorizationValue = _ruleNumberKey(rule, RFAPIDefineAuthorizationKey); + NSString *class = _ruleStrKey(rule, RFAPIDefineRequestSerializerKey); + if (class) { + self.requestSerializerClass = NSClassFromString(class); + NSAssert(self.requestSerializerClass, @"Can not create requestSerializerClass from name: %@", class); + } + class = _ruleStrKey(rule, RFAPIDefineResponseSerializerKey); + if (class) { + self.responseSerializerClass = NSClassFromString(class); + NSAssert(self.responseSerializerClass, @"Can not create responseSerializerClass from name: %@", class); + } + self.cachePolicyValue = _ruleNumberKey(rule, RFAPIDefineCachePolicyKey); + self.cacheExpireValue = _ruleNumberKey(rule, RFAPIDefineExpireKey); + self.offlinePolicyValue = _ruleNumberKey(rule, RFAPIDefineOfflinePolicyKey); + self.responseExpectTypeValue = _ruleNumberKey(rule, RFAPIDefineResponseTypeKey); + self.responseAcceptNullValue = _ruleNumberKey(rule, RFAPIDefineResponseAcceptNullKey); + self.responseClass = _ruleStrKey(rule, RFAPIDefineResponseClassKey); + + self.userInfo = _ruleDicKey(rule, RFAPIDefineUserInfoKey); + self.notes = _ruleStrKey(rule, RFAPIDefineNotesKey); + + return self; +} + +@end + + +@implementation RFAPIDefineManager (RFConfigFile) + +- (void)setDefinesWithRulesInfo:(NSDictionary *> *)rules { + NSParameterAssert(rules); + + NSMutableDictionary *prules = [NSMutableDictionary.alloc initWithCapacity:64]; + __block NSInteger ruleCount = 0; + [rules enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *obj, BOOL *stop) { +#if RFDEBUG + NSAssert([obj isKindOfClass:NSDictionary.class], @"All items in define configuration should be dictionaries. Current: %@", obj); +#endif + if ([key hasPrefix:@"@"]) { +#if RFDEBUG + for (RFAPIDefineRawConfig item in obj.allValues) { + NSAssert([item isKindOfClass:NSDictionary.class], @"Items that begin with the @ character are treated as group. All items in a group should be dictionaries. Current: %@", item); + } +#endif + [prules addEntriesFromDictionary:obj]; + + ruleCount += obj.count; + } + else { + prules[key] = obj; + ruleCount++; + } + }]; + RFAssert(ruleCount == prules.count, @"There are defines with the same name.") + + NSMutableArray *defines = [NSMutableArray.alloc initWithCapacity:prules.count]; + RFAPIDefineRawConfig defaultRule = prules[RFAPIDefineDefaultKey]; + [prules enumerateKeysAndObjectsUsingBlock:^(RFAPIName _Nonnull key, RFAPIDefineRawConfig _Nonnull obj, BOOL * _Nonnull stop) { + [defines addObject:[RFAPIDefine.alloc initWithRule:obj name:key]]; + }]; + if (defaultRule) { + self.defaultDefine = [RFAPIDefine.alloc initWithRule:defaultRule name:RFAPIDefineDefaultKey]; + } + self.defines = defines; +} + +@end diff --git a/Sources/RFAPI/Define/RFAPIDefineManager.h b/Sources/RFAPI/Define/RFAPIDefineManager.h new file mode 100644 index 0000000..90bea78 --- /dev/null +++ b/Sources/RFAPI/Define/RFAPIDefineManager.h @@ -0,0 +1,63 @@ +/* + RFAPIDefineManager + RFAPI + + Copyright © 2014-2016, 2018-2019 BB9z + https://github.com/RFUI/RFAPI + + The MIT License (MIT) + http://www.opensource.org/licenses/mit-license.php + */ + +#import "RFAPIDefine.h" + +@protocol AFURLRequestSerialization; +@protocol AFURLResponseSerialization; + +@interface RFAPIDefineManager : NSObject + +/** + Returns the define object with the specified name. + + You cannot get default rule with this method. + + The return value is not copied. If you modify the return value, the instance in the define manager is also modified. + + @return A define object with it's name. + */ +- (nullable RFAPIDefine *)defineForName:(nonnull RFAPIName)defineName; + +@property (nullable) RFAPIDefine *defaultDefine; + +/** + getter get defines in the define manager. + + setter update defines in the define manager. Passing `nil` has no effect. + */ +@property (copy, null_resettable, nonatomic) NSArray *defines; + +#pragma mark - Authorization + +/// Additional HTTP headers sent with APIs that requires authorization. +@property (readonly, nonnull) NSMutableDictionary *authorizationHeader; + +/// Additional parameters sent with APIs that requires authorization. +@property (readonly, nonnull) NSMutableDictionary *authorizationParameters; + +#pragma mark - Request + +/// Default is `AFJSONRequestSerializer`. +@property (null_resettable, nonatomic) id defaultRequestSerializer; + +- (nonnull id)requestSerializerForDefine:(nullable RFAPIDefine *)define; + +- (nullable NSURL *)requestURLForDefine:(nonnull RFAPIDefine *)define parameters:(nullable NSMutableDictionary *)parameters error:(NSError *__nullable __autoreleasing *__nullable)error; + +#pragma mark Response + +/// Default is a `AFJSONResponseSerializer` with allow fragments option. +@property (null_resettable, nonatomic) id defaultResponseSerializer; + +- (nonnull id)responseSerializerForDefine:(nullable RFAPIDefine *)define; + +@end diff --git a/Sources/RFAPI/Define/RFAPIDefineManager.m b/Sources/RFAPI/Define/RFAPIDefineManager.m new file mode 100644 index 0000000..25eb387 --- /dev/null +++ b/Sources/RFAPI/Define/RFAPIDefineManager.m @@ -0,0 +1,156 @@ + +#import "RFAPIDefineManager.h" +#import "RFAPIPrivate.h" +#import "RFAPIDefineConfigFile.h" +#import +#import + + +@interface RFAPIDefineManager () +@property (nonnull) NSMutableDictionary *_defines; +@end + +@implementation RFAPIDefineManager + ++ (NSRegularExpression *)cachedPathParameterRegularExpression { + static NSRegularExpression *sharedInstance = nil; + static dispatch_once_t oncePredicate; + dispatch_once(&oncePredicate, ^{ + sharedInstance = [[NSRegularExpression alloc] initWithPattern:@"\\{[\\w-]+\\}" options:NSRegularExpressionAnchorsMatchLines error:nil]; + NSAssert(sharedInstance, @"Cannot create path parameter regular expression"); + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + __defines = [NSMutableDictionary.alloc initWithCapacity:64]; + _authorizationHeader = [NSMutableDictionary.alloc initWithCapacity:2]; + _authorizationParameters = [NSMutableDictionary.alloc initWithCapacity:2]; + } + return self; +} + +- (RFAPIDefine *)defineForName:(RFAPIName)defineName { + if (!defineName) return nil; + @synchronized(self) { + return self._defines[defineName]; + } +} + +@dynamic defines; +- (NSArray *)defines { + @synchronized(self) { + return self._defines.allValues; + } +} +- (void)setDefines:(NSArray *)defines { + @synchronized(self) { + NSMutableDictionary *newDefines = self._defines; + [newDefines removeAllObjects]; + + for (RFAPIDefine *d in defines) { + RFAPIName name = d.name; + if (!name) { + NSAssert(false, @"API Define no name: %@", d); + continue; + } + newDefines[name] = d; + } + } +} + +#pragma mark - RFAPI Support + +- (NSURL *)requestURLForDefine:(RFAPIDefine *)define parameters:(NSMutableDictionary *)parameters error:(NSError *__autoreleasing *)error { + NSMutableString *path = define.path.mutableCopy; + if (!path) { + if (error) { + *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:@{ + NSLocalizedDescriptionKey : @"API define path is nil." + }]; + } + return nil; + } + + // Replace {PARAMETER} in path + NSArray *matches = [RFAPIDefineManager.cachedPathParameterRegularExpression matchesInString:path options:kNilOptions range:NSMakeRange(0, path.length)]; + + for (NSTextCheckingResult *match in matches.reverseObjectEnumerator) { + NSRange keyRange = match.range; + keyRange.location++; + keyRange.length -= 2; + NSString *key = [path substringWithRange:keyRange]; + + id parameter = parameters[key]; + if (parameter) { + NSString *encodedParameter = [[(NSObject *)parameter description] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + [path replaceCharactersInRange:match.range withString:encodedParameter]; + [parameters removeObjectForKey:key]; + } + else { + [path replaceCharactersInRange:match.range withString:@""]; + } + } + + NSURL *url = [NSURL.alloc initWithString:path]; + if (!url.scheme.length) { + NSString *URLString = define.pathPrefix? [define.pathPrefix stringByAppendingString:path] : path; + url = [NSURL.alloc initWithString:URLString relativeToURL:define.baseURL]; + } + + if (!url) { + RFAPILogError_(@"无法拼接路径 %@ 到 %@\n请检查接口定义", path, define.baseURL); + if (error) { + *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:@{ + NSLocalizedDescriptionKey : @"内部错误,无法创建请求", + NSLocalizedFailureReasonErrorKey : @"很可能是应用 bug", + NSLocalizedRecoverySuggestionErrorKey : @"请再试一次,如果依旧请尝试重启应用。给您带来不便,敬请谅解" + }]; + } + return nil; + } + + NSDictionary *forceQuryStringParameters = parameters[RFAPIRequestForceQuryStringParametersKey]; + if (forceQuryStringParameters) { + [parameters removeObjectForKey:RFAPIRequestForceQuryStringParametersKey]; + NSMutableArray *queryStringPair = [NSMutableArray.alloc initWithCapacity:forceQuryStringParameters.count]; + for (NSString *key in forceQuryStringParameters.allKeys) { + [queryStringPair addObject:[NSString stringWithFormat:@"%@=%@", key, forceQuryStringParameters[key]]]; + } + NSString *query = [queryStringPair componentsJoinedByString:@"&"]; + NSString *urlString = [url.absoluteString stringByAppendingFormat:url.query.length ? @"&%@" : @"?%@", query]; + url = [NSURL.alloc initWithString:urlString]; + } + + return url; +} + +- (id)defaultRequestSerializer { + if (!_defaultRequestSerializer) { + _defaultRequestSerializer = [AFJSONRequestSerializer serializer]; + } + return _defaultRequestSerializer; +} +- (id)defaultResponseSerializer { + if (!_defaultResponseSerializer) { + _defaultResponseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments]; + } + return _defaultResponseSerializer; +} + +- (id)requestSerializerForDefine:(RFAPIDefine *)define { + if (define.requestSerializerClass) { + return [define.requestSerializerClass serializer]; + } + return self.defaultRequestSerializer; +} +- (id)responseSerializerForDefine:(RFAPIDefine *)define { + if (define.responseSerializerClass) { + return [define.responseSerializerClass serializer]; + } + return self.defaultResponseSerializer; +} + +@end diff --git a/Sources/RFAPI/ModelTransformer/RFAPIJSONModelTransformer.h b/Sources/RFAPI/ModelTransformer/RFAPIJSONModelTransformer.h new file mode 100644 index 0000000..b98c9e1 --- /dev/null +++ b/Sources/RFAPI/ModelTransformer/RFAPIJSONModelTransformer.h @@ -0,0 +1,19 @@ +/* + RFAPIJSONModelTransformer + RFAPI + + Copyright © 2019-2020 BB9z + https://github.com/RFUI/RFAPI + + The MIT License (MIT) + http://www.opensource.org/licenses/mit-license.php + */ + +#import "RFAPIModelTransformer.h" + +/** + + */ +@interface RFAPIJSONModelTransformer : NSObject + +@end diff --git a/Sources/RFAPI/ModelTransformer/RFAPIJSONModelTransformer.m b/Sources/RFAPI/ModelTransformer/RFAPIJSONModelTransformer.m new file mode 100644 index 0000000..70e5a4e --- /dev/null +++ b/Sources/RFAPI/ModelTransformer/RFAPIJSONModelTransformer.m @@ -0,0 +1,73 @@ + +#import "RFAPIJSONModelTransformer.h" +#import "RFAPIPrivate.h" +#import + +@implementation RFAPIJSONModelTransformer + +- (id)transformResponse:(id)response toType:(RFAPIDefineResponseExpectType)type kind:(NSString *)modelKind error:(NSError * _Nullable __autoreleasing *)error { + Class modelClass = NSClassFromString(modelKind); + switch (type) { + case RFAPIDefineResponseExpectObject: { + NSDictionary *responseObject = response; + if (![responseObject isKindOfClass:NSDictionary.class]) { + RFAPILogError_(@"期望的数据类型是字典,而实际是 %@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", responseObject.class) + *error = [NSError errorWithDomain:RFAPIErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: @"返回数据异常", + NSLocalizedFailureReasonErrorKey: @"可能服务器正在升级或者维护,也可能是应用 bug", + NSLocalizedRecoverySuggestionErrorKey: @"建议稍后重试,如果持续报告这个错误请检查 AppStore 是否有新版本" + }]; + return nil; + } + + NSError *e = nil; + id JSONModelObject = [(JSONModel *)[modelClass alloc] initWithDictionary:responseObject error:&e]; + if (!JSONModelObject) { + RFAPILogError_(@"不能将返回内容转换为Model:%@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", e) + + *error = [NSError errorWithDomain:RFAPIErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: @"返回数据异常", + NSLocalizedFailureReasonErrorKey: @"可能服务器正在升级或者维护,也可能是应用 bug", + NSLocalizedRecoverySuggestionErrorKey: @"建议稍后重试,如果持续报告这个错误请检查 AppStore 是否有新版本" + }]; + } + return JSONModelObject; + } + case RFAPIDefineResponseExpectObjects: { + NSArray *responseObject = response; + if (![responseObject isKindOfClass:NSArray.class]) { + RFAPILogError_(@"期望的数据类型是数组,而实际是 %@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", responseObject.class) + *error = [NSError errorWithDomain:RFAPIErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: @"返回数据异常", + NSLocalizedFailureReasonErrorKey: @"可能服务器正在升级或者维护,也可能是应用 bug", + NSLocalizedRecoverySuggestionErrorKey: @"建议稍后重试,如果持续报告这个错误请检查 AppStore 是否有新版本" + }]; + return nil; + } + + NSMutableArray *objects = [NSMutableArray.alloc initWithCapacity:responseObject.count]; + for (NSDictionary *info in responseObject) { + id obj = [(JSONModel *)[modelClass alloc] initWithDictionary:info error:error]; + if (obj) { + [objects addObject:obj]; + continue; + } + + RFAPILogError_(@"不能将数组中的元素转换为Model %@\n请先确认一下代码,如果服务器没按要求返回请联系后台人员", *error) + *error = [NSError errorWithDomain:RFAPIErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey: @"返回数据异常", + NSLocalizedFailureReasonErrorKey: @"可能服务器正在升级或者维护,也可能是应用 bug", + NSLocalizedRecoverySuggestionErrorKey: @"建议稍后重试,如果持续报告这个错误请检查 AppStore 是否有新版本" + }]; + return nil; + } + return objects; + } + case RFAPIDefineResponseExpectDefault: + case RFAPIDefineResponseExpectSuccess: + default: + return response; + } +} + +@end diff --git a/Sources/RFAPI/ModelTransformer/RFAPIModelTransformer.h b/Sources/RFAPI/ModelTransformer/RFAPIModelTransformer.h new file mode 100644 index 0000000..cc714ff --- /dev/null +++ b/Sources/RFAPI/ModelTransformer/RFAPIModelTransformer.h @@ -0,0 +1,19 @@ +/* +RFAPIModelTransformer +RFAPI + +Copyright © 2019-2020 BB9z +https://github.com/RFUI/RFAPI + +The MIT License (MIT) +http://www.opensource.org/licenses/mit-license.php +*/ + +#import "RFAPIDefine.h" + +@protocol RFAPIModelTransformer +@required + +- (nullable id)transformResponse:(nonnull id)response toType:(RFAPIDefineResponseExpectType)type kind:(nullable NSString *)modelKind error:(NSError *__nullable *__nonnull)error; + +@end diff --git a/Sources/RFAPI/ModelTransformer/RFAPIModelTransformer.m b/Sources/RFAPI/ModelTransformer/RFAPIModelTransformer.m new file mode 100644 index 0000000..c95d408 --- /dev/null +++ b/Sources/RFAPI/ModelTransformer/RFAPIModelTransformer.m @@ -0,0 +1,2 @@ + +#import "RFAPIModelTransformer.h" diff --git a/Sources/RFAPI/RFAPI.h b/Sources/RFAPI/RFAPI.h new file mode 100644 index 0000000..3940333 --- /dev/null +++ b/Sources/RFAPI/RFAPI.h @@ -0,0 +1,271 @@ +/* + RFAPI + + Copyright © 2014-2016, 2018-2020 BB9z + https://github.com/RFUI/RFAPI + + The MIT License (MIT) + http://www.opensource.org/licenses/mit-license.php + */ + +#import +#import +#import "RFAPIDefine.h" +#import "RFAPIDefineManager.h" +#import +#import + +@class AFNetworkReachabilityManager; +@class AFSecurityPolicy; +@class RFAPIRequestConext; +@class RFMessageManager; +@class RFNetworkActivityMessage; +@protocol RFAPIModelTransformer; + +@protocol RFAPITask +@required + +/// API define object of current request. +@property (readonly, nonnull) RFAPIDefine *define; + +/// Identifier string of current request. +@property (readonly, nonnull) NSString *identifier; + +/// Group identifier string of current request. +@property (readonly, nullable) NSString *groupIdentifier; + +/// The original request object passed when the task was created. +@property (readonly, copy, nullable, nonatomic) NSURLRequest *currentRequest; + +/// The original request object passed when the task was created. +@property (readonly, copy, nullable, nonatomic) NSURLRequest *originalRequest; + +/// The server’s response to the currently active request. +@property (readonly, copy, nullable, nonatomic) NSURLResponse *response; + +/// Serialized response object from server response. +@property (nullable) id responseObject; + +/// An error object that indicates why the task failed. +@property (nullable) NSError *error; + +/// This property is the dictionary pass through the request context. +@property (nullable) NSDictionary *userInfo; + +/// Cancels the task. +- (void)cancel; +@end + +typedef void(^RFAPIRequestProgressBlock)(id __nonnull task, NSProgress *__nonnull progress); +typedef void(^RFAPIRequestSuccessCallback)(id __nonnull task, id __nullable responseObject); +typedef void(^RFAPIRequestFailureCallback)(id __nullable task, NSError *__nonnull error); +typedef void(^RFAPIRequestFinishedCallback)(id __nullable task, BOOL success); +typedef void(^RFAPIRequestCombinedCompletionCallback)(id __nullable task, id __nullable responseObject, NSError *__nullable error); + +/** + Full-featured URL session wrapper designed for API requests. + */ +@interface RFAPI : NSObject < + RFInitializing +> + +#pragma mark Configurations before use + +/** + Used for reachabilityManager creation. + Or when define does not have a baseURL when making a request. + */ +@property (nullable) NSURL *baseURL; + +/** + Used for url session creation. If the session object has been created, setting this property has no effect. + */ +@property (nullable, nonatomic) NSURLSessionConfiguration *sessionConfiguration; + +#pragma mark - + +#if !TARGET_OS_WATCH +/** + The network reachability manager. Default value is the `sharedManager`. + */ +@property (null_resettable, nonatomic) AFNetworkReachabilityManager *reachabilityManager; +#endif + +/** + The dispatch queue serialize response. Default is a shared private concurrent queue. + */ +@property (null_resettable, nonatomic) dispatch_queue_t processingQueue; + +/** + The dispatch queue for `completionBlock`. Default value is the main queue. + */ +@property (null_resettable, nonatomic) dispatch_queue_t completionQueue; + +/** + The dispatch group for `completionBlock`. Default is a private dispatch group. + */ +@property (null_resettable, nonatomic) dispatch_group_t completionGroup; + +#pragma mark - + +@property (readonly, nonnull) RFAPIDefineManager *defineManager; + +#if !TARGET_OS_WATCH +@property (nullable) __kindof RFMessageManager *networkActivityIndicatorManager; +#endif + +/** + The security policy used by created session to evaluate server trust for secure connections. + + Default is the `defaultPolicy`. + */ +@property (nullable, nonatomic) AFSecurityPolicy *securityPolicy; + +#pragma mark - Request management + +- (nonnull NSArray> *)operationsWithIdentifier:(nullable NSString *)identifier; +- (nonnull NSArray> *)operationsWithGroupIdentifier:(nullable NSString *)identifier; + +- (void)cancelOperationWithIdentifier:(nullable NSString *)identifier; +- (void)cancelOperationsWithGroupIdentifier:(nullable NSString *)identifier; + +#pragma mark - Request + +@property (nullable) Class requestConextClass; + + +- (nullable id)requestWithName:(nonnull NSString *)APIName context:(NS_NOESCAPE void (^__nullable)(__kindof RFAPIRequestConext *__nonnull))contextBlock NS_SWIFT_NAME(request(name:context:)); + +- (nullable id)requestWithDefine:(nonnull RFAPIDefine *)APIDefine context:(NS_NOESCAPE void (^__nullable)(__kindof RFAPIRequestConext *__nonnull))contextBlock NS_SWIFT_NAME(request(define:context:)); + +#pragma mark - Response + +@property (nullable) id modelTransformer; + +#pragma mark - Methods for overwrite + +/** + Default implementation first add parameters from APIDefine then add parameters from define manager. + */ +- (void)preprocessingRequestParameters:(NSMutableDictionary *__nullable __strong *__nonnull)parametersRef HTTPHeaders:(NSMutableDictionary *__nullable __strong *__nonnull)httpHeadersRef withParameters:(nullable NSDictionary *)parameters define:(nonnull RFAPIDefine *)define context:(nonnull RFAPIRequestConext *)context NS_SWIFT_NAME(preprocessingRequest(parametersRef:httpHeadersRef:parameters:define:context:)); + +/** + The default implementation apply `requestCustomization` of the request context. + */ +- (nonnull NSMutableURLRequest *)finalizeSerializedRequest:(nonnull NSMutableURLRequest *)request withDefine:(nonnull RFAPIDefine *)define context:(nonnull RFAPIRequestConext *)context NS_SWIFT_NAME(finalizeSerializedRequest(_:define:context:)); + +/** + Handle and progress all request errors in one place. + + Default implementation just return YES. + + This method is called on the processingQueue. + + @return Returning YES will continue error processing and continue with the callback processing of the request, if NO processing ends immediately. + */ +- (BOOL)generalHandlerForError:(nonnull NSError *)error withDefine:(nonnull RFAPIDefine *)define task:(nonnull id)task failureCallback:(nullable RFAPIRequestFailureCallback)failure NS_SWIFT_NAME(generalHandlerForError(_:define:task:failure:)); + +/** + Determine if the response was a successful response. + + Default implementation just return YES. + + This method is called on the processingQueue. + + @param responseObjectRef Can be used to modify the response object. + @param error Optional error. + */ +- (BOOL)isSuccessResponse:(id __nullable __strong *__nonnull)responseObjectRef error:(NSError *__nullable __autoreleasing *__nonnull)error NS_SWIFT_NOTHROW; + +@end + +/// Send array parameters +FOUNDATION_EXTERN NSString *__nonnull const RFAPIRequestArrayParameterKey; +/// Sent parameters throgh qury string +FOUNDATION_EXTERN NSString *__nonnull const RFAPIRequestForceQuryStringParametersKey; + +/// Errors generated inside RFAPI +FOUNDATION_EXTERN NSErrorDomain __nonnull const RFAPIErrorDomain; + +/// +@interface RFAPIRequestConext : NSObject + +/// The timeout interval for the request, in seconds. +/// If not set or its value is not greater than 0, the default timeout will be used. +@property NSTimeInterval timeoutInterval; + +/** + The parameters to be encoded. + + If you want to send an array parameters, set `RFAPIRequestArrayParameterKey` key with the array. + If you want some parameters sent throgh qury string of the URL, set `RFAPIRequestForceQuryStringParametersKey` with a dictionary contains these parameters. + */ +@property (nullable) NSDictionary *parameters; + +/// Use this block to appends data to the HTTP body. +@property (nullable) void (^formData)(id __nonnull); + +/// A dictionary of additional headers to send with requests. +@property (nullable) NSDictionary *HTTPHeaders; + +/// Customization URL request object +@property (nullable) NSMutableURLRequest *__nonnull (^requestCustomization)(NSMutableURLRequest *__nonnull request); + +/// Identifier for request. If `nil`, the api name will be used. +@property (nullable) NSString *identifier; + +/// Group identifier for request. +@property (nullable) NSString *groupIdentifier; + +/// An activity message to be displayed durning the request executing. +/// If the request is finished right after been make, eg. it has been already cached, the message will not be displayed. +@property (nullable) RFNetworkActivityMessage *activityMessage; + +/// If not nil and the `activityMessage` is not be set, a message will be create automatically. +@property (nullable) NSString *loadMessage; +@property BOOL loadMessageShownModal; + +/// A block object to be executed when the upload progress is updated. +/// Note this block is called on the session queue, not the main queue. +@property (nullable) RFAPIRequestProgressBlock uploadProgress; + +/// A block object to be executed when the download progress is updated. +/// Note this block is called on the session queue, not the main queue. +@property (nullable) RFAPIRequestProgressBlock downloadProgress; + +/// A block object to be executed when the request finishes successfully. +@property (nullable) RFAPIRequestSuccessCallback success NS_SWIFT_NAME(successCallback); + +/// A block object to be executed when the request finishes unsuccessfully. +@property (nullable) RFAPIRequestFailureCallback failure NS_SWIFT_NAME(failureCallback); + +/// A block object to be executed when the request is complated. +@property (nullable) RFAPIRequestFinishedCallback finished NS_SWIFT_NAME(finishedCallback); + +/// A block object to be executed when the request is complated. +@property (nullable) RFAPIRequestCombinedCompletionCallback combinedComplation NS_SWIFT_NAME(complationCallback); + +/// For debugging purposes, delaying the sending of network requests. +/// This may be used to test whether the UI is in a proper state when network latency. +@property NSTimeInterval debugDelayRequestSend; + +/// This value will be passed to the task object. +@property (nullable) NSDictionary *userInfo; + +@end + +@interface RFAPIRequestConext (Swift) + +/// Set callback to be executed when the request finishes successfully. +- (void)setSuccessCallback:(nullable void(^)(id __nonnull task, id __nullable responseObject))success NS_SWIFT_NAME(success(_:)); + +/// Set callback to be executed when the request finishes unsuccessfully. +- (void)setFailureCallback:(nullable void (^)(id __nullable task, NSError *__nonnull error))failure NS_SWIFT_NAME(failure(_:)); + +/// Set callback to be executed when the request is complated. +- (void)setFinishedCallback:(nullable void (^)(id __nullable task, BOOL success))finished NS_SWIFT_NAME(finished(_:)); + +/// Set callback to be executed when the request is complated. +- (void)setComplationCallback:(nullable void (^)(id __nullable task, id __nullable responseObject, NSError *__nullable error))complation NS_SWIFT_NAME(complation(_:)); + +@end diff --git a/Sources/RFAPI/RFAPI.m b/Sources/RFAPI/RFAPI.m new file mode 100644 index 0000000..7fdaade --- /dev/null +++ b/Sources/RFAPI/RFAPI.m @@ -0,0 +1,502 @@ + +#import "RFAPIPrivate.h" +#import "RFAPIDefineManager.h" +#import "RFAPIModelTransformer.h" +#import "RFAPISessionManager.h" +#import "RFAPISessionTask.h" +#import + +NSErrorDomain const RFAPIErrorDomain = @"RFAPIErrorDomain"; +NSString *const RFAPIRequestArrayParameterKey = @"_RFParmArray_"; +NSString *const RFAPIRequestForceQuryStringParametersKey = @"_RFParmForceQuryString_"; + +NSString *RFAPILocalizedString(NSString *key, NSString *value) { + return [NSBundle.mainBundle localizedStringForKey:key value:value table:nil]; +} + +// Avoid create many concurrent GCD queue. +static dispatch_queue_t api_default_processing_queue() { + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("com.github.RFUI.RFAPI.session.processing", DISPATCH_QUEUE_CONCURRENT); + }); + return queue; +} + +static dispatch_group_t api_default_completion_group() { + static dispatch_group_t group; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + group = dispatch_group_create(); + }); + return group; +} + +@implementation RFAPI +RFInitializingRootForNSObject + +- (void)onInit { +} + +- (void)afterInit { +} + +- (NSString *)debugDescription { + return [NSString stringWithFormat:@"<%@: %p, operations: %@>", self.class, (void *)self, self._RFAPI_sessionManager.allTasks]; +} + +- (NSURLSessionConfiguration *)sessionConfiguration { + return self._RFAPI_sessionManager.session.configuration ?: _sessionConfiguration; +} + +- (_RFURLSessionManager *)http { + _RFURLSessionManager *http = self._RFAPI_sessionManager; + if (http) return http; + NSURLSessionConfiguration *config = self.sessionConfiguration ?: NSURLSessionConfiguration.defaultSessionConfiguration; + http = [_RFURLSessionManager.alloc initWithSessionConfiguration:config]; + http.master = self; + self._RFAPI_sessionManager = http; + return http; +} + +#if !TARGET_OS_WATCH +- (AFNetworkReachabilityManager *)reachabilityManager { + if (!_reachabilityManager) { + NSString *host = self.baseURL.host; + _reachabilityManager = (host.length) ? [AFNetworkReachabilityManager managerForDomain:host] : [AFNetworkReachabilityManager sharedManager]; + } + return _reachabilityManager; +} +#endif + +- (dispatch_queue_t)processingQueue { + if (!_processingQueue) { + _processingQueue = api_default_processing_queue(); + } + return _processingQueue; +} + +- (dispatch_queue_t)completionQueue { + if (!_completionQueue) { + _completionQueue = dispatch_get_main_queue(); + } + return _completionQueue; +} + +- (dispatch_group_t)completionGroup { + if (!_completionGroup) { + _completionGroup = api_default_completion_group(); + } + return _completionGroup; +} + +- (RFAPIDefineManager *)defineManager { + if (!_defineManager) { + _defineManager = [RFAPIDefineManager.alloc init]; + } + return _defineManager; +} + +- (AFSecurityPolicy *)securityPolicy { + if (!_securityPolicy) { + _securityPolicy = [AFSecurityPolicy defaultPolicy]; + } + return _securityPolicy; +} + +#pragma mark - Request management + +- (void)cancelOperationWithIdentifier:(nullable NSString *)identifier { + for (idop in [self operationsWithIdentifier:identifier]) { + _dout_debug(@"Cancel HTTP request operation(%p) with identifier: %@", (void *)op, identifier); + [op cancel]; + } +} + +- (void)cancelOperationsWithGroupIdentifier:(nullable NSString *)identifier { + for (idop in [self operationsWithGroupIdentifier:identifier]) { + _dout_debug(@"Cancel HTTP request operation(%p) with group identifier: %@", (void *)op, identifier); + [op cancel]; + } +} + +- (nonnull NSArray> *)operationsWithIdentifier:(nullable NSString *)identifier { + @autoreleasepool { + _RFURLSessionManager *http = self._RFAPI_sessionManager; + if (!http) return @[]; + return [http.allTasks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K == %@", @keypathClassInstance(_RFAPISessionTask, identifier), identifier]]; + } +} + +- (nonnull NSArray> *)operationsWithGroupIdentifier:(nullable NSString *)identifier { + @autoreleasepool { + _RFURLSessionManager *http = self._RFAPI_sessionManager; + if (!http) return @[]; + return [http.allTasks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"%K == %@", @keypathClassInstance(_RFAPISessionTask, groupIdentifier), identifier]]; + } +} + +#pragma mark - Request + +- (id)requestWithName:(NSString *)APIName context:(NS_NOESCAPE void (^)(__kindof RFAPIRequestConext * _Nonnull))contextBlock { + NSParameterAssert(APIName); + RFAPIDefine *define = [self.defineManager defineForName:APIName]; + if (!define.name) { + define.name = APIName; + } + RFAssert(define, @"Can not find an API with name: %@.", APIName) + if (!define) return nil; + return [self requestWithDefine:define context:contextBlock]; +} + +- (id)requestWithDefine:(RFAPIDefine *)APIDefine context:(NS_NOESCAPE void (^)(__kindof RFAPIRequestConext * _Nonnull))contextBlock { + __kindof RFAPIRequestConext *context = [(self.requestConextClass?: RFAPIRequestConext.class) new]; + if (contextBlock) { + contextBlock(context); + } + NSString *identifier = context.identifier; + if (!identifier) { + identifier = APIDefine.name; + RFAssert(identifier, @"Context identifier and define name both are nil.") + context.identifier = identifier; + } + if (!context.activityMessage && context.loadMessage) { + RFNetworkActivityMessage *m = [[RFNetworkActivityMessage alloc] initWithIdentifier:identifier message:context.loadMessage status:RFNetworkActivityStatusLoading]; + m.modal = context.loadMessageShownModal; + context.activityMessage = m; + } + + RFAPIDefine *defaultDefine = self.defineManager.defaultDefine; + RFAPIDefine *define = defaultDefine ? [APIDefine newDefineMergedDefault:defaultDefine] : APIDefine; + if (!define.baseURL && self.baseURL) { + define.baseURL = self.baseURL; + } + + NSError *e = nil; + NSMutableURLRequest *request = [self _RFAPI_makeURLRequestWithDefine:define context:context error:&e]; + if (!request) { + RFAPILogError_(@"无法创建请求: %@", e) + NSMutableDictionary *eInfo = [NSMutableDictionary.alloc initWithCapacity:4]; + eInfo[NSLocalizedDescriptionKey] = @"内部错误,无法创建请求"; + eInfo[NSLocalizedFailureReasonErrorKey] = @"很可能是应用 bug"; + eInfo[NSLocalizedRecoverySuggestionErrorKey] = @"请再试一次,如果依旧请尝试重启应用。给您带来不便,敬请谅解"; + if (e) { + eInfo[NSUnderlyingErrorKey] = e; + } + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:eInfo]; + [self _RFAPI_executeContext:context failure:error]; + return nil; + } + + NSURLSessionDataTask *dataTask = [self.http.session dataTaskWithRequest:request]; + _RFAPISessionTask *task = [self.http addSessionTask:dataTask]; + task.manager = self; + task.define = define; + [self transferContext:context toTask:task]; + + // Start request + RFNetworkActivityMessage *message = task.activityMessage; + if (message) { + dispatch_async_on_main(^{ + if (task.isEnd) return; + [self.networkActivityIndicatorManager showMessage:message]; + }); + } + dispatch_block_t work = ^{ + if (task.isEnd) return; + [dataTask resume]; + }; + if (task.debugDelayRequestSend > 0) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(task.debugDelayRequestSend * NSEC_PER_SEC)), self.processingQueue, work); + } + else { + dispatch_async(self.processingQueue, work); + } + return task; +} + +- (void)transferContext:(RFAPIRequestConext *)context toTask:(_RFAPISessionTask *)task { + task.identifier = context.identifier ?: task.define.name; + task.groupIdentifier = context.groupIdentifier; + task.activityMessage = context.activityMessage; + task.uploadProgressBlock = context.uploadProgress; + task.downloadProgressBlock = context.downloadProgress; + task.success = context.success; + task.failure = context.failure; + task.complation = context.finished; + task.combinedComplation = context.combinedComplation; + task.userInfo = context.userInfo; + task.debugDelayRequestSend = context.debugDelayRequestSend; +} + +#pragma mark - Build Request + +- (nullable NSMutableURLRequest *)_RFAPI_makeURLRequestWithDefine:(RFAPIDefine *)define context:(RFAPIRequestConext *)context error:(NSError *_Nullable __autoreleasing *)error { + NSParameterAssert(define); + NSParameterAssert(context); + NSParameterAssert(error); + + // Preprocessing arguments + NSMutableDictionary *parameters = [NSMutableDictionary.alloc initWithCapacity:16]; + NSMutableDictionary *headers = [NSMutableDictionary.alloc initWithCapacity:4]; + [self preprocessingRequestParameters:¶meters HTTPHeaders:&headers withParameters:context.parameters define:define context:context]; + + // Creat URL + NSURL *url = [self.defineManager requestURLForDefine:define parameters:parameters error:error]; + if (!url) return nil; + + // Creat URLRequest + NSMutableURLRequest *mutableRequest = nil; + + id serializer = [self.defineManager requestSerializerForDefine:define]; + if (context.formData) { + NSAssert([serializer respondsToSelector:@selector(multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:)], @"For a request needs post form data, the serializer should be an AFHTTPRequestSerializer or response to multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:."); + NSString *urlString = url.absoluteString; + AFHTTPRequestSerializer *hs = serializer; + mutableRequest = [hs multipartFormRequestWithMethod:define.method ?: @"POST" URLString:urlString parameters:parameters constructingBodyWithBlock:context.formData error:error]; + } + else { + mutableRequest = [NSMutableURLRequest.alloc initWithURL:url]; + [mutableRequest setHTTPMethod:define.method ?: @"GET"]; + NSArray *arrayParameter = parameters[RFAPIRequestArrayParameterKey]; + mutableRequest = [[serializer requestBySerializingRequest:mutableRequest withParameters:arrayParameter?: parameters error:error] mutableCopy]; + } + if (!mutableRequest) return nil; + if (context.timeoutInterval > 0) { + mutableRequest.timeoutInterval = context.timeoutInterval; + } + + // Set header + [headers enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *_) { + [mutableRequest setValue:value forHTTPHeaderField:field]; + }]; + + // Finalization + mutableRequest = [self finalizeSerializedRequest:mutableRequest withDefine:define context:context]; + return mutableRequest; +} + +- (void)preprocessingRequestParameters:(NSMutableDictionary * _Nullable __strong *)requestParameters HTTPHeaders:(NSMutableDictionary * _Nullable __strong *)requestHeaders withParameters:(NSDictionary *)parameters define:(RFAPIDefine *)define context:(RFAPIRequestConext *)context { + BOOL needsAuthorization = define.needsAuthorization; + NSDictionary *entries = nil; + if ((entries = define.defaultParameters)) { + [*requestParameters addEntriesFromDictionary:entries]; + } + if (needsAuthorization) { + if ((entries = self.defineManager.authorizationParameters)) { + [*requestParameters addEntriesFromDictionary:entries]; + } + } + if ((entries = parameters)) { + [*requestParameters addEntriesFromDictionary:entries]; + } + + if ((entries = define.HTTPRequestHeaders)) { + [*requestHeaders addEntriesFromDictionary:entries]; + } + if (needsAuthorization) { + if ((entries = self.defineManager.authorizationHeader)) { + [*requestHeaders addEntriesFromDictionary:entries]; + } + } + if ((entries = context.HTTPHeaders)) { + [*requestHeaders addEntriesFromDictionary:entries]; + } +} + +- (NSMutableURLRequest *)finalizeSerializedRequest:(NSMutableURLRequest *)request withDefine:(RFAPIDefine *)define context:(nonnull RFAPIRequestConext *)context { + if (context.requestCustomization) { + request = context.requestCustomization(request); + NSAssert(request, @"requestCustomization must not return nil."); + } + return request; +} + +#pragma mark - Handel Response + +- (void)_RFAPI_handleTaskComplete:(_RFAPISessionTask *)task response:(NSURLResponse *)response data:(NSData *)data error:(NSError *)error { + dispatch_async(self.processingQueue, ^{ + if (error) { + [self _RFAPI_executeTaskCallback:task failure:error]; + return; + } + + if (task.downloadFileURL) { + [self _RFAPI_executeTaskCallback:task success:task.downloadFileURL]; + return; + } + + NSError *serializationError = nil; + id serializer = [self.defineManager responseSerializerForDefine:task.define]; + id responseObject = [serializer responseObjectForResponse:response data:data error:&serializationError]; + task.responseObject = responseObject; + if (serializationError) { + [self _RFAPI_executeTaskCallback:task failure:serializationError]; + return; + } + + if ((!responseObject || responseObject == NSNull.null) + && task.define.responseAcceptNull) { + [self _RFAPI_executeTaskCallback:task success:responseObject]; + return; + } + + RFAPIDefineResponseExpectType type = task.define.responseExpectType; + switch (type) { + case RFAPIDefineResponseExpectDefault: { + [self _RFAPI_executeTaskCallback:task success:responseObject]; + return; + } + case RFAPIDefineResponseExpectSuccess: { + NSError *e = nil; + if (![self isSuccessResponse:&responseObject error:&e]) { + [self _RFAPI_executeTaskCallback:task failure:e]; + } + else { + [self _RFAPI_executeTaskCallback:task success:responseObject]; + } + return; + } + case RFAPIDefineResponseExpectObject: + case RFAPIDefineResponseExpectObjects: { + NSError *error = nil; + id modelObject = [self.modelTransformer transformResponse:(id)responseObject toType:type kind:task.define.responseClass error:&error]; + if (error) { + [self _RFAPI_executeTaskCallback:task failure:error]; + } + else { + [self _RFAPI_executeTaskCallback:task success:modelObject]; + } + return; + } + default: + NSAssert(false, @"Unexcept response type: %d", type); + return; + } + }); +} + +- (void)_RFAPI_executeTaskCallback:(nonnull _RFAPISessionTask *)task success:(nullable id)responseObject { + task.responseObject = responseObject; + dispatch_group_async(self.completionGroup, self.completionQueue, ^{ + task.failure = nil; + RFAPIRequestSuccessCallback scb = task.success; + if (scb) { + task.success = nil; + scb(task, responseObject); + } + RFNetworkActivityMessage *message = task.activityMessage; + if (message) { + dispatch_sync_on_main(^{ + [self.networkActivityIndicatorManager hideMessage:message]; + }); + } + RFAPIRequestFinishedCallback ccb = task.complation; + if (ccb) { + task.complation = nil; + ccb(task, YES); + } + RFAPIRequestCombinedCompletionCallback cbcb = task.combinedComplation; + if (cbcb) { + task.combinedComplation = nil; + cbcb(task, responseObject, nil); + } + }); +} + +- (void)_RFAPI_executeTaskCallback:(nonnull _RFAPISessionTask *)task failure:(nonnull NSError *)error { + task.error = error; + BOOL shouldContinue = [self generalHandlerForError:error withDefine:task.define task:task failureCallback:task.failure]; + + dispatch_group_async(self.completionGroup, self.completionQueue, ^{ + task.success = nil; + RFMessageManager *messageManager = self.networkActivityIndicatorManager; + if (shouldContinue) { + BOOL isCancel = (error.code == NSURLErrorCancelled && [error.domain isEqualToString:NSURLErrorDomain]); + + RFAPIRequestFailureCallback fcb = task.failure; + if (fcb) { + if (!isCancel) { + fcb(task, error); + } + } + else { + if (messageManager) { + dispatch_sync_on_main(^{ + [messageManager alertError:error title:nil fallbackMessage:@"Request Failed"]; + }); + } + } + } + task.failure = nil; + + RFNetworkActivityMessage *message = task.activityMessage; + if (message && messageManager) { + dispatch_sync_on_main(^{ + [messageManager hideMessage:message]; + }); + } + RFAPIRequestFinishedCallback ccb = task.complation; + if (ccb) { + task.complation = nil; + ccb(task, NO); + } + RFAPIRequestCombinedCompletionCallback cbcb = task.combinedComplation; + if (cbcb) { + task.combinedComplation = nil; + cbcb(task, nil, error); + } + }); +} + +- (void)_RFAPI_executeContext:(nonnull RFAPIRequestConext *)context failure:(nonnull NSError *)error { + dispatch_group_async(self.completionGroup, self.completionQueue, ^{ + RFAPIRequestFailureCallback fcb = context.failure; + if (fcb) { + fcb(nil, error); + } + + RFAPIRequestFinishedCallback ccb = context.finished; + if (ccb) { + ccb(nil, NO); + } + RFAPIRequestCombinedCompletionCallback cbcb = context.combinedComplation; + if (cbcb) { + cbcb(nil, nil, error); + } + }); +} + +- (BOOL)generalHandlerForError:(NSError *)error withDefine:(RFAPIDefine *)define task:(id)task failureCallback:(RFAPIRequestFailureCallback)failure { + return YES; +} + +- (BOOL)isSuccessResponse:(id _Nullable __strong *)responseObjectRef error:(NSError * _Nullable __autoreleasing *)error { + return YES; +} + +@end + + +@implementation RFAPIRequestConext + +@end + +@implementation RFAPIRequestConext (Swift) + +- (void)setSuccessCallback:(void (^)(id _Nonnull, id _Nullable))success { + self.success = success; +} +- (void)setFailureCallback:(void (^)(id _Nullable, NSError * _Nonnull))failure { + self.failure = failure; +} +- (void)setFinishedCallback:(void (^)(id _Nullable, BOOL))finished { + self.finished = finished; +} +- (void)setComplationCallback:(void (^)(id _Nullable, id _Nullable, NSError * _Nullable))complation { + self.combinedComplation = complation; +} + +@end + diff --git a/Sources/RFAPI/RFAPIPrivate.h b/Sources/RFAPI/RFAPIPrivate.h new file mode 100644 index 0000000..2a09b1b --- /dev/null +++ b/Sources/RFAPI/RFAPIPrivate.h @@ -0,0 +1,46 @@ +/* + Private Header + RFAPI + + Copyright © 2019 BB9z + https://github.com/RFUI/RFAPI + + The MIT License (MIT) + http://www.opensource.org/licenses/mit-license.php + */ + +#import "RFAPI.h" +#import +#import +#import + +/// The localized string loaded from main bundle's default table. +extern NSString *__nonnull RFAPILocalizedString(NSString *__nonnull key, NSString *__nonnull value); + +#if RFDEBUG +# define RFAPILogError_(DEBUG_ERROR, ...) dout_error(DEBUG_ERROR, __VA_ARGS__) +#else +# define RFAPILogError_(DEBUG_ERROR, ...) +#endif + +@class _RFURLSessionManager, _RFAPISessionTask; +@class RFAPIDefineManager; + +@interface RFAPI () +@property (nullable) _RFURLSessionManager *_RFAPI_sessionManager; +@property (readonly, nonnull, nonatomic) _RFURLSessionManager *http; +@property (null_resettable, nonatomic) RFAPIDefineManager *defineManager; + +- (void)_RFAPI_handleTaskComplete:(nonnull _RFAPISessionTask *)task response:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(nullable NSError *)error; + +@end + + +@interface RFAPIDefine () +@property (nullable) NSNumber *needsAuthorizationValue; +@property (nullable) NSNumber *cachePolicyValue; +@property (nullable) NSNumber *cacheExpireValue; +@property (nullable) NSNumber *offlinePolicyValue; +@property (nullable) NSNumber *responseExpectTypeValue; +@property (nullable) NSNumber *responseAcceptNullValue; +@end diff --git a/Sources/RFAPI/URLSession/RFAPISessionManager.h b/Sources/RFAPI/URLSession/RFAPISessionManager.h new file mode 100644 index 0000000..7982822 --- /dev/null +++ b/Sources/RFAPI/URLSession/RFAPISessionManager.h @@ -0,0 +1,207 @@ +/* +RFAPISessionManager +RFAPI + +Copyright © 2019 BB9z +https://github.com/RFUI/RFAPI + +The MIT License (MIT) +http://www.opensource.org/licenses/mit-license.php +*/ + +#import "RFAPIPrivate.h" + +@class AFHTTPRequestSerializer; +@class AFHTTPResponseSerializer; +@class RFAPI; +@class _RFAPISessionTask; + +@interface _RFURLSessionManager : NSObject < + NSURLSessionDelegate, + NSURLSessionTaskDelegate, + NSURLSessionDataDelegate, + NSURLSessionDownloadDelegate +> + +@property (weak, nullable) RFAPI *master; + +/** + Creates and returns a manager for a session created with the specified configuration. This is the designated initializer. + + @param configuration The configuration used to create the managed session. + + @return A manager for a newly-created session. + */ +- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER; + +/** + The managed session. + */ +@property (readonly, nonnull) NSURLSession *session; + +#pragma mark - Queue + +/** + The operation queue on which delegate callbacks are run. + */ +@property (readonly, nonnull) NSOperationQueue *operationQueue; + +#pragma mark - Session Tasks + +/** + The data, upload, and download tasks currently run by the managed session. + */ +@property (readonly, nonnull, nonatomic) NSArray *tasks; + +/** + The data tasks currently run by the managed session. + */ +@property (readonly, nonnull, nonatomic) NSArray *dataTasks; + +/** + The upload tasks currently run by the managed session. + */ +@property (readonly, nonnull, nonatomic) NSArray *uploadTasks; + +/** + The download tasks currently run by the managed session. + */ +@property (readonly, nonnull, nonatomic) NSArray *downloadTasks; + +/** + Invalidates the managed session, optionally canceling pending tasks. + + @param cancelPendingTasks Whether or not to cancel pending tasks. + */ +- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks; + +#pragma mark - API Tasks + +- (nonnull NSArray<_RFAPISessionTask *> *)allTasks; + +- (nullable _RFAPISessionTask *)addSessionTask:(nullable NSURLSessionTask *)sessionTask; + +#pragma mark - Getting Progress for Tasks + +/** + Returns the upload progress of the specified task. + + @param task The session task. Must not be `nil`. + + @return An `NSProgress` object reporting the upload progress of a task, or `nil` if the progress is unavailable. + */ +- (nullable NSProgress *)uploadProgressForTask:(nonnull NSURLSessionTask *)task; + +/** + Returns the download progress of the specified task. + + @param task The session task. Must not be `nil`. + + @return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable. + */ +- (nullable NSProgress *)downloadProgressForTask:(nonnull NSURLSessionTask *)task; + +#pragma mark - Callbacks + +NS_ASSUME_NONNULL_BEGIN + +/** + Sets a block to be executed when the managed session becomes invalid, as handled by the `NSURLSessionDelegate` method `URLSession:didBecomeInvalidWithError:`. + + @param block A block object to be executed when the managed session becomes invalid. The block has no return value, and takes two arguments: the session, and the error related to the cause of invalidation. + */ +@property (nullable) void (^sessionDidBecomeInvalid)(NSURLSession *session, NSError *error); + +/** + Sets a block to be executed when a connection level authentication challenge has occurred, as handled by the `NSURLSessionDelegate` method `URLSession:didReceiveChallenge:completionHandler:`. + + @param block A block object to be executed when a connection level authentication challenge has occurred. The block returns the disposition of the authentication challenge, and takes three arguments: the session, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge. + */ +@property (nullable) NSURLSessionAuthChallengeDisposition (^sessionDidReceiveAuthenticationChallenge)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential); + +/** + Sets a block to be executed when a task requires a new request body stream to send to the remote server, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:needNewBodyStream:`. + + @param block A block object to be executed when a task requires a new request body stream. + */ +@property (nullable) NSInputStream *__nullable (^taskNeedNewBodyStream)(NSURLSession *session, NSURLSessionTask *task); + +/** + Sets a block to be executed when an HTTP request is attempting to perform a redirection to a different URL, as handled by the `NSURLSessionTaskDelegate` method `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`. + + @param block A block object to be executed when an HTTP request is attempting to perform a redirection to a different URL. The block returns the request to be made for the redirection, and takes four arguments: the session, the task, the redirection response, and the request corresponding to the redirection response. + */ +@property (nullable) NSURLRequest * _Nullable (^taskWillPerformHTTPRedirection)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request); + +/** + Sets a block to be executed when a session task has received a request specific authentication challenge, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didReceiveChallenge:completionHandler:`. + + @param block A block object to be executed when a session task has received a request specific authentication challenge. The block returns the disposition of the authentication challenge, and takes four arguments: the session, the task, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge. + */ +@property (nullable) NSURLSessionAuthChallengeDisposition (^taskDidReceiveAuthenticationChallenge)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential); + +/** + Sets a block to be executed periodically to track upload progress, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`. + + @param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes five arguments: the session, the task, the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread. + */ +@property (nullable) void (^taskDidSendBodyData)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend); + +/** + Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`. + + @param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task. + */ +@property (nullable) void (^taskDidComplete)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error); + +/** + Sets a block to be executed when a data task has received a response, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveResponse:completionHandler:`. + + @param block A block object to be executed when a data task has received a response. The block returns the disposition of the session response, and takes three arguments: the session, the data task, and the received response. + */ +@property (nullable) NSURLSessionResponseDisposition (^dataTaskDidReceiveResponse)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response); + +/** + Sets a block to be executed when a data task has become a download task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didBecomeDownloadTask:`. + + @param block A block object to be executed when a data task has become a download task. The block has no return value, and takes three arguments: the session, the data task, and the download task it has become. + */ +@property (nullable) void (^dataTaskDidBecomeDownloadTask)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask); + +/** + Sets a block to be executed when a data task receives data, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveData:`. + + @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the session, the data task, and the data received. This block may be called multiple times, and will execute on the session manager operation queue. + */ +@property (nullable) void (^dataTaskDidReceiveData)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data); + +/** + Sets a block to be executed to determine the caching behavior of a data task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:willCacheResponse:completionHandler:`. + + @param block A block object to be executed to determine the caching behavior of a data task. The block returns the response to cache, and takes three arguments: the session, the data task, and the proposed cached URL response. + */ +@property (nullable) NSCachedURLResponse * (^dataTaskWillCacheResponse)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse); + +/** +Sets a block to be executed when a download task has completed a download, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didFinishDownloadingToURL:`. + +@param block A block object to be executed when a download task has completed. The block returns the URL the download should be moved to, and takes three arguments: the session, the download task, and the temporary location of the downloaded file. If the file manager encounters an error while attempting to move the temporary file to the destination, an `AFURLSessionDownloadTaskDidFailToMoveFileNotification` will be posted, with the download task as its object, and the user info of the error. +*/ +@property (nullable) NSURL * _Nullable (^downloadTaskDidFinishDownloading)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location); + +/** +Sets a block to be executed periodically to track download progress, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`. + +@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes five arguments: the session, the download task, the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the session manager operation queue. +*/ +@property (nullable) void (^downloadTaskDidWriteData)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); + +/** +Sets a block to be executed when a download task has been resumed, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`. + +@param block A block object to be executed when a download task has been resumed. The block has no return value and takes four arguments: the session, the download task, the file offset of the resumed download, and the total number of bytes expected to be downloaded. +*/ +@property (nullable) void (^downloadTaskDidResume)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes); + +NS_ASSUME_NONNULL_END +@end diff --git a/Sources/RFAPI/URLSession/RFAPISessionManager.m b/Sources/RFAPI/URLSession/RFAPISessionManager.m new file mode 100644 index 0000000..a1c17e2 --- /dev/null +++ b/Sources/RFAPI/URLSession/RFAPISessionManager.m @@ -0,0 +1,385 @@ + +#import "RFAPISessionManager.h" +#import +#import "RFAPISessionTask.h" + +@interface _RFURLSessionManager () +@property NSOperationQueue *operationQueue; +@property NSURLSession *session; +@property NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier; +@property NSString *taskDescriptionForSessionTasks; +@property NSLock *lock; +@end + +@implementation _RFURLSessionManager + +- (instancetype)init { + return [self initWithSessionConfiguration:nil]; +} + +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { + self = [super init]; + if (!self) { + return nil; + } + + if (!configuration) { + configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + } + + NSOperationQueue *sessionQueue = [NSOperationQueue.alloc init]; + sessionQueue.maxConcurrentOperationCount = 1; + sessionQueue.name = @"com.github.RFUI.RFAPI.session.delegate"; + + self.operationQueue = sessionQueue; + self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:sessionQueue]; + + self.mutableTaskDelegatesKeyedByTaskIdentifier = [NSMutableDictionary.alloc initWithCapacity:8]; + + self.lock = [NSLock.alloc init]; + self.lock.name = @"com.github.RFUI.RFAPI.session.lock"; + self.taskDescriptionForSessionTasks = NSUUID.UUID.UUIDString; + + [self.session getTasksWithCompletionHandler:^(NSArray * dataTasks, NSArray * uploadTasks, NSArray * downloadTasks) { + for (NSURLSessionDataTask *task in dataTasks) { + [self addSessionTask:task]; + } + + for (NSURLSessionUploadTask *task in uploadTasks) { + [self addSessionTask:task]; + } + + for (NSURLSessionDownloadTask *task in downloadTasks) { + [self addSessionTask:task]; + } + }]; + + return self; +} + +#pragma mark API Task / Delegate + +- (NSArray<_RFAPISessionTask *> *)allTasks { + NSArray *allTasks = nil; + [self.lock lock]; + allTasks = self.mutableTaskDelegatesKeyedByTaskIdentifier.allValues; + [self.lock unlock]; + return allTasks; +} + +- (_RFAPISessionTask *)delegateForTask:(NSURLSessionTask *)task { + NSParameterAssert(task); + + _RFAPISessionTask *delegate = nil; + [self.lock lock]; + delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; + [self.lock unlock]; + + return delegate; +} + +- (void)setDelegate:(_RFAPISessionTask *)delegate forTask:(NSURLSessionTask *)task { + NSParameterAssert(task); + NSParameterAssert(delegate); + + if (delegate.task != task) { + delegate.task = task; + } + [self.lock lock]; + self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; + [self.lock unlock]; +} + +- (void)removeDelegateForTask:(NSURLSessionTask *)task { + NSParameterAssert(task); + + [self.lock lock]; + [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; + [self.lock unlock]; +} + +- (_RFAPISessionTask *)addSessionTask:(NSURLSessionTask *)sessionTask { + if (!sessionTask) return nil; + + _RFAPISessionTask *delegate = _RFAPISessionTask.new; + sessionTask.taskDescription = self.taskDescriptionForSessionTasks; + [self setDelegate:delegate forTask:sessionTask]; + return delegate; +} + +#pragma mark Session Tasks + +- (NSArray *)tasksForKeyPath:(NSString *)keyPath { + __block NSArray *tasks = nil; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { + if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) { + tasks = dataTasks; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) { + tasks = uploadTasks; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) { + tasks = downloadTasks; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) { + tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"]; + } + + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + return tasks; +} + +- (NSArray *)tasks { + return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; +} + +- (NSArray *)dataTasks { + return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; +} + +- (NSArray *)uploadTasks { + return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; +} + +- (NSArray *)downloadTasks { + return [self tasksForKeyPath:NSStringFromSelector(_cmd)]; +} + +#pragma mark - + +- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks { + if (cancelPendingTasks) { + [self.session invalidateAndCancel]; + } else { + [self.session finishTasksAndInvalidate]; + } +} + +#pragma mark Progress + +- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task { + return [[self delegateForTask:task] uploadProgress]; +} + +- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task { + return [[self delegateForTask:task] downloadProgress]; +} + +#pragma mark - NSObject + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, session: %@, operationQueue: %@>", NSStringFromClass([self class]), (void *)self, self.session, self.operationQueue]; +} + +- (BOOL)respondsToSelector:(SEL)selector { + if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) { + return self.taskWillPerformHTTPRedirection != nil; + } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) { + return self.dataTaskDidReceiveResponse != nil; + } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) { + return self.dataTaskWillCacheResponse != nil; + } + return [[self class] instancesRespondToSelector:selector]; +} + +#pragma mark NSURLSessionDelegate + +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { + if (self.sessionDidBecomeInvalid) { + self.sessionDidBecomeInvalid(session, error); + } +} + +- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; + NSURLCredential *credential = nil; + + if (self.sessionDidReceiveAuthenticationChallenge) { + disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); + } + else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + SecTrustRef trust = challenge.protectionSpace.serverTrust; + if ([self.master.securityPolicy evaluateServerTrust:trust forDomain:challenge.protectionSpace.host]) { + credential = [NSURLCredential credentialForTrust:trust]; + if (credential) { + disposition = NSURLSessionAuthChallengeUseCredential; + } + } + else { + disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; + } + } + + if (completionHandler) { + completionHandler(disposition, credential); + } +} + +#pragma mark NSURLSessionTaskDelegate + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler { + NSURLRequest *redirectRequest = request; + + if (self.taskWillPerformHTTPRedirection) { + redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request); + } + + if (completionHandler) { + completionHandler(redirectRequest); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; + __block NSURLCredential *credential = nil; + + if (self.taskDidReceiveAuthenticationChallenge) { + disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); + } + else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + SecTrustRef trust = challenge.protectionSpace.serverTrust; + if ([self.master.securityPolicy evaluateServerTrust:trust forDomain:challenge.protectionSpace.host]) { + disposition = NSURLSessionAuthChallengeUseCredential; + credential = [NSURLCredential credentialForTrust:trust]; + } else { + disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; + } + } + + if (completionHandler) { + completionHandler(disposition, credential); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler { + NSInputStream *inputStream = nil; + + if (self.taskNeedNewBodyStream) { + inputStream = self.taskNeedNewBodyStream(session, task); + } else if ([task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { + inputStream = [task.originalRequest.HTTPBodyStream copy]; + } + + if (completionHandler) { + completionHandler(inputStream); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + + int64_t totalUnitCount = totalBytesExpectedToSend; + if (totalUnitCount == NSURLSessionTransferSizeUnknown) { + NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"]; + if (contentLength) { + totalUnitCount = (int64_t) [contentLength longLongValue]; + } + } + + _RFAPISessionTask *delegate = [self delegateForTask:task]; + [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend]; + + if (self.taskDidSendBodyData) { + self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + _RFAPISessionTask *delegate = [self delegateForTask:task]; + + // delegate may be nil when completing a task in the background + if (delegate) { + [delegate URLSession:session task:task didCompleteWithError:error]; + + [self removeDelegateForTask:task]; + } + + if (self.taskDidComplete) { + self.taskDidComplete(session, task, error); + } +} + +#pragma mark NSURLSessionDataDelegate + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; + + if (self.dataTaskDidReceiveResponse) { + disposition = self.dataTaskDidReceiveResponse(session, dataTask, response); + } + + if (completionHandler) { + completionHandler(disposition); + } +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { + _RFAPISessionTask *delegate = [self delegateForTask:dataTask]; + if (delegate) { + [self removeDelegateForTask:dataTask]; + [self setDelegate:delegate forTask:downloadTask]; + } + + if (self.dataTaskDidBecomeDownloadTask) { + self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); + } +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { + + _RFAPISessionTask *delegate = [self delegateForTask:dataTask]; + [delegate URLSession:session dataTask:dataTask didReceiveData:data]; + + if (self.dataTaskDidReceiveData) { + self.dataTaskDidReceiveData(session, dataTask, data); + } +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { + NSCachedURLResponse *cachedResponse = proposedResponse; + + if (self.dataTaskWillCacheResponse) { + cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse); + } + + if (completionHandler) { + completionHandler(cachedResponse); + } +} + +#pragma mark NSURLSessionDownloadDelegate + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { + _RFAPISessionTask *delegate = [self delegateForTask:downloadTask]; + if (self.downloadTaskDidFinishDownloading) { + NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); + if (fileURL) { + delegate.downloadFileURL = fileURL; + NSError *error = nil; + [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]; + return; + } + } + [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + + _RFAPISessionTask *delegate = [self delegateForTask:downloadTask]; + [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; + if (self.downloadTaskDidWriteData) { + self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + } +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { + + _RFAPISessionTask *delegate = [self delegateForTask:downloadTask]; + [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes]; + if (self.downloadTaskDidResume) { + self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes); + } +} + +@end diff --git a/Sources/RFAPI/URLSession/RFAPISessionTask.h b/Sources/RFAPI/URLSession/RFAPISessionTask.h new file mode 100644 index 0000000..8950b0f --- /dev/null +++ b/Sources/RFAPI/URLSession/RFAPISessionTask.h @@ -0,0 +1,79 @@ +/* +RFAPISessionTask +RFAPI + +Copyright © 2019-2020 BB9z +https://github.com/RFUI/RFAPI + +The MIT License (MIT) +http://www.opensource.org/licenses/mit-license.php +*/ + +#import "RFAPIPrivate.h" + +@class _RFURLSessionManager; +@class RFNetworkActivityMessage; + +typedef void(^RFAPITaskComplation)(id __nullable responseObject, NSURLResponse *__nullable response, NSError *__nullable error); + +/** + Private object manage status. + + Some properties will be set to nil after use to save memory. + */ +@interface _RFAPISessionTask : NSObject < + RFAPITask, + NSURLSessionTaskDelegate, + NSURLSessionDataDelegate, + NSURLSessionDownloadDelegate +> + +@property (nonnull) NSURLSessionTask *task; + +#pragma mark - + +@property (nullable) RFAPI *manager; +@property (nonnull) RFAPIDefine *define; + +@property (nonnull) NSString *identifier; +@property (nullable) NSString *groupIdentifier; +@property (nullable) RFNetworkActivityMessage *activityMessage; + +/// From request context. +@property (nullable) NSDictionary *userInfo; +@property NSTimeInterval debugDelayRequestSend; + +#pragma mark - States + +@property (readonly, copy, nullable, nonatomic) NSURLRequest *currentRequest; +@property (readonly, copy, nullable, nonatomic) NSURLRequest *originalRequest; +@property (readonly, copy, nullable, nonatomic) NSURLResponse *response; +@property (nullable) id responseObject; +@property (nullable) NSError *error; + +/// +@property (readonly, nonatomic) BOOL isEnd; + +@property (nonnull) NSProgress *uploadProgress; +@property (nonnull) NSProgress *downloadProgress; +@property (nullable) RFAPIRequestProgressBlock uploadProgressBlock; +@property (nullable) RFAPIRequestProgressBlock downloadProgressBlock; + +#pragma mark - Callback + +/// Reset after use +@property (nullable) RFAPIRequestSuccessCallback success; +/// Reset after use +@property (nullable) RFAPIRequestFailureCallback failure; +/// Reset after use +@property (nullable) RFAPIRequestFinishedCallback complation; +/// Reset after use +@property (nullable) RFAPIRequestCombinedCompletionCallback combinedComplation; + + +// NO implementation +@property (copy, nullable) NSURL *downloadFileURL; +// NO implementation +@property (nullable) NSURL *__nullable (^downloadTaskDidFinishDownloading)(NSURLSession *__nonnull session, NSURLSessionDownloadTask *__nonnull downloadTask, NSURL *__nonnull location); + +@end diff --git a/Sources/RFAPI/URLSession/RFAPISessionTask.m b/Sources/RFAPI/URLSession/RFAPISessionTask.m new file mode 100644 index 0000000..2abef1f --- /dev/null +++ b/Sources/RFAPI/URLSession/RFAPISessionTask.m @@ -0,0 +1,142 @@ + +#import "RFAPISessionTask.h" +#import "RFAPISessionManager.h" + +@interface _RFAPISessionTask () +@property NSMutableData *mutableData; +@end + +@implementation _RFAPISessionTask + +- (instancetype)init { + self = [super init]; + if (self) { + _mutableData = [NSMutableData.alloc initWithCapacity:512]; + _uploadProgress = [NSProgress.alloc initWithParent:nil userInfo:nil]; + _downloadProgress = [NSProgress.alloc initWithParent:nil userInfo:nil]; + + for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ]) { + progress.totalUnitCount = NSURLSessionTransferSizeUnknown; + [progress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; + } + } + return self; +} + +- (void)dealloc { + [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; + [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; +} + +#pragma mark - + +- (NSURLRequest *)currentRequest { + return self.task.currentRequest; +} + +- (NSURLRequest *)originalRequest { + return self.task.originalRequest; +} + +- (NSURLResponse *)response { + return self.task.response; +} + +#pragma mark - + +- (BOOL)isEnd { + switch (self.task.state) { + case NSURLSessionTaskStateRunning: + case NSURLSessionTaskStateSuspended: + return NO; + case NSURLSessionTaskStateCanceling: + case NSURLSessionTaskStateCompleted: + return YES; + } +} + +- (void)suspend { + [self.task suspend]; +} + +- (void)resume { + [self.task resume]; +} + +- (void)cancel { + [self.task cancel]; +} + +#pragma mark NSProgress Tracking + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (object == self.downloadProgress) { + if (self.downloadProgressBlock) { + self.downloadProgressBlock(self, object); + } + } + else if (object == self.uploadProgress) { + if (self.uploadProgressBlock) { + self.uploadProgressBlock(self, object); + } + } +} + +#pragma mark NSURLSessionTaskDelegate + +- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + RFAPI *manager = self.manager; + NSAssert(manager, nil); + self.manager = nil; + + NSData *data = self.mutableData; + if (data) { + self.mutableData = nil; + } + + [manager _RFAPI_handleTaskComplete:self response:task.response data:data error:error]; +} + +#pragma mark NSURLSessionDataDelegate + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { + self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive; + self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived; + + [self.mutableData appendData:data]; +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + + self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend; + self.uploadProgress.completedUnitCount = task.countOfBytesSent; +} + +#pragma mark NSURLSessionDownloadDelegate + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + + self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite; + self.downloadProgress.completedUnitCount = totalBytesWritten; +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { + + self.downloadProgress.totalUnitCount = expectedTotalBytes; + self.downloadProgress.completedUnitCount = fileOffset; +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { + self.downloadFileURL = nil; + + if (self.downloadTaskDidFinishDownloading) { + NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); + self.downloadFileURL = fileURL; + if (fileURL) { + NSError *fileManagerError = nil; + [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&fileManagerError]; + } + } +} + +@end diff --git a/Test/Shared/Test_DefineManager.swift b/Test/Shared/Test_DefineManager.swift deleted file mode 100644 index ab143b0..0000000 --- a/Test/Shared/Test_DefineManager.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Test_DefineManager.swift -// RFAPI -// -// Created by BB9z on 29/03/2018. -// Copyright © 2018 RFUI. All rights reserved. -// - -import XCTest - -class Test_DefineManager: XCTestCase { - -} diff --git a/Tests/Shared/Data/big_json.data b/Tests/Shared/Data/big_json.data new file mode 100644 index 0000000..d55081f --- /dev/null +++ b/Tests/Shared/Data/big_json.data @@ -0,0 +1,9002 @@ +[ + { + "_id": "5e25839e29f8ccc6214cbece", + "index": 0, + "guid": "2acaa82e-a895-4c5a-8877-c0f9230d1ec8", + "isActive": true, + "balance": "$3,941.76", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "brown", + "name": "Tania Benson", + "gender": "female", + "company": "MEDIFAX", + "email": "taniabenson@medifax.com", + "phone": "+1 (911) 534-2412", + "address": "976 Dakota Place, Marshall, Guam, 756", + "about": "In incididunt ipsum est pariatur magna id adipisicing ut occaecat occaecat dolor et labore eiusmod. Qui quis eiusmod commodo dolor qui incididunt tempor consectetur mollit deserunt. Nulla elit aliquip adipisicing ullamco adipisicing. Veniam ea deserunt ipsum irure ad veniam voluptate. Consequat reprehenderit amet deserunt pariatur dolore cillum consequat reprehenderit cillum et. Nisi ea dolore sunt in reprehenderit deserunt. Cupidatat elit incididunt velit et ad sint ex dolor occaecat anim eiusmod Lorem.\r\n", + "registered": "2015-02-28T10:59:22 -08:00", + "latitude": 51.581934, + "longitude": 110.633235, + "tags": [ + "exercitation", + "reprehenderit", + "ullamco", + "reprehenderit", + "do", + "laboris", + "esse" + ], + "friends": [ + { + "id": 0, + "name": "Bridges Flynn" + }, + { + "id": 1, + "name": "Kristine Snow" + }, + { + "id": 2, + "name": "Adrienne Mcfadden" + } + ], + "greeting": "Hello, Tania Benson! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ea67944b67601f0e8", + "index": 1, + "guid": "0cba6651-c502-49f2-9ff7-90967748ab3b", + "isActive": true, + "balance": "$1,207.34", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "green", + "name": "Herrera Dotson", + "gender": "male", + "company": "COSMETEX", + "email": "herreradotson@cosmetex.com", + "phone": "+1 (888) 403-2634", + "address": "584 Lawton Street, Elliston, Virgin Islands, 2429", + "about": "Laborum non veniam aute dolor cupidatat magna incididunt fugiat qui ullamco officia excepteur mollit. Excepteur nulla consectetur in voluptate. In voluptate voluptate in id nulla culpa sint excepteur sint.\r\n", + "registered": "2016-11-13T08:34:15 -08:00", + "latitude": -60.895168, + "longitude": 2.753226, + "tags": [ + "aute", + "aliquip", + "nostrud", + "esse", + "nisi", + "et", + "reprehenderit" + ], + "friends": [ + { + "id": 0, + "name": "Darla Cochran" + }, + { + "id": 1, + "name": "Hope Randall" + }, + { + "id": 2, + "name": "Maggie Reid" + } + ], + "greeting": "Hello, Herrera Dotson! You have 5 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e5e3104248482f277", + "index": 2, + "guid": "862a6077-f291-46f7-a8d7-2261ce4a503e", + "isActive": false, + "balance": "$2,277.50", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "blue", + "name": "Whitley Valentine", + "gender": "male", + "company": "ROCKLOGIC", + "email": "whitleyvalentine@rocklogic.com", + "phone": "+1 (952) 530-3635", + "address": "514 Mill Street, Whitehaven, Vermont, 2608", + "about": "Minim dolor nisi amet do consectetur et non Lorem ullamco est excepteur quis. Exercitation sunt aliqua nostrud cillum reprehenderit laboris dolore laboris sint exercitation officia proident officia. Duis cupidatat deserunt in enim veniam deserunt ipsum fugiat qui pariatur. Velit dolore dolor cupidatat quis nostrud labore. Laborum mollit proident tempor elit laboris quis aliqua excepteur velit.\r\n", + "registered": "2015-03-22T10:25:34 -08:00", + "latitude": -8.517329, + "longitude": -71.940492, + "tags": [ + "anim", + "occaecat", + "fugiat", + "id", + "ullamco", + "elit", + "qui" + ], + "friends": [ + { + "id": 0, + "name": "Evangeline Rosario" + }, + { + "id": 1, + "name": "Warner Newman" + }, + { + "id": 2, + "name": "Karen Ryan" + } + ], + "greeting": "Hello, Whitley Valentine! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ee95022125131b640", + "index": 3, + "guid": "cbcb0eb5-8817-465f-bcfc-8ad0ea271b13", + "isActive": true, + "balance": "$2,520.82", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "blue", + "name": "Tran Castillo", + "gender": "male", + "company": "SLOFAST", + "email": "trancastillo@slofast.com", + "phone": "+1 (911) 496-3780", + "address": "546 Winthrop Street, Lydia, Alabama, 7669", + "about": "Reprehenderit aliqua Lorem cupidatat Lorem non do fugiat cillum velit. Tempor sit velit eiusmod culpa id id tempor non nisi consequat. Qui irure qui do officia consectetur et.\r\n", + "registered": "2019-02-03T04:06:25 -08:00", + "latitude": 37.64622, + "longitude": -15.677358, + "tags": [ + "incididunt", + "consectetur", + "ex", + "nisi", + "magna", + "aute", + "nisi" + ], + "friends": [ + { + "id": 0, + "name": "Katina Moss" + }, + { + "id": 1, + "name": "Sykes Caldwell" + }, + { + "id": 2, + "name": "Latonya Atkinson" + } + ], + "greeting": "Hello, Tran Castillo! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ee9a7925e5a22f2fa", + "index": 4, + "guid": "53fbfb2a-5d3a-45a2-a7c1-56b1b9abf863", + "isActive": false, + "balance": "$3,948.47", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "green", + "name": "Jones Aguirre", + "gender": "male", + "company": "ASSISTIA", + "email": "jonesaguirre@assistia.com", + "phone": "+1 (958) 402-3594", + "address": "568 Forbell Street, Odessa, New York, 7111", + "about": "Laboris voluptate aliquip occaecat duis anim officia ex eu consequat ut sit. Pariatur dolor ea sint aliqua aute magna ex irure ea et duis et commodo Lorem. Consequat reprehenderit occaecat adipisicing sit cupidatat voluptate Lorem cillum aliquip deserunt ea anim sunt do. Qui id occaecat minim et aliqua quis officia sit labore eu. Sint quis fugiat dolor fugiat do voluptate irure in aliqua. Exercitation esse ex consequat tempor occaecat amet duis consequat labore.\r\n", + "registered": "2015-05-22T01:23:22 -08:00", + "latitude": 57.772003, + "longitude": 151.394049, + "tags": [ + "tempor", + "sunt", + "proident", + "labore", + "cillum", + "nisi", + "excepteur" + ], + "friends": [ + { + "id": 0, + "name": "Weaver Morton" + }, + { + "id": 1, + "name": "Madge Huber" + }, + { + "id": 2, + "name": "Pollard Rodriquez" + } + ], + "greeting": "Hello, Jones Aguirre! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eaa7b1866bc6c256c", + "index": 5, + "guid": "30ffc155-c2ff-4154-94f2-7061a1d2f97a", + "isActive": true, + "balance": "$1,199.62", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "brown", + "name": "Jeanette Horton", + "gender": "female", + "company": "SLUMBERIA", + "email": "jeanettehorton@slumberia.com", + "phone": "+1 (969) 532-3624", + "address": "848 Bowne Street, Gambrills, Tennessee, 6512", + "about": "Nulla et amet cupidatat fugiat. Nisi quis eiusmod quis aliquip exercitation consectetur amet elit adipisicing velit labore dolor do est. Culpa irure eiusmod dolor eu excepteur laboris dolor mollit est.\r\n", + "registered": "2019-06-07T01:38:11 -08:00", + "latitude": 44.867289, + "longitude": 18.257038, + "tags": [ + "nostrud", + "aliqua", + "officia", + "officia", + "ut", + "velit", + "anim" + ], + "friends": [ + { + "id": 0, + "name": "Cornelia Ferguson" + }, + { + "id": 1, + "name": "Talley Alford" + }, + { + "id": 2, + "name": "Cote Bradshaw" + } + ], + "greeting": "Hello, Jeanette Horton! You have 1 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e4c1a3e7fd66f801f", + "index": 6, + "guid": "a9f82d31-2dae-40a4-b8d6-8572dc23b666", + "isActive": true, + "balance": "$1,553.95", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "blue", + "name": "Schwartz Sexton", + "gender": "male", + "company": "KINDALOO", + "email": "schwartzsexton@kindaloo.com", + "phone": "+1 (975) 527-3686", + "address": "522 Provost Street, Blanco, Delaware, 4362", + "about": "Exercitation laboris deserunt anim sunt. Elit culpa ullamco exercitation incididunt aute pariatur. Enim ut magna irure ut tempor exercitation eiusmod duis sit id. Velit consectetur duis minim laboris ipsum sint dolor. Non Lorem enim et labore enim incididunt. Anim consectetur laboris dolor anim laborum cillum qui exercitation eiusmod do.\r\n", + "registered": "2014-01-31T09:35:20 -08:00", + "latitude": -44.40675, + "longitude": -161.41052, + "tags": [ + "est", + "amet", + "excepteur", + "irure", + "veniam", + "eiusmod", + "sunt" + ], + "friends": [ + { + "id": 0, + "name": "Tammie Brooks" + }, + { + "id": 1, + "name": "Morton Walton" + }, + { + "id": 2, + "name": "Jasmine Kelley" + } + ], + "greeting": "Hello, Schwartz Sexton! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ee7f146bb69b9308d", + "index": 7, + "guid": "08d7348e-e889-4ee0-b2b8-83ef193192a6", + "isActive": false, + "balance": "$2,200.89", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "green", + "name": "Mollie Decker", + "gender": "female", + "company": "LYRICHORD", + "email": "molliedecker@lyrichord.com", + "phone": "+1 (997) 473-3648", + "address": "529 Tabor Court, Healy, Minnesota, 1406", + "about": "Sint fugiat ut aliquip culpa cupidatat esse. Proident consequat non dolor cillum. Amet incididunt officia dolor ullamco aliqua ullamco consequat ut anim ex. Aute reprehenderit commodo sunt laborum deserunt adipisicing culpa aute id aliquip magna. Qui cupidatat duis ex ipsum consequat reprehenderit id eu officia irure reprehenderit commodo. Reprehenderit ipsum enim ea amet exercitation ex sit dolor. Id qui commodo sit mollit enim proident reprehenderit nisi aute occaecat Lorem sit et ea.\r\n", + "registered": "2016-11-18T09:22:56 -08:00", + "latitude": 26.353188, + "longitude": 166.83388, + "tags": [ + "voluptate", + "enim", + "culpa", + "occaecat", + "nulla", + "dolore", + "do" + ], + "friends": [ + { + "id": 0, + "name": "Mcguire Armstrong" + }, + { + "id": 1, + "name": "Bernard Fernandez" + }, + { + "id": 2, + "name": "Lane Steele" + } + ], + "greeting": "Hello, Mollie Decker! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e289c6e002de036b2", + "index": 8, + "guid": "993b987b-3597-4ff0-8ac3-3846a073bc16", + "isActive": false, + "balance": "$2,648.83", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "blue", + "name": "English Salas", + "gender": "male", + "company": "ZENTRY", + "email": "englishsalas@zentry.com", + "phone": "+1 (973) 453-2194", + "address": "968 Doscher Street, Kerby, Maryland, 679", + "about": "Ex enim excepteur voluptate ad irure reprehenderit aliqua. Excepteur adipisicing elit voluptate proident ex minim tempor aliqua ipsum voluptate tempor. Exercitation nisi veniam tempor deserunt enim esse tempor in consequat duis nisi duis sint. Irure consectetur adipisicing ex pariatur nostrud magna esse et non officia irure. Eu nostrud in sint ad magna reprehenderit do velit anim ut amet ipsum eiusmod laboris.\r\n", + "registered": "2018-01-11T11:36:25 -08:00", + "latitude": -3.918638, + "longitude": -21.846545, + "tags": [ + "eu", + "velit", + "esse", + "qui", + "Lorem", + "commodo", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Pickett Gordon" + }, + { + "id": 1, + "name": "Ollie Tran" + }, + { + "id": 2, + "name": "Meyers Quinn" + } + ], + "greeting": "Hello, English Salas! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e9c7a86f5a161cdb3", + "index": 9, + "guid": "8defbd5b-e8ae-427c-9eb9-57a11e4fbce2", + "isActive": false, + "balance": "$3,655.31", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "green", + "name": "Robin Boyer", + "gender": "female", + "company": "AUTOMON", + "email": "robinboyer@automon.com", + "phone": "+1 (803) 501-2452", + "address": "391 Harrison Avenue, Matthews, Florida, 2716", + "about": "Ad aliqua duis sint ullamco labore elit consequat. Consectetur duis culpa sunt aute ipsum non. Sunt nostrud non tempor non ad aute ullamco occaecat elit ut officia ex. Nulla laborum aliquip ea id nisi exercitation occaecat excepteur eiusmod deserunt.\r\n", + "registered": "2019-06-18T06:15:10 -08:00", + "latitude": 51.530902, + "longitude": -21.297883, + "tags": [ + "et", + "commodo", + "et", + "elit", + "laborum", + "ad", + "pariatur" + ], + "friends": [ + { + "id": 0, + "name": "Juliet Gonzalez" + }, + { + "id": 1, + "name": "Bowers Wilkerson" + }, + { + "id": 2, + "name": "Vaughn Dale" + } + ], + "greeting": "Hello, Robin Boyer! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e58e813c25cb472b7", + "index": 10, + "guid": "600c53bc-57f8-4d3a-ae11-f1428c4e4ffd", + "isActive": false, + "balance": "$3,217.22", + "picture": "http://placehold.it/32x32", + "age": 32, + "eyeColor": "blue", + "name": "Stephens Bradford", + "gender": "male", + "company": "VIAGRAND", + "email": "stephensbradford@viagrand.com", + "phone": "+1 (996) 598-3401", + "address": "459 Lake Street, Barstow, Marshall Islands, 4910", + "about": "Lorem do voluptate aliqua consequat. Eiusmod esse laboris proident proident non consequat deserunt consequat irure ad. Cillum labore commodo anim laboris enim anim magna irure dolor mollit anim. Velit et proident anim ex minim. Ea magna officia proident aute occaecat mollit aliquip eiusmod. Velit non tempor voluptate ut. Esse ut pariatur irure occaecat amet nulla velit adipisicing pariatur sit pariatur velit Lorem.\r\n", + "registered": "2018-09-04T03:37:35 -08:00", + "latitude": 46.946095, + "longitude": -110.497562, + "tags": [ + "duis", + "exercitation", + "commodo", + "labore", + "enim", + "et", + "officia" + ], + "friends": [ + { + "id": 0, + "name": "Frank Strickland" + }, + { + "id": 1, + "name": "Patterson Hamilton" + }, + { + "id": 2, + "name": "Martina Todd" + } + ], + "greeting": "Hello, Stephens Bradford! You have 5 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ebc8f0f53807a0f39", + "index": 11, + "guid": "89f64a46-a04d-4c65-913d-07cd3340ec35", + "isActive": false, + "balance": "$1,306.64", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "blue", + "name": "Craig Cash", + "gender": "male", + "company": "DOGTOWN", + "email": "craigcash@dogtown.com", + "phone": "+1 (922) 445-2034", + "address": "387 Madison Street, Riceville, Wyoming, 291", + "about": "Ad esse Lorem duis eu adipisicing laborum adipisicing qui et aliquip non sit enim nostrud. Ex ex ea labore ex id dolor ad. Deserunt qui veniam nulla culpa fugiat dolor. Nulla laborum ipsum voluptate nisi cillum ea est in enim ut sit id cillum. Sit eu nulla ea anim laboris veniam ad amet anim officia.\r\n", + "registered": "2016-04-12T02:24:54 -08:00", + "latitude": 85.903996, + "longitude": 51.418901, + "tags": [ + "duis", + "nostrud", + "reprehenderit", + "cillum", + "consectetur", + "eiusmod", + "reprehenderit" + ], + "friends": [ + { + "id": 0, + "name": "Stout Melton" + }, + { + "id": 1, + "name": "Tamara Burton" + }, + { + "id": 2, + "name": "Tara Mccormick" + } + ], + "greeting": "Hello, Craig Cash! You have 5 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e68f2e57e115ea962", + "index": 12, + "guid": "040aede2-772e-4340-822d-4259588596e8", + "isActive": false, + "balance": "$3,993.28", + "picture": "http://placehold.it/32x32", + "age": 35, + "eyeColor": "brown", + "name": "Hardy Schmidt", + "gender": "male", + "company": "TERRAGO", + "email": "hardyschmidt@terrago.com", + "phone": "+1 (850) 596-3620", + "address": "272 Schweikerts Walk, Fairhaven, Illinois, 4957", + "about": "Culpa dolore proident ipsum laborum amet laborum nulla tempor deserunt laborum reprehenderit minim dolore. Incididunt cupidatat amet nostrud pariatur sit dolore. Id proident est duis labore non fugiat nisi dolore dolore eiusmod et officia minim. Labore ullamco eu ea cupidatat enim anim proident non cupidatat consequat mollit anim est et.\r\n", + "registered": "2016-09-25T10:42:27 -08:00", + "latitude": -44.348517, + "longitude": 73.65271, + "tags": [ + "veniam", + "ad", + "occaecat", + "qui", + "sunt", + "aliquip", + "cupidatat" + ], + "friends": [ + { + "id": 0, + "name": "Casandra Rogers" + }, + { + "id": 1, + "name": "Margie Miranda" + }, + { + "id": 2, + "name": "Dianna Gilmore" + } + ], + "greeting": "Hello, Hardy Schmidt! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e7028e02ed754168c", + "index": 13, + "guid": "c53c0347-5969-4dee-b22b-ad181d90ea6f", + "isActive": false, + "balance": "$3,318.87", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "brown", + "name": "Sophia Gilbert", + "gender": "female", + "company": "CENTREGY", + "email": "sophiagilbert@centregy.com", + "phone": "+1 (880) 471-3899", + "address": "282 Neptune Avenue, Shawmut, South Dakota, 2313", + "about": "Labore laboris commodo id sint eu ipsum enim fugiat Lorem adipisicing. Incididunt tempor duis occaecat nisi do incididunt Lorem duis incididunt et labore cupidatat consequat. Nulla commodo adipisicing voluptate velit dolore aliqua adipisicing id sunt magna ut pariatur. Amet elit aliqua cupidatat cupidatat dolor qui id Lorem adipisicing. Exercitation consequat cupidatat consectetur nostrud adipisicing enim ea sunt aliquip incididunt est. Incididunt incididunt cillum consectetur minim velit deserunt sint est dolor in do commodo cupidatat.\r\n", + "registered": "2019-09-04T02:05:30 -08:00", + "latitude": 34.422643, + "longitude": 35.596251, + "tags": [ + "ex", + "cupidatat", + "fugiat", + "excepteur", + "eu", + "consequat", + "tempor" + ], + "friends": [ + { + "id": 0, + "name": "Moses Elliott" + }, + { + "id": 1, + "name": "Carpenter Coleman" + }, + { + "id": 2, + "name": "Michelle Bowman" + } + ], + "greeting": "Hello, Sophia Gilbert! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e666fa793237d5af0", + "index": 14, + "guid": "6ec3a5cb-a611-48e5-a3bf-0da6e1446a40", + "isActive": false, + "balance": "$3,056.37", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "brown", + "name": "Ladonna Harrington", + "gender": "female", + "company": "CEDWARD", + "email": "ladonnaharrington@cedward.com", + "phone": "+1 (975) 502-3298", + "address": "349 Pilling Street, Finderne, Alaska, 8876", + "about": "Esse pariatur anim veniam irure minim adipisicing proident ea est officia culpa nostrud veniam amet. Minim ullamco occaecat elit sint exercitation consequat est adipisicing dolor amet proident commodo fugiat. Non cupidatat fugiat irure exercitation quis cillum incididunt velit eu magna nulla labore consequat non. Eu eiusmod aute deserunt consectetur Lorem in veniam sit incididunt fugiat. Enim veniam est velit occaecat Lorem proident ipsum do enim.\r\n", + "registered": "2018-03-22T06:54:31 -08:00", + "latitude": -80.308581, + "longitude": 173.848698, + "tags": [ + "mollit", + "occaecat", + "amet", + "deserunt", + "exercitation", + "mollit", + "dolore" + ], + "friends": [ + { + "id": 0, + "name": "Donaldson Swanson" + }, + { + "id": 1, + "name": "Elnora Lindsay" + }, + { + "id": 2, + "name": "Jana Marshall" + } + ], + "greeting": "Hello, Ladonna Harrington! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e20862bffd1e0234d", + "index": 15, + "guid": "66d5942a-bedd-4e67-9904-581e05c665fd", + "isActive": true, + "balance": "$2,904.00", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "green", + "name": "Addie Robertson", + "gender": "female", + "company": "IMAGINART", + "email": "addierobertson@imaginart.com", + "phone": "+1 (866) 465-3649", + "address": "259 Rockaway Avenue, Stewart, Arizona, 2607", + "about": "Incididunt pariatur dolore culpa minim est commodo aliquip duis ullamco sit pariatur deserunt. Veniam nulla labore veniam ut magna Lorem irure. Ex do sunt dolore ex in veniam irure non cillum excepteur reprehenderit aliqua est. Do elit veniam quis irure id quis in aliqua. Ad laboris ad consectetur quis enim.\r\n", + "registered": "2018-01-27T11:21:57 -08:00", + "latitude": 72.281071, + "longitude": -21.733265, + "tags": [ + "laborum", + "aliquip", + "officia", + "quis", + "cillum", + "fugiat", + "incididunt" + ], + "friends": [ + { + "id": 0, + "name": "Rosanne Downs" + }, + { + "id": 1, + "name": "Terrie Shelton" + }, + { + "id": 2, + "name": "Mann Kline" + } + ], + "greeting": "Hello, Addie Robertson! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839edd738c92a597c633", + "index": 16, + "guid": "c1fce23e-586e-459b-9b26-9ebfa86b6677", + "isActive": true, + "balance": "$3,494.03", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Castro Huff", + "gender": "male", + "company": "EXTREMO", + "email": "castrohuff@extremo.com", + "phone": "+1 (847) 443-2423", + "address": "678 Kenmore Court, Wheaton, Louisiana, 5730", + "about": "Nostrud voluptate do nostrud irure veniam sint. Nulla ad quis quis velit. Sit adipisicing reprehenderit ad laboris exercitation. Do deserunt Lorem aliqua duis Lorem cupidatat irure deserunt culpa nisi do adipisicing.\r\n", + "registered": "2017-01-08T01:05:01 -08:00", + "latitude": -27.958916, + "longitude": 152.325731, + "tags": [ + "enim", + "enim", + "et", + "dolore", + "reprehenderit", + "Lorem", + "ut" + ], + "friends": [ + { + "id": 0, + "name": "Gilda Holden" + }, + { + "id": 1, + "name": "Nannie Stone" + }, + { + "id": 2, + "name": "Howard Noble" + } + ], + "greeting": "Hello, Castro Huff! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e99ec0827416d65ea", + "index": 17, + "guid": "f4d3e4ea-b046-4e19-be77-7fe0d5b80ae0", + "isActive": true, + "balance": "$2,769.98", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "blue", + "name": "Hollie Nguyen", + "gender": "female", + "company": "STELAECOR", + "email": "hollienguyen@stelaecor.com", + "phone": "+1 (870) 563-3539", + "address": "833 Delmonico Place, Elbert, Hawaii, 2277", + "about": "Exercitation sunt fugiat id cillum amet irure cupidatat ipsum nostrud nisi nulla eu commodo sit. Irure proident exercitation aute quis nulla eiusmod incididunt fugiat. Magna laborum laboris duis cillum aliqua aliqua occaecat tempor labore incididunt esse commodo minim mollit. Velit quis deserunt incididunt velit culpa. Do cillum amet duis non sit dolor id dolore reprehenderit ullamco. Sit pariatur voluptate commodo est in excepteur eu dolore nisi laborum.\r\n", + "registered": "2015-06-21T11:12:01 -08:00", + "latitude": 83.931477, + "longitude": 10.153869, + "tags": [ + "dolore", + "nisi", + "nostrud", + "duis", + "ex", + "nostrud", + "aute" + ], + "friends": [ + { + "id": 0, + "name": "Glenda Romero" + }, + { + "id": 1, + "name": "Tanisha Hawkins" + }, + { + "id": 2, + "name": "Kirkland Garza" + } + ], + "greeting": "Hello, Hollie Nguyen! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e2348909948641d81", + "index": 18, + "guid": "d5dead48-d78a-4eb7-9a78-3da51ea0119a", + "isActive": false, + "balance": "$2,384.50", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "green", + "name": "Jenny Gutierrez", + "gender": "female", + "company": "OMATOM", + "email": "jennygutierrez@omatom.com", + "phone": "+1 (844) 497-3065", + "address": "131 Matthews Place, Emison, Ohio, 8998", + "about": "In commodo velit consequat proident id ullamco ut eiusmod ex sunt duis dolore. Est consectetur aliquip et ullamco quis. Nostrud sint aliqua duis eiusmod minim magna non incididunt magna nulla aute. Eu nisi laborum consectetur exercitation officia esse esse veniam laboris. Eiusmod excepteur quis exercitation laboris sit velit duis laborum sunt dolor tempor incididunt excepteur in. Nostrud duis est culpa non. Aliqua deserunt incididunt sunt labore voluptate sint tempor reprehenderit ut enim cillum.\r\n", + "registered": "2019-02-24T01:58:40 -08:00", + "latitude": 34.024879, + "longitude": -175.468037, + "tags": [ + "ut", + "non", + "non", + "consequat", + "et", + "excepteur", + "incididunt" + ], + "friends": [ + { + "id": 0, + "name": "Holder Jenkins" + }, + { + "id": 1, + "name": "Solis Keller" + }, + { + "id": 2, + "name": "Mathis Copeland" + } + ], + "greeting": "Hello, Jenny Gutierrez! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eb842e8bda05fed83", + "index": 19, + "guid": "ef53768c-3ee8-464c-a41f-a2c5c83d8164", + "isActive": false, + "balance": "$3,955.04", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "blue", + "name": "Deanne Chapman", + "gender": "female", + "company": "VANTAGE", + "email": "deannechapman@vantage.com", + "phone": "+1 (815) 453-2828", + "address": "906 Prospect Street, Ahwahnee, Palau, 3227", + "about": "Amet cillum do non in excepteur adipisicing in minim. Laborum qui veniam esse irure voluptate. Mollit enim mollit ad voluptate Lorem mollit.\r\n", + "registered": "2014-12-09T02:43:38 -08:00", + "latitude": 84.321989, + "longitude": -102.602139, + "tags": [ + "aute", + "nulla", + "consectetur", + "reprehenderit", + "consectetur", + "quis", + "sint" + ], + "friends": [ + { + "id": 0, + "name": "Hattie Walter" + }, + { + "id": 1, + "name": "Stacie Munoz" + }, + { + "id": 2, + "name": "Deidre Cunningham" + } + ], + "greeting": "Hello, Deanne Chapman! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e410a5951f65bc9ff", + "index": 20, + "guid": "644cd201-63f9-4e93-85df-1f786b1c66ba", + "isActive": true, + "balance": "$1,442.08", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "blue", + "name": "Bridget Mann", + "gender": "female", + "company": "CIRCUM", + "email": "bridgetmann@circum.com", + "phone": "+1 (812) 401-2809", + "address": "735 Frank Court, Delshire, North Dakota, 1087", + "about": "Ipsum voluptate ipsum aliqua qui ad consectetur sint proident dolor do aute. Amet tempor amet in et cillum esse cupidatat nisi. Consectetur non reprehenderit eu exercitation tempor ex aliquip eiusmod. Fugiat sit magna ex nostrud minim ut sit sunt deserunt exercitation ut.\r\n", + "registered": "2019-01-21T12:08:30 -08:00", + "latitude": 76.34281, + "longitude": 15.926472, + "tags": [ + "ex", + "amet", + "officia", + "cillum", + "in", + "incididunt", + "do" + ], + "friends": [ + { + "id": 0, + "name": "Clay Harrell" + }, + { + "id": 1, + "name": "Lauren Bowen" + }, + { + "id": 2, + "name": "Marcella Mathews" + } + ], + "greeting": "Hello, Bridget Mann! You have 4 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e54f150a19c80809e", + "index": 21, + "guid": "c26b21dd-0a84-428d-a5e4-dffde14f2c9d", + "isActive": false, + "balance": "$2,383.22", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "blue", + "name": "Polly Ferrell", + "gender": "female", + "company": "GEOFORMA", + "email": "pollyferrell@geoforma.com", + "phone": "+1 (906) 468-3027", + "address": "516 Jerome Street, Ticonderoga, California, 8482", + "about": "Ipsum incididunt cupidatat ullamco consequat aute aliqua. Mollit enim non excepteur incididunt consequat proident amet voluptate cillum elit. Excepteur id sit nostrud irure officia deserunt deserunt cillum commodo. Commodo reprehenderit minim dolore aute nostrud enim consectetur.\r\n", + "registered": "2018-10-13T11:13:47 -08:00", + "latitude": 16.48978, + "longitude": 67.880098, + "tags": [ + "excepteur", + "et", + "officia", + "pariatur", + "est", + "anim", + "culpa" + ], + "friends": [ + { + "id": 0, + "name": "Rebecca Brewer" + }, + { + "id": 1, + "name": "Matthews Russo" + }, + { + "id": 2, + "name": "Woodard Fleming" + } + ], + "greeting": "Hello, Polly Ferrell! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e741598ea2dd14888", + "index": 22, + "guid": "a788fa77-7efd-47c8-a8a5-41798b749fad", + "isActive": true, + "balance": "$2,841.82", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "brown", + "name": "Bonner Dominguez", + "gender": "male", + "company": "NIPAZ", + "email": "bonnerdominguez@nipaz.com", + "phone": "+1 (983) 511-3878", + "address": "486 Cooke Court, Inkerman, Nevada, 6265", + "about": "Sint elit officia minim id est duis anim nisi. Ea velit sit magna in cupidatat mollit ipsum commodo. Proident mollit non nisi sunt velit cupidatat nostrud mollit id irure duis excepteur cillum. Labore eu minim culpa est nisi cupidatat cupidatat.\r\n", + "registered": "2018-04-17T09:38:08 -08:00", + "latitude": 23.071206, + "longitude": 116.353145, + "tags": [ + "enim", + "commodo", + "laboris", + "non", + "excepteur", + "amet", + "consequat" + ], + "friends": [ + { + "id": 0, + "name": "Patti Hahn" + }, + { + "id": 1, + "name": "Kane Mccoy" + }, + { + "id": 2, + "name": "Naomi Sampson" + } + ], + "greeting": "Hello, Bonner Dominguez! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e1c4bf69cb76171e3", + "index": 23, + "guid": "9cf797fc-f386-4441-8769-dd64fe797222", + "isActive": true, + "balance": "$1,315.24", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "brown", + "name": "Willis Pratt", + "gender": "male", + "company": "GOKO", + "email": "willispratt@goko.com", + "phone": "+1 (964) 453-3355", + "address": "855 Woodside Avenue, Jacksonwald, Washington, 9391", + "about": "Dolore pariatur irure dolore enim quis ea eu. Enim sit mollit fugiat excepteur et adipisicing nostrud aliquip eu qui. Ex anim occaecat consequat culpa. Voluptate elit nostrud duis non mollit excepteur nulla dolore occaecat. Nulla ad mollit cillum occaecat laborum. Nulla veniam deserunt magna reprehenderit id ex.\r\n", + "registered": "2016-07-19T10:29:13 -08:00", + "latitude": -4.528696, + "longitude": 157.798858, + "tags": [ + "dolore", + "magna", + "consequat", + "eiusmod", + "tempor", + "do", + "elit" + ], + "friends": [ + { + "id": 0, + "name": "Diann Kerr" + }, + { + "id": 1, + "name": "Rochelle Weiss" + }, + { + "id": 2, + "name": "Holmes Byers" + } + ], + "greeting": "Hello, Willis Pratt! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e88028f1d539052b6", + "index": 24, + "guid": "3fe6eb05-ea77-458e-8f03-1bdeac988498", + "isActive": true, + "balance": "$1,583.26", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "blue", + "name": "Castillo Sherman", + "gender": "male", + "company": "ARTIQ", + "email": "castillosherman@artiq.com", + "phone": "+1 (871) 549-3297", + "address": "626 Indiana Place, Morgandale, Arkansas, 7498", + "about": "Labore magna sit fugiat aliqua aliquip adipisicing aliqua incididunt quis ut incididunt. Nulla quis sint non veniam commodo consequat enim. Commodo cupidatat minim officia fugiat officia exercitation eu reprehenderit. Aliquip laborum consectetur laborum consequat velit proident commodo elit tempor aliqua velit cillum veniam.\r\n", + "registered": "2017-12-20T09:55:27 -08:00", + "latitude": 29.344803, + "longitude": 7.490124, + "tags": [ + "laborum", + "commodo", + "proident", + "cillum", + "et", + "dolor", + "quis" + ], + "friends": [ + { + "id": 0, + "name": "Blake Joyner" + }, + { + "id": 1, + "name": "Lori Washington" + }, + { + "id": 2, + "name": "Tricia English" + } + ], + "greeting": "Hello, Castillo Sherman! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e87fe8d6a7ba2a108", + "index": 25, + "guid": "f578fc44-8c93-4a47-9c25-8404d83184ff", + "isActive": true, + "balance": "$1,150.63", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "brown", + "name": "Rowena Kramer", + "gender": "female", + "company": "VIDTO", + "email": "rowenakramer@vidto.com", + "phone": "+1 (847) 441-2146", + "address": "842 Baughman Place, Kapowsin, Wisconsin, 5840", + "about": "Incididunt mollit veniam enim qui elit. Id quis in et nostrud minim tempor irure. Minim eiusmod commodo ullamco cillum veniam aliquip ut nostrud. Tempor adipisicing aliqua laborum nulla deserunt do tempor cillum irure laborum id laboris. Veniam in do do aute ex cillum. Minim minim commodo aute nostrud reprehenderit.\r\n", + "registered": "2014-09-11T09:56:00 -08:00", + "latitude": -59.8347, + "longitude": -138.098954, + "tags": [ + "dolor", + "reprehenderit", + "id", + "aliquip", + "duis", + "est", + "sunt" + ], + "friends": [ + { + "id": 0, + "name": "Shawn Slater" + }, + { + "id": 1, + "name": "Riddle Faulkner" + }, + { + "id": 2, + "name": "Wyatt Coffey" + } + ], + "greeting": "Hello, Rowena Kramer! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839eba0be39cab58c78c", + "index": 26, + "guid": "92cdde9b-742b-43dc-9605-233948da6406", + "isActive": true, + "balance": "$2,668.41", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "blue", + "name": "Isabelle Fox", + "gender": "female", + "company": "NETILITY", + "email": "isabellefox@netility.com", + "phone": "+1 (836) 545-3087", + "address": "279 Sullivan Street, Yonah, West Virginia, 4598", + "about": "Officia pariatur minim consectetur mollit adipisicing anim qui aute ea aliquip aliquip. Sit commodo exercitation nisi in sint ut sint magna eu. Lorem ut minim labore commodo laborum. Labore velit irure aliqua eiusmod reprehenderit aute et labore laboris quis.\r\n", + "registered": "2017-02-25T05:57:02 -08:00", + "latitude": 44.376202, + "longitude": -68.271985, + "tags": [ + "commodo", + "ea", + "id", + "proident", + "proident", + "sit", + "dolore" + ], + "friends": [ + { + "id": 0, + "name": "Mclaughlin Murphy" + }, + { + "id": 1, + "name": "Earnestine Carter" + }, + { + "id": 2, + "name": "Liz Horn" + } + ], + "greeting": "Hello, Isabelle Fox! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ec441bc01c8a310fb", + "index": 27, + "guid": "3de1e335-342c-444f-8cbe-d40d3c8fe054", + "isActive": true, + "balance": "$2,797.19", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "brown", + "name": "Jillian Ochoa", + "gender": "female", + "company": "KONGENE", + "email": "jillianochoa@kongene.com", + "phone": "+1 (981) 574-2287", + "address": "392 McClancy Place, Eagletown, Mississippi, 137", + "about": "Veniam eu occaecat fugiat nisi ad id ut excepteur. Proident esse voluptate enim voluptate ut. Laboris consectetur occaecat exercitation exercitation mollit quis cillum in elit. Sunt do in sunt ullamco.\r\n", + "registered": "2016-05-24T11:10:38 -08:00", + "latitude": -67.058989, + "longitude": -27.044471, + "tags": [ + "aliqua", + "aliqua", + "nisi", + "commodo", + "magna", + "aliqua", + "et" + ], + "friends": [ + { + "id": 0, + "name": "Lucia York" + }, + { + "id": 1, + "name": "Myers Spencer" + }, + { + "id": 2, + "name": "Estela Burt" + } + ], + "greeting": "Hello, Jillian Ochoa! You have 3 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ee32e4db97de8949b", + "index": 28, + "guid": "35cc11e7-732a-480e-9682-2b29af3190e8", + "isActive": true, + "balance": "$3,879.59", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "brown", + "name": "Rosemary Freeman", + "gender": "female", + "company": "KENEGY", + "email": "rosemaryfreeman@kenegy.com", + "phone": "+1 (817) 572-2752", + "address": "311 Elm Place, Rockhill, American Samoa, 8690", + "about": "Duis ex ipsum laboris labore. Anim reprehenderit id irure labore non ea aliquip consectetur proident ea est incididunt in. Aliqua exercitation cupidatat eu duis dolor excepteur ex eiusmod consectetur reprehenderit exercitation incididunt. Laborum dolor eu aliqua cillum ea ipsum sint ipsum ipsum magna quis laborum duis. Ut reprehenderit dolor deserunt irure ad voluptate aute reprehenderit. Duis qui qui pariatur nulla est tempor voluptate irure deserunt.\r\n", + "registered": "2018-03-19T10:57:16 -08:00", + "latitude": 25.552615, + "longitude": -71.204266, + "tags": [ + "proident", + "voluptate", + "cillum", + "officia", + "laboris", + "ipsum", + "deserunt" + ], + "friends": [ + { + "id": 0, + "name": "Noreen Morrison" + }, + { + "id": 1, + "name": "Serena Berg" + }, + { + "id": 2, + "name": "Nielsen Hardin" + } + ], + "greeting": "Hello, Rosemary Freeman! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ee75ea3bbc704e163", + "index": 29, + "guid": "acceeb9c-2f81-4d63-ab75-4c4785812d87", + "isActive": true, + "balance": "$2,834.37", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "green", + "name": "Herminia Castaneda", + "gender": "female", + "company": "FROSNEX", + "email": "herminiacastaneda@frosnex.com", + "phone": "+1 (937) 474-2021", + "address": "985 Kane Street, Hayes, New Mexico, 2483", + "about": "Est in duis veniam adipisicing culpa magna laboris officia. Ullamco proident proident dolor velit mollit mollit nisi. Do commodo est ea veniam cillum laborum veniam deserunt pariatur excepteur elit.\r\n", + "registered": "2018-02-25T01:33:56 -08:00", + "latitude": -57.104127, + "longitude": 101.452272, + "tags": [ + "eu", + "nostrud", + "consectetur", + "Lorem", + "irure", + "dolor", + "nulla" + ], + "friends": [ + { + "id": 0, + "name": "Ingrid Bauer" + }, + { + "id": 1, + "name": "Zamora Greer" + }, + { + "id": 2, + "name": "John Rowe" + } + ], + "greeting": "Hello, Herminia Castaneda! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e75375f21e0931244", + "index": 30, + "guid": "f21dc7d0-f48e-4ddc-b3a3-8681a012952e", + "isActive": false, + "balance": "$2,774.98", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "green", + "name": "Eloise Woods", + "gender": "female", + "company": "CINASTER", + "email": "eloisewoods@cinaster.com", + "phone": "+1 (976) 443-3135", + "address": "324 Will Place, Joes, Massachusetts, 362", + "about": "Reprehenderit aute et officia eu et proident deserunt dolor nisi occaecat culpa nostrud. Incididunt fugiat qui non eu amet eiusmod do aliqua aliqua esse irure commodo. Non consequat enim qui consequat nulla laborum qui ea. Laboris id eu eu veniam laboris deserunt ex ea.\r\n", + "registered": "2014-10-16T02:30:32 -08:00", + "latitude": 80.447815, + "longitude": 91.193779, + "tags": [ + "et", + "duis", + "labore", + "laborum", + "est", + "laborum", + "sunt" + ], + "friends": [ + { + "id": 0, + "name": "Simon Rush" + }, + { + "id": 1, + "name": "May Lindsey" + }, + { + "id": 2, + "name": "Banks Maldonado" + } + ], + "greeting": "Hello, Eloise Woods! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ea5c6cd5133f18bb1", + "index": 31, + "guid": "cc15c840-87d9-4fcc-a1d0-8b39357d2a9a", + "isActive": true, + "balance": "$2,029.32", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "brown", + "name": "Juarez Wilson", + "gender": "male", + "company": "JUMPSTACK", + "email": "juarezwilson@jumpstack.com", + "phone": "+1 (872) 517-3769", + "address": "322 Pierrepont Street, Canoochee, Colorado, 2249", + "about": "Nisi elit minim excepteur mollit dolor commodo adipisicing ex amet aute occaecat incididunt. Pariatur et et nulla velit officia anim et in laborum sunt qui nisi dolore. Irure excepteur ex nisi amet excepteur sint sit ea mollit irure ipsum adipisicing pariatur. Excepteur nisi consequat ipsum consequat proident ea mollit do cupidatat tempor voluptate consectetur. Magna in deserunt nisi in cillum tempor magna esse enim minim minim occaecat et et. Sint eiusmod do culpa laborum anim anim anim. Aute mollit Lorem deserunt occaecat.\r\n", + "registered": "2016-10-11T11:53:34 -08:00", + "latitude": -18.593163, + "longitude": -97.341994, + "tags": [ + "dolore", + "consequat", + "tempor", + "laboris", + "reprehenderit", + "enim", + "qui" + ], + "friends": [ + { + "id": 0, + "name": "Luann Holt" + }, + { + "id": 1, + "name": "Muriel Orr" + }, + { + "id": 2, + "name": "Evangelina Horne" + } + ], + "greeting": "Hello, Juarez Wilson! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e4cd09804072275e6", + "index": 32, + "guid": "da6f351d-a588-4f6b-84e3-3cd00494f188", + "isActive": true, + "balance": "$2,223.15", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "blue", + "name": "Neal Dawson", + "gender": "male", + "company": "KIDGREASE", + "email": "nealdawson@kidgrease.com", + "phone": "+1 (835) 411-3423", + "address": "406 Harway Avenue, Maplewood, District Of Columbia, 4383", + "about": "Proident exercitation voluptate voluptate cupidatat reprehenderit do Lorem enim dolor commodo labore id dolore. Mollit irure ex ex sint. Aliquip pariatur cupidatat sit duis dolore ullamco reprehenderit. Ullamco sunt irure velit do nulla eu officia aliquip enim mollit qui nisi aliqua dolor. Quis in minim nisi magna.\r\n", + "registered": "2017-05-30T06:10:36 -08:00", + "latitude": 60.955601, + "longitude": 135.436005, + "tags": [ + "et", + "aliquip", + "in", + "labore", + "deserunt", + "aliqua", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Gay Wong" + }, + { + "id": 1, + "name": "Vicky Foley" + }, + { + "id": 2, + "name": "Nanette Hood" + } + ], + "greeting": "Hello, Neal Dawson! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ec4e44bae9c1b2b85", + "index": 33, + "guid": "79e0e997-bc96-46f1-b717-5cc6b1b80a8d", + "isActive": false, + "balance": "$2,216.64", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "blue", + "name": "Rivera Fischer", + "gender": "male", + "company": "POOCHIES", + "email": "riverafischer@poochies.com", + "phone": "+1 (977) 433-3275", + "address": "522 Hart Street, Cazadero, Pennsylvania, 483", + "about": "Eu dolor laborum fugiat do esse laboris veniam labore. Nulla cupidatat elit incididunt ea aute dolor in eiusmod magna. Dolor excepteur reprehenderit eu quis dolore pariatur veniam pariatur proident officia sit veniam. Quis pariatur voluptate ullamco ea dolore dolor et sint ullamco aute sint. Incididunt ad commodo proident do Lorem eu consequat quis Lorem enim veniam tempor.\r\n", + "registered": "2016-10-01T05:35:40 -08:00", + "latitude": 89.223842, + "longitude": -43.475522, + "tags": [ + "velit", + "anim", + "enim", + "cupidatat", + "elit", + "veniam", + "laboris" + ], + "friends": [ + { + "id": 0, + "name": "Jennie Daugherty" + }, + { + "id": 1, + "name": "Sanford Brown" + }, + { + "id": 2, + "name": "Carr Rios" + } + ], + "greeting": "Hello, Rivera Fischer! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e5d8c48dee9542483", + "index": 34, + "guid": "81b264e8-429b-4f1a-88f9-b4aff26f8ea9", + "isActive": true, + "balance": "$2,989.55", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "brown", + "name": "Hamilton Moore", + "gender": "male", + "company": "EARTHWAX", + "email": "hamiltonmoore@earthwax.com", + "phone": "+1 (855) 459-3806", + "address": "420 Fleet Walk, Ladera, Montana, 4806", + "about": "Dolor nostrud incididunt ex laboris Lorem laborum. Cillum nostrud ad proident sit. Esse anim et deserunt laborum aute. Ad est et sint laboris voluptate minim proident culpa non est deserunt ea magna aliqua.\r\n", + "registered": "2016-03-21T05:50:44 -08:00", + "latitude": 26.822496, + "longitude": 145.403285, + "tags": [ + "mollit", + "officia", + "ex", + "do", + "elit", + "sint", + "eu" + ], + "friends": [ + { + "id": 0, + "name": "Bartlett Mcclure" + }, + { + "id": 1, + "name": "Sharon Fitzpatrick" + }, + { + "id": 2, + "name": "Marquez Johns" + } + ], + "greeting": "Hello, Hamilton Moore! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ea412bdf42e60ce48", + "index": 35, + "guid": "37e12f0c-ff0b-4139-8fef-a116656dd6b7", + "isActive": true, + "balance": "$2,934.52", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "green", + "name": "Wheeler Morgan", + "gender": "male", + "company": "UNCORP", + "email": "wheelermorgan@uncorp.com", + "phone": "+1 (832) 427-2051", + "address": "855 Henderson Walk, Allendale, Northern Mariana Islands, 349", + "about": "Incididunt duis aliquip reprehenderit amet fugiat ex cupidatat ipsum. Qui veniam mollit exercitation culpa nulla. Dolor incididunt amet in Lorem duis ea adipisicing veniam et commodo. Incididunt ea in cillum culpa incididunt est amet Lorem quis veniam velit.\r\n", + "registered": "2018-03-01T03:47:39 -08:00", + "latitude": 68.919783, + "longitude": 36.884913, + "tags": [ + "cupidatat", + "irure", + "do", + "qui", + "irure", + "culpa", + "eiusmod" + ], + "friends": [ + { + "id": 0, + "name": "Norma Rivers" + }, + { + "id": 1, + "name": "Kathy Sparks" + }, + { + "id": 2, + "name": "Lewis Blankenship" + } + ], + "greeting": "Hello, Wheeler Morgan! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e5ad09ff45d41a63e", + "index": 36, + "guid": "6a141c3d-795c-4bc8-aa05-7e7498e55e92", + "isActive": false, + "balance": "$1,848.95", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "blue", + "name": "Alissa Garrett", + "gender": "female", + "company": "LOCAZONE", + "email": "alissagarrett@locazone.com", + "phone": "+1 (847) 418-3507", + "address": "468 Cozine Avenue, Bawcomville, Missouri, 5786", + "about": "Lorem dolor dolore dolore anim occaecat sit labore aute do nisi enim magna. Velit nostrud mollit commodo nostrud nulla occaecat cupidatat duis fugiat fugiat consectetur sit mollit. Ullamco sunt pariatur sint excepteur consequat qui pariatur exercitation fugiat fugiat. Lorem tempor culpa eu commodo aliqua do. Dolor ut aute cupidatat dolor sit. Do deserunt culpa aliquip veniam laboris aliquip nisi reprehenderit.\r\n", + "registered": "2018-07-13T03:00:49 -08:00", + "latitude": -34.969308, + "longitude": 62.639595, + "tags": [ + "dolor", + "id", + "proident", + "velit", + "ut", + "ea", + "minim" + ], + "friends": [ + { + "id": 0, + "name": "Baker Patterson" + }, + { + "id": 1, + "name": "Bertie Hall" + }, + { + "id": 2, + "name": "Joanna Meyers" + } + ], + "greeting": "Hello, Alissa Garrett! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839eb06b2be5fbafbf5a", + "index": 37, + "guid": "10676856-a615-4b3b-b43c-d1e83c778b47", + "isActive": false, + "balance": "$3,975.52", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "brown", + "name": "Bush Meyer", + "gender": "male", + "company": "CYTREX", + "email": "bushmeyer@cytrex.com", + "phone": "+1 (960) 597-2626", + "address": "553 Bogart Street, Lorraine, Kansas, 879", + "about": "Ad laboris fugiat enim fugiat incididunt dolor est commodo nisi do mollit ut occaecat. In cupidatat deserunt est sunt exercitation aute nulla ad cupidatat id irure veniam. Veniam ad fugiat nulla culpa et anim sint. Officia deserunt excepteur ex nisi ullamco labore ut officia Lorem. Proident irure elit sint nulla.\r\n", + "registered": "2017-09-13T04:13:11 -08:00", + "latitude": 41.101991, + "longitude": -12.838799, + "tags": [ + "deserunt", + "nulla", + "fugiat", + "voluptate", + "nisi", + "ex", + "adipisicing" + ], + "friends": [ + { + "id": 0, + "name": "Lloyd Ross" + }, + { + "id": 1, + "name": "Horn Christian" + }, + { + "id": 2, + "name": "Madeline Page" + } + ], + "greeting": "Hello, Bush Meyer! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839eabb4ad5de178276e", + "index": 38, + "guid": "9a174df0-7212-4663-8611-27024f1b2f16", + "isActive": true, + "balance": "$3,714.10", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "brown", + "name": "Ferguson Kaufman", + "gender": "male", + "company": "GINK", + "email": "fergusonkaufman@gink.com", + "phone": "+1 (925) 452-3862", + "address": "545 Borinquen Pl, Fedora, Georgia, 1032", + "about": "Consequat sunt consectetur dolore in ea enim proident aliquip. Officia cupidatat duis voluptate aliquip elit qui elit culpa. Tempor ex ad irure quis laborum quis aliquip sunt. Elit cillum esse deserunt ea cupidatat proident nostrud dolore incididunt mollit laboris in voluptate ullamco.\r\n", + "registered": "2017-01-03T03:59:14 -08:00", + "latitude": 70.326801, + "longitude": 62.462349, + "tags": [ + "ut", + "sit", + "dolor", + "tempor", + "ex", + "veniam", + "laborum" + ], + "friends": [ + { + "id": 0, + "name": "Jacqueline Avery" + }, + { + "id": 1, + "name": "Yvonne Peck" + }, + { + "id": 2, + "name": "Haley Lee" + } + ], + "greeting": "Hello, Ferguson Kaufman! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e6359b53f895f5005", + "index": 39, + "guid": "cc2860b8-4e31-490e-b97c-d43469692332", + "isActive": true, + "balance": "$1,136.88", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "brown", + "name": "Amanda Hooper", + "gender": "female", + "company": "BARKARAMA", + "email": "amandahooper@barkarama.com", + "phone": "+1 (893) 482-2431", + "address": "111 Degraw Street, Bridgetown, New Jersey, 9254", + "about": "Exercitation veniam tempor tempor reprehenderit cillum. Nisi cupidatat nulla ad pariatur et. Ex cupidatat sunt sunt ullamco cupidatat tempor eiusmod tempor consectetur mollit eiusmod dolor eu ex. Do eu occaecat exercitation excepteur. Aute reprehenderit consequat elit Lorem fugiat commodo laborum aliquip esse commodo ex ullamco.\r\n", + "registered": "2016-07-18T02:19:37 -08:00", + "latitude": 36.096551, + "longitude": -111.450982, + "tags": [ + "id", + "aliquip", + "laborum", + "enim", + "sit", + "ad", + "officia" + ], + "friends": [ + { + "id": 0, + "name": "Paul Perry" + }, + { + "id": 1, + "name": "Elisabeth Fuentes" + }, + { + "id": 2, + "name": "Shauna Higgins" + } + ], + "greeting": "Hello, Amanda Hooper! You have 5 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e8dda0d8e309b59e5", + "index": 40, + "guid": "39e7df4a-7c1a-41a1-be09-319087b582c9", + "isActive": false, + "balance": "$3,200.88", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "brown", + "name": "Page Valdez", + "gender": "male", + "company": "MIRACLIS", + "email": "pagevaldez@miraclis.com", + "phone": "+1 (947) 466-3330", + "address": "823 Rapelye Street, Williams, Oklahoma, 181", + "about": "Do laboris dolor reprehenderit ipsum mollit et. Anim exercitation adipisicing ea amet aliqua laborum laboris eu aliqua. Et tempor mollit eiusmod pariatur qui dolor eu velit magna velit eiusmod. Mollit excepteur nisi anim id sint nulla commodo reprehenderit ut commodo.\r\n", + "registered": "2019-05-07T10:32:06 -08:00", + "latitude": -42.350032, + "longitude": 69.357593, + "tags": [ + "deserunt", + "voluptate", + "cupidatat", + "fugiat", + "in", + "reprehenderit", + "commodo" + ], + "friends": [ + { + "id": 0, + "name": "Romero Robinson" + }, + { + "id": 1, + "name": "Chen Petty" + }, + { + "id": 2, + "name": "Fields Roberson" + } + ], + "greeting": "Hello, Page Valdez! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ebaa4abb902869f36", + "index": 41, + "guid": "04a3755d-cc6f-43f4-9329-2db07410950c", + "isActive": false, + "balance": "$1,868.22", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "blue", + "name": "Willa Chang", + "gender": "female", + "company": "ZENSURE", + "email": "willachang@zensure.com", + "phone": "+1 (915) 520-3448", + "address": "943 Ross Street, Stockwell, North Carolina, 6292", + "about": "Qui ullamco est do et incididunt pariatur ad velit Lorem nostrud velit qui. Occaecat qui occaecat ut laboris excepteur eiusmod. Officia ad ut est non proident. Eiusmod cillum pariatur nisi quis cupidatat sit ad qui. Proident veniam incididunt exercitation reprehenderit tempor reprehenderit labore duis labore et enim in.\r\n", + "registered": "2016-06-03T09:33:38 -08:00", + "latitude": -1.500737, + "longitude": -107.565888, + "tags": [ + "mollit", + "est", + "sit", + "tempor", + "nostrud", + "sit", + "aliqua" + ], + "friends": [ + { + "id": 0, + "name": "Jordan Holland" + }, + { + "id": 1, + "name": "Mullen Landry" + }, + { + "id": 2, + "name": "House Gamble" + } + ], + "greeting": "Hello, Willa Chang! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e9e1b1e5ecae9a9d2", + "index": 42, + "guid": "01c5c7e1-f16d-48d1-a6e8-f52cdeccf543", + "isActive": false, + "balance": "$3,717.15", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Rush Reeves", + "gender": "male", + "company": "COMTOURS", + "email": "rushreeves@comtours.com", + "phone": "+1 (874) 505-3533", + "address": "892 Hampton Place, Zortman, Rhode Island, 701", + "about": "Eiusmod tempor aliqua tempor nisi. Fugiat consectetur pariatur cillum excepteur officia cupidatat excepteur est deserunt cillum occaecat cupidatat est. Consectetur incididunt enim eiusmod nostrud cupidatat fugiat reprehenderit culpa aliqua in in ea.\r\n", + "registered": "2017-02-07T11:26:03 -08:00", + "latitude": 41.400936, + "longitude": 45.747997, + "tags": [ + "cillum", + "aliqua", + "minim", + "cupidatat", + "laborum", + "aliquip", + "reprehenderit" + ], + "friends": [ + { + "id": 0, + "name": "Reba Singleton" + }, + { + "id": 1, + "name": "Tracey Adkins" + }, + { + "id": 2, + "name": "Delores Jones" + } + ], + "greeting": "Hello, Rush Reeves! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ec318575a50787700", + "index": 43, + "guid": "d454b3d7-6e31-424d-80bc-93c10d2c5722", + "isActive": false, + "balance": "$2,727.66", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "blue", + "name": "Acevedo Guthrie", + "gender": "male", + "company": "ZENTHALL", + "email": "acevedoguthrie@zenthall.com", + "phone": "+1 (878) 573-3637", + "address": "491 Sandford Street, Johnsonburg, Maine, 4605", + "about": "Mollit non commodo laboris consectetur amet mollit occaecat ea ipsum pariatur amet ad labore non. Sunt elit nulla nostrud nisi in fugiat. Exercitation id irure consectetur sunt. Non nulla aute id cupidatat est.\r\n", + "registered": "2015-09-27T06:53:27 -08:00", + "latitude": -56.546095, + "longitude": -138.486852, + "tags": [ + "commodo", + "in", + "non", + "mollit", + "irure", + "cupidatat", + "enim" + ], + "friends": [ + { + "id": 0, + "name": "Watkins Haley" + }, + { + "id": 1, + "name": "Klein Finch" + }, + { + "id": 2, + "name": "Marta Jacobs" + } + ], + "greeting": "Hello, Acevedo Guthrie! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ec209b2e5cad17523", + "index": 44, + "guid": "8efea0a2-9681-4539-be4e-5a46e03a617a", + "isActive": false, + "balance": "$1,470.60", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "blue", + "name": "Susana Casey", + "gender": "female", + "company": "SEALOUD", + "email": "susanacasey@sealoud.com", + "phone": "+1 (987) 536-3002", + "address": "278 Baycliff Terrace, Delwood, Connecticut, 3686", + "about": "Elit deserunt dolor qui pariatur dolore et. Est est esse labore aliqua aliqua enim culpa laboris Lorem minim eu irure. Et ad laboris mollit magna in voluptate veniam eiusmod. Labore veniam consequat ut proident duis nulla aliquip sit dolor amet id excepteur quis.\r\n", + "registered": "2018-02-14T10:12:18 -08:00", + "latitude": -18.416496, + "longitude": -115.023728, + "tags": [ + "dolore", + "et", + "et", + "aliquip", + "et", + "deserunt", + "laboris" + ], + "friends": [ + { + "id": 0, + "name": "Angie Dalton" + }, + { + "id": 1, + "name": "Wallace Bowers" + }, + { + "id": 2, + "name": "Humphrey Goff" + } + ], + "greeting": "Hello, Susana Casey! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e3b99cada75f409d8", + "index": 45, + "guid": "358dba77-b6ae-46c2-93c5-d6a6e4e0ab0e", + "isActive": false, + "balance": "$1,412.21", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "green", + "name": "Valeria Cotton", + "gender": "female", + "company": "PROVIDCO", + "email": "valeriacotton@providco.com", + "phone": "+1 (859) 487-3099", + "address": "351 Fleet Street, Carlton, Kentucky, 5640", + "about": "Ipsum cupidatat qui velit eiusmod nisi. Sit tempor nisi elit excepteur officia aliqua ea dolore. Officia ex in consectetur laboris excepteur. Ea labore deserunt nostrud adipisicing sint dolor ullamco. Quis ipsum voluptate in dolor anim. Sunt anim sit culpa elit proident. Et non aliqua id magna sit magna adipisicing do.\r\n", + "registered": "2019-11-26T01:12:53 -08:00", + "latitude": 69.180675, + "longitude": 157.174099, + "tags": [ + "cillum", + "nostrud", + "velit", + "labore", + "proident", + "tempor", + "in" + ], + "friends": [ + { + "id": 0, + "name": "Jackie Clay" + }, + { + "id": 1, + "name": "Doyle Miles" + }, + { + "id": 2, + "name": "Knowles Serrano" + } + ], + "greeting": "Hello, Valeria Cotton! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e1a4547255c975bd7", + "index": 46, + "guid": "3bc7de54-76f4-4607-8c7e-0ecc71e7c4ab", + "isActive": false, + "balance": "$1,104.44", + "picture": "http://placehold.it/32x32", + "age": 24, + "eyeColor": "green", + "name": "Brenda Barlow", + "gender": "female", + "company": "OTHERSIDE", + "email": "brendabarlow@otherside.com", + "phone": "+1 (923) 490-3377", + "address": "786 Auburn Place, Albany, Puerto Rico, 5392", + "about": "Do ea fugiat officia nulla fugiat minim in consequat anim ut. Minim aute dolore laboris dolor ullamco incididunt sit ut magna nulla adipisicing pariatur. In elit deserunt eiusmod fugiat fugiat ipsum cillum voluptate dolor fugiat qui.\r\n", + "registered": "2015-08-01T12:29:52 -08:00", + "latitude": -1.876089, + "longitude": 106.13797, + "tags": [ + "est", + "do", + "commodo", + "occaecat", + "in", + "ipsum", + "exercitation" + ], + "friends": [ + { + "id": 0, + "name": "April Acosta" + }, + { + "id": 1, + "name": "Garza Tyson" + }, + { + "id": 2, + "name": "Roseann Ballard" + } + ], + "greeting": "Hello, Brenda Barlow! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ebc3b0592df62c727", + "index": 47, + "guid": "4d9e2eb2-b1c9-4a9e-a1ca-090e89557c8e", + "isActive": false, + "balance": "$1,302.13", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "brown", + "name": "Alfreda Bishop", + "gender": "female", + "company": "IMAGEFLOW", + "email": "alfredabishop@imageflow.com", + "phone": "+1 (984) 452-3866", + "address": "491 Broadway , Gadsden, Indiana, 4575", + "about": "Nulla nisi est quis dolore magna aliqua voluptate labore aliqua aliqua reprehenderit mollit. Reprehenderit in laboris eiusmod consequat consequat elit pariatur do. Anim non incididunt officia consectetur. Esse occaecat nisi amet consequat.\r\n", + "registered": "2018-10-24T05:40:00 -08:00", + "latitude": 76.601402, + "longitude": 25.742198, + "tags": [ + "amet", + "aliqua", + "irure", + "culpa", + "nostrud", + "et", + "laboris" + ], + "friends": [ + { + "id": 0, + "name": "Stella Osborn" + }, + { + "id": 1, + "name": "Silvia Collins" + }, + { + "id": 2, + "name": "Winters Ewing" + } + ], + "greeting": "Hello, Alfreda Bishop! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e32893b62ee86c5de", + "index": 48, + "guid": "cec71941-1a94-48ef-b547-1dfaef363624", + "isActive": true, + "balance": "$2,336.27", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "blue", + "name": "Pauline Huffman", + "gender": "female", + "company": "SARASONIC", + "email": "paulinehuffman@sarasonic.com", + "phone": "+1 (871) 401-3520", + "address": "465 Beacon Court, Kansas, Idaho, 7773", + "about": "Dolore sunt duis adipisicing minim magna cupidatat ea esse excepteur. Ut irure sit officia exercitation pariatur cupidatat aliqua consectetur id tempor exercitation esse sunt. Sit dolore velit elit aliquip exercitation voluptate cupidatat quis magna tempor consequat excepteur esse aliquip. Aliqua ullamco et consectetur excepteur pariatur deserunt ullamco. Anim tempor nisi deserunt cillum qui cupidatat ea minim ad qui. Quis occaecat deserunt ipsum deserunt. Exercitation in non sunt minim sunt veniam.\r\n", + "registered": "2019-08-14T11:53:13 -08:00", + "latitude": 14.068454, + "longitude": 31.317708, + "tags": [ + "irure", + "nulla", + "nulla", + "aute", + "tempor", + "magna", + "aliquip" + ], + "friends": [ + { + "id": 0, + "name": "Mavis Sargent" + }, + { + "id": 1, + "name": "Shields Contreras" + }, + { + "id": 2, + "name": "Angelina Bryant" + } + ], + "greeting": "Hello, Pauline Huffman! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e3d04fa2f3233db33", + "index": 49, + "guid": "b41377df-6cc1-4bb7-bcd5-ba002afc2285", + "isActive": true, + "balance": "$3,572.32", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "blue", + "name": "Vargas Skinner", + "gender": "male", + "company": "GEOLOGIX", + "email": "vargasskinner@geologix.com", + "phone": "+1 (803) 581-3585", + "address": "393 Wolf Place, Naomi, Federated States Of Micronesia, 3740", + "about": "Esse eiusmod et adipisicing dolor Lorem pariatur quis nulla. Qui occaecat do deserunt esse enim enim et ipsum nisi proident. Laboris reprehenderit aute labore mollit qui reprehenderit.\r\n", + "registered": "2018-10-27T07:35:35 -08:00", + "latitude": 61.221609, + "longitude": -53.439721, + "tags": [ + "fugiat", + "ut", + "deserunt", + "exercitation", + "et", + "deserunt", + "ex" + ], + "friends": [ + { + "id": 0, + "name": "Myra Sweet" + }, + { + "id": 1, + "name": "Amparo Spears" + }, + { + "id": 2, + "name": "Gilbert Hubbard" + } + ], + "greeting": "Hello, Vargas Skinner! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ef1a42ce067791236", + "index": 50, + "guid": "f13b6446-3721-45e1-a7d5-af500c09eddd", + "isActive": false, + "balance": "$3,568.58", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "brown", + "name": "Candice Barr", + "gender": "female", + "company": "CANOPOLY", + "email": "candicebarr@canopoly.com", + "phone": "+1 (818) 449-3135", + "address": "988 Chestnut Street, Sehili, South Carolina, 2874", + "about": "Duis id ea duis sunt aliqua non. Esse dolor ad non nulla. Occaecat in ipsum pariatur proident sunt sint enim adipisicing consectetur dolor mollit commodo laboris. Cupidatat id in magna occaecat ea officia in qui dolor aliqua. Sint ea occaecat in sint excepteur aliqua aliqua mollit dolore anim. Velit exercitation fugiat nostrud ex adipisicing proident laborum aliqua ut cupidatat ex. Enim nisi sunt anim incididunt.\r\n", + "registered": "2018-08-07T05:28:57 -08:00", + "latitude": -51.909727, + "longitude": -46.859537, + "tags": [ + "consequat", + "veniam", + "non", + "aute", + "pariatur", + "consectetur", + "esse" + ], + "friends": [ + { + "id": 0, + "name": "Ann Potts" + }, + { + "id": 1, + "name": "Murphy Lyons" + }, + { + "id": 2, + "name": "Justine Watkins" + } + ], + "greeting": "Hello, Candice Barr! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ed89f2bbe65779925", + "index": 51, + "guid": "3ea6c7de-2103-4182-9d83-37b7cb122b8e", + "isActive": true, + "balance": "$2,749.34", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "green", + "name": "Nelda Henson", + "gender": "female", + "company": "NSPIRE", + "email": "neldahenson@nspire.com", + "phone": "+1 (803) 573-2679", + "address": "951 Merit Court, Noxen, Utah, 8478", + "about": "Sunt qui tempor minim ipsum aute mollit eiusmod aliquip mollit cupidatat id exercitation mollit. Occaecat amet occaecat in aute nostrud Lorem officia eu sunt nisi voluptate. Reprehenderit magna enim consectetur deserunt anim duis. Aliquip adipisicing aliquip adipisicing aliquip nulla quis cupidatat occaecat tempor esse. Laboris nulla tempor esse aute. Do reprehenderit ipsum nulla et sint cupidatat.\r\n", + "registered": "2016-06-08T01:54:20 -08:00", + "latitude": -47.369112, + "longitude": 179.965439, + "tags": [ + "pariatur", + "veniam", + "officia", + "cupidatat", + "ut", + "excepteur", + "veniam" + ], + "friends": [ + { + "id": 0, + "name": "Camacho Neal" + }, + { + "id": 1, + "name": "Andrews Sutton" + }, + { + "id": 2, + "name": "Mckinney Saunders" + } + ], + "greeting": "Hello, Nelda Henson! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eb8baaebb11019d0e", + "index": 52, + "guid": "fc9c4aab-29ec-4348-b34f-5b701c2fdc01", + "isActive": true, + "balance": "$1,511.87", + "picture": "http://placehold.it/32x32", + "age": 24, + "eyeColor": "green", + "name": "Bryan Gonzales", + "gender": "male", + "company": "GINKLE", + "email": "bryangonzales@ginkle.com", + "phone": "+1 (969) 499-2735", + "address": "641 Leonard Street, Harborton, Virginia, 9808", + "about": "Qui consectetur nulla do duis duis officia ullamco occaecat do. Labore cupidatat laborum adipisicing dolore veniam amet. Esse exercitation velit occaecat sunt nisi. Officia reprehenderit quis id qui et aliquip nostrud. Ad et aute non dolor quis consequat voluptate nulla proident fugiat. Esse adipisicing tempor sunt Lorem laborum.\r\n", + "registered": "2019-12-21T12:16:40 -08:00", + "latitude": -68.61019, + "longitude": 43.406102, + "tags": [ + "tempor", + "Lorem", + "enim", + "minim", + "Lorem", + "dolor", + "consequat" + ], + "friends": [ + { + "id": 0, + "name": "Hawkins Beck" + }, + { + "id": 1, + "name": "Michael Fuller" + }, + { + "id": 2, + "name": "Larsen Foster" + } + ], + "greeting": "Hello, Bryan Gonzales! You have 7 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e2b6aa271e9843704", + "index": 53, + "guid": "a5d3416c-d14d-4cc9-8427-586cf3541eaf", + "isActive": false, + "balance": "$1,555.24", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "brown", + "name": "Aileen Parks", + "gender": "female", + "company": "TUBESYS", + "email": "aileenparks@tubesys.com", + "phone": "+1 (876) 497-2634", + "address": "532 Farragut Road, Topaz, Oregon, 130", + "about": "Enim mollit amet labore occaecat exercitation fugiat est cillum pariatur nisi. Fugiat labore exercitation deserunt amet occaecat mollit cupidatat proident. Qui in et aliquip quis exercitation eiusmod consequat adipisicing qui incididunt tempor ea. Excepteur mollit nisi aliqua proident ipsum ullamco. Aute ex sint amet esse. Voluptate enim pariatur dolore consequat officia duis. Proident dolor veniam ex est mollit anim aliqua voluptate.\r\n", + "registered": "2017-06-26T06:00:15 -08:00", + "latitude": 49.682744, + "longitude": 94.772297, + "tags": [ + "reprehenderit", + "esse", + "eiusmod", + "officia", + "nulla", + "consequat", + "culpa" + ], + "friends": [ + { + "id": 0, + "name": "Penny May" + }, + { + "id": 1, + "name": "Ina Pena" + }, + { + "id": 2, + "name": "Madeleine Harris" + } + ], + "greeting": "Hello, Aileen Parks! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e6fda671efc759a5f", + "index": 54, + "guid": "f01baf3b-7c9c-4200-8716-cb4c33dc6965", + "isActive": true, + "balance": "$1,164.98", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "blue", + "name": "Lilly Ball", + "gender": "female", + "company": "INTRAWEAR", + "email": "lillyball@intrawear.com", + "phone": "+1 (879) 582-3601", + "address": "924 Jardine Place, Omar, Iowa, 3875", + "about": "Nisi nostrud aliqua ad velit proident. Sint minim enim veniam qui fugiat. Deserunt magna do pariatur eiusmod occaecat dolore. Non velit do ullamco aliqua pariatur occaecat. In nostrud aute ea esse exercitation consectetur ex minim id cupidatat exercitation velit est. Anim aute id do commodo tempor non.\r\n", + "registered": "2018-01-24T05:43:50 -08:00", + "latitude": -26.104348, + "longitude": -121.770108, + "tags": [ + "nulla", + "ex", + "sunt", + "qui", + "anim", + "sint", + "deserunt" + ], + "friends": [ + { + "id": 0, + "name": "Carson Morse" + }, + { + "id": 1, + "name": "Stefanie Cherry" + }, + { + "id": 2, + "name": "Pugh Massey" + } + ], + "greeting": "Hello, Lilly Ball! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839eb72646c19fdc71c2", + "index": 55, + "guid": "ea3e34e8-e810-4081-abdd-b07e43c2b22e", + "isActive": false, + "balance": "$1,677.14", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "brown", + "name": "Anne Goodman", + "gender": "female", + "company": "BUGSALL", + "email": "annegoodman@bugsall.com", + "phone": "+1 (924) 482-2628", + "address": "851 Love Lane, Finzel, New Hampshire, 6235", + "about": "Cillum aute deserunt ipsum ad ut aliquip duis cillum cupidatat enim. Cillum sit culpa eiusmod aliquip. Anim labore proident velit consequat occaecat ea sit fugiat nisi quis. Officia duis do aliquip aliqua ea in sit sunt sint cillum laboris qui non consectetur. Laborum non dolore aute incididunt magna duis pariatur aute duis laboris amet nostrud anim. Elit tempor labore dolore nulla in commodo aliqua aliquip aliquip.\r\n", + "registered": "2019-06-11T02:34:02 -08:00", + "latitude": 13.843685, + "longitude": 160.084342, + "tags": [ + "in", + "ex", + "velit", + "eu", + "reprehenderit", + "elit", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Kate Bush" + }, + { + "id": 1, + "name": "Everett Hayes" + }, + { + "id": 2, + "name": "Hoover Villarreal" + } + ], + "greeting": "Hello, Anne Goodman! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e3c1c74ceed9f2a67", + "index": 56, + "guid": "9bd1b937-8921-4f42-98ea-030d82894b40", + "isActive": false, + "balance": "$2,841.95", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "blue", + "name": "Baird Little", + "gender": "male", + "company": "PYRAMIA", + "email": "bairdlittle@pyramia.com", + "phone": "+1 (977) 564-2708", + "address": "319 President Street, Deseret, Texas, 7662", + "about": "Nisi mollit excepteur nostrud excepteur tempor ea velit reprehenderit in. Ad velit non sit incididunt. Tempor velit aliqua in in id tempor sint et pariatur sit.\r\n", + "registered": "2017-06-15T06:29:26 -08:00", + "latitude": -87.080203, + "longitude": -113.786563, + "tags": [ + "ipsum", + "ad", + "sunt", + "nulla", + "commodo", + "proident", + "laborum" + ], + "friends": [ + { + "id": 0, + "name": "Mandy Golden" + }, + { + "id": 1, + "name": "Shelby Burnett" + }, + { + "id": 2, + "name": "Velazquez Spence" + } + ], + "greeting": "Hello, Baird Little! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eac6f163f658ebc82", + "index": 57, + "guid": "e49dcde5-271a-4db0-9520-7ed543b47837", + "isActive": false, + "balance": "$3,628.36", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "blue", + "name": "Melissa Glenn", + "gender": "female", + "company": "KYAGURU", + "email": "melissaglenn@kyaguru.com", + "phone": "+1 (986) 506-2374", + "address": "674 Prescott Place, Breinigsville, Michigan, 2770", + "about": "Proident ea cillum quis consequat consequat sunt minim mollit sint est nostrud. Adipisicing ipsum occaecat deserunt est ullamco ad quis culpa est eu. Cupidatat nulla ea nostrud laboris duis consectetur commodo pariatur ullamco nisi pariatur voluptate.\r\n", + "registered": "2015-02-09T07:57:41 -08:00", + "latitude": -50.551618, + "longitude": 29.228075, + "tags": [ + "nostrud", + "excepteur", + "elit", + "magna", + "ad", + "ea", + "tempor" + ], + "friends": [ + { + "id": 0, + "name": "Kelly Burks" + }, + { + "id": 1, + "name": "Rhonda Ellison" + }, + { + "id": 2, + "name": "Jennifer Morin" + } + ], + "greeting": "Hello, Melissa Glenn! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ea1cca8ca5724d738", + "index": 58, + "guid": "f110093f-1bbd-4881-b89a-c93918f8911a", + "isActive": true, + "balance": "$1,400.60", + "picture": "http://placehold.it/32x32", + "age": 34, + "eyeColor": "green", + "name": "Bobbi Barnes", + "gender": "female", + "company": "ISOTRACK", + "email": "bobbibarnes@isotrack.com", + "phone": "+1 (819) 469-2579", + "address": "787 Vine Street, Leland, Guam, 7932", + "about": "Aliquip cillum consectetur occaecat qui ea labore ad labore nulla commodo duis non quis. Ut est nisi eiusmod laboris. Minim eiusmod Lorem minim reprehenderit dolore cupidatat aliqua minim elit ea laborum et tempor. Irure commodo laboris nulla elit. Qui nulla quis aliquip enim aute est consectetur minim quis non qui. Deserunt commodo irure quis minim esse exercitation excepteur nisi id reprehenderit irure ex reprehenderit. Est cupidatat velit ea fugiat laboris exercitation est ullamco.\r\n", + "registered": "2019-06-06T01:16:44 -08:00", + "latitude": -50.526098, + "longitude": 85.277054, + "tags": [ + "qui", + "culpa", + "qui", + "commodo", + "ex", + "dolor", + "eiusmod" + ], + "friends": [ + { + "id": 0, + "name": "Roberts Kidd" + }, + { + "id": 1, + "name": "Cassie Williams" + }, + { + "id": 2, + "name": "Nancy Salazar" + } + ], + "greeting": "Hello, Bobbi Barnes! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e647ff4521ee6d1a5", + "index": 59, + "guid": "8c302000-3cf1-43d5-8d2f-040680b50f48", + "isActive": false, + "balance": "$1,621.78", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Jeannine Vincent", + "gender": "female", + "company": "ZYTREX", + "email": "jeanninevincent@zytrex.com", + "phone": "+1 (944) 534-2554", + "address": "368 Bay Parkway, Gratton, Virgin Islands, 4517", + "about": "Irure veniam id esse culpa elit irure elit in. Labore incididunt reprehenderit eu aliqua cillum. Dolor amet dolore fugiat minim reprehenderit exercitation sunt occaecat nostrud. Ad labore mollit excepteur ea nisi dolor sit. Exercitation excepteur et id aliquip ex. Et consectetur nostrud in commodo consectetur deserunt Lorem ad adipisicing pariatur ex eu est consequat.\r\n", + "registered": "2015-04-12T06:26:30 -08:00", + "latitude": -30.459439, + "longitude": -174.234008, + "tags": [ + "officia", + "ut", + "culpa", + "laborum", + "deserunt", + "culpa", + "et" + ], + "friends": [ + { + "id": 0, + "name": "Barnes Hernandez" + }, + { + "id": 1, + "name": "Lydia Ramsey" + }, + { + "id": 2, + "name": "Nolan Galloway" + } + ], + "greeting": "Hello, Jeannine Vincent! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e490b7c5302710151", + "index": 60, + "guid": "b5985d1b-63ff-4800-b003-36a61747f57b", + "isActive": true, + "balance": "$3,417.35", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "blue", + "name": "Blackburn Parsons", + "gender": "male", + "company": "DIGIPRINT", + "email": "blackburnparsons@digiprint.com", + "phone": "+1 (860) 501-2248", + "address": "631 Hart Place, Cataract, Vermont, 832", + "about": "Commodo culpa esse ullamco aliqua occaecat velit est eu pariatur deserunt dolore labore. Veniam fugiat magna adipisicing Lorem non amet. Culpa velit pariatur dolor ex. Amet duis sint nostrud esse occaecat proident consectetur et laborum quis. Ex aliqua quis voluptate elit aute minim ipsum Lorem irure laborum. Enim minim consectetur mollit do fugiat dolore dolor laboris labore pariatur eiusmod excepteur enim. Eu sint proident Lorem duis voluptate do do commodo officia minim sint pariatur labore do.\r\n", + "registered": "2016-02-27T02:27:13 -08:00", + "latitude": -11.298966, + "longitude": -53.005067, + "tags": [ + "aliqua", + "excepteur", + "Lorem", + "consectetur", + "cupidatat", + "anim", + "ipsum" + ], + "friends": [ + { + "id": 0, + "name": "Natasha Murray" + }, + { + "id": 1, + "name": "Morrow Prince" + }, + { + "id": 2, + "name": "Elva Walters" + } + ], + "greeting": "Hello, Blackburn Parsons! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e446f9964a9428843", + "index": 61, + "guid": "cd17254d-f95e-4992-b19d-4904de01aeb4", + "isActive": true, + "balance": "$1,647.87", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "blue", + "name": "Ware Ruiz", + "gender": "male", + "company": "BUZZNESS", + "email": "wareruiz@buzzness.com", + "phone": "+1 (878) 475-3886", + "address": "661 Midwood Street, Eagleville, Alabama, 6000", + "about": "Enim eiusmod Lorem ut consequat. Ex sint duis nisi officia ad sunt velit elit sit aliquip qui. Ipsum deserunt voluptate in deserunt Lorem proident reprehenderit do aute. Aute sit deserunt nostrud est Lorem amet voluptate. Ullamco magna laborum labore culpa laboris veniam do aute exercitation enim consectetur tempor. Irure minim sunt ex ex fugiat et ad officia. Labore ea aliquip ex aliqua id officia ut.\r\n", + "registered": "2016-12-06T06:48:17 -08:00", + "latitude": -7.781475, + "longitude": 127.648595, + "tags": [ + "aute", + "minim", + "sit", + "pariatur", + "voluptate", + "deserunt", + "deserunt" + ], + "friends": [ + { + "id": 0, + "name": "Frost Noel" + }, + { + "id": 1, + "name": "Riggs Long" + }, + { + "id": 2, + "name": "Stein Odom" + } + ], + "greeting": "Hello, Ware Ruiz! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e0bdeafa47075739a", + "index": 62, + "guid": "36cc86ce-afd9-4054-862c-12374e889dd7", + "isActive": true, + "balance": "$2,672.67", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "green", + "name": "Weeks Blair", + "gender": "male", + "company": "NIXELT", + "email": "weeksblair@nixelt.com", + "phone": "+1 (983) 435-2787", + "address": "809 Vanderveer Place, Statenville, New York, 5629", + "about": "Culpa nisi labore consectetur elit qui dolor Lorem eiusmod cupidatat aliqua in do exercitation irure. Consectetur ex nisi adipisicing mollit culpa ullamco ipsum non eiusmod. Est reprehenderit magna culpa proident irure. Est ea ad mollit voluptate irure nulla non veniam laboris incididunt esse pariatur occaecat aute. Et ullamco voluptate anim id. Sint amet proident culpa laboris excepteur consectetur duis qui.\r\n", + "registered": "2014-11-02T10:46:49 -08:00", + "latitude": 41.422626, + "longitude": 129.499166, + "tags": [ + "consequat", + "ex", + "tempor", + "ex", + "nulla", + "ea", + "ad" + ], + "friends": [ + { + "id": 0, + "name": "Conley Manning" + }, + { + "id": 1, + "name": "Petra Roach" + }, + { + "id": 2, + "name": "Fisher Bruce" + } + ], + "greeting": "Hello, Weeks Blair! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ebda96ad53737d346", + "index": 63, + "guid": "40284d1b-302b-4c3c-bd66-dc0d0527f620", + "isActive": false, + "balance": "$1,369.61", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "green", + "name": "Lucinda Dickerson", + "gender": "female", + "company": "CODAX", + "email": "lucindadickerson@codax.com", + "phone": "+1 (871) 473-2317", + "address": "918 Melrose Street, Cobbtown, Tennessee, 3380", + "about": "Pariatur Lorem ex excepteur esse enim cillum voluptate duis officia et enim incididunt do enim. Culpa aute do pariatur consectetur amet enim aliqua occaecat nisi dolor nostrud velit culpa. Est deserunt qui ipsum quis pariatur irure ad ad. Excepteur non qui nisi aute nisi pariatur et elit. Nisi cillum adipisicing laborum ad officia fugiat enim deserunt sunt amet consectetur labore enim mollit.\r\n", + "registered": "2014-09-19T05:20:30 -08:00", + "latitude": -70.662697, + "longitude": 119.333445, + "tags": [ + "reprehenderit", + "exercitation", + "consectetur", + "laboris", + "amet", + "culpa", + "aliquip" + ], + "friends": [ + { + "id": 0, + "name": "Joyce Barry" + }, + { + "id": 1, + "name": "Tabatha Rutledge" + }, + { + "id": 2, + "name": "Mcfadden Rasmussen" + } + ], + "greeting": "Hello, Lucinda Dickerson! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e37e5b094a607e3d9", + "index": 64, + "guid": "a8ed3f08-2982-44a5-aebe-1e281d4cd436", + "isActive": true, + "balance": "$1,009.43", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "blue", + "name": "Blevins Alvarado", + "gender": "male", + "company": "KAGGLE", + "email": "blevinsalvarado@kaggle.com", + "phone": "+1 (886) 535-3811", + "address": "172 Gardner Avenue, Sylvanite, Delaware, 7952", + "about": "Exercitation sit laboris laboris dolore duis nulla occaecat mollit dolore incididunt cupidatat nostrud Lorem in. Esse tempor commodo duis incididunt in id. Et est amet id sint sint Lorem pariatur. Eiusmod consectetur do fugiat aliquip sint culpa incididunt ipsum esse eiusmod. Dolore nisi amet voluptate ullamco reprehenderit reprehenderit enim laborum. Amet est qui ad magna occaecat.\r\n", + "registered": "2019-02-23T07:54:55 -08:00", + "latitude": 13.622359, + "longitude": 12.529768, + "tags": [ + "dolore", + "eu", + "qui", + "minim", + "dolore", + "adipisicing", + "nisi" + ], + "friends": [ + { + "id": 0, + "name": "Yates Sharp" + }, + { + "id": 1, + "name": "Santiago Lowery" + }, + { + "id": 2, + "name": "Rasmussen West" + } + ], + "greeting": "Hello, Blevins Alvarado! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ed4db80059244b2e0", + "index": 65, + "guid": "b6e5bab6-ec40-4fdb-89b2-f1795b88720f", + "isActive": true, + "balance": "$2,260.56", + "picture": "http://placehold.it/32x32", + "age": 24, + "eyeColor": "brown", + "name": "Mccray Taylor", + "gender": "male", + "company": "DUOFLEX", + "email": "mccraytaylor@duoflex.com", + "phone": "+1 (954) 589-3869", + "address": "490 Oxford Walk, Eastvale, Minnesota, 7516", + "about": "Ipsum amet voluptate est nostrud. Aliqua enim dolor occaecat aliquip. Quis ex qui anim tempor amet non laborum non amet cillum laboris ut. Magna nostrud amet culpa cupidatat non.\r\n", + "registered": "2017-11-14T12:46:20 -08:00", + "latitude": 52.266552, + "longitude": 9.786283, + "tags": [ + "reprehenderit", + "magna", + "mollit", + "tempor", + "Lorem", + "aliquip", + "sint" + ], + "friends": [ + { + "id": 0, + "name": "Lisa Parrish" + }, + { + "id": 1, + "name": "Melba Wells" + }, + { + "id": 2, + "name": "Martin Bates" + } + ], + "greeting": "Hello, Mccray Taylor! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e54314aa4cf866c6c", + "index": 66, + "guid": "daac2e56-d167-4e3e-a6cc-c3a338412025", + "isActive": false, + "balance": "$2,371.32", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "green", + "name": "Antonia William", + "gender": "female", + "company": "ORBIXTAR", + "email": "antoniawilliam@orbixtar.com", + "phone": "+1 (952) 506-2074", + "address": "895 Vanderbilt Street, Hobucken, Maryland, 3798", + "about": "Esse irure excepteur voluptate ut incididunt esse Lorem sunt veniam. In duis ad adipisicing et consectetur id quis enim. Irure incididunt qui laborum consequat eu dolore sint. Consectetur amet elit officia cupidatat enim magna. Enim ut eiusmod exercitation consectetur reprehenderit. Amet occaecat proident occaecat laborum laboris deserunt id laborum excepteur eiusmod.\r\n", + "registered": "2018-08-28T01:37:47 -08:00", + "latitude": -2.26398, + "longitude": -149.698048, + "tags": [ + "ullamco", + "dolor", + "ullamco", + "amet", + "aliquip", + "do", + "id" + ], + "friends": [ + { + "id": 0, + "name": "Clarissa Patrick" + }, + { + "id": 1, + "name": "Vega Albert" + }, + { + "id": 2, + "name": "Morin Boyd" + } + ], + "greeting": "Hello, Antonia William! You have 1 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ec4b746e34639669f", + "index": 67, + "guid": "553d4341-46fb-4999-aa45-9994979131c1", + "isActive": true, + "balance": "$2,627.07", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "green", + "name": "Ursula Jackson", + "gender": "female", + "company": "OLUCORE", + "email": "ursulajackson@olucore.com", + "phone": "+1 (892) 459-3163", + "address": "587 Ira Court, Harleigh, Florida, 2967", + "about": "Occaecat sint elit duis laborum tempor non dolor consequat mollit laboris et nulla eu nisi. Exercitation deserunt eiusmod commodo fugiat officia cupidatat veniam veniam amet nostrud sunt commodo. Eu culpa deserunt commodo do nostrud in sit tempor occaecat laboris aute cupidatat tempor.\r\n", + "registered": "2019-04-02T03:16:29 -08:00", + "latitude": -65.444375, + "longitude": 35.031683, + "tags": [ + "reprehenderit", + "aute", + "do", + "non", + "minim", + "irure", + "dolore" + ], + "friends": [ + { + "id": 0, + "name": "Tabitha Hoover" + }, + { + "id": 1, + "name": "Lena Herrera" + }, + { + "id": 2, + "name": "Mayra Nicholson" + } + ], + "greeting": "Hello, Ursula Jackson! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e62b7eea03c6df5dd", + "index": 68, + "guid": "6e2f8a10-3b17-40a5-b08e-9f4c62f15d7a", + "isActive": true, + "balance": "$2,178.19", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "green", + "name": "Fowler Campos", + "gender": "male", + "company": "COREPAN", + "email": "fowlercampos@corepan.com", + "phone": "+1 (876) 449-3749", + "address": "526 Bliss Terrace, Sedley, Marshall Islands, 1932", + "about": "Qui pariatur ex magna ullamco non cillum qui exercitation dolore. Eu Lorem eiusmod sint minim commodo non. Sint mollit aliquip officia excepteur exercitation quis ipsum sit officia magna magna. Voluptate duis sint quis voluptate. Proident occaecat ex aliqua duis. Dolore deserunt mollit est proident ullamco. Pariatur ipsum in ullamco fugiat.\r\n", + "registered": "2015-01-17T04:57:29 -08:00", + "latitude": -89.949762, + "longitude": 67.991063, + "tags": [ + "incididunt", + "sit", + "magna", + "dolore", + "dolore", + "aute", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Francisca Beasley" + }, + { + "id": 1, + "name": "Marissa Harvey" + }, + { + "id": 2, + "name": "Hartman Tillman" + } + ], + "greeting": "Hello, Fowler Campos! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839eb77d60e76803d098", + "index": 69, + "guid": "88e71b1b-8c46-43e8-b3b7-e5fdf7703624", + "isActive": true, + "balance": "$1,673.91", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "blue", + "name": "Sallie Dennis", + "gender": "female", + "company": "IMMUNICS", + "email": "salliedennis@immunics.com", + "phone": "+1 (849) 434-3219", + "address": "451 Miller Place, Yogaville, Wyoming, 4531", + "about": "Ullamco commodo occaecat laborum minim do cupidatat nulla aliqua consectetur ea. Tempor Lorem esse sunt duis exercitation culpa non. Ea voluptate aute aute minim ea occaecat nostrud dolore ipsum nostrud laboris consectetur do in. Commodo velit Lorem cupidatat id. Elit id consequat amet non eu. Est elit cupidatat sit nostrud laborum est culpa labore tempor minim do voluptate. Culpa non tempor eiusmod veniam Lorem officia nostrud quis enim eiusmod cupidatat ullamco esse.\r\n", + "registered": "2019-06-08T04:44:21 -08:00", + "latitude": -53.704616, + "longitude": 89.448427, + "tags": [ + "ea", + "mollit", + "proident", + "minim", + "magna", + "commodo", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Olive Bennett" + }, + { + "id": 1, + "name": "Hutchinson Reese" + }, + { + "id": 2, + "name": "Vasquez Nash" + } + ], + "greeting": "Hello, Sallie Dennis! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ebb9a607f1a094a27", + "index": 70, + "guid": "523e50e1-a334-4184-ae7f-3706bcfad675", + "isActive": false, + "balance": "$3,804.91", + "picture": "http://placehold.it/32x32", + "age": 35, + "eyeColor": "blue", + "name": "Kelly Knapp", + "gender": "male", + "company": "ZYTRAX", + "email": "kellyknapp@zytrax.com", + "phone": "+1 (888) 499-2656", + "address": "726 Fenimore Street, Gila, Illinois, 6579", + "about": "Sint ex sunt Lorem id sit irure magna dolore aliquip est anim laboris ullamco et. Aliqua elit ut voluptate non aute. Velit consequat ea mollit aliquip cupidatat nisi ullamco nisi anim enim incididunt. Fugiat consequat non dolor culpa laborum. Aute ipsum sint ad veniam laborum do do excepteur tempor. Ea ea minim veniam id ex labore incididunt velit et.\r\n", + "registered": "2014-07-20T03:24:20 -08:00", + "latitude": 18.457738, + "longitude": 0.464131, + "tags": [ + "dolore", + "non", + "anim", + "consequat", + "deserunt", + "officia", + "laborum" + ], + "friends": [ + { + "id": 0, + "name": "Parsons Doyle" + }, + { + "id": 1, + "name": "Merritt Hoffman" + }, + { + "id": 2, + "name": "Hancock Gross" + } + ], + "greeting": "Hello, Kelly Knapp! You have 10 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e3ce43a5cebda336d", + "index": 71, + "guid": "ea4ad80d-a5da-45c5-b1ef-f84f14160ab1", + "isActive": true, + "balance": "$2,095.16", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "blue", + "name": "Suarez Vargas", + "gender": "male", + "company": "GEEKOSIS", + "email": "suarezvargas@geekosis.com", + "phone": "+1 (854) 569-2935", + "address": "990 Lewis Avenue, Greenwich, South Dakota, 9557", + "about": "Eu nisi adipisicing est elit nisi excepteur dolore minim proident aute velit ea. Anim esse nostrud consequat duis. Reprehenderit culpa nulla aliquip magna proident est. Deserunt amet voluptate elit do et cillum ea nulla ullamco dolor deserunt.\r\n", + "registered": "2017-05-24T12:12:20 -08:00", + "latitude": 60.565319, + "longitude": 110.597631, + "tags": [ + "amet", + "cupidatat", + "minim", + "sit", + "laborum", + "et", + "voluptate" + ], + "friends": [ + { + "id": 0, + "name": "Rosetta Valenzuela" + }, + { + "id": 1, + "name": "Leblanc Livingston" + }, + { + "id": 2, + "name": "Cathy Reynolds" + } + ], + "greeting": "Hello, Suarez Vargas! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e7b53911fb6fafe9f", + "index": 72, + "guid": "3247e03e-f121-43f8-99d4-62ac5b069f55", + "isActive": true, + "balance": "$3,058.90", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "brown", + "name": "Gillespie Marks", + "gender": "male", + "company": "XYMONK", + "email": "gillespiemarks@xymonk.com", + "phone": "+1 (834) 598-2988", + "address": "504 Union Street, Faywood, Alaska, 6251", + "about": "Fugiat esse labore eiusmod sit labore labore exercitation mollit cupidatat amet sit. Occaecat officia cupidatat enim enim id Lorem dolore amet proident nostrud labore velit occaecat. Esse enim esse ut commodo ullamco tempor amet duis ea incididunt pariatur.\r\n", + "registered": "2019-11-28T04:05:55 -08:00", + "latitude": 35.748021, + "longitude": -44.650276, + "tags": [ + "amet", + "aute", + "nulla", + "tempor", + "ullamco", + "consectetur", + "cupidatat" + ], + "friends": [ + { + "id": 0, + "name": "Velma Mccullough" + }, + { + "id": 1, + "name": "Jeanine Gregory" + }, + { + "id": 2, + "name": "Gomez Gray" + } + ], + "greeting": "Hello, Gillespie Marks! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e0f4299c0a1971269", + "index": 73, + "guid": "0b6df838-7361-47c5-9c25-a10a89fac404", + "isActive": false, + "balance": "$2,537.32", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "green", + "name": "Rosie Foreman", + "gender": "female", + "company": "MEDIOT", + "email": "rosieforeman@mediot.com", + "phone": "+1 (938) 537-2710", + "address": "288 Knickerbocker Avenue, Ryderwood, Arizona, 8189", + "about": "Nisi irure do commodo aliqua deserunt adipisicing voluptate dolor. Id Lorem laboris ipsum nisi dolore. Id ipsum aute ipsum esse sunt esse.\r\n", + "registered": "2016-01-17T07:31:29 -08:00", + "latitude": 42.349842, + "longitude": -36.31374, + "tags": [ + "esse", + "exercitation", + "commodo", + "laborum", + "magna", + "veniam", + "aliqua" + ], + "friends": [ + { + "id": 0, + "name": "Jackson Burgess" + }, + { + "id": 1, + "name": "Deborah Cleveland" + }, + { + "id": 2, + "name": "Gracie Mcconnell" + } + ], + "greeting": "Hello, Rosie Foreman! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ebeddf42c39a01bad", + "index": 74, + "guid": "4030c8ea-a6d5-4c53-a92b-762a48b6f50f", + "isActive": false, + "balance": "$1,246.41", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "blue", + "name": "Lessie Stuart", + "gender": "female", + "company": "STUCCO", + "email": "lessiestuart@stucco.com", + "phone": "+1 (937) 417-3490", + "address": "492 Logan Street, Volta, Louisiana, 3039", + "about": "Quis pariatur enim Lorem pariatur est sit amet excepteur et tempor. Ex commodo eu duis reprehenderit reprehenderit proident exercitation fugiat. Dolore consectetur reprehenderit aute velit amet velit ex pariatur ad cupidatat deserunt. Occaecat officia aliquip aliquip eiusmod ullamco pariatur aliquip irure do est commodo id dolor. Id quis Lorem officia laboris. Consequat laborum sint reprehenderit dolore Lorem proident minim laborum pariatur. Occaecat duis ea irure enim ex.\r\n", + "registered": "2015-01-08T06:17:09 -08:00", + "latitude": -8.263925, + "longitude": -168.118788, + "tags": [ + "tempor", + "in", + "sunt", + "aliqua", + "cupidatat", + "deserunt", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "Goodwin Paul" + }, + { + "id": 1, + "name": "Kramer Weaver" + }, + { + "id": 2, + "name": "Mccarty Lloyd" + } + ], + "greeting": "Hello, Lessie Stuart! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839eb9d823f81e518b0a", + "index": 75, + "guid": "3271067c-0933-4961-86f7-e34f41338cce", + "isActive": true, + "balance": "$3,541.05", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "green", + "name": "Reyes Mckay", + "gender": "male", + "company": "NAMEBOX", + "email": "reyesmckay@namebox.com", + "phone": "+1 (842) 413-3917", + "address": "180 Irwin Street, Broadlands, Hawaii, 7571", + "about": "Adipisicing ullamco deserunt labore est cillum laboris incididunt exercitation minim. Irure commodo eiusmod ea sint nostrud. Esse ex eu aliquip fugiat ullamco quis ipsum adipisicing nulla non sint nisi non. Eu voluptate enim tempor aliqua aliquip quis cillum aute. Ea officia elit nostrud id eiusmod consectetur cillum consequat fugiat. Irure sunt mollit dolor anim et adipisicing proident nostrud commodo dolor deserunt ut voluptate reprehenderit. Aute aliquip ad consectetur aliquip exercitation anim.\r\n", + "registered": "2019-09-11T06:30:16 -08:00", + "latitude": -81.195577, + "longitude": -108.529911, + "tags": [ + "voluptate", + "exercitation", + "et", + "aute", + "excepteur", + "voluptate", + "occaecat" + ], + "friends": [ + { + "id": 0, + "name": "Margaret Abbott" + }, + { + "id": 1, + "name": "Hyde Vaughan" + }, + { + "id": 2, + "name": "Shelton Dorsey" + } + ], + "greeting": "Hello, Reyes Mckay! You have 7 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ecba5d28871382bea", + "index": 76, + "guid": "4b37b238-3607-4b84-9c53-521abbf336aa", + "isActive": false, + "balance": "$2,040.46", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "green", + "name": "Tillman Cervantes", + "gender": "male", + "company": "NETROPIC", + "email": "tillmancervantes@netropic.com", + "phone": "+1 (995) 477-3823", + "address": "163 Madison Place, Welch, Ohio, 2365", + "about": "Est adipisicing in excepteur cillum ut cillum consequat velit sunt officia. Est reprehenderit Lorem occaecat cillum deserunt id laborum cupidatat adipisicing. Consequat aliqua officia cillum sit occaecat ullamco pariatur et officia culpa adipisicing in consectetur. Aliqua exercitation elit veniam tempor consectetur duis do ex. Aliqua laboris aliqua pariatur nisi consectetur consectetur duis cupidatat ullamco. In ut elit elit qui nulla aliquip est voluptate. Non ullamco nisi irure do officia occaecat ipsum ullamco nisi.\r\n", + "registered": "2016-07-09T03:59:29 -08:00", + "latitude": 22.260068, + "longitude": 44.268483, + "tags": [ + "aliquip", + "duis", + "qui", + "sunt", + "fugiat", + "voluptate", + "est" + ], + "friends": [ + { + "id": 0, + "name": "Carmella Sykes" + }, + { + "id": 1, + "name": "Raquel Hess" + }, + { + "id": 2, + "name": "Keith Hines" + } + ], + "greeting": "Hello, Tillman Cervantes! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839eb89297bbd50021bb", + "index": 77, + "guid": "4ebc267d-8a48-4e77-962c-f923158807fa", + "isActive": true, + "balance": "$1,875.34", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "brown", + "name": "Carter Wyatt", + "gender": "male", + "company": "FILODYNE", + "email": "carterwyatt@filodyne.com", + "phone": "+1 (966) 422-2933", + "address": "918 Boerum Street, Kanauga, Palau, 3198", + "about": "Lorem voluptate velit id Lorem incididunt officia sit consequat amet quis culpa duis ullamco ea. Quis aliquip reprehenderit quis commodo sit ut ex. Nostrud aliqua anim sint cupidatat aliqua id fugiat. Duis qui consectetur enim id. Anim ex irure minim irure eu velit voluptate veniam. Elit ex laboris et pariatur est excepteur amet. Non mollit adipisicing Lorem adipisicing nulla enim veniam Lorem id minim sunt anim aliqua ut.\r\n", + "registered": "2015-08-22T07:41:06 -08:00", + "latitude": -52.946753, + "longitude": 166.631722, + "tags": [ + "reprehenderit", + "ipsum", + "adipisicing", + "adipisicing", + "in", + "laborum", + "id" + ], + "friends": [ + { + "id": 0, + "name": "Cameron Snider" + }, + { + "id": 1, + "name": "Swanson Boone" + }, + { + "id": 2, + "name": "Rose Witt" + } + ], + "greeting": "Hello, Carter Wyatt! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e76b22a471586ba14", + "index": 78, + "guid": "0071dbae-5f89-44c6-a5eb-b3ccb72f2b53", + "isActive": true, + "balance": "$3,906.30", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "brown", + "name": "Barnett Barnett", + "gender": "male", + "company": "SPACEWAX", + "email": "barnettbarnett@spacewax.com", + "phone": "+1 (805) 527-3537", + "address": "833 Eckford Street, Coinjock, North Dakota, 9927", + "about": "Duis ea cillum eiusmod cillum ipsum eu deserunt consequat deserunt tempor eu. Consequat sunt ut quis occaecat in voluptate sint minim reprehenderit enim. Consectetur do adipisicing id aute elit mollit ullamco ullamco. Anim occaecat deserunt consectetur ipsum.\r\n", + "registered": "2016-11-20T01:01:06 -08:00", + "latitude": -59.650702, + "longitude": -105.521434, + "tags": [ + "irure", + "cillum", + "occaecat", + "mollit", + "minim", + "ipsum", + "consequat" + ], + "friends": [ + { + "id": 0, + "name": "Hillary Mcmillan" + }, + { + "id": 1, + "name": "Camille Medina" + }, + { + "id": 2, + "name": "Bailey Cortez" + } + ], + "greeting": "Hello, Barnett Barnett! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e1a15237b41d799c9", + "index": 79, + "guid": "04727a17-d14a-44e6-b963-46a21fa0db53", + "isActive": true, + "balance": "$1,727.74", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Cummings Moody", + "gender": "male", + "company": "ACCIDENCY", + "email": "cummingsmoody@accidency.com", + "phone": "+1 (897) 430-3038", + "address": "645 Liberty Avenue, Herlong, California, 1552", + "about": "Aute laborum aliqua et commodo nulla pariatur proident voluptate fugiat adipisicing do consectetur amet adipisicing. Sunt commodo quis cupidatat aliqua exercitation proident tempor. Sunt quis deserunt labore ullamco adipisicing proident.\r\n", + "registered": "2018-08-24T01:12:58 -08:00", + "latitude": 30.638243, + "longitude": 27.790045, + "tags": [ + "est", + "minim", + "Lorem", + "esse", + "Lorem", + "adipisicing", + "eiusmod" + ], + "friends": [ + { + "id": 0, + "name": "Britt Hardy" + }, + { + "id": 1, + "name": "Pam Calhoun" + }, + { + "id": 2, + "name": "Marion Raymond" + } + ], + "greeting": "Hello, Cummings Moody! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e558d30ef435bf2d6", + "index": 80, + "guid": "5b1d225a-18d1-43f0-afa9-4fed4b406254", + "isActive": false, + "balance": "$3,179.70", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "green", + "name": "Young Hayden", + "gender": "female", + "company": "ZENSOR", + "email": "younghayden@zensor.com", + "phone": "+1 (809) 463-2936", + "address": "176 Pitkin Avenue, Richville, Nevada, 2857", + "about": "Nulla culpa adipisicing veniam labore cupidatat excepteur qui exercitation ut veniam. Et aute cillum anim aliquip adipisicing est sunt sunt aliqua incididunt laboris aliqua sunt sunt. Irure ipsum aliqua voluptate pariatur aute quis incididunt.\r\n", + "registered": "2018-05-02T06:21:09 -08:00", + "latitude": -6.184739, + "longitude": 146.723277, + "tags": [ + "exercitation", + "magna", + "cillum", + "nisi", + "commodo", + "nisi", + "id" + ], + "friends": [ + { + "id": 0, + "name": "Pierce Campbell" + }, + { + "id": 1, + "name": "Sally Joseph" + }, + { + "id": 2, + "name": "Reva Davis" + } + ], + "greeting": "Hello, Young Hayden! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e8fb9cbd763d25378", + "index": 81, + "guid": "311f64c0-8006-4e8e-99e0-fe56c8e26ca5", + "isActive": true, + "balance": "$2,998.49", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "brown", + "name": "Sloan Dodson", + "gender": "male", + "company": "IRACK", + "email": "sloandodson@irack.com", + "phone": "+1 (813) 441-2827", + "address": "172 Calder Place, Glendale, Washington, 6213", + "about": "Nisi ipsum incididunt velit aliquip laborum esse mollit fugiat ut Lorem nisi nulla. Irure aliquip cillum duis incididunt deserunt adipisicing sunt eiusmod mollit. Reprehenderit quis in ipsum deserunt. Anim deserunt tempor ullamco cupidatat ut veniam incididunt reprehenderit ipsum irure.\r\n", + "registered": "2019-01-23T01:54:16 -08:00", + "latitude": -13.288432, + "longitude": 174.602421, + "tags": [ + "velit", + "ullamco", + "ea", + "duis", + "esse", + "consequat", + "elit" + ], + "friends": [ + { + "id": 0, + "name": "Tamra Clements" + }, + { + "id": 1, + "name": "Margarita Moran" + }, + { + "id": 2, + "name": "Bryant Atkins" + } + ], + "greeting": "Hello, Sloan Dodson! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e8097ab9339fdea55", + "index": 82, + "guid": "b760fb09-26d0-4591-972d-fc8e07cb2611", + "isActive": false, + "balance": "$1,179.72", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "brown", + "name": "Santana Lawrence", + "gender": "male", + "company": "KONNECT", + "email": "santanalawrence@konnect.com", + "phone": "+1 (810) 527-2103", + "address": "977 Euclid Avenue, Oretta, Arkansas, 5346", + "about": "Veniam cillum magna enim aliquip non commodo amet est aliquip excepteur minim eu non. Minim mollit fugiat ut proident pariatur laboris. Incididunt pariatur adipisicing tempor nostrud consequat occaecat aliqua dolor sit proident duis do. Enim eu consectetur velit ad ut. Irure enim consequat irure minim adipisicing reprehenderit esse tempor veniam velit aliqua mollit. Commodo esse amet incididunt magna minim commodo laborum mollit excepteur.\r\n", + "registered": "2014-09-28T02:59:46 -08:00", + "latitude": 32.733995, + "longitude": 121.567257, + "tags": [ + "occaecat", + "nostrud", + "exercitation", + "commodo", + "officia", + "esse", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Justice Reilly" + }, + { + "id": 1, + "name": "Vicki Mosley" + }, + { + "id": 2, + "name": "Jolene Le" + } + ], + "greeting": "Hello, Santana Lawrence! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e4c6ab296a34ef08b", + "index": 83, + "guid": "f75c0bc9-c957-45be-b73c-144cf023c214", + "isActive": true, + "balance": "$3,124.20", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "brown", + "name": "Hood Kirby", + "gender": "male", + "company": "SUNCLIPSE", + "email": "hoodkirby@sunclipse.com", + "phone": "+1 (900) 485-3224", + "address": "569 Tech Place, Graball, Wisconsin, 8001", + "about": "Dolore non eu laborum anim anim reprehenderit labore enim Lorem elit ullamco. Fugiat cillum quis aliquip elit qui culpa deserunt. Incididunt ad dolore nulla in Lorem do ullamco reprehenderit consectetur do mollit. Est laboris dolore dolor excepteur excepteur qui ad do. Reprehenderit cupidatat officia duis minim id non. Id magna voluptate sunt aliquip id irure adipisicing dolore laborum laboris. Do eu ea nulla sit consequat.\r\n", + "registered": "2018-04-11T10:56:42 -08:00", + "latitude": 13.710058, + "longitude": -114.042377, + "tags": [ + "incididunt", + "fugiat", + "irure", + "quis", + "culpa", + "deserunt", + "commodo" + ], + "friends": [ + { + "id": 0, + "name": "Clara Robbins" + }, + { + "id": 1, + "name": "Griffin Bender" + }, + { + "id": 2, + "name": "Bennett Soto" + } + ], + "greeting": "Hello, Hood Kirby! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e5fc22d2ef78b979f", + "index": 84, + "guid": "dea7eb50-3ac9-4a4c-ad91-0c1c23cbd71b", + "isActive": false, + "balance": "$3,134.26", + "picture": "http://placehold.it/32x32", + "age": 34, + "eyeColor": "blue", + "name": "Robbins Vazquez", + "gender": "male", + "company": "ILLUMITY", + "email": "robbinsvazquez@illumity.com", + "phone": "+1 (961) 497-2700", + "address": "366 Lott Avenue, Yettem, West Virginia, 1746", + "about": "Excepteur culpa labore eu aliqua dolore irure reprehenderit adipisicing consequat id ad fugiat. Irure nisi do aute exercitation incididunt amet anim aliqua ut officia laborum reprehenderit laborum. Reprehenderit eiusmod ipsum labore est culpa velit commodo id exercitation duis qui. Enim laborum do ut cupidatat non elit. Dolore magna dolor mollit laborum nulla aliqua consectetur deserunt. Non labore nulla quis eu voluptate laborum est amet ullamco eu sint excepteur nostrud amet. Qui nulla Lorem dolore cillum nostrud reprehenderit laboris dolor ullamco laboris mollit velit exercitation.\r\n", + "registered": "2016-02-11T07:42:47 -08:00", + "latitude": 55.576699, + "longitude": 175.313963, + "tags": [ + "veniam", + "elit", + "voluptate", + "id", + "officia", + "ex", + "est" + ], + "friends": [ + { + "id": 0, + "name": "Salas Boyle" + }, + { + "id": 1, + "name": "Kathleen Levine" + }, + { + "id": 2, + "name": "Harding Sanford" + } + ], + "greeting": "Hello, Robbins Vazquez! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e743c561316b7efc8", + "index": 85, + "guid": "586aa37d-2b1c-4ab0-bd65-ed541e52cb36", + "isActive": false, + "balance": "$1,533.89", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "brown", + "name": "Cortez Leon", + "gender": "male", + "company": "MANGLO", + "email": "cortezleon@manglo.com", + "phone": "+1 (869) 566-3982", + "address": "928 Lincoln Place, Monument, Mississippi, 5762", + "about": "Ad enim officia aliqua reprehenderit exercitation laborum nostrud qui non. Aliqua ad do aliqua proident incididunt Lorem consectetur nostrud proident veniam voluptate fugiat anim et. Ut proident aliqua ex ea do non non nostrud nulla voluptate. Ut pariatur dolor duis amet sint. Veniam fugiat consequat nisi reprehenderit. Sunt minim magna proident laboris.\r\n", + "registered": "2016-04-27T11:39:29 -08:00", + "latitude": -74.65588, + "longitude": -105.029545, + "tags": [ + "officia", + "occaecat", + "exercitation", + "dolor", + "cillum", + "tempor", + "est" + ], + "friends": [ + { + "id": 0, + "name": "Vivian Hunter" + }, + { + "id": 1, + "name": "Ramirez Colon" + }, + { + "id": 2, + "name": "Mcleod Mcneil" + } + ], + "greeting": "Hello, Cortez Leon! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ea86c5b3ccb66f2f5", + "index": 86, + "guid": "8e74b08d-5a52-473c-8127-53e5c6e26332", + "isActive": false, + "balance": "$2,187.60", + "picture": "http://placehold.it/32x32", + "age": 34, + "eyeColor": "blue", + "name": "Mathews Leach", + "gender": "male", + "company": "PAWNAGRA", + "email": "mathewsleach@pawnagra.com", + "phone": "+1 (980) 419-2838", + "address": "729 Coleman Street, Vandiver, American Samoa, 5901", + "about": "Ipsum exercitation dolore elit sint occaecat. Labore irure consectetur do pariatur tempor ea ut et esse nulla commodo. Irure cupidatat id aute ea minim quis aliqua eiusmod voluptate duis eiusmod magna irure.\r\n", + "registered": "2018-09-07T01:35:23 -08:00", + "latitude": -45.006428, + "longitude": -123.140439, + "tags": [ + "anim", + "ad", + "sunt", + "est", + "cupidatat", + "proident", + "duis" + ], + "friends": [ + { + "id": 0, + "name": "Paula Tucker" + }, + { + "id": 1, + "name": "Cohen Bright" + }, + { + "id": 2, + "name": "Russo Terry" + } + ], + "greeting": "Hello, Mathews Leach! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e7815ddd4955d3cc3", + "index": 87, + "guid": "f101f957-05b3-4409-8736-d89afb917bcb", + "isActive": false, + "balance": "$1,688.36", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "brown", + "name": "Latisha Mayer", + "gender": "female", + "company": "GAZAK", + "email": "latishamayer@gazak.com", + "phone": "+1 (908) 409-3557", + "address": "904 Otsego Street, Emerald, New Mexico, 7638", + "about": "Id ut qui veniam sunt amet aliquip. Exercitation ipsum esse sint deserunt aute. Irure sit Lorem eiusmod proident incididunt culpa consectetur laborum quis ea. Qui minim laboris ipsum ex minim esse ex nulla mollit mollit occaecat. Reprehenderit laboris mollit ex velit sunt non mollit et eiusmod consequat quis deserunt.\r\n", + "registered": "2014-06-30T08:51:04 -08:00", + "latitude": -39.753098, + "longitude": -145.119856, + "tags": [ + "ea", + "nisi", + "Lorem", + "velit", + "commodo", + "cupidatat", + "ut" + ], + "friends": [ + { + "id": 0, + "name": "Boyle Conway" + }, + { + "id": 1, + "name": "Alta Mcpherson" + }, + { + "id": 2, + "name": "Mcmillan Kim" + } + ], + "greeting": "Hello, Latisha Mayer! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e5e013b78074b0b49", + "index": 88, + "guid": "ebd2416f-bf84-4f2f-b047-cab98b340461", + "isActive": false, + "balance": "$1,754.08", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "green", + "name": "Toni Peterson", + "gender": "female", + "company": "ENDICIL", + "email": "tonipeterson@endicil.com", + "phone": "+1 (821) 412-3114", + "address": "153 Johnson Street, Starks, Massachusetts, 2175", + "about": "Sit mollit labore non exercitation anim exercitation aute. Magna est dolor et pariatur minim minim ullamco. Nisi minim amet voluptate occaecat consequat reprehenderit cillum eu culpa minim fugiat laborum. Ullamco ad consequat amet incididunt cupidatat culpa fugiat id commodo proident adipisicing anim ea sint. Ut labore sunt cillum anim sunt dolore exercitation magna sint magna dolore sint.\r\n", + "registered": "2017-04-05T06:01:43 -08:00", + "latitude": 74.582464, + "longitude": -77.404683, + "tags": [ + "incididunt", + "culpa", + "sit", + "irure", + "magna", + "do", + "Lorem" + ], + "friends": [ + { + "id": 0, + "name": "Randall Lucas" + }, + { + "id": 1, + "name": "Angelita Joyce" + }, + { + "id": 2, + "name": "Frankie Rhodes" + } + ], + "greeting": "Hello, Toni Peterson! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ef4cedc0146b4cd5c", + "index": 89, + "guid": "edd7d5a9-3655-4f58-ab8e-a6130732b40a", + "isActive": true, + "balance": "$1,919.80", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "blue", + "name": "Tammi Santiago", + "gender": "female", + "company": "EXPOSA", + "email": "tammisantiago@exposa.com", + "phone": "+1 (939) 414-3146", + "address": "404 Gotham Avenue, Brethren, Colorado, 5747", + "about": "Culpa adipisicing nulla incididunt ipsum culpa ut minim ut aliquip magna. Laboris nostrud et anim dolore et anim laborum. Culpa pariatur qui nisi do cillum nisi in consequat ullamco magna pariatur nulla deserunt voluptate. Commodo aliquip voluptate sint pariatur non do non ad esse dolore ad magna eu laboris. Veniam tempor cupidatat qui in aliqua quis nostrud labore consequat et nisi.\r\n", + "registered": "2017-11-20T06:24:35 -08:00", + "latitude": 29.956812, + "longitude": -103.291303, + "tags": [ + "dolor", + "enim", + "duis", + "occaecat", + "deserunt", + "incididunt", + "sint" + ], + "friends": [ + { + "id": 0, + "name": "Effie Ingram" + }, + { + "id": 1, + "name": "Williams Levy" + }, + { + "id": 2, + "name": "Odom Griffin" + } + ], + "greeting": "Hello, Tammi Santiago! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839eaef59c719ad3af15", + "index": 90, + "guid": "50faf0d6-10b2-409d-9b94-cd46dfa7aa93", + "isActive": false, + "balance": "$1,701.85", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "green", + "name": "Cline Chaney", + "gender": "male", + "company": "SULTRAX", + "email": "clinechaney@sultrax.com", + "phone": "+1 (886) 416-2728", + "address": "874 Linden Street, Bentonville, District Of Columbia, 519", + "about": "Id eiusmod nisi ipsum nisi duis. Ea aliqua voluptate magna mollit eu ea ad aute excepteur Lorem duis. Aliquip aliquip enim mollit quis ea. Reprehenderit ad consectetur laborum duis esse in. Dolor non enim cillum laboris. Nostrud ad non velit cillum cupidatat elit in.\r\n", + "registered": "2016-08-27T02:13:35 -08:00", + "latitude": -85.165138, + "longitude": 171.635566, + "tags": [ + "consequat", + "sunt", + "sunt", + "aliquip", + "excepteur", + "occaecat", + "mollit" + ], + "friends": [ + { + "id": 0, + "name": "Veronica Davidson" + }, + { + "id": 1, + "name": "Reyna Baxter" + }, + { + "id": 2, + "name": "Pacheco Compton" + } + ], + "greeting": "Hello, Cline Chaney! You have 3 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ec82b5ecacfdfa045", + "index": 91, + "guid": "823ab0b8-ecbc-49a9-ac73-392903b11c5c", + "isActive": true, + "balance": "$2,414.03", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "green", + "name": "Leonor Branch", + "gender": "female", + "company": "DAISU", + "email": "leonorbranch@daisu.com", + "phone": "+1 (928) 405-3685", + "address": "772 Waldane Court, Bend, Pennsylvania, 6487", + "about": "Consectetur culpa nulla et culpa cupidatat aliqua voluptate occaecat excepteur. Nulla mollit aliqua voluptate voluptate minim ut. Ex pariatur aliquip officia elit tempor ad ullamco amet officia. Sunt aute exercitation elit id velit esse consectetur officia nulla reprehenderit quis laboris eu consequat. Ex sit proident eiusmod dolore nisi et occaecat quis amet incididunt. Dolore pariatur sint aliqua mollit adipisicing minim esse sint non deserunt do.\r\n", + "registered": "2019-09-26T12:00:34 -08:00", + "latitude": 45.131172, + "longitude": 149.6994, + "tags": [ + "adipisicing", + "ex", + "laboris", + "anim", + "qui", + "culpa", + "nisi" + ], + "friends": [ + { + "id": 0, + "name": "Mueller Franks" + }, + { + "id": 1, + "name": "Koch Torres" + }, + { + "id": 2, + "name": "Abigail Rowland" + } + ], + "greeting": "Hello, Leonor Branch! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e30cb3a1ba1a7eeed", + "index": 92, + "guid": "49b10f41-2ec9-4585-8d21-5499eedd3392", + "isActive": false, + "balance": "$3,501.08", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "blue", + "name": "Deanna Dunlap", + "gender": "female", + "company": "ZAYA", + "email": "deannadunlap@zaya.com", + "phone": "+1 (936) 477-2284", + "address": "727 Aberdeen Street, Konterra, Montana, 5961", + "about": "Mollit amet nulla exercitation sunt ex exercitation incididunt ad reprehenderit do in ex. Nulla voluptate ad nostrud dolor minim velit labore Lorem do magna dolore est id pariatur. Ex anim aliqua mollit in excepteur reprehenderit aliqua tempor veniam voluptate eiusmod.\r\n", + "registered": "2018-10-14T08:19:14 -08:00", + "latitude": -83.455356, + "longitude": 146.23357, + "tags": [ + "aliqua", + "adipisicing", + "est", + "laboris", + "duis", + "aliquip", + "laborum" + ], + "friends": [ + { + "id": 0, + "name": "Chasity Blake" + }, + { + "id": 1, + "name": "Anderson Britt" + }, + { + "id": 2, + "name": "Glass Ramirez" + } + ], + "greeting": "Hello, Deanna Dunlap! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e4e695f76678a3c63", + "index": 93, + "guid": "6ad78906-0e55-4ab0-9201-1d3157a7d855", + "isActive": false, + "balance": "$1,927.59", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "blue", + "name": "Frederick Vang", + "gender": "male", + "company": "EXTRAGEN", + "email": "frederickvang@extragen.com", + "phone": "+1 (821) 538-2120", + "address": "234 Locust Street, Coaldale, Northern Mariana Islands, 2273", + "about": "Ut officia amet esse nostrud velit. Qui id laborum velit ex mollit reprehenderit ullamco aliqua. Labore voluptate commodo mollit commodo et et aliqua minim magna sint enim tempor et veniam. Consequat cupidatat eu ea tempor pariatur Lorem. Ipsum deserunt ex anim adipisicing. Irure amet nostrud non enim esse laboris labore.\r\n", + "registered": "2014-10-17T04:58:26 -08:00", + "latitude": -41.460001, + "longitude": -84.789471, + "tags": [ + "non", + "eu", + "quis", + "pariatur", + "labore", + "nisi", + "eiusmod" + ], + "friends": [ + { + "id": 0, + "name": "Bass Ellis" + }, + { + "id": 1, + "name": "Saundra Mayo" + }, + { + "id": 2, + "name": "Jewell Henry" + } + ], + "greeting": "Hello, Frederick Vang! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e659b69553173b7aa", + "index": 94, + "guid": "8f80a0d7-8dad-4025-8cd2-41d77be7f277", + "isActive": false, + "balance": "$3,329.77", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "blue", + "name": "Joseph Bridges", + "gender": "male", + "company": "UNISURE", + "email": "josephbridges@unisure.com", + "phone": "+1 (921) 499-3626", + "address": "968 Scholes Street, Nelson, Missouri, 4274", + "about": "Laborum laborum magna incididunt eu exercitation minim consectetur sint irure sunt id. In excepteur nisi esse consectetur voluptate minim qui velit ullamco minim ipsum officia tempor. Non et deserunt sunt proident ea pariatur nisi. Tempor dolor exercitation culpa proident cupidatat qui consequat pariatur. Irure dolor enim eu elit veniam elit dolor dolore adipisicing labore nulla nisi dolor. Nulla aute eu ut sit irure qui.\r\n", + "registered": "2016-11-18T04:15:52 -08:00", + "latitude": -0.141991, + "longitude": -108.229143, + "tags": [ + "amet", + "anim", + "officia", + "mollit", + "adipisicing", + "laborum", + "irure" + ], + "friends": [ + { + "id": 0, + "name": "Miriam Roberts" + }, + { + "id": 1, + "name": "Richards Chavez" + }, + { + "id": 2, + "name": "Burnett Jensen" + } + ], + "greeting": "Hello, Joseph Bridges! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839efb8991282a6bb552", + "index": 95, + "guid": "c6bcef09-2139-41cf-8314-f16fbd3d88ef", + "isActive": true, + "balance": "$3,875.28", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "blue", + "name": "Karina Walsh", + "gender": "female", + "company": "COMBOGENE", + "email": "karinawalsh@combogene.com", + "phone": "+1 (882) 492-3192", + "address": "521 Bethel Loop, Blanford, Kansas, 4812", + "about": "Commodo nisi cupidatat aliquip occaecat aute. Pariatur qui eu fugiat ut consequat tempor ex sunt aute. Magna ex est consectetur laborum culpa sint labore est laboris velit esse pariatur. Qui exercitation est proident commodo laboris cupidatat occaecat do id.\r\n", + "registered": "2016-12-31T01:53:38 -08:00", + "latitude": 80.054862, + "longitude": 14.521184, + "tags": [ + "ullamco", + "consectetur", + "incididunt", + "minim", + "cillum", + "cupidatat", + "deserunt" + ], + "friends": [ + { + "id": 0, + "name": "Lyons Mcmahon" + }, + { + "id": 1, + "name": "Kent Weber" + }, + { + "id": 2, + "name": "Dean Pittman" + } + ], + "greeting": "Hello, Karina Walsh! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ee57cf6f18d3360cc", + "index": 96, + "guid": "dd364c33-617a-429d-9550-5b3e8cdd8f55", + "isActive": false, + "balance": "$2,398.48", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "green", + "name": "Lawrence Black", + "gender": "male", + "company": "NORALI", + "email": "lawrenceblack@norali.com", + "phone": "+1 (909) 455-3972", + "address": "975 High Street, Shelby, Georgia, 1361", + "about": "Et aute ad Lorem nulla aliquip officia laborum dolore fugiat. Excepteur voluptate cupidatat non ex elit irure nostrud aliquip laboris sint do qui incididunt. Ullamco Lorem voluptate nulla eu non adipisicing. Ipsum proident ut cupidatat cillum duis excepteur exercitation. Reprehenderit proident laboris non ut qui Lorem ut ex sunt nostrud. Eiusmod nulla dolor nisi nostrud ex consequat mollit et sunt excepteur ad minim culpa. Ullamco irure pariatur officia culpa consequat deserunt commodo officia.\r\n", + "registered": "2018-01-17T11:28:58 -08:00", + "latitude": -54.033094, + "longitude": -166.213551, + "tags": [ + "nostrud", + "proident", + "aliqua", + "culpa", + "laborum", + "aliquip", + "ullamco" + ], + "friends": [ + { + "id": 0, + "name": "Singleton Stephens" + }, + { + "id": 1, + "name": "Angelique Shepard" + }, + { + "id": 2, + "name": "Concepcion Patel" + } + ], + "greeting": "Hello, Lawrence Black! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e681157ba4028cafb", + "index": 97, + "guid": "1cc051a6-d72e-4dd9-b64c-ceaf31a4adda", + "isActive": true, + "balance": "$2,000.59", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "brown", + "name": "Claudia Mack", + "gender": "female", + "company": "TALENDULA", + "email": "claudiamack@talendula.com", + "phone": "+1 (823) 534-3997", + "address": "743 Grimes Road, Ruffin, New Jersey, 7426", + "about": "Non elit ea fugiat deserunt nisi deserunt excepteur irure. Consequat commodo amet cupidatat enim exercitation mollit consequat ad sit anim. Aliqua ut enim Lorem amet proident. Labore veniam ex est ut eu labore ullamco irure.\r\n", + "registered": "2015-11-23T01:46:37 -08:00", + "latitude": -7.319198, + "longitude": -92.073709, + "tags": [ + "amet", + "eiusmod", + "sit", + "laboris", + "do", + "occaecat", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "Langley Hurley" + }, + { + "id": 1, + "name": "Peters Allison" + }, + { + "id": 2, + "name": "Padilla Mcintyre" + } + ], + "greeting": "Hello, Claudia Mack! You have 5 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ee079761417a89a00", + "index": 98, + "guid": "ab630bb8-b97d-4484-913b-42a9a3cf6912", + "isActive": true, + "balance": "$1,411.16", + "picture": "http://placehold.it/32x32", + "age": 24, + "eyeColor": "green", + "name": "Garner Stark", + "gender": "male", + "company": "FUTURITY", + "email": "garnerstark@futurity.com", + "phone": "+1 (828) 495-2812", + "address": "767 Irving Place, Saddlebrooke, Oklahoma, 4328", + "about": "Ex ex velit sit esse nisi excepteur reprehenderit voluptate culpa laboris ad ipsum elit aute. Eiusmod sint dolore voluptate minim et. Cupidatat magna culpa ex laboris fugiat anim. Minim nulla commodo ex elit. Officia ex deserunt ipsum excepteur sint do do enim deserunt aliqua cupidatat consequat eiusmod. Incididunt adipisicing excepteur voluptate sit.\r\n", + "registered": "2017-08-28T09:51:27 -08:00", + "latitude": 46.510593, + "longitude": 158.56317, + "tags": [ + "incididunt", + "incididunt", + "labore", + "nulla", + "quis", + "cupidatat", + "proident" + ], + "friends": [ + { + "id": 0, + "name": "Hahn Morales" + }, + { + "id": 1, + "name": "Tasha Hester" + }, + { + "id": 2, + "name": "Mitzi Greene" + } + ], + "greeting": "Hello, Garner Stark! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ee3e874b02ccc23a0", + "index": 99, + "guid": "f2b138a2-d7aa-496b-8787-32f78379fa84", + "isActive": false, + "balance": "$3,626.36", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "brown", + "name": "Valentine Meadows", + "gender": "male", + "company": "OVIUM", + "email": "valentinemeadows@ovium.com", + "phone": "+1 (858) 470-2701", + "address": "762 Newel Street, Keyport, North Carolina, 8936", + "about": "Incididunt cillum fugiat proident in ipsum amet eu qui nulla nostrud magna enim aliquip duis. Ipsum quis enim non irure occaecat eiusmod non culpa officia amet esse qui. Velit est ex laborum velit. Nostrud magna magna et non veniam do ex. Nisi non pariatur occaecat aliquip dolore commodo ipsum aliquip officia irure excepteur cillum officia. Excepteur qui veniam cupidatat elit qui ut ad minim esse deserunt nisi deserunt id irure.\r\n", + "registered": "2018-02-27T03:26:14 -08:00", + "latitude": -83.719401, + "longitude": -115.631983, + "tags": [ + "excepteur", + "minim", + "minim", + "cillum", + "ullamco", + "reprehenderit", + "nisi" + ], + "friends": [ + { + "id": 0, + "name": "Travis Buchanan" + }, + { + "id": 1, + "name": "Lesa George" + }, + { + "id": 2, + "name": "Marla Thornton" + } + ], + "greeting": "Hello, Valentine Meadows! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e32b9af987b4a645e", + "index": 100, + "guid": "9a031b31-7a15-41e4-978b-817b22da6902", + "isActive": true, + "balance": "$3,210.08", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "blue", + "name": "Irma Dickson", + "gender": "female", + "company": "ENTROFLEX", + "email": "irmadickson@entroflex.com", + "phone": "+1 (819) 419-3085", + "address": "221 Bridgewater Street, Cowiche, Rhode Island, 7778", + "about": "Pariatur voluptate eu sint magna anim quis veniam. Id quis esse cupidatat officia enim reprehenderit. Exercitation amet sunt Lorem nostrud.\r\n", + "registered": "2016-02-07T08:48:50 -08:00", + "latitude": -48.420937, + "longitude": -179.313469, + "tags": [ + "fugiat", + "adipisicing", + "nisi", + "ad", + "esse", + "ex", + "cillum" + ], + "friends": [ + { + "id": 0, + "name": "Petty Ashley" + }, + { + "id": 1, + "name": "Munoz Daniels" + }, + { + "id": 2, + "name": "Browning Charles" + } + ], + "greeting": "Hello, Irma Dickson! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e8ea298d9f72e1729", + "index": 101, + "guid": "c56063d0-8e5b-4b51-8281-ce8e1e78f151", + "isActive": true, + "balance": "$2,527.53", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "green", + "name": "Roxanne Conrad", + "gender": "female", + "company": "THREDZ", + "email": "roxanneconrad@thredz.com", + "phone": "+1 (970) 587-2419", + "address": "791 Legion Street, Derwood, Maine, 6087", + "about": "Do qui quis et aliqua eu commodo culpa et ad elit voluptate labore consectetur. Eu culpa ut nostrud irure pariatur ipsum dolore labore ipsum cupidatat in cupidatat reprehenderit mollit. Fugiat laborum in do nisi est consectetur consequat magna id eiusmod. Qui nulla reprehenderit exercitation est ad.\r\n", + "registered": "2018-10-15T05:41:46 -08:00", + "latitude": 83.173526, + "longitude": 64.012595, + "tags": [ + "pariatur", + "nostrud", + "esse", + "ad", + "nostrud", + "qui", + "et" + ], + "friends": [ + { + "id": 0, + "name": "Lindsey Bailey" + }, + { + "id": 1, + "name": "Kristen Shepherd" + }, + { + "id": 2, + "name": "Harmon Snyder" + } + ], + "greeting": "Hello, Roxanne Conrad! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e57b10da0ab1da5d0", + "index": 102, + "guid": "26062bd5-8e5e-4bb9-a9b1-7c78aa2240dc", + "isActive": true, + "balance": "$1,061.66", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Gentry Bradley", + "gender": "male", + "company": "EYERIS", + "email": "gentrybradley@eyeris.com", + "phone": "+1 (994) 423-2766", + "address": "118 Nassau Street, Garfield, Connecticut, 7363", + "about": "Commodo aute magna consequat tempor nulla ea consequat reprehenderit culpa ut nostrud. Exercitation mollit labore cupidatat ea consectetur duis ipsum id ullamco commodo ullamco exercitation incididunt. Excepteur esse anim proident nisi amet mollit quis velit culpa irure nisi incididunt. Est ea velit proident laborum ad in reprehenderit voluptate non eiusmod fugiat enim eu. Velit consequat dolore non deserunt adipisicing amet ut minim aliquip deserunt irure cillum. Non laboris ea ipsum ullamco.\r\n", + "registered": "2018-06-13T07:38:02 -08:00", + "latitude": -64.870933, + "longitude": 151.781936, + "tags": [ + "aliquip", + "tempor", + "esse", + "incididunt", + "ut", + "enim", + "est" + ], + "friends": [ + { + "id": 0, + "name": "Courtney Hewitt" + }, + { + "id": 1, + "name": "Hurley Rosa" + }, + { + "id": 2, + "name": "Samantha Sandoval" + } + ], + "greeting": "Hello, Gentry Bradley! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e90b43cc764206971", + "index": 103, + "guid": "3e699c70-0746-4318-a9b8-1a60c03734fd", + "isActive": false, + "balance": "$3,742.52", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "blue", + "name": "Sanchez Bernard", + "gender": "male", + "company": "IDETICA", + "email": "sanchezbernard@idetica.com", + "phone": "+1 (829) 591-3342", + "address": "626 Adler Place, Dragoon, Kentucky, 1512", + "about": "Duis consectetur aute sint laboris ea anim qui proident magna. Ad dolor elit adipisicing dolore sunt anim excepteur ipsum Lorem minim labore. Enim in anim nisi deserunt non do dolor excepteur nostrud dolore.\r\n", + "registered": "2018-06-27T06:59:15 -08:00", + "latitude": 42.83409, + "longitude": -148.294356, + "tags": [ + "consectetur", + "aute", + "velit", + "aliquip", + "duis", + "do", + "aliqua" + ], + "friends": [ + { + "id": 0, + "name": "Carrie Hansen" + }, + { + "id": 1, + "name": "Melanie Navarro" + }, + { + "id": 2, + "name": "Roslyn Guzman" + } + ], + "greeting": "Hello, Sanchez Bernard! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e6a54571fc2e660ba", + "index": 104, + "guid": "4401e845-6243-402d-ae49-3586e70b1dcb", + "isActive": false, + "balance": "$3,760.63", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "blue", + "name": "Megan Kelly", + "gender": "female", + "company": "CINESANCT", + "email": "megankelly@cinesanct.com", + "phone": "+1 (823) 578-3070", + "address": "905 Vista Place, Canterwood, Puerto Rico, 1556", + "about": "Deserunt id sint in qui commodo id consectetur cillum nostrud in dolor in. Nostrud tempor culpa nisi duis consequat officia et cupidatat elit excepteur laborum laborum et sint. Et adipisicing veniam irure nisi. Dolor pariatur magna reprehenderit id irure sunt laborum sunt pariatur nulla enim consequat ea aliqua. Sunt ullamco est commodo mollit cupidatat. Elit ullamco nostrud quis eiusmod aute labore qui quis aliquip officia do consectetur.\r\n", + "registered": "2015-06-26T09:32:59 -08:00", + "latitude": -8.228944, + "longitude": 1.881314, + "tags": [ + "cupidatat", + "aute", + "consectetur", + "sit", + "deserunt", + "ex", + "aliqua" + ], + "friends": [ + { + "id": 0, + "name": "Nikki Brennan" + }, + { + "id": 1, + "name": "Ella Alvarez" + }, + { + "id": 2, + "name": "Dionne Hudson" + } + ], + "greeting": "Hello, Megan Kelly! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e28cfec0f22fb6130", + "index": 105, + "guid": "5fb3bb79-ff85-481d-8fda-0487c86d6da1", + "isActive": false, + "balance": "$3,764.28", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "blue", + "name": "Barron Gibbs", + "gender": "male", + "company": "INSURON", + "email": "barrongibbs@insuron.com", + "phone": "+1 (822) 431-3366", + "address": "155 Eldert Lane, Henrietta, Indiana, 6339", + "about": "Velit amet aliqua dolor deserunt sint eu. Nulla non dolor do tempor enim cupidatat do deserunt. Nisi adipisicing amet ut pariatur sit cillum laborum aute duis ipsum in. Magna anim et occaecat cupidatat Lorem voluptate nisi ea aute. Incididunt cupidatat ea in tempor. Sunt laboris et magna consectetur est dolore enim adipisicing veniam consequat labore magna ex ipsum.\r\n", + "registered": "2018-03-11T07:07:04 -08:00", + "latitude": -66.287104, + "longitude": 2.336872, + "tags": [ + "esse", + "sint", + "enim", + "labore", + "qui", + "cupidatat", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "Karin Burris" + }, + { + "id": 1, + "name": "Annmarie Vance" + }, + { + "id": 2, + "name": "Angelia Tanner" + } + ], + "greeting": "Hello, Barron Gibbs! You have 7 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e70909d330cc7e7cd", + "index": 106, + "guid": "3017028f-fc55-4b71-92d6-6f6fe1db6c10", + "isActive": false, + "balance": "$2,060.78", + "picture": "http://placehold.it/32x32", + "age": 34, + "eyeColor": "blue", + "name": "Young Hill", + "gender": "male", + "company": "SENTIA", + "email": "younghill@sentia.com", + "phone": "+1 (812) 441-3985", + "address": "872 Ridge Court, Rutherford, Idaho, 5723", + "about": "Labore cupidatat mollit minim excepteur minim dolor ut magna deserunt sunt cupidatat reprehenderit nulla do. Voluptate deserunt proident ipsum commodo enim eu qui nisi ullamco enim. Do aliquip amet cillum cillum non culpa tempor velit dolor sunt laborum ullamco cillum consequat. Occaecat officia Lorem cupidatat aute. Laboris sunt voluptate cupidatat et nostrud enim anim commodo sit consequat.\r\n", + "registered": "2014-03-17T08:29:51 -08:00", + "latitude": -32.725065, + "longitude": 84.543696, + "tags": [ + "ex", + "eiusmod", + "mollit", + "velit", + "incididunt", + "fugiat", + "eu" + ], + "friends": [ + { + "id": 0, + "name": "Dorsey Turner" + }, + { + "id": 1, + "name": "Olga Duke" + }, + { + "id": 2, + "name": "Tucker Lynn" + } + ], + "greeting": "Hello, Young Hill! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e1f902eaaedc98475", + "index": 107, + "guid": "2e030dec-236b-4d99-ae8d-953be1a0dc25", + "isActive": false, + "balance": "$2,790.24", + "picture": "http://placehold.it/32x32", + "age": 35, + "eyeColor": "blue", + "name": "Ester Good", + "gender": "female", + "company": "ELENTRIX", + "email": "estergood@elentrix.com", + "phone": "+1 (996) 461-2674", + "address": "739 Veronica Place, Fowlerville, Federated States Of Micronesia, 8396", + "about": "Occaecat quis sit excepteur fugiat laboris consequat esse irure. Cillum Lorem nulla ut velit. Sint consequat officia nisi nostrud ea qui sint magna. Aliqua non do anim ullamco do veniam labore cillum Lorem deserunt commodo nulla aliquip sit. Reprehenderit proident consectetur minim non velit velit elit occaecat do enim adipisicing ut culpa.\r\n", + "registered": "2019-10-08T05:19:51 -08:00", + "latitude": -10.177832, + "longitude": -36.23077, + "tags": [ + "proident", + "voluptate", + "nostrud", + "dolor", + "id", + "irure", + "sit" + ], + "friends": [ + { + "id": 0, + "name": "Combs Bond" + }, + { + "id": 1, + "name": "Lynnette Lynch" + }, + { + "id": 2, + "name": "Tonia Austin" + } + ], + "greeting": "Hello, Ester Good! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ed0a87b134946a021", + "index": 108, + "guid": "394c3fe9-c02f-4195-a886-d9ef95700a0c", + "isActive": false, + "balance": "$3,336.76", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "green", + "name": "Angeline Mccarthy", + "gender": "female", + "company": "EQUICOM", + "email": "angelinemccarthy@equicom.com", + "phone": "+1 (906) 497-3070", + "address": "267 Lombardy Street, Avalon, South Carolina, 6248", + "about": "Fugiat mollit velit in eiusmod dolore eu sint Lorem. Ut tempor occaecat duis eu eiusmod proident non. Pariatur irure proident reprehenderit voluptate adipisicing. Eu enim magna proident dolor enim magna voluptate magna aliquip sit qui. Culpa id aliquip sunt ipsum duis non eu sint adipisicing voluptate minim exercitation. Nisi eu officia ea ipsum minim velit eu fugiat quis irure sunt exercitation.\r\n", + "registered": "2014-01-06T12:55:13 -08:00", + "latitude": -62.973241, + "longitude": -104.853256, + "tags": [ + "consectetur", + "enim", + "cillum", + "veniam", + "dolore", + "irure", + "qui" + ], + "friends": [ + { + "id": 0, + "name": "Alvarez Burch" + }, + { + "id": 1, + "name": "Austin Park" + }, + { + "id": 2, + "name": "Dixie Peters" + } + ], + "greeting": "Hello, Angeline Mccarthy! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e31f1943ffc191d95", + "index": 109, + "guid": "8c31947a-05fc-4533-a735-12aa5bdc265d", + "isActive": true, + "balance": "$3,707.85", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "blue", + "name": "Petersen Farmer", + "gender": "male", + "company": "CENTICE", + "email": "petersenfarmer@centice.com", + "phone": "+1 (942) 428-3241", + "address": "867 Regent Place, Alafaya, Utah, 7286", + "about": "Ipsum quis anim nisi nulla dolore ea anim consectetur ipsum ut ea adipisicing sint. Id nulla elit voluptate excepteur labore elit. Duis ipsum adipisicing aliqua nulla reprehenderit ad. Aliquip do quis tempor duis fugiat dolore non fugiat.\r\n", + "registered": "2017-05-01T01:45:38 -08:00", + "latitude": 85.334267, + "longitude": -2.855114, + "tags": [ + "esse", + "sit", + "culpa", + "esse", + "anim", + "mollit", + "magna" + ], + "friends": [ + { + "id": 0, + "name": "Tanya Ware" + }, + { + "id": 1, + "name": "Hebert Benjamin" + }, + { + "id": 2, + "name": "Woods Mercer" + } + ], + "greeting": "Hello, Petersen Farmer! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e92ec2c2f47e86eba", + "index": 110, + "guid": "4e6c07e9-3d90-4064-bf36-f8f350393ac5", + "isActive": false, + "balance": "$1,432.15", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "brown", + "name": "Luisa Thomas", + "gender": "female", + "company": "COMDOM", + "email": "luisathomas@comdom.com", + "phone": "+1 (809) 427-3247", + "address": "429 Conover Street, Greenbackville, Virginia, 5568", + "about": "Nostrud non commodo officia laborum veniam laborum in sint quis. Sit anim aute laborum non nisi et adipisicing. Proident ullamco id dolor Lorem eu labore. Cupidatat et non incididunt veniam cillum exercitation laborum reprehenderit pariatur aute sunt.\r\n", + "registered": "2016-01-20T09:05:48 -08:00", + "latitude": -79.579368, + "longitude": -18.890684, + "tags": [ + "nulla", + "pariatur", + "pariatur", + "laboris", + "mollit", + "ullamco", + "aute" + ], + "friends": [ + { + "id": 0, + "name": "Esmeralda Wall" + }, + { + "id": 1, + "name": "Benton Craft" + }, + { + "id": 2, + "name": "Duran Pierce" + } + ], + "greeting": "Hello, Luisa Thomas! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e698aa469d05d86e8", + "index": 111, + "guid": "64ecf100-77d2-4750-b84d-35115a605d2e", + "isActive": false, + "balance": "$2,675.74", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "blue", + "name": "Katrina Gillespie", + "gender": "female", + "company": "GEEKY", + "email": "katrinagillespie@geeky.com", + "phone": "+1 (881) 546-3615", + "address": "302 Gaylord Drive, Hamilton, Oregon, 6643", + "about": "Non pariatur exercitation consectetur aliquip deserunt tempor cupidatat magna. Dolore non elit dolor voluptate. Excepteur sit mollit aliqua labore consequat. Ex pariatur est labore non nostrud minim exercitation non. Dolore exercitation sit tempor officia dolor velit.\r\n", + "registered": "2015-10-21T12:25:47 -08:00", + "latitude": 89.455496, + "longitude": -111.057037, + "tags": [ + "cillum", + "aliquip", + "nisi", + "esse", + "aute", + "ex", + "amet" + ], + "friends": [ + { + "id": 0, + "name": "Essie Langley" + }, + { + "id": 1, + "name": "Roach Merrill" + }, + { + "id": 2, + "name": "Hensley Waters" + } + ], + "greeting": "Hello, Katrina Gillespie! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e8596fb261b48f05a", + "index": 112, + "guid": "81c8c08b-0a1f-4b59-b5d8-c8dc4dcb493e", + "isActive": false, + "balance": "$2,088.06", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "blue", + "name": "Yang Cohen", + "gender": "male", + "company": "DAIDO", + "email": "yangcohen@daido.com", + "phone": "+1 (859) 571-2821", + "address": "500 Metropolitan Avenue, Collins, Iowa, 7021", + "about": "Et aute dolore elit consequat excepteur deserunt velit laborum do. Minim ut ut magna excepteur et est anim. Excepteur enim tempor incididunt amet magna aliqua ut esse. Quis in quis Lorem occaecat est nisi elit sunt consectetur ipsum irure consectetur commodo.\r\n", + "registered": "2015-02-05T05:06:49 -08:00", + "latitude": -29.164918, + "longitude": 155.538837, + "tags": [ + "dolor", + "sit", + "incididunt", + "occaecat", + "ea", + "excepteur", + "nulla" + ], + "friends": [ + { + "id": 0, + "name": "Grace Nieves" + }, + { + "id": 1, + "name": "Loretta Buck" + }, + { + "id": 2, + "name": "Benita Conley" + } + ], + "greeting": "Hello, Yang Cohen! You have 3 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e7575de8ebaada7f7", + "index": 113, + "guid": "e7169945-e15b-473b-8afc-d83752420e7e", + "isActive": true, + "balance": "$2,219.01", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "brown", + "name": "Cochran Brock", + "gender": "male", + "company": "FURNIGEER", + "email": "cochranbrock@furnigeer.com", + "phone": "+1 (974) 409-3250", + "address": "945 Nautilus Avenue, Floriston, New Hampshire, 9287", + "about": "Aute ullamco nulla eu eiusmod do amet. Excepteur nostrud laborum laboris officia in tempor cillum proident. Ex excepteur cillum cupidatat laboris nostrud qui et excepteur velit voluptate ex incididunt irure. Veniam anim minim consectetur laboris aliquip enim fugiat dolore ea excepteur minim eiusmod. Enim laborum proident est cillum nostrud reprehenderit sint qui minim ex consequat officia. Aute anim amet occaecat ad laboris enim.\r\n", + "registered": "2015-08-14T08:37:37 -08:00", + "latitude": -80.695159, + "longitude": -57.774048, + "tags": [ + "officia", + "quis", + "sunt", + "occaecat", + "et", + "ea", + "esse" + ], + "friends": [ + { + "id": 0, + "name": "Hodge Rodriguez" + }, + { + "id": 1, + "name": "Walls Macias" + }, + { + "id": 2, + "name": "Greene Stanley" + } + ], + "greeting": "Hello, Cochran Brock! You have 3 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ece5223d20622fe8f", + "index": 114, + "guid": "27fdd5df-2a58-4c63-b63c-c516709b47a9", + "isActive": true, + "balance": "$1,055.47", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "brown", + "name": "Albert Richards", + "gender": "male", + "company": "PYRAMIS", + "email": "albertrichards@pyramis.com", + "phone": "+1 (960) 534-2368", + "address": "424 Ivan Court, Haring, Texas, 9708", + "about": "Et nostrud cillum sint cillum laborum velit mollit aliqua Lorem laborum eiusmod sunt aliquip non. Ea et cupidatat aliqua adipisicing ut ea cupidatat. Do nulla cupidatat enim velit est laborum nisi irure consectetur amet aliquip incididunt. Culpa quis nulla nostrud cillum anim consequat velit consequat et aliquip tempor fugiat cupidatat ex.\r\n", + "registered": "2017-07-19T08:49:27 -08:00", + "latitude": -73.301986, + "longitude": 62.168076, + "tags": [ + "anim", + "aliquip", + "anim", + "nostrud", + "eu", + "exercitation", + "dolore" + ], + "friends": [ + { + "id": 0, + "name": "Magdalena Berger" + }, + { + "id": 1, + "name": "Shanna Carroll" + }, + { + "id": 2, + "name": "Morgan Cote" + } + ], + "greeting": "Hello, Albert Richards! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e9ea3b43ea4bfd49c", + "index": 115, + "guid": "85bc1726-8244-4c38-a18d-a8b89d788ab8", + "isActive": false, + "balance": "$1,385.75", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Brock Gaines", + "gender": "male", + "company": "ZIDOX", + "email": "brockgaines@zidox.com", + "phone": "+1 (862) 449-2557", + "address": "419 Everett Avenue, Rivereno, Michigan, 7815", + "about": "Nisi deserunt velit laborum et eiusmod laboris minim nisi Lorem nulla do sint amet aute. Ad eu proident duis Lorem. Culpa eu nostrud nulla cupidatat Lorem excepteur. Sunt mollit voluptate pariatur ipsum incididunt nostrud ex cupidatat. Proident aute cupidatat minim duis exercitation enim ad ipsum sit dolore officia aliqua.\r\n", + "registered": "2019-09-15T02:11:27 -08:00", + "latitude": -14.171686, + "longitude": 23.162185, + "tags": [ + "nisi", + "commodo", + "ea", + "est", + "enim", + "ut", + "nulla" + ], + "friends": [ + { + "id": 0, + "name": "Oneill Perez" + }, + { + "id": 1, + "name": "Mcpherson Justice" + }, + { + "id": 2, + "name": "Rosalinda Hale" + } + ], + "greeting": "Hello, Brock Gaines! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e27bdf3de69b42081", + "index": 116, + "guid": "6971d666-9e92-4a70-bf2f-bfd08aa7d262", + "isActive": false, + "balance": "$1,190.38", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "green", + "name": "Whitfield Lambert", + "gender": "male", + "company": "SONGLINES", + "email": "whitfieldlambert@songlines.com", + "phone": "+1 (939) 413-2099", + "address": "832 Lynch Street, Waukeenah, Guam, 3496", + "about": "Nulla velit ex est ipsum magna dolore deserunt sunt. Reprehenderit cillum excepteur et eiusmod magna dolore irure ea culpa laborum anim duis eu ipsum. Enim dolor occaecat dolor in enim anim et. Voluptate deserunt do sunt occaecat cupidatat enim incididunt nostrud incididunt occaecat. Ullamco aute amet incididunt qui enim aute aliqua ut anim cupidatat elit veniam. Exercitation tempor nisi ipsum nostrud amet do ipsum. Do qui voluptate dolor aliquip.\r\n", + "registered": "2019-02-10T02:42:12 -08:00", + "latitude": 46.993763, + "longitude": -19.567964, + "tags": [ + "magna", + "cupidatat", + "velit", + "culpa", + "exercitation", + "pariatur", + "id" + ], + "friends": [ + { + "id": 0, + "name": "Molina Flowers" + }, + { + "id": 1, + "name": "Chan Davenport" + }, + { + "id": 2, + "name": "Mccarthy Mckinney" + } + ], + "greeting": "Hello, Whitfield Lambert! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e434e313ece442eaa", + "index": 117, + "guid": "e456b75d-79bb-4f8e-8919-0cc18b2778ea", + "isActive": true, + "balance": "$3,826.37", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "blue", + "name": "Key Price", + "gender": "male", + "company": "TETAK", + "email": "keyprice@tetak.com", + "phone": "+1 (834) 475-3728", + "address": "761 Gates Avenue, Orovada, Virgin Islands, 3641", + "about": "Ullamco sit pariatur excepteur in ad esse commodo duis magna qui amet laborum proident veniam. Reprehenderit tempor commodo culpa veniam ullamco aute aute magna sit. Ullamco dolor pariatur adipisicing sit fugiat nostrud proident consequat mollit.\r\n", + "registered": "2015-10-18T10:08:08 -08:00", + "latitude": 55.28089, + "longitude": -73.955207, + "tags": [ + "ad", + "excepteur", + "est", + "cillum", + "tempor", + "cillum", + "culpa" + ], + "friends": [ + { + "id": 0, + "name": "Grimes Cross" + }, + { + "id": 1, + "name": "Olsen Holman" + }, + { + "id": 2, + "name": "Rosa Mcdaniel" + } + ], + "greeting": "Hello, Key Price! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ecabf14a570ecbb90", + "index": 118, + "guid": "494bbf39-3592-47c6-ad73-95f0a3eadec2", + "isActive": false, + "balance": "$1,894.11", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "blue", + "name": "Gabriela Russell", + "gender": "female", + "company": "GENESYNK", + "email": "gabrielarussell@genesynk.com", + "phone": "+1 (980) 558-3109", + "address": "546 Coleridge Street, Templeton, Vermont, 352", + "about": "Dolor mollit duis sit nulla elit irure proident occaecat. Ipsum qui consectetur aute Lorem adipisicing excepteur laboris id enim sunt. Aliqua magna amet irure ullamco minim in pariatur sunt labore tempor magna.\r\n", + "registered": "2017-04-18T04:36:45 -08:00", + "latitude": 80.966511, + "longitude": 150.380498, + "tags": [ + "consequat", + "dolore", + "consequat", + "minim", + "nostrud", + "esse", + "velit" + ], + "friends": [ + { + "id": 0, + "name": "Willie Simpson" + }, + { + "id": 1, + "name": "Mai Wilcox" + }, + { + "id": 2, + "name": "Candace Cole" + } + ], + "greeting": "Hello, Gabriela Russell! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e9f28785fda99c18d", + "index": 119, + "guid": "78b45be1-16ca-44db-976c-b28d79dfc7bf", + "isActive": true, + "balance": "$3,204.79", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "blue", + "name": "Christian Hogan", + "gender": "female", + "company": "QUANTALIA", + "email": "christianhogan@quantalia.com", + "phone": "+1 (843) 561-3191", + "address": "846 Diamond Street, Nescatunga, Alabama, 1573", + "about": "Esse culpa exercitation exercitation irure nulla Lorem aliquip ipsum. Commodo magna ex ipsum ullamco sunt. Eiusmod reprehenderit dolore pariatur ad esse aute occaecat quis nisi ex fugiat tempor. Amet enim esse excepteur eiusmod consequat adipisicing id id labore culpa aliquip magna proident. Officia laboris anim enim elit elit sint.\r\n", + "registered": "2018-05-25T04:49:18 -08:00", + "latitude": 61.800815, + "longitude": -90.212976, + "tags": [ + "ad", + "labore", + "cillum", + "tempor", + "aliqua", + "ex", + "laborum" + ], + "friends": [ + { + "id": 0, + "name": "Acosta Stafford" + }, + { + "id": 1, + "name": "Marguerite Parker" + }, + { + "id": 2, + "name": "Rachael Macdonald" + } + ], + "greeting": "Hello, Christian Hogan! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e3e988e66cbea08d8", + "index": 120, + "guid": "68ffea74-509e-4431-bc29-1496401db4ce", + "isActive": false, + "balance": "$1,139.64", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "green", + "name": "Hart Montoya", + "gender": "male", + "company": "ROCKYARD", + "email": "hartmontoya@rockyard.com", + "phone": "+1 (873) 402-2428", + "address": "433 Homecrest Avenue, Grantville, New York, 5405", + "about": "Consectetur adipisicing culpa ex enim culpa cillum Lorem occaecat cupidatat aliquip laborum fugiat anim. Minim adipisicing dolor aliquip labore fugiat cillum proident. Laborum ad tempor nostrud irure cupidatat non quis commodo. Non sint incididunt officia proident fugiat.\r\n", + "registered": "2015-04-27T01:01:30 -08:00", + "latitude": 77.838599, + "longitude": -24.805296, + "tags": [ + "sit", + "nostrud", + "amet", + "non", + "pariatur", + "culpa", + "velit" + ], + "friends": [ + { + "id": 0, + "name": "Simpson Workman" + }, + { + "id": 1, + "name": "Kemp Martin" + }, + { + "id": 2, + "name": "Morrison Ford" + } + ], + "greeting": "Hello, Hart Montoya! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e69e11fb71f1888a5", + "index": 121, + "guid": "5f0f6f45-5e98-4e8c-b5df-1a338e071006", + "isActive": false, + "balance": "$1,552.59", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "brown", + "name": "Lorene House", + "gender": "female", + "company": "TERRAGEN", + "email": "lorenehouse@terragen.com", + "phone": "+1 (981) 496-3919", + "address": "947 McKibbin Street, Drytown, Tennessee, 4103", + "about": "Et exercitation anim minim sint laborum est. Excepteur elit minim sunt eu do. Pariatur duis veniam velit eiusmod.\r\n", + "registered": "2015-03-13T03:41:37 -08:00", + "latitude": 41.910671, + "longitude": -33.017183, + "tags": [ + "labore", + "Lorem", + "est", + "excepteur", + "commodo", + "ut", + "dolor" + ], + "friends": [ + { + "id": 0, + "name": "Meadows Cox" + }, + { + "id": 1, + "name": "Welch Wooten" + }, + { + "id": 2, + "name": "Marsha Frye" + } + ], + "greeting": "Hello, Lorene House! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e0a6037924e019646", + "index": 122, + "guid": "b5136b0f-5a3c-4c9f-b2fd-a297a5a156e5", + "isActive": false, + "balance": "$1,402.66", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "brown", + "name": "Misty Bean", + "gender": "female", + "company": "DAYCORE", + "email": "mistybean@daycore.com", + "phone": "+1 (902) 501-2437", + "address": "931 Apollo Street, Galesville, Delaware, 2129", + "about": "Nostrud pariatur voluptate nostrud ex occaecat commodo exercitation qui elit. Sit do officia ex eu adipisicing ad. Culpa in eu ex incididunt eu ea pariatur excepteur consequat exercitation.\r\n", + "registered": "2018-11-21T12:05:22 -08:00", + "latitude": 79.119765, + "longitude": 169.759059, + "tags": [ + "nulla", + "nulla", + "voluptate", + "et", + "laborum", + "culpa", + "ut" + ], + "friends": [ + { + "id": 0, + "name": "Duncan Harmon" + }, + { + "id": 1, + "name": "Tyson Mays" + }, + { + "id": 2, + "name": "Delia Larsen" + } + ], + "greeting": "Hello, Misty Bean! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ef720f900f293b83a", + "index": 123, + "guid": "8c794994-0b6e-4816-9fea-0adbf708cf33", + "isActive": false, + "balance": "$2,761.56", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "blue", + "name": "Sonya Barrera", + "gender": "female", + "company": "DYMI", + "email": "sonyabarrera@dymi.com", + "phone": "+1 (902) 554-2779", + "address": "810 Veranda Place, Why, Minnesota, 1671", + "about": "Dolore sit consectetur ut adipisicing ad deserunt. Ex non id aute officia fugiat laborum aliquip sit est voluptate amet commodo esse. Reprehenderit excepteur laboris ex mollit eu dolor eiusmod nulla consequat eu adipisicing. Minim laborum sunt anim Lorem tempor dolor qui cupidatat consequat. Cillum velit dolor id qui ipsum laboris minim sunt culpa consequat sit commodo duis.\r\n", + "registered": "2017-05-06T10:28:26 -08:00", + "latitude": -12.31491, + "longitude": 129.777213, + "tags": [ + "minim", + "et", + "mollit", + "cillum", + "excepteur", + "Lorem", + "aliquip" + ], + "friends": [ + { + "id": 0, + "name": "Harrington Gomez" + }, + { + "id": 1, + "name": "Wall Reyes" + }, + { + "id": 2, + "name": "Ewing Sweeney" + } + ], + "greeting": "Hello, Sonya Barrera! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839eae21f000d0e4b214", + "index": 124, + "guid": "97adf5f6-9fc1-42ca-bd36-520f5a09bb57", + "isActive": true, + "balance": "$2,563.77", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "brown", + "name": "Lopez Santos", + "gender": "male", + "company": "ZILLACOM", + "email": "lopezsantos@zillacom.com", + "phone": "+1 (969) 555-3080", + "address": "706 Amersfort Place, Slovan, Maryland, 250", + "about": "Quis exercitation irure elit ut sit eu ut ad qui non quis elit. Quis aute tempor eu cupidatat elit nostrud non cillum cupidatat non nostrud ea. Fugiat aliqua tempor enim eiusmod in. Magna eiusmod do non ea exercitation cupidatat tempor est cillum ad laboris incididunt. Nisi aliqua officia id laboris mollit dolore irure in duis commodo officia. Ullamco sit deserunt officia qui minim.\r\n", + "registered": "2018-06-18T12:36:25 -08:00", + "latitude": 38.144975, + "longitude": 174.376286, + "tags": [ + "pariatur", + "consectetur", + "Lorem", + "enim", + "do", + "velit", + "dolor" + ], + "friends": [ + { + "id": 0, + "name": "Kerry Cobb" + }, + { + "id": 1, + "name": "Guzman Becker" + }, + { + "id": 2, + "name": "Lolita Calderon" + } + ], + "greeting": "Hello, Lopez Santos! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e070f11e98e30ed2d", + "index": 125, + "guid": "681e3fad-2b06-4c54-a1bc-a2cdb5a37dd2", + "isActive": false, + "balance": "$2,912.46", + "picture": "http://placehold.it/32x32", + "age": 32, + "eyeColor": "green", + "name": "Maureen Smith", + "gender": "female", + "company": "MICROLUXE", + "email": "maureensmith@microluxe.com", + "phone": "+1 (971) 486-3609", + "address": "116 Estate Road, Wheatfields, Florida, 8721", + "about": "Proident deserunt culpa incididunt qui ex ea exercitation in veniam commodo laborum pariatur do. Magna et reprehenderit dolore occaecat dolore mollit veniam ut magna reprehenderit et. Nisi excepteur amet tempor esse et. Voluptate consectetur laborum elit mollit irure fugiat cupidatat. Culpa pariatur elit id ut ipsum sunt anim ea voluptate amet dolore labore non in. Commodo enim commodo in laborum sunt consequat.\r\n", + "registered": "2016-11-30T10:20:51 -08:00", + "latitude": -51.780424, + "longitude": -95.372511, + "tags": [ + "quis", + "consequat", + "deserunt", + "laboris", + "nulla", + "irure", + "minim" + ], + "friends": [ + { + "id": 0, + "name": "Ila Mccarty" + }, + { + "id": 1, + "name": "Phillips Riley" + }, + { + "id": 2, + "name": "Daisy Stanton" + } + ], + "greeting": "Hello, Maureen Smith! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e9557ac769a79b132", + "index": 126, + "guid": "5370d61b-ab2d-40fa-8d18-597b88a66631", + "isActive": false, + "balance": "$1,418.56", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "green", + "name": "Erickson Frost", + "gender": "male", + "company": "ATGEN", + "email": "ericksonfrost@atgen.com", + "phone": "+1 (882) 459-3205", + "address": "498 Abbey Court, Rose, Marshall Islands, 1689", + "about": "Magna laborum qui ad dolore voluptate dolore deserunt dolor esse adipisicing. Magna in veniam labore qui ipsum occaecat reprehenderit fugiat. Minim veniam labore eu eiusmod reprehenderit mollit nisi anim laboris eiusmod velit minim ea dolor. Pariatur veniam culpa id aliqua in consectetur culpa. Laboris ex nulla mollit cillum veniam fugiat. Laborum nisi nisi incididunt minim veniam in aute cillum velit laboris adipisicing. Deserunt do dolore duis dolore irure cupidatat reprehenderit exercitation commodo qui reprehenderit.\r\n", + "registered": "2016-01-12T11:18:14 -08:00", + "latitude": -1.097325, + "longitude": -130.554485, + "tags": [ + "ipsum", + "elit", + "ut", + "aliqua", + "voluptate", + "dolore", + "tempor" + ], + "friends": [ + { + "id": 0, + "name": "Baldwin Hunt" + }, + { + "id": 1, + "name": "Hall Mendoza" + }, + { + "id": 2, + "name": "Brittany Battle" + } + ], + "greeting": "Hello, Erickson Frost! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e491d6d1f50a600b5", + "index": 127, + "guid": "0a2d526b-aeea-47ce-8869-657bf7b5b8ed", + "isActive": false, + "balance": "$1,626.29", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "brown", + "name": "Liza Ortiz", + "gender": "female", + "company": "NORSUL", + "email": "lizaortiz@norsul.com", + "phone": "+1 (871) 479-2579", + "address": "927 Stryker Court, Goodville, Wyoming, 8774", + "about": "Eu fugiat et occaecat qui quis. Ullamco labore exercitation quis tempor dolore dolor proident Lorem consectetur minim cupidatat anim nulla. Et exercitation exercitation excepteur incididunt proident qui Lorem deserunt. Ullamco veniam id id irure esse exercitation voluptate do quis est dolor mollit non do.\r\n", + "registered": "2016-07-27T07:16:28 -08:00", + "latitude": 23.304554, + "longitude": 64.643313, + "tags": [ + "occaecat", + "ad", + "amet", + "ex", + "nostrud", + "proident", + "cillum" + ], + "friends": [ + { + "id": 0, + "name": "Monique Heath" + }, + { + "id": 1, + "name": "Ingram Potter" + }, + { + "id": 2, + "name": "Campbell Lowe" + } + ], + "greeting": "Hello, Liza Ortiz! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e9cc1335ac38c8c36", + "index": 128, + "guid": "7ab95d37-c920-46bc-905c-42a163919341", + "isActive": true, + "balance": "$3,575.93", + "picture": "http://placehold.it/32x32", + "age": 24, + "eyeColor": "blue", + "name": "Angelica Kane", + "gender": "female", + "company": "PEARLESEX", + "email": "angelicakane@pearlesex.com", + "phone": "+1 (873) 574-2660", + "address": "439 Boulevard Court, Carlos, Illinois, 6033", + "about": "Elit id magna tempor dolor officia in ipsum culpa ipsum. Id enim dolore sit ipsum non commodo minim voluptate voluptate dolor eiusmod sit elit et. Ullamco Lorem in qui qui. Cillum non officia excepteur esse nostrud consequat exercitation. Ut laborum incididunt anim laboris dolor ullamco ipsum exercitation enim.\r\n", + "registered": "2015-07-09T07:40:14 -08:00", + "latitude": -6.416694, + "longitude": 152.973026, + "tags": [ + "labore", + "eiusmod", + "proident", + "fugiat", + "laboris", + "ipsum", + "cupidatat" + ], + "friends": [ + { + "id": 0, + "name": "Snider Mcknight" + }, + { + "id": 1, + "name": "Vonda Dyer" + }, + { + "id": 2, + "name": "Miles Olsen" + } + ], + "greeting": "Hello, Angelica Kane! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e7850350c348c8a10", + "index": 129, + "guid": "6af87582-0060-482c-a739-062ff73557c7", + "isActive": true, + "balance": "$3,204.33", + "picture": "http://placehold.it/32x32", + "age": 32, + "eyeColor": "blue", + "name": "Felecia Crosby", + "gender": "female", + "company": "BICOL", + "email": "feleciacrosby@bicol.com", + "phone": "+1 (882) 554-2689", + "address": "933 Evergreen Avenue, Sultana, South Dakota, 2538", + "about": "Et excepteur exercitation magna ipsum dolore aliqua consequat fugiat officia nulla cillum incididunt velit dolor. Enim enim do non est minim fugiat veniam nisi duis enim nulla amet. Sunt ea veniam mollit incididunt enim. Occaecat adipisicing ex voluptate anim velit. Deserunt officia excepteur duis reprehenderit enim nisi mollit ipsum veniam aliqua sit sunt mollit. Voluptate Lorem et do consectetur.\r\n", + "registered": "2019-03-22T06:27:11 -08:00", + "latitude": -0.891362, + "longitude": -53.82374, + "tags": [ + "sint", + "est", + "adipisicing", + "irure", + "consequat", + "aliquip", + "sint" + ], + "friends": [ + { + "id": 0, + "name": "Twila Hutchinson" + }, + { + "id": 1, + "name": "Owens Deleon" + }, + { + "id": 2, + "name": "Angela Olson" + } + ], + "greeting": "Hello, Felecia Crosby! You have 10 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e93abc5a230f2dea2", + "index": 130, + "guid": "74529ae3-6889-4378-ab68-32b718cdd122", + "isActive": false, + "balance": "$2,036.13", + "picture": "http://placehold.it/32x32", + "age": 32, + "eyeColor": "blue", + "name": "Merle Mcfarland", + "gender": "female", + "company": "LIQUICOM", + "email": "merlemcfarland@liquicom.com", + "phone": "+1 (962) 412-2410", + "address": "634 Kenilworth Place, Richmond, Alaska, 7078", + "about": "Proident amet duis aute dolore labore dolore dolore labore minim labore aliqua consequat do. Minim pariatur eu velit cillum velit laboris cillum. Et culpa qui pariatur Lorem nostrud mollit. Commodo quis amet et occaecat laboris adipisicing veniam officia. Consectetur do sint sint amet magna laborum laborum velit culpa ullamco consequat.\r\n", + "registered": "2016-01-22T10:11:11 -08:00", + "latitude": 88.896272, + "longitude": -26.829954, + "tags": [ + "pariatur", + "deserunt", + "cillum", + "duis", + "sit", + "esse", + "tempor" + ], + "friends": [ + { + "id": 0, + "name": "Maura Carney" + }, + { + "id": 1, + "name": "Sargent Monroe" + }, + { + "id": 2, + "name": "Robyn Wilder" + } + ], + "greeting": "Hello, Merle Mcfarland! You have 10 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e45b45f3e346ed078", + "index": 131, + "guid": "0331d7af-5e08-4852-8c4e-a7b6d6c09d34", + "isActive": false, + "balance": "$2,828.19", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "blue", + "name": "Parks Owens", + "gender": "male", + "company": "ACUSAGE", + "email": "parksowens@acusage.com", + "phone": "+1 (884) 593-2548", + "address": "915 Richards Street, Mahtowa, Arizona, 7661", + "about": "Dolor ad irure fugiat ut dolor fugiat ea velit. Eiusmod incididunt eu sit pariatur pariatur ex labore reprehenderit. Magna anim laboris reprehenderit aliquip pariatur incididunt laboris ad dolor magna quis ea qui reprehenderit. Magna tempor non incididunt anim enim culpa dolore sit amet ex. Sunt voluptate ea esse cillum duis fugiat magna ad deserunt et culpa aliqua aliqua. Officia occaecat est veniam non.\r\n", + "registered": "2016-12-07T01:34:15 -08:00", + "latitude": -58.574277, + "longitude": -155.129304, + "tags": [ + "occaecat", + "mollit", + "quis", + "velit", + "dolore", + "aliquip", + "irure" + ], + "friends": [ + { + "id": 0, + "name": "Pearl Daniel" + }, + { + "id": 1, + "name": "Huber Leblanc" + }, + { + "id": 2, + "name": "Delaney Clayton" + } + ], + "greeting": "Hello, Parks Owens! You have 1 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e5c07bdc293662945", + "index": 132, + "guid": "85c914e4-960a-4334-9ab4-4afb099cad5d", + "isActive": true, + "balance": "$3,339.04", + "picture": "http://placehold.it/32x32", + "age": 32, + "eyeColor": "brown", + "name": "Eva Wood", + "gender": "female", + "company": "ORBAXTER", + "email": "evawood@orbaxter.com", + "phone": "+1 (841) 403-3649", + "address": "988 Fay Court, Lloyd, Louisiana, 9124", + "about": "Lorem eiusmod amet nisi dolor sunt. Nulla nostrud ut veniam elit veniam proident dolor fugiat aliquip. Anim dolore consequat nulla amet pariatur voluptate. Aliquip nostrud esse velit amet eiusmod. Reprehenderit culpa consectetur sit ad esse ad id eiusmod voluptate. Duis nostrud fugiat sunt nulla velit laboris elit amet culpa proident sunt.\r\n", + "registered": "2015-06-19T11:04:23 -08:00", + "latitude": -85.179217, + "longitude": -0.479997, + "tags": [ + "incididunt", + "reprehenderit", + "anim", + "in", + "deserunt", + "nostrud", + "deserunt" + ], + "friends": [ + { + "id": 0, + "name": "Carmen Graham" + }, + { + "id": 1, + "name": "Tina Bell" + }, + { + "id": 2, + "name": "Livingston Lawson" + } + ], + "greeting": "Hello, Eva Wood! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e35f8a01f06dc4d5f", + "index": 133, + "guid": "e4d636aa-3ec1-4580-92fe-bc8b91f8720c", + "isActive": false, + "balance": "$2,607.34", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "green", + "name": "Gale Allen", + "gender": "female", + "company": "DADABASE", + "email": "galeallen@dadabase.com", + "phone": "+1 (825) 477-2558", + "address": "208 Mersereau Court, Homestead, Hawaii, 3142", + "about": "Deserunt elit sint ullamco minim consequat nulla. Elit ea mollit ipsum aliquip amet reprehenderit labore. Eiusmod sunt veniam duis irure consectetur proident exercitation pariatur occaecat cupidatat amet incididunt. Pariatur labore ipsum incididunt labore nisi occaecat qui dolore deserunt exercitation incididunt velit. Minim do mollit esse elit ut irure esse anim.\r\n", + "registered": "2019-09-27T03:08:57 -08:00", + "latitude": -82.450271, + "longitude": -174.428003, + "tags": [ + "anim", + "laboris", + "aliquip", + "qui", + "in", + "labore", + "id" + ], + "friends": [ + { + "id": 0, + "name": "Darcy Mason" + }, + { + "id": 1, + "name": "Shirley Hinton" + }, + { + "id": 2, + "name": "Anastasia England" + } + ], + "greeting": "Hello, Gale Allen! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ee8da394456090908", + "index": 134, + "guid": "777e3bab-084a-4195-b41a-605122ff5963", + "isActive": false, + "balance": "$3,632.83", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "green", + "name": "Michael Hopper", + "gender": "female", + "company": "AFFLUEX", + "email": "michaelhopper@affluex.com", + "phone": "+1 (913) 502-3838", + "address": "492 Beverly Road, Camino, Ohio, 3091", + "about": "Duis consectetur pariatur officia sint ad pariatur irure cupidatat culpa ut exercitation. Officia cillum amet dolor dolor exercitation incididunt anim culpa cupidatat aliqua. Quis incididunt incididunt in aliqua.\r\n", + "registered": "2014-05-04T06:10:54 -08:00", + "latitude": 6.72564, + "longitude": -116.728335, + "tags": [ + "mollit", + "amet", + "aute", + "occaecat", + "quis", + "id", + "et" + ], + "friends": [ + { + "id": 0, + "name": "Theresa Beard" + }, + { + "id": 1, + "name": "Elliott Brady" + }, + { + "id": 2, + "name": "Cain Barrett" + } + ], + "greeting": "Hello, Michael Hopper! You have 4 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ed0b0d474e1bc5af8", + "index": 135, + "guid": "3bf16aa3-dd88-4aee-8c2b-45172d2c0cf4", + "isActive": false, + "balance": "$3,336.27", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "green", + "name": "Gregory Emerson", + "gender": "male", + "company": "QUARMONY", + "email": "gregoryemerson@quarmony.com", + "phone": "+1 (983) 528-2944", + "address": "494 Bayard Street, Boling, Palau, 3481", + "about": "Consectetur labore qui adipisicing dolor. Minim do do pariatur cillum laboris commodo veniam. Elit amet irure adipisicing nulla pariatur sit in occaecat consequat quis et pariatur duis. Officia est aute proident cillum eu eu. Ex ex mollit duis proident elit irure aliqua proident. Sint id mollit cupidatat officia dolor.\r\n", + "registered": "2016-08-15T11:55:28 -08:00", + "latitude": 86.141242, + "longitude": -113.897079, + "tags": [ + "dolore", + "officia", + "sint", + "nisi", + "cupidatat", + "sit", + "ut" + ], + "friends": [ + { + "id": 0, + "name": "Rutledge Carey" + }, + { + "id": 1, + "name": "Ramos Berry" + }, + { + "id": 2, + "name": "Alston Morris" + } + ], + "greeting": "Hello, Gregory Emerson! You have 2 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839efe08135e0de8270a", + "index": 136, + "guid": "85e1b31c-0f95-4c82-89ee-2becc68b94b9", + "isActive": false, + "balance": "$3,574.80", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "green", + "name": "Doris Stein", + "gender": "female", + "company": "MANTRIX", + "email": "dorisstein@mantrix.com", + "phone": "+1 (825) 491-3223", + "address": "301 Seigel Court, Chautauqua, North Dakota, 2390", + "about": "Cillum aute nisi in ea ipsum commodo consectetur. Qui ad aute enim aute culpa dolor do laborum ad adipisicing nulla mollit labore. Nisi commodo deserunt pariatur minim incididunt magna consectetur proident quis. Excepteur adipisicing aliqua anim pariatur occaecat consectetur ad non id. Laboris sit cupidatat in esse proident occaecat aliquip qui nostrud consequat ea commodo nostrud aliqua.\r\n", + "registered": "2019-01-26T06:34:33 -08:00", + "latitude": -49.339302, + "longitude": 42.516079, + "tags": [ + "reprehenderit", + "nulla", + "est", + "aliquip", + "ullamco", + "nisi", + "consectetur" + ], + "friends": [ + { + "id": 0, + "name": "Haney Poole" + }, + { + "id": 1, + "name": "Helene Moon" + }, + { + "id": 2, + "name": "Brandy Buckner" + } + ], + "greeting": "Hello, Doris Stein! You have 5 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e67cd9d4a5c22947e", + "index": 137, + "guid": "e40e2e37-913b-4ab0-849e-b9de5f3c03f2", + "isActive": true, + "balance": "$1,703.59", + "picture": "http://placehold.it/32x32", + "age": 20, + "eyeColor": "blue", + "name": "Jo Norman", + "gender": "female", + "company": "EPLODE", + "email": "jonorman@eplode.com", + "phone": "+1 (980) 468-2711", + "address": "841 Senator Street, Idamay, California, 5514", + "about": "Duis minim esse ea ullamco magna dolor mollit eiusmod. Deserunt laborum aliquip nisi ipsum anim incididunt aliquip pariatur consectetur Lorem. Ad commodo ullamco mollit amet excepteur amet. Aute labore aute incididunt consequat eiusmod est dolor aliquip sunt culpa officia in. Amet irure ea nisi nisi aliqua dolor eiusmod sit qui aliquip id culpa officia. Dolore cupidatat amet elit est proident adipisicing mollit et adipisicing adipisicing ut officia voluptate.\r\n", + "registered": "2018-10-14T03:09:46 -08:00", + "latitude": 73.346376, + "longitude": 94.982853, + "tags": [ + "aute", + "ex", + "magna", + "minim", + "dolor", + "cillum", + "tempor" + ], + "friends": [ + { + "id": 0, + "name": "Maynard Osborne" + }, + { + "id": 1, + "name": "Kinney Guerra" + }, + { + "id": 2, + "name": "Odonnell Wheeler" + } + ], + "greeting": "Hello, Jo Norman! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ea60aafbf45aa0d13", + "index": 138, + "guid": "2e41e544-83b1-4cd1-a69c-8c156af7b4e8", + "isActive": true, + "balance": "$1,445.30", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "green", + "name": "Flora Lamb", + "gender": "female", + "company": "BLEEKO", + "email": "floralamb@bleeko.com", + "phone": "+1 (971) 424-2455", + "address": "730 Duryea Place, Edmund, Nevada, 1191", + "about": "Ex elit Lorem aute officia ipsum occaecat labore non proident. Pariatur Lorem occaecat voluptate in irure consectetur occaecat eu do adipisicing tempor in. Proident proident labore ex id excepteur. Do excepteur cillum ut ipsum deserunt aute ad. Nulla ea enim reprehenderit et qui. Cillum sit consectetur duis proident sit nisi.\r\n", + "registered": "2017-12-31T07:20:18 -08:00", + "latitude": -46.003122, + "longitude": -125.47843, + "tags": [ + "duis", + "in", + "consectetur", + "aliqua", + "ipsum", + "commodo", + "veniam" + ], + "friends": [ + { + "id": 0, + "name": "Gladys Wynn" + }, + { + "id": 1, + "name": "Claudette Blevins" + }, + { + "id": 2, + "name": "Kasey Maxwell" + } + ], + "greeting": "Hello, Flora Lamb! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839edcacd2a8ccc9c647", + "index": 139, + "guid": "fde7983e-0720-4009-a136-390e8f4597b5", + "isActive": false, + "balance": "$3,882.28", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "green", + "name": "Vazquez Suarez", + "gender": "male", + "company": "NUTRALAB", + "email": "vazquezsuarez@nutralab.com", + "phone": "+1 (954) 550-2535", + "address": "441 Throop Avenue, Cedarville, Washington, 2730", + "about": "Est veniam ipsum officia ullamco amet id minim consectetur aute dolore veniam aliqua excepteur. Amet sunt voluptate tempor amet quis ullamco ipsum ullamco officia. Culpa eu occaecat dolore ullamco ad consequat laboris aliqua dolore ad in anim. Adipisicing aliqua velit amet eu magna magna ut adipisicing quis sunt culpa eu proident irure.\r\n", + "registered": "2017-02-06T07:03:51 -08:00", + "latitude": -37.997756, + "longitude": 25.536192, + "tags": [ + "quis", + "labore", + "qui", + "occaecat", + "laboris", + "magna", + "adipisicing" + ], + "friends": [ + { + "id": 0, + "name": "Helen Maynard" + }, + { + "id": 1, + "name": "Vincent Garner" + }, + { + "id": 2, + "name": "Beasley Harding" + } + ], + "greeting": "Hello, Vazquez Suarez! You have 10 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ee7926bf058fa0a22", + "index": 140, + "guid": "cd096569-7909-4519-9d5b-e951838ba306", + "isActive": true, + "balance": "$2,655.98", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "blue", + "name": "Garrett Terrell", + "gender": "male", + "company": "PEARLESSA", + "email": "garrettterrell@pearlessa.com", + "phone": "+1 (810) 578-3379", + "address": "480 Sackman Street, Wilsonia, Arkansas, 9517", + "about": "Eiusmod velit in deserunt cupidatat elit dolor proident ut commodo quis consectetur ad ut. Commodo nulla fugiat consectetur dolore mollit ut pariatur cillum nostrud proident nostrud eiusmod exercitation amet. Ad non aliqua laboris nostrud cupidatat ullamco deserunt esse pariatur dolore tempor. Aute et nostrud voluptate laboris adipisicing eu id sit est est. Elit anim velit ea velit exercitation ex veniam eu occaecat sit.\r\n", + "registered": "2016-08-01T06:45:56 -08:00", + "latitude": 22.803716, + "longitude": 119.557518, + "tags": [ + "Lorem", + "ea", + "qui", + "consequat", + "amet", + "minim", + "excepteur" + ], + "friends": [ + { + "id": 0, + "name": "Montoya Castro" + }, + { + "id": 1, + "name": "Pope Vinson" + }, + { + "id": 2, + "name": "Georgette Hancock" + } + ], + "greeting": "Hello, Garrett Terrell! You have 9 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ef2748dcdc8ae87c6", + "index": 141, + "guid": "eeb1fa76-7f69-4e36-872e-da3649ba3ac0", + "isActive": true, + "balance": "$3,257.62", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "brown", + "name": "Harrison Cooper", + "gender": "male", + "company": "SNOWPOKE", + "email": "harrisoncooper@snowpoke.com", + "phone": "+1 (816) 403-2410", + "address": "501 McKibben Street, Stouchsburg, Wisconsin, 3417", + "about": "Dolor est quis voluptate ad veniam reprehenderit aute ullamco esse aute incididunt magna sunt. Pariatur magna quis sunt eiusmod est exercitation laboris anim minim ullamco excepteur laboris consectetur cillum. Reprehenderit elit laborum velit est commodo. Minim dolor minim aliquip sint do veniam in excepteur id laboris culpa pariatur. Ipsum proident consectetur et voluptate quis sint dolor eiusmod fugiat esse commodo quis proident. Irure qui mollit eu amet est dolore aliquip nulla irure labore proident duis excepteur. Nulla cupidatat culpa ullamco consequat deserunt tempor.\r\n", + "registered": "2016-12-14T05:51:50 -08:00", + "latitude": 64.677014, + "longitude": 84.146953, + "tags": [ + "est", + "ad", + "ex", + "fugiat", + "mollit", + "et", + "voluptate" + ], + "friends": [ + { + "id": 0, + "name": "Janna Tyler" + }, + { + "id": 1, + "name": "Jean Randolph" + }, + { + "id": 2, + "name": "Macias Mccray" + } + ], + "greeting": "Hello, Harrison Cooper! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ec803a398364f9c37", + "index": 142, + "guid": "92c28d8a-5c69-4235-a28e-81fada1daa45", + "isActive": true, + "balance": "$3,836.36", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "blue", + "name": "Ball Savage", + "gender": "male", + "company": "CONFERIA", + "email": "ballsavage@conferia.com", + "phone": "+1 (873) 567-3808", + "address": "176 Prince Street, Tilleda, West Virginia, 2913", + "about": "Enim sint est cupidatat cillum commodo velit occaecat pariatur. Consectetur et in eu deserunt officia do cupidatat ea ut laborum officia ad irure. Nulla veniam dolor adipisicing nostrud duis enim velit aliqua laboris consectetur exercitation duis Lorem dolor. Et cillum quis veniam culpa. Velit nisi adipisicing nisi veniam. Laboris excepteur ex consectetur tempor magna exercitation deserunt sint duis dolore do fugiat Lorem.\r\n", + "registered": "2018-07-11T04:50:20 -08:00", + "latitude": -41.941974, + "longitude": -70.162739, + "tags": [ + "officia", + "est", + "quis", + "eu", + "commodo", + "duis", + "duis" + ], + "friends": [ + { + "id": 0, + "name": "Ayala Lang" + }, + { + "id": 1, + "name": "Constance Mcdowell" + }, + { + "id": 2, + "name": "Katy Strong" + } + ], + "greeting": "Hello, Ball Savage! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e78602e3ddfbadbc9", + "index": 143, + "guid": "e3737f77-6514-409f-a592-8a5d1d544c4b", + "isActive": true, + "balance": "$1,205.09", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "green", + "name": "Aguirre French", + "gender": "male", + "company": "EBIDCO", + "email": "aguirrefrench@ebidco.com", + "phone": "+1 (800) 590-3826", + "address": "487 Arkansas Drive, Faxon, Mississippi, 6075", + "about": "Velit non laborum irure excepteur in officia. Aute amet nulla cupidatat Lorem reprehenderit eu proident. Nostrud excepteur irure quis id ea.\r\n", + "registered": "2014-08-29T02:12:43 -08:00", + "latitude": 36.105943, + "longitude": -107.918805, + "tags": [ + "dolor", + "eiusmod", + "cupidatat", + "id", + "occaecat", + "incididunt", + "non" + ], + "friends": [ + { + "id": 0, + "name": "Velez Oliver" + }, + { + "id": 1, + "name": "Howe White" + }, + { + "id": 2, + "name": "Mccormick Cameron" + } + ], + "greeting": "Hello, Aguirre French! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ecaeb3e8cb7d69a46", + "index": 144, + "guid": "d421580c-fe1f-4234-b038-3f278f3d86e8", + "isActive": false, + "balance": "$1,522.65", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "blue", + "name": "Benson Garcia", + "gender": "male", + "company": "TALKOLA", + "email": "bensongarcia@talkola.com", + "phone": "+1 (905) 439-2130", + "address": "392 Oriental Court, Cumberland, American Samoa, 3092", + "about": "Reprehenderit eu aliqua do irure non et id pariatur magna laborum aute mollit ipsum. Esse elit qui reprehenderit sint voluptate. Aliquip consequat consectetur incididunt esse excepteur deserunt veniam ullamco irure nostrud fugiat consectetur. Culpa amet velit aliquip exercitation exercitation reprehenderit laboris excepteur et. Do quis anim cillum minim aliqua laboris excepteur minim exercitation eiusmod.\r\n", + "registered": "2018-04-20T03:32:54 -08:00", + "latitude": 84.21937, + "longitude": 126.254952, + "tags": [ + "minim", + "excepteur", + "Lorem", + "sint", + "sit", + "ea", + "esse" + ], + "friends": [ + { + "id": 0, + "name": "Joyce Mcgee" + }, + { + "id": 1, + "name": "Iva Guerrero" + }, + { + "id": 2, + "name": "Gay Adams" + } + ], + "greeting": "Hello, Benson Garcia! You have 3 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e8fe592b0be85745b", + "index": 145, + "guid": "22a4999b-8cc1-409b-b51b-f0746d90880b", + "isActive": true, + "balance": "$1,860.74", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "green", + "name": "Ethel Dudley", + "gender": "female", + "company": "NEBULEAN", + "email": "etheldudley@nebulean.com", + "phone": "+1 (803) 430-2939", + "address": "354 Delevan Street, Macdona, New Mexico, 4657", + "about": "Est voluptate voluptate deserunt sint consequat nulla dolore enim amet ex velit labore. Pariatur nostrud occaecat cillum in officia voluptate proident deserunt aliqua laborum ullamco et. Fugiat anim ullamco consectetur proident do excepteur commodo aliqua ullamco. Mollit exercitation officia aliquip aliquip occaecat laborum occaecat in qui culpa proident irure. Voluptate adipisicing amet occaecat ullamco excepteur sit ipsum et eu in.\r\n", + "registered": "2016-02-01T02:29:14 -08:00", + "latitude": -47.959725, + "longitude": -37.50123, + "tags": [ + "consectetur", + "duis", + "ea", + "duis", + "eiusmod", + "pariatur", + "ullamco" + ], + "friends": [ + { + "id": 0, + "name": "Scott Barron" + }, + { + "id": 1, + "name": "Fitzgerald Moreno" + }, + { + "id": 2, + "name": "Harrell Gay" + } + ], + "greeting": "Hello, Ethel Dudley! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839eb4b5c3470b9b2830", + "index": 146, + "guid": "5472501c-2d93-4714-9a1e-696ff3ff920d", + "isActive": true, + "balance": "$3,322.56", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "brown", + "name": "Rene Jefferson", + "gender": "female", + "company": "EARTHPURE", + "email": "renejefferson@earthpure.com", + "phone": "+1 (937) 523-3297", + "address": "215 Mermaid Avenue, Martell, Massachusetts, 5509", + "about": "Quis cillum velit veniam occaecat laboris officia irure ipsum consectetur est pariatur irure minim. Excepteur laborum exercitation amet tempor incididunt ea officia officia consequat qui exercitation consequat eu consequat. Exercitation non nostrud sint esse ipsum minim fugiat cupidatat proident nulla veniam excepteur sint. Laboris aliqua fugiat ea laborum. Dolor tempor do in nulla. Excepteur ut nulla ad eu.\r\n", + "registered": "2014-08-16T06:54:11 -08:00", + "latitude": -31.153899, + "longitude": 43.77609, + "tags": [ + "officia", + "ex", + "ipsum", + "sunt", + "amet", + "in", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "French Kinney" + }, + { + "id": 1, + "name": "Calderon Gilliam" + }, + { + "id": 2, + "name": "Murray Owen" + } + ], + "greeting": "Hello, Rene Jefferson! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e46a51c2011ae1438", + "index": 147, + "guid": "58f67043-7a40-49e5-8604-5b6b54f86372", + "isActive": true, + "balance": "$1,591.57", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "brown", + "name": "Nora Williamson", + "gender": "female", + "company": "FUELWORKS", + "email": "norawilliamson@fuelworks.com", + "phone": "+1 (909) 599-3698", + "address": "643 Willow Place, Gwynn, Colorado, 1042", + "about": "Occaecat nulla ullamco Lorem sint sint consequat labore. Ipsum ullamco qui ex adipisicing qui qui et officia veniam pariatur voluptate nisi. Minim pariatur quis ex nulla fugiat velit occaecat enim.\r\n", + "registered": "2017-02-12T05:00:34 -08:00", + "latitude": 43.489196, + "longitude": -101.071301, + "tags": [ + "commodo", + "velit", + "Lorem", + "nisi", + "anim", + "aliquip", + "non" + ], + "friends": [ + { + "id": 0, + "name": "Stanton Bryan" + }, + { + "id": 1, + "name": "Chris Glass" + }, + { + "id": 2, + "name": "Wolf Perkins" + } + ], + "greeting": "Hello, Nora Williamson! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eec6ff2692624bf57", + "index": 148, + "guid": "5b161815-76be-4949-a232-b890153f5300", + "isActive": false, + "balance": "$1,113.95", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "green", + "name": "Russell Willis", + "gender": "male", + "company": "FOSSIEL", + "email": "russellwillis@fossiel.com", + "phone": "+1 (992) 478-2836", + "address": "937 Ryder Avenue, Adamstown, District Of Columbia, 4253", + "about": "Ea elit proident tempor minim anim deserunt occaecat. Ullamco aliqua aliqua proident id magna dolor dolore in deserunt. Sit consequat deserunt mollit aliqua sint incididunt aliquip dolor anim do enim. Eiusmod officia dolore occaecat excepteur ipsum duis et consequat id veniam culpa. Sit ipsum est commodo amet non nostrud. Esse id labore sit in est aliquip voluptate.\r\n", + "registered": "2018-09-29T07:11:19 -08:00", + "latitude": 17.514946, + "longitude": -88.609155, + "tags": [ + "laborum", + "velit", + "tempor", + "amet", + "ullamco", + "non", + "sunt" + ], + "friends": [ + { + "id": 0, + "name": "Coleen Hyde" + }, + { + "id": 1, + "name": "Callie Vaughn" + }, + { + "id": 2, + "name": "Patrice Hartman" + } + ], + "greeting": "Hello, Russell Willis! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e7b9d5478c7539b02", + "index": 149, + "guid": "fd6fa7b9-3e2b-488f-860b-a26dfa4bfbec", + "isActive": true, + "balance": "$2,342.77", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "green", + "name": "Terrell Warren", + "gender": "male", + "company": "BIOTICA", + "email": "terrellwarren@biotica.com", + "phone": "+1 (976) 434-3453", + "address": "482 Hope Street, Leyner, Pennsylvania, 9008", + "about": "Velit et incididunt minim voluptate dolor laboris tempor ipsum. Elit irure dolor exercitation ullamco magna duis do tempor. Elit exercitation amet esse pariatur nulla dolore ex. Laborum duis amet ut exercitation tempor mollit pariatur.\r\n", + "registered": "2016-02-13T08:08:48 -08:00", + "latitude": 85.221986, + "longitude": 144.709952, + "tags": [ + "nostrud", + "culpa", + "proident", + "anim", + "occaecat", + "minim", + "nulla" + ], + "friends": [ + { + "id": 0, + "name": "Zimmerman Baird" + }, + { + "id": 1, + "name": "Anna Townsend" + }, + { + "id": 2, + "name": "Dickerson Whitehead" + } + ], + "greeting": "Hello, Terrell Warren! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e404bbd6079c9d4b0", + "index": 150, + "guid": "b0229f1b-0802-4165-9c4b-a71f09e209e2", + "isActive": false, + "balance": "$1,985.65", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "green", + "name": "Leonard Rodgers", + "gender": "male", + "company": "PROWASTE", + "email": "leonardrodgers@prowaste.com", + "phone": "+1 (806) 576-2513", + "address": "764 Himrod Street, Veyo, Montana, 1791", + "about": "Ex eu adipisicing quis voluptate. Duis pariatur voluptate consectetur aute. Amet esse duis nisi irure sint incididunt eiusmod labore magna deserunt Lorem ex. Ullamco adipisicing eiusmod irure nostrud amet deserunt adipisicing adipisicing sunt nostrud occaecat ea aliqua. Veniam incididunt tempor reprehenderit esse velit reprehenderit velit voluptate amet dolore elit. Ea aute ex irure ullamco eu sunt do aliqua sunt laboris dolor dolor anim. Reprehenderit pariatur consequat nulla et consequat.\r\n", + "registered": "2016-08-08T12:33:32 -08:00", + "latitude": -17.415602, + "longitude": 6.013364, + "tags": [ + "tempor", + "cillum", + "ad", + "adipisicing", + "labore", + "ipsum", + "mollit" + ], + "friends": [ + { + "id": 0, + "name": "Nguyen Finley" + }, + { + "id": 1, + "name": "Trevino Wade" + }, + { + "id": 2, + "name": "Mona Jennings" + } + ], + "greeting": "Hello, Leonard Rodgers! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e13e35f6dcdbaa720", + "index": 151, + "guid": "e2667cd1-c7a0-4a25-b86f-2fda2cd0ab2c", + "isActive": true, + "balance": "$2,167.76", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "blue", + "name": "Horne Stevenson", + "gender": "male", + "company": "TROPOLI", + "email": "hornestevenson@tropoli.com", + "phone": "+1 (899) 597-2508", + "address": "276 Ferris Street, Townsend, Northern Mariana Islands, 9251", + "about": "Officia aute irure occaecat ipsum Lorem officia mollit sit eiusmod labore. Sint reprehenderit ex id aliquip mollit enim Lorem proident. Officia et amet excepteur aliquip dolore dolore culpa velit. Exercitation ex nulla cillum magna magna quis est. Ad eu voluptate quis voluptate. Ex in eu non commodo aute consectetur est consectetur. Velit culpa aliqua deserunt Lorem occaecat voluptate do.\r\n", + "registered": "2018-05-23T04:58:05 -08:00", + "latitude": 27.360444, + "longitude": -34.046011, + "tags": [ + "est", + "do", + "magna", + "eiusmod", + "voluptate", + "minim", + "cupidatat" + ], + "friends": [ + { + "id": 0, + "name": "Griffith Franco" + }, + { + "id": 1, + "name": "Bradford Talley" + }, + { + "id": 2, + "name": "Nadia Baker" + } + ], + "greeting": "Hello, Horne Stevenson! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e97eb417df3aff3ec", + "index": 152, + "guid": "8be9c45a-b584-46ad-b00e-26ecad464f95", + "isActive": true, + "balance": "$3,435.54", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "green", + "name": "Josephine Wilkinson", + "gender": "female", + "company": "GINKOGENE", + "email": "josephinewilkinson@ginkogene.com", + "phone": "+1 (957) 451-2394", + "address": "384 Williams Court, Robinette, Missouri, 4314", + "about": "Adipisicing veniam exercitation nulla ex reprehenderit sit do ad occaecat pariatur occaecat eu. Occaecat irure deserunt eiusmod elit et eu nisi laboris magna mollit. Cupidatat id eu aliquip nostrud enim veniam occaecat cupidatat est laborum est anim.\r\n", + "registered": "2019-02-01T09:26:17 -08:00", + "latitude": 67.160306, + "longitude": 36.3973, + "tags": [ + "sint", + "nulla", + "enim", + "nostrud", + "anim", + "aliquip", + "est" + ], + "friends": [ + { + "id": 0, + "name": "Lottie Schwartz" + }, + { + "id": 1, + "name": "Dillon Whitaker" + }, + { + "id": 2, + "name": "Kathie Miller" + } + ], + "greeting": "Hello, Josephine Wilkinson! You have 7 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ebc7055b40fcf85a4", + "index": 153, + "guid": "8ebbaed9-7f11-48c8-aa77-462ff8662419", + "isActive": true, + "balance": "$3,846.25", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "green", + "name": "Elvia Pate", + "gender": "female", + "company": "HANDSHAKE", + "email": "elviapate@handshake.com", + "phone": "+1 (980) 503-2099", + "address": "973 Hunts Lane, Vienna, Kansas, 5500", + "about": "Aliquip quis occaecat exercitation mollit est occaecat ipsum labore et occaecat ex cillum culpa ex. Fugiat et ut aliqua reprehenderit duis. Velit sunt mollit eu nostrud aliquip ea sit ex culpa id labore est qui. Cillum consequat ex non velit et aliquip laboris veniam incididunt. Labore eu ad ad ad amet. Deserunt nostrud fugiat adipisicing anim culpa nulla veniam deserunt cillum non occaecat. Aliquip sint amet adipisicing ut consectetur voluptate non.\r\n", + "registered": "2014-08-26T08:53:56 -08:00", + "latitude": -51.453941, + "longitude": -1.056605, + "tags": [ + "ut", + "aute", + "Lorem", + "ad", + "veniam", + "labore", + "in" + ], + "friends": [ + { + "id": 0, + "name": "Best Crane" + }, + { + "id": 1, + "name": "Vang Mclean" + }, + { + "id": 2, + "name": "Kay Kirkland" + } + ], + "greeting": "Hello, Elvia Pate! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ed3923c8cb41df0b5", + "index": 154, + "guid": "d036fa79-26a1-495c-8762-cb65de8f64fd", + "isActive": true, + "balance": "$3,046.81", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "green", + "name": "Ayers Booker", + "gender": "male", + "company": "KYAGORO", + "email": "ayersbooker@kyagoro.com", + "phone": "+1 (836) 486-3336", + "address": "614 Amity Street, Condon, Georgia, 4117", + "about": "Deserunt anim qui in sunt laboris officia duis labore in adipisicing exercitation non qui dolor. Id exercitation ex irure consectetur sunt. Eu duis velit non labore ea in do occaecat ullamco ea. Officia velit consectetur veniam non sunt nulla veniam enim velit in occaecat exercitation.\r\n", + "registered": "2018-12-30T05:11:46 -08:00", + "latitude": -73.657008, + "longitude": -84.547069, + "tags": [ + "Lorem", + "qui", + "labore", + "ullamco", + "enim", + "et", + "consequat" + ], + "friends": [ + { + "id": 0, + "name": "Madden Phillips" + }, + { + "id": 1, + "name": "Mosley Baldwin" + }, + { + "id": 2, + "name": "Duffy Day" + } + ], + "greeting": "Hello, Ayers Booker! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e2ac5b5e631a16bcf", + "index": 155, + "guid": "75ce5ff1-f267-43ec-bda1-bca036402bf1", + "isActive": true, + "balance": "$1,823.84", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "blue", + "name": "Beatrice Hensley", + "gender": "female", + "company": "ZOLAREX", + "email": "beatricehensley@zolarex.com", + "phone": "+1 (860) 423-3917", + "address": "896 Albee Square, Day, New Jersey, 6063", + "about": "Amet magna labore ex excepteur aliqua pariatur nulla consequat velit magna proident mollit esse proident. Deserunt Lorem elit occaecat sit. Reprehenderit laboris voluptate do excepteur id aliqua amet ex do laborum minim exercitation ex.\r\n", + "registered": "2019-02-17T07:25:47 -08:00", + "latitude": -63.245426, + "longitude": 24.107627, + "tags": [ + "ad", + "mollit", + "tempor", + "eiusmod", + "irure", + "amet", + "velit" + ], + "friends": [ + { + "id": 0, + "name": "Cherry Forbes" + }, + { + "id": 1, + "name": "Daniel Short" + }, + { + "id": 2, + "name": "David Myers" + } + ], + "greeting": "Hello, Beatrice Hensley! You have 1 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e53300af14da43c5f", + "index": 156, + "guid": "8725309a-bc28-4c06-a99c-db9ba51109d0", + "isActive": true, + "balance": "$1,324.68", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "green", + "name": "Katie Donaldson", + "gender": "female", + "company": "BOILCAT", + "email": "katiedonaldson@boilcat.com", + "phone": "+1 (997) 511-3166", + "address": "412 Rodney Street, Sattley, Oklahoma, 6937", + "about": "Nisi ad occaecat laboris id. Pariatur quis cillum est quis. Pariatur non ex eu velit deserunt fugiat eu pariatur. Velit qui deserunt et id amet culpa magna commodo elit elit magna commodo. Ad esse culpa sint magna do laborum ad labore adipisicing consectetur ea ut.\r\n", + "registered": "2016-03-16T03:55:22 -08:00", + "latitude": 2.403949, + "longitude": -56.46773, + "tags": [ + "consequat", + "fugiat", + "minim", + "pariatur", + "proident", + "id", + "elit" + ], + "friends": [ + { + "id": 0, + "name": "Juana Clemons" + }, + { + "id": 1, + "name": "Lenore Mcleod" + }, + { + "id": 2, + "name": "Gaines Arnold" + } + ], + "greeting": "Hello, Katie Donaldson! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e88cdc1d13d84e881", + "index": 157, + "guid": "aa292658-bcd9-4c1e-a7e0-afa7a0529957", + "isActive": true, + "balance": "$2,830.32", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "blue", + "name": "Ruby Ortega", + "gender": "female", + "company": "MARVANE", + "email": "rubyortega@marvane.com", + "phone": "+1 (955) 462-2344", + "address": "159 Bowery Street, Limestone, North Carolina, 4715", + "about": "Adipisicing ut velit nulla cupidatat magna deserunt sunt. Est nisi id amet deserunt et. Ipsum voluptate consectetur est in. Et fugiat in velit fugiat dolor veniam aliquip fugiat tempor consectetur quis dolore in. Laborum ipsum enim commodo Lorem duis adipisicing. Qui enim dolor consectetur ut commodo reprehenderit deserunt consequat excepteur tempor do non enim.\r\n", + "registered": "2015-09-16T06:38:59 -08:00", + "latitude": -67.618039, + "longitude": 154.97412, + "tags": [ + "irure", + "voluptate", + "enim", + "mollit", + "labore", + "reprehenderit", + "incididunt" + ], + "friends": [ + { + "id": 0, + "name": "Robinson Rivera" + }, + { + "id": 1, + "name": "Christian Shaw" + }, + { + "id": 2, + "name": "Phelps Shaffer" + } + ], + "greeting": "Hello, Ruby Ortega! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e6e6488c6881e7d4a", + "index": 158, + "guid": "e9317294-709d-4795-8644-1bc39a01e343", + "isActive": true, + "balance": "$3,343.00", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "brown", + "name": "Kris Mooney", + "gender": "female", + "company": "GOLISTIC", + "email": "krismooney@golistic.com", + "phone": "+1 (824) 556-3583", + "address": "972 Rockaway Parkway, Morriston, Rhode Island, 8931", + "about": "Officia eu ullamco ipsum et eu tempor irure ipsum excepteur amet est cupidatat. Minim duis exercitation ipsum quis culpa dolor. Nulla cupidatat ex ut minim commodo sint ad non tempor aliqua. Enim elit consequat Lorem Lorem in exercitation. Tempor ex esse esse et labore esse velit culpa aliquip nisi pariatur consequat ad sint.\r\n", + "registered": "2015-09-11T06:04:28 -08:00", + "latitude": 8.398341, + "longitude": -7.288626, + "tags": [ + "irure", + "ipsum", + "do", + "sunt", + "cillum", + "ullamco", + "sit" + ], + "friends": [ + { + "id": 0, + "name": "Eula Larson" + }, + { + "id": 1, + "name": "Alexandra Avila" + }, + { + "id": 2, + "name": "Tyler Sellers" + } + ], + "greeting": "Hello, Kris Mooney! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e782c17119a444ab8", + "index": 159, + "guid": "f273dcc5-9d83-4e25-8299-d7112f1c1753", + "isActive": true, + "balance": "$1,127.39", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "brown", + "name": "Ola Woodward", + "gender": "female", + "company": "WARETEL", + "email": "olawoodward@waretel.com", + "phone": "+1 (924) 415-3769", + "address": "577 Tompkins Place, Grahamtown, Maine, 6023", + "about": "Proident dolor voluptate non dolore nostrud Lorem. Duis quis reprehenderit ad et aute commodo. Excepteur cillum nostrud cillum cupidatat fugiat incididunt. Sit est esse culpa sunt dolor ullamco mollit magna. Esse ut deserunt ex ullamco adipisicing qui mollit do reprehenderit non aliquip eu. Pariatur est in et tempor eu labore.\r\n", + "registered": "2016-09-30T01:06:26 -08:00", + "latitude": 64.688514, + "longitude": 52.889216, + "tags": [ + "nulla", + "excepteur", + "non", + "id", + "laboris", + "amet", + "exercitation" + ], + "friends": [ + { + "id": 0, + "name": "Pearlie Garrison" + }, + { + "id": 1, + "name": "Madelyn Diaz" + }, + { + "id": 2, + "name": "Hunt Franklin" + } + ], + "greeting": "Hello, Ola Woodward! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e9445c1dc0951c045", + "index": 160, + "guid": "abc0b91f-fbd4-46e2-a395-d7c25a51e3ef", + "isActive": false, + "balance": "$2,427.56", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "blue", + "name": "Blackwell Wise", + "gender": "male", + "company": "ECOLIGHT", + "email": "blackwellwise@ecolight.com", + "phone": "+1 (868) 434-2878", + "address": "131 Ridge Boulevard, Saranap, Connecticut, 7860", + "about": "Ut eu elit sit nulla ea voluptate veniam dolor duis laborum. Culpa excepteur anim sit ullamco laborum reprehenderit. Sint cupidatat elit commodo duis excepteur aute aliqua anim dolore velit. Ullamco quis ipsum minim nisi sit labore voluptate mollit ad est ex proident excepteur ex.\r\n", + "registered": "2016-07-26T07:56:46 -08:00", + "latitude": 20.938881, + "longitude": 46.628178, + "tags": [ + "sunt", + "consequat", + "enim", + "minim", + "cillum", + "officia", + "mollit" + ], + "friends": [ + { + "id": 0, + "name": "Caldwell Puckett" + }, + { + "id": 1, + "name": "Heath Ayers" + }, + { + "id": 2, + "name": "Natalia Trujillo" + } + ], + "greeting": "Hello, Blackwell Wise! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ededd3c3325bd5bac", + "index": 161, + "guid": "c882ed86-c587-4b62-a5da-08d82e0c287b", + "isActive": true, + "balance": "$3,440.02", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "blue", + "name": "Stanley Pruitt", + "gender": "male", + "company": "QUARX", + "email": "stanleypruitt@quarx.com", + "phone": "+1 (919) 544-3210", + "address": "953 Knight Court, Rockingham, Kentucky, 7412", + "about": "Laborum eiusmod quis elit ex aute ex. Ipsum sint in pariatur adipisicing fugiat adipisicing irure fugiat sunt. Quis exercitation pariatur tempor amet incididunt. Ea sint culpa nulla ut qui amet.\r\n", + "registered": "2016-10-17T09:45:56 -08:00", + "latitude": 57.189153, + "longitude": 90.810688, + "tags": [ + "non", + "dolor", + "incididunt", + "consequat", + "occaecat", + "aute", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "Lilian Cline" + }, + { + "id": 1, + "name": "Charlene Grimes" + }, + { + "id": 2, + "name": "Linda Santana" + } + ], + "greeting": "Hello, Stanley Pruitt! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e9b1806224dc0cc90", + "index": 162, + "guid": "8798dd4f-9752-4453-a241-79636b1d89b7", + "isActive": false, + "balance": "$2,221.50", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "brown", + "name": "Briana Robles", + "gender": "female", + "company": "JUNIPOOR", + "email": "brianarobles@junipoor.com", + "phone": "+1 (906) 401-2741", + "address": "594 Lafayette Avenue, Coloma, Puerto Rico, 6471", + "about": "Enim consequat anim ut enim sint do incididunt excepteur. Veniam voluptate ullamco est proident dolor consequat magna ea occaecat cillum dolore eu. Labore minim quis aliquip magna irure velit consequat reprehenderit nulla consectetur non. Officia mollit do pariatur anim do. Laborum incididunt aliqua velit aliquip id sint non do laborum. Ea anim commodo voluptate irure.\r\n", + "registered": "2015-06-23T12:22:28 -08:00", + "latitude": -45.64874, + "longitude": -97.173935, + "tags": [ + "culpa", + "laboris", + "id", + "culpa", + "consequat", + "occaecat", + "consequat" + ], + "friends": [ + { + "id": 0, + "name": "Lee Marquez" + }, + { + "id": 1, + "name": "Marquita Fulton" + }, + { + "id": 2, + "name": "Weber Alston" + } + ], + "greeting": "Hello, Briana Robles! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e071c99bd628942d5", + "index": 163, + "guid": "dabb1eea-4701-4567-b0a7-fcc32f02f3ea", + "isActive": false, + "balance": "$2,330.29", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "blue", + "name": "Case Head", + "gender": "male", + "company": "KIGGLE", + "email": "casehead@kiggle.com", + "phone": "+1 (904) 498-3184", + "address": "130 Holly Street, Greenbush, Indiana, 5159", + "about": "Cillum aliqua voluptate enim velit et culpa non velit. Sint pariatur ipsum nostrud pariatur voluptate do excepteur ullamco do sit duis consequat laboris aliquip. Aliquip ipsum culpa fugiat laborum esse cillum in amet et pariatur dolor officia.\r\n", + "registered": "2017-02-07T02:03:13 -08:00", + "latitude": -58.944411, + "longitude": 17.080141, + "tags": [ + "laboris", + "anim", + "Lorem", + "sint", + "exercitation", + "magna", + "minim" + ], + "friends": [ + { + "id": 0, + "name": "Horton Carlson" + }, + { + "id": 1, + "name": "Althea Drake" + }, + { + "id": 2, + "name": "Audrey Ramos" + } + ], + "greeting": "Hello, Case Head! You have 1 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ee1c29bd02c7de339", + "index": 164, + "guid": "c15cd024-53ac-4b51-a4cf-4108c510ec2c", + "isActive": false, + "balance": "$3,401.32", + "picture": "http://placehold.it/32x32", + "age": 24, + "eyeColor": "blue", + "name": "Pennington Fitzgerald", + "gender": "male", + "company": "AMRIL", + "email": "penningtonfitzgerald@amril.com", + "phone": "+1 (976) 548-2322", + "address": "853 Lenox Road, Weeksville, Idaho, 9933", + "about": "Et qui duis ex quis quis. Minim magna irure anim enim consectetur aute sit. Eiusmod tempor ullamco anim qui. Reprehenderit nostrud velit eu deserunt. Cillum proident aliqua pariatur exercitation proident do. Dolor dolore eu mollit exercitation. Exercitation esse nostrud incididunt ea sit et consectetur reprehenderit proident.\r\n", + "registered": "2018-01-18T03:50:34 -08:00", + "latitude": 48.75045, + "longitude": -62.164755, + "tags": [ + "culpa", + "ipsum", + "est", + "aute", + "nostrud", + "sunt", + "eiusmod" + ], + "friends": [ + { + "id": 0, + "name": "Sharron Pickett" + }, + { + "id": 1, + "name": "Richard Mcintosh" + }, + { + "id": 2, + "name": "Cardenas Anthony" + } + ], + "greeting": "Hello, Pennington Fitzgerald! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e6a531c4a4adf7a87", + "index": 165, + "guid": "940aaca9-cc55-4287-9b57-399918c547bf", + "isActive": false, + "balance": "$3,167.30", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "green", + "name": "Pat Sosa", + "gender": "female", + "company": "AUSTECH", + "email": "patsosa@austech.com", + "phone": "+1 (931) 418-2915", + "address": "638 Conduit Boulevard, Bancroft, Federated States Of Micronesia, 600", + "about": "Voluptate duis anim laboris commodo cillum qui aliqua exercitation aliquip sit id minim veniam. Deserunt laborum dolor culpa occaecat laborum nostrud ad quis anim duis. Reprehenderit ex consectetur non anim ullamco in quis non do eiusmod amet officia Lorem.\r\n", + "registered": "2017-05-16T02:01:24 -08:00", + "latitude": -80.530278, + "longitude": -176.199912, + "tags": [ + "ipsum", + "nulla", + "elit", + "fugiat", + "irure", + "ex", + "in" + ], + "friends": [ + { + "id": 0, + "name": "Walters Dunn" + }, + { + "id": 1, + "name": "Sybil Floyd" + }, + { + "id": 2, + "name": "Henry Obrien" + } + ], + "greeting": "Hello, Pat Sosa! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e5562128809106466", + "index": 166, + "guid": "e927b6dd-a87e-494d-9517-7dccd7333435", + "isActive": true, + "balance": "$1,185.46", + "picture": "http://placehold.it/32x32", + "age": 32, + "eyeColor": "green", + "name": "Tammy Gates", + "gender": "female", + "company": "OVERPLEX", + "email": "tammygates@overplex.com", + "phone": "+1 (902) 426-2748", + "address": "751 Ford Street, Hartsville/Hartley, South Carolina, 6339", + "about": "Elit incididunt consectetur proident elit labore labore sit deserunt laboris. Deserunt amet ut non fugiat aliqua cillum nisi cupidatat occaecat ad cillum Lorem. Lorem ipsum ipsum dolore labore commodo dolor. Non voluptate occaecat aliqua quis irure anim adipisicing cupidatat officia voluptate. Esse laboris excepteur fugiat cillum ut eiusmod eu nostrud. Ex officia culpa culpa eu ad excepteur enim consequat ad ullamco voluptate.\r\n", + "registered": "2015-07-20T04:48:00 -08:00", + "latitude": 8.043928, + "longitude": -5.664367, + "tags": [ + "mollit", + "exercitation", + "laborum", + "duis", + "tempor", + "incididunt", + "proident" + ], + "friends": [ + { + "id": 0, + "name": "Maxwell Booth" + }, + { + "id": 1, + "name": "Watts Carson" + }, + { + "id": 2, + "name": "Allen Ward" + } + ], + "greeting": "Hello, Tammy Gates! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e568bebc01a343c05", + "index": 167, + "guid": "ff6239b2-b4ce-445c-8231-4aa58f150463", + "isActive": false, + "balance": "$1,286.14", + "picture": "http://placehold.it/32x32", + "age": 34, + "eyeColor": "blue", + "name": "Bean Weeks", + "gender": "male", + "company": "GEEKULAR", + "email": "beanweeks@geekular.com", + "phone": "+1 (852) 591-2296", + "address": "346 Seacoast Terrace, Tibbie, Utah, 873", + "about": "Velit amet duis sit proident. In enim do commodo voluptate commodo. Anim sit sit velit amet proident dolor pariatur exercitation sint dolor proident qui ullamco. Eu qui sunt mollit magna veniam ea incididunt amet ex ullamco tempor.\r\n", + "registered": "2014-08-08T10:25:52 -08:00", + "latitude": -72.648085, + "longitude": 103.368292, + "tags": [ + "deserunt", + "laborum", + "ut", + "magna", + "dolore", + "Lorem", + "aliqua" + ], + "friends": [ + { + "id": 0, + "name": "Tommie Hart" + }, + { + "id": 1, + "name": "Bianca Norris" + }, + { + "id": 2, + "name": "Dyer Walker" + } + ], + "greeting": "Hello, Bean Weeks! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e09b3e7a3daa2a34d", + "index": 168, + "guid": "f08fa4a0-7867-4ec8-9e82-797ae8abb9b6", + "isActive": false, + "balance": "$1,637.99", + "picture": "http://placehold.it/32x32", + "age": 35, + "eyeColor": "green", + "name": "Ford Lott", + "gender": "male", + "company": "INSURETY", + "email": "fordlott@insurety.com", + "phone": "+1 (814) 414-2800", + "address": "671 Central Avenue, Mathews, Virginia, 2939", + "about": "Ea non adipisicing ut nulla culpa qui duis amet ad. Voluptate cillum cillum aute pariatur proident eiusmod Lorem consectetur labore. Occaecat deserunt ex nisi sunt magna anim consequat aute ad tempor. Est Lorem consequat proident exercitation qui voluptate quis elit adipisicing velit.\r\n", + "registered": "2016-06-03T02:16:23 -08:00", + "latitude": -49.730266, + "longitude": -173.088127, + "tags": [ + "mollit", + "veniam", + "irure", + "amet", + "aute", + "qui", + "irure" + ], + "friends": [ + { + "id": 0, + "name": "Lizzie Pugh" + }, + { + "id": 1, + "name": "Cheri Travis" + }, + { + "id": 2, + "name": "Audra Barker" + } + ], + "greeting": "Hello, Ford Lott! You have 5 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e8351ef5cae8de24d", + "index": 169, + "guid": "35cbb291-13b7-4951-9f9c-a19a8fca6d55", + "isActive": false, + "balance": "$2,937.96", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "blue", + "name": "Mari Mills", + "gender": "female", + "company": "FIREWAX", + "email": "marimills@firewax.com", + "phone": "+1 (932) 407-3211", + "address": "454 Malta Street, Ventress, Oregon, 8144", + "about": "Qui ea cillum laboris proident officia aute minim eiusmod. Lorem deserunt Lorem Lorem consectetur aute magna sint quis deserunt. Ex irure voluptate Lorem reprehenderit sunt. Amet sint esse consequat deserunt.\r\n", + "registered": "2018-11-12T06:44:08 -08:00", + "latitude": -88.182773, + "longitude": -149.779838, + "tags": [ + "aliqua", + "eu", + "irure", + "aliquip", + "esse", + "reprehenderit", + "commodo" + ], + "friends": [ + { + "id": 0, + "name": "Henson Wiggins" + }, + { + "id": 1, + "name": "Reilly Francis" + }, + { + "id": 2, + "name": "Prince Summers" + } + ], + "greeting": "Hello, Mari Mills! You have 4 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ee62a7dd863b5492e", + "index": 170, + "guid": "28ddb38c-d04e-4dfc-9dcb-112e8f661c77", + "isActive": false, + "balance": "$3,453.31", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "green", + "name": "Whitehead Dean", + "gender": "male", + "company": "MEDMEX", + "email": "whiteheaddean@medmex.com", + "phone": "+1 (848) 523-2301", + "address": "940 Friel Place, Laurelton, Iowa, 2250", + "about": "Labore mollit amet sint nostrud exercitation laborum ut. Veniam Lorem non pariatur aliqua labore magna. Adipisicing laborum fugiat esse reprehenderit aliquip id cillum tempor proident.\r\n", + "registered": "2017-01-30T11:43:27 -08:00", + "latitude": 25.96303, + "longitude": -45.455184, + "tags": [ + "ullamco", + "dolor", + "aute", + "dolore", + "laborum", + "do", + "consequat" + ], + "friends": [ + { + "id": 0, + "name": "Bolton Herring" + }, + { + "id": 1, + "name": "Keri Wiley" + }, + { + "id": 2, + "name": "Kelsey Vasquez" + } + ], + "greeting": "Hello, Whitehead Dean! You have 1 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ea10216050237978e", + "index": 171, + "guid": "9c2f0072-3c9d-43d9-b1d7-618ee5fd57a3", + "isActive": true, + "balance": "$3,437.27", + "picture": "http://placehold.it/32x32", + "age": 35, + "eyeColor": "brown", + "name": "Noemi Duncan", + "gender": "female", + "company": "INCUBUS", + "email": "noemiduncan@incubus.com", + "phone": "+1 (841) 463-2042", + "address": "265 Olive Street, Succasunna, New Hampshire, 8156", + "about": "Mollit ut excepteur fugiat nostrud non in exercitation dolor ullamco elit in aute nulla. Enim aute id laboris ea. Laboris adipisicing qui in ipsum quis mollit id irure esse labore aliquip eu labore consequat.\r\n", + "registered": "2014-07-29T03:12:21 -08:00", + "latitude": 53.183677, + "longitude": 90.138148, + "tags": [ + "tempor", + "ex", + "in", + "fugiat", + "deserunt", + "enim", + "commodo" + ], + "friends": [ + { + "id": 0, + "name": "Manning Simon" + }, + { + "id": 1, + "name": "Virgie Underwood" + }, + { + "id": 2, + "name": "Elsa Mccall" + } + ], + "greeting": "Hello, Noemi Duncan! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e4a692be0c9e8f2f0", + "index": 172, + "guid": "720b48c7-12ed-49f4-b764-d33f527ccf0a", + "isActive": false, + "balance": "$1,620.62", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "brown", + "name": "Hernandez Tate", + "gender": "male", + "company": "SUPREMIA", + "email": "hernandeztate@supremia.com", + "phone": "+1 (886) 483-3193", + "address": "812 Beach Place, Cartwright, Texas, 6508", + "about": "Incididunt laborum nostrud labore est ipsum sunt officia tempor velit aliquip officia reprehenderit eu. Labore do eu dolor nostrud duis. Tempor aliquip occaecat sit cupidatat sint in qui velit ea enim.\r\n", + "registered": "2015-11-02T04:57:23 -08:00", + "latitude": 47.785803, + "longitude": -169.815953, + "tags": [ + "elit", + "commodo", + "deserunt", + "ipsum", + "cillum", + "laboris", + "Lorem" + ], + "friends": [ + { + "id": 0, + "name": "Buckley Eaton" + }, + { + "id": 1, + "name": "Colleen Richardson" + }, + { + "id": 2, + "name": "Tanner Hays" + } + ], + "greeting": "Hello, Hernandez Tate! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e8fa3d2f83e78ab02", + "index": 173, + "guid": "f3acd2e0-e859-4bb7-9477-ec626cf4e665", + "isActive": true, + "balance": "$2,074.18", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "green", + "name": "Ora Mejia", + "gender": "female", + "company": "COMCUR", + "email": "oramejia@comcur.com", + "phone": "+1 (907) 511-3489", + "address": "774 Brown Street, Bluffview, Michigan, 4799", + "about": "Tempor ex non irure ut laboris ullamco. Quis veniam pariatur sit quis sint pariatur nulla culpa eu do irure. Voluptate nulla ad do ea. Non reprehenderit Lorem tempor do magna pariatur nostrud dolore. Laboris amet cupidatat id dolor nostrud. Eu aliqua veniam ea quis id in eu.\r\n", + "registered": "2017-05-14T06:14:18 -08:00", + "latitude": 19.456754, + "longitude": -10.248338, + "tags": [ + "labore", + "tempor", + "consectetur", + "mollit", + "voluptate", + "ut", + "velit" + ], + "friends": [ + { + "id": 0, + "name": "Casey Benton" + }, + { + "id": 1, + "name": "Marshall Guy" + }, + { + "id": 2, + "name": "Leann Craig" + } + ], + "greeting": "Hello, Ora Mejia! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e5b653ca2cf24a74c", + "index": 174, + "guid": "5444538f-36a2-445b-bb4a-898487dd6176", + "isActive": false, + "balance": "$1,671.19", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "blue", + "name": "Sonia Buckley", + "gender": "female", + "company": "ENAUT", + "email": "soniabuckley@enaut.com", + "phone": "+1 (861) 531-3129", + "address": "195 Montauk Avenue, Twilight, Guam, 7517", + "about": "Exercitation in incididunt magna minim sit minim magna irure adipisicing duis eu dolore ea. Aute ea Lorem cupidatat ut. Irure laboris tempor pariatur voluptate ea ea aute deserunt magna. Mollit nisi reprehenderit pariatur qui ullamco enim proident. Consequat officia sint anim deserunt sint cupidatat reprehenderit cupidatat sunt. Dolore velit laborum ea Lorem dolore. Sunt nulla ad duis eu nulla velit aliqua consectetur.\r\n", + "registered": "2017-06-24T02:24:45 -08:00", + "latitude": 54.067057, + "longitude": 154.817177, + "tags": [ + "fugiat", + "dolore", + "ipsum", + "do", + "adipisicing", + "est", + "nulla" + ], + "friends": [ + { + "id": 0, + "name": "Bond Burke" + }, + { + "id": 1, + "name": "Ashley Nunez" + }, + { + "id": 2, + "name": "Tami Howe" + } + ], + "greeting": "Hello, Sonia Buckley! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eb5bdf3d37df561db", + "index": 175, + "guid": "30948ce8-0212-4c84-afc8-17d0fccab17e", + "isActive": true, + "balance": "$1,393.66", + "picture": "http://placehold.it/32x32", + "age": 35, + "eyeColor": "brown", + "name": "Trudy Rollins", + "gender": "female", + "company": "BUZZOPIA", + "email": "trudyrollins@buzzopia.com", + "phone": "+1 (970) 599-2686", + "address": "488 Pulaski Street, Advance, Virgin Islands, 4629", + "about": "Magna magna exercitation magna Lorem culpa dolor nisi nulla exercitation consequat exercitation consequat occaecat. Ullamco mollit ut ea ullamco. Excepteur mollit veniam mollit nulla duis. Minim tempor eu laboris veniam. Ipsum tempor ea voluptate in duis. Non adipisicing minim deserunt fugiat ad labore laborum pariatur est. Aliqua sint ex nostrud ex sunt ipsum cillum consequat labore consequat aliquip dolore.\r\n", + "registered": "2019-03-18T02:06:33 -08:00", + "latitude": -10.570824, + "longitude": -142.794728, + "tags": [ + "cupidatat", + "labore", + "ipsum", + "magna", + "mollit", + "mollit", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Raymond Curtis" + }, + { + "id": 1, + "name": "Barker Harper" + }, + { + "id": 2, + "name": "Crystal Estes" + } + ], + "greeting": "Hello, Trudy Rollins! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e5772278f52406784", + "index": 176, + "guid": "2720592b-ce09-4baf-b6c9-0587dafb0674", + "isActive": false, + "balance": "$1,787.18", + "picture": "http://placehold.it/32x32", + "age": 29, + "eyeColor": "blue", + "name": "Cross Figueroa", + "gender": "male", + "company": "ENQUILITY", + "email": "crossfigueroa@enquility.com", + "phone": "+1 (948) 484-2734", + "address": "549 Hampton Avenue, Coral, Vermont, 3632", + "about": "Ad excepteur veniam consectetur tempor sint amet. Est aliquip incididunt ut commodo occaecat id fugiat mollit occaecat sit ut eu nisi. Aliquip non enim occaecat do magna quis consequat eu. Quis fugiat minim exercitation velit ex quis exercitation sint eiusmod. Voluptate cillum id magna pariatur incididunt veniam eiusmod pariatur in esse qui. Laboris consectetur fugiat culpa proident consequat ea Lorem veniam nostrud do dolore aute enim. Laborum labore ullamco nisi sint voluptate eu anim anim Lorem est nostrud sit proident.\r\n", + "registered": "2014-07-26T04:59:18 -08:00", + "latitude": 70.935486, + "longitude": -134.75788, + "tags": [ + "magna", + "adipisicing", + "occaecat", + "nisi", + "duis", + "voluptate", + "non" + ], + "friends": [ + { + "id": 0, + "name": "Hays Middleton" + }, + { + "id": 1, + "name": "Dale Douglas" + }, + { + "id": 2, + "name": "Jacobson Gill" + } + ], + "greeting": "Hello, Cross Figueroa! You have 5 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e6ac6c07f87eef3f1", + "index": 177, + "guid": "df0a11bf-927f-4ca7-b7f8-9a8ed918f17e", + "isActive": false, + "balance": "$2,023.41", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "brown", + "name": "Selena Maddox", + "gender": "female", + "company": "ZILCH", + "email": "selenamaddox@zilch.com", + "phone": "+1 (938) 467-3612", + "address": "378 Suydam Street, Boykin, Alabama, 2415", + "about": "Proident qui incididunt est exercitation. Sunt nostrud magna aute aliqua excepteur do consectetur pariatur deserunt pariatur. Reprehenderit non Lorem enim laborum cupidatat.\r\n", + "registered": "2016-11-03T11:20:05 -08:00", + "latitude": 64.044276, + "longitude": -52.462251, + "tags": [ + "voluptate", + "nisi", + "nisi", + "adipisicing", + "ullamco", + "irure", + "culpa" + ], + "friends": [ + { + "id": 0, + "name": "Earlene Hodges" + }, + { + "id": 1, + "name": "Caroline Rosales" + }, + { + "id": 2, + "name": "Duke Gardner" + } + ], + "greeting": "Hello, Selena Maddox! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e24b633df81e91936", + "index": 178, + "guid": "0dbe3e19-a74f-4ed8-96b1-258669978667", + "isActive": false, + "balance": "$2,652.39", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "blue", + "name": "Allison Stewart", + "gender": "female", + "company": "PLUTORQUE", + "email": "allisonstewart@plutorque.com", + "phone": "+1 (869) 541-2523", + "address": "965 Crosby Avenue, Callaghan, New York, 1158", + "about": "Pariatur ullamco magna enim commodo adipisicing aliquip sunt laboris anim. Eu ad consectetur nostrud occaecat irure commodo. Labore sint dolore proident sit elit qui nostrud amet do. Elit esse ea veniam proident cillum ex mollit aute do nostrud mollit occaecat eiusmod.\r\n", + "registered": "2015-11-05T07:36:11 -08:00", + "latitude": -24.549031, + "longitude": -66.169185, + "tags": [ + "pariatur", + "do", + "sint", + "cillum", + "adipisicing", + "exercitation", + "eiusmod" + ], + "friends": [ + { + "id": 0, + "name": "Karyn Stevens" + }, + { + "id": 1, + "name": "Peck Malone" + }, + { + "id": 2, + "name": "Oneal Hull" + } + ], + "greeting": "Hello, Allison Stewart! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e8888d817d23fe221", + "index": 179, + "guid": "4148488a-e72a-4d0a-977d-d69a7d354cc5", + "isActive": true, + "balance": "$2,724.91", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "brown", + "name": "Lidia Roy", + "gender": "female", + "company": "HELIXO", + "email": "lidiaroy@helixo.com", + "phone": "+1 (928) 402-2255", + "address": "496 Windsor Place, Silkworth, Tennessee, 7145", + "about": "Anim consequat ipsum ea ipsum et eu qui aliqua veniam deserunt culpa. Qui adipisicing veniam in aliquip voluptate esse nostrud irure aliquip. Ut exercitation duis fugiat sint tempor occaecat minim. Laborum pariatur nisi laboris irure quis non. Consequat aliquip exercitation enim veniam dolor qui commodo aute ut tempor.\r\n", + "registered": "2014-03-18T08:50:18 -08:00", + "latitude": -66.787397, + "longitude": 4.693636, + "tags": [ + "amet", + "anim", + "duis", + "eu", + "mollit", + "adipisicing", + "ipsum" + ], + "friends": [ + { + "id": 0, + "name": "Jerry Oneal" + }, + { + "id": 1, + "name": "Buchanan Hammond" + }, + { + "id": 2, + "name": "Snow Chandler" + } + ], + "greeting": "Hello, Lidia Roy! You have 4 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839ed0a54b262f04c6f4", + "index": 180, + "guid": "0280be5a-379a-4d40-8605-0f66ef3ebbca", + "isActive": false, + "balance": "$2,359.27", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "green", + "name": "Manuela King", + "gender": "female", + "company": "ORBALIX", + "email": "manuelaking@orbalix.com", + "phone": "+1 (935) 503-2302", + "address": "366 Riverdale Avenue, Stagecoach, Delaware, 6886", + "about": "Aute adipisicing incididunt enim enim nostrud. Do incididunt nulla pariatur cillum culpa voluptate. Et labore esse duis est magna fugiat aliquip. Fugiat nostrud ea excepteur laboris duis voluptate voluptate cillum sint ea do velit fugiat. Ut non dolor adipisicing duis consectetur.\r\n", + "registered": "2014-01-15T11:56:17 -08:00", + "latitude": 16.765324, + "longitude": -14.054695, + "tags": [ + "non", + "nostrud", + "consequat", + "dolor", + "laboris", + "sunt", + "do" + ], + "friends": [ + { + "id": 0, + "name": "Holden Conner" + }, + { + "id": 1, + "name": "Catherine Koch" + }, + { + "id": 2, + "name": "Cindy Sims" + } + ], + "greeting": "Hello, Manuela King! You have 9 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e35a17dfc9076625d", + "index": 181, + "guid": "6e35aff7-0c8c-464c-ab80-56f11bacf348", + "isActive": true, + "balance": "$3,494.24", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "brown", + "name": "Phyllis Holcomb", + "gender": "female", + "company": "RUGSTARS", + "email": "phyllisholcomb@rugstars.com", + "phone": "+1 (903) 592-2548", + "address": "307 Evans Street, Shrewsbury, Minnesota, 1608", + "about": "Consequat voluptate dolor Lorem aliquip voluptate aliquip irure veniam dolor do est culpa magna in. Occaecat ea eu est nisi laborum. Deserunt officia cupidatat tempor exercitation ex aute.\r\n", + "registered": "2019-02-12T10:45:29 -08:00", + "latitude": -0.75367, + "longitude": 99.825322, + "tags": [ + "ea", + "duis", + "nulla", + "et", + "anim", + "sit", + "proident" + ], + "friends": [ + { + "id": 0, + "name": "Julie Moses" + }, + { + "id": 1, + "name": "Trina Keith" + }, + { + "id": 2, + "name": "Alicia Lester" + } + ], + "greeting": "Hello, Phyllis Holcomb! You have 5 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e7b10d5b0e4eb118f", + "index": 182, + "guid": "0467c2ab-ee37-419c-81b8-76e6e0b60d31", + "isActive": false, + "balance": "$2,996.69", + "picture": "http://placehold.it/32x32", + "age": 26, + "eyeColor": "brown", + "name": "Joan Sheppard", + "gender": "female", + "company": "FLUM", + "email": "joansheppard@flum.com", + "phone": "+1 (889) 545-2419", + "address": "457 Kermit Place, Kohatk, Maryland, 1936", + "about": "Enim ipsum esse duis amet sit exercitation elit mollit anim. Cillum ullamco aliqua ipsum consequat ullamco sunt aliquip laboris duis consectetur esse sunt sint excepteur. Ipsum culpa sunt culpa minim dolor est aliquip officia laborum aliqua sunt cillum. Non consectetur veniam aliquip mollit cillum incididunt non laborum et Lorem enim commodo commodo aute. Pariatur pariatur cupidatat non elit nulla ipsum do laborum occaecat enim.\r\n", + "registered": "2016-01-22T07:29:34 -08:00", + "latitude": -64.960247, + "longitude": 130.649958, + "tags": [ + "in", + "adipisicing", + "est", + "magna", + "incididunt", + "duis", + "aliquip" + ], + "friends": [ + { + "id": 0, + "name": "Reynolds Griffith" + }, + { + "id": 1, + "name": "Gallagher Andrews" + }, + { + "id": 2, + "name": "Ines Vega" + } + ], + "greeting": "Hello, Joan Sheppard! You have 5 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e77e552aade94a4e2", + "index": 183, + "guid": "b1125a5c-c665-4bd8-a13d-9ee0e9747b88", + "isActive": false, + "balance": "$3,724.68", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "green", + "name": "Blankenship Martinez", + "gender": "male", + "company": "INQUALA", + "email": "blankenshipmartinez@inquala.com", + "phone": "+1 (994) 532-2120", + "address": "544 Hall Street, Vale, Florida, 2593", + "about": "Irure anim cupidatat adipisicing do pariatur enim nulla minim eiusmod veniam irure. Quis commodo elit ad nulla excepteur deserunt nisi et esse cupidatat proident ut et. Dolor ex officia et ut sit incididunt. Deserunt non mollit dolor anim labore eiusmod esse do officia pariatur excepteur. Amet adipisicing nulla ea fugiat. Elit reprehenderit sunt id consequat elit ad ad officia tempor. Sit commodo reprehenderit laborum labore eiusmod.\r\n", + "registered": "2015-01-10T07:17:11 -08:00", + "latitude": 23.972379, + "longitude": -10.949596, + "tags": [ + "labore", + "id", + "aliqua", + "amet", + "esse", + "incididunt", + "veniam" + ], + "friends": [ + { + "id": 0, + "name": "Graham Palmer" + }, + { + "id": 1, + "name": "Vanessa James" + }, + { + "id": 2, + "name": "Maldonado Mcdonald" + } + ], + "greeting": "Hello, Blankenship Martinez! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eb8cc7ce3cf73c61b", + "index": 184, + "guid": "81d66251-6cc4-43cc-8faa-b9bba7df1d5c", + "isActive": false, + "balance": "$1,660.64", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "green", + "name": "Davenport Dixon", + "gender": "male", + "company": "LUNCHPOD", + "email": "davenportdixon@lunchpod.com", + "phone": "+1 (977) 423-3035", + "address": "602 Beekman Place, Dorneyville, Marshall Islands, 6952", + "about": "Officia tempor proident anim nisi. Velit aliqua qui tempor sunt voluptate id duis tempor nostrud aute reprehenderit. Et amet officia eu exercitation exercitation laboris sunt proident aliquip anim quis amet labore ea.\r\n", + "registered": "2016-07-28T08:02:10 -08:00", + "latitude": 56.078466, + "longitude": 93.498776, + "tags": [ + "aliqua", + "minim", + "deserunt", + "anim", + "exercitation", + "pariatur", + "non" + ], + "friends": [ + { + "id": 0, + "name": "Chase Montgomery" + }, + { + "id": 1, + "name": "Frazier Delacruz" + }, + { + "id": 2, + "name": "Kaitlin Juarez" + } + ], + "greeting": "Hello, Davenport Dixon! You have 5 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ef3fede1010d172ea", + "index": 185, + "guid": "f0312fad-110a-4758-8fe3-d2722e6f6054", + "isActive": false, + "balance": "$2,041.15", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "green", + "name": "Emma Sharpe", + "gender": "female", + "company": "MIXERS", + "email": "emmasharpe@mixers.com", + "phone": "+1 (979) 496-2871", + "address": "976 Ashford Street, Independence, Wyoming, 9134", + "about": "Magna cupidatat aliquip dolor adipisicing irure adipisicing consectetur reprehenderit. Aute nostrud elit eu aliquip minim ullamco esse elit culpa do deserunt aliqua consectetur aliquip. Cillum cillum pariatur consectetur aliqua nisi velit adipisicing dolore mollit. Deserunt consectetur ea voluptate consequat id. Aliqua culpa reprehenderit reprehenderit amet.\r\n", + "registered": "2015-07-27T05:16:16 -08:00", + "latitude": 74.610316, + "longitude": -153.149813, + "tags": [ + "non", + "aliqua", + "culpa", + "esse", + "dolore", + "reprehenderit", + "cupidatat" + ], + "friends": [ + { + "id": 0, + "name": "Latoya Dejesus" + }, + { + "id": 1, + "name": "Shawna Stephenson" + }, + { + "id": 2, + "name": "Fannie Velazquez" + } + ], + "greeting": "Hello, Emma Sharpe! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e815586ad300fd7e9", + "index": 186, + "guid": "493947f1-4abe-475a-b033-4f14c22e1885", + "isActive": false, + "balance": "$3,272.21", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "blue", + "name": "Eliza Merritt", + "gender": "female", + "company": "PERKLE", + "email": "elizamerritt@perkle.com", + "phone": "+1 (897) 416-3849", + "address": "367 Bouck Court, Calverton, Illinois, 5380", + "about": "Lorem id commodo fugiat sint labore velit cupidatat ut qui sit est commodo qui qui. In minim est id officia commodo aute aute laborum. Cupidatat irure ea anim mollit.\r\n", + "registered": "2019-07-19T04:09:08 -08:00", + "latitude": -19.722838, + "longitude": -141.28862, + "tags": [ + "deserunt", + "sint", + "est", + "ea", + "cillum", + "id", + "sit" + ], + "friends": [ + { + "id": 0, + "name": "Aurelia Stokes" + }, + { + "id": 1, + "name": "Abby Nolan" + }, + { + "id": 2, + "name": "Wagner Irwin" + } + ], + "greeting": "Hello, Eliza Merritt! You have 7 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839e39dde0225dbe3ed3", + "index": 187, + "guid": "6d1202cc-a610-4449-9ae5-03b419d6c4c6", + "isActive": false, + "balance": "$3,776.66", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "brown", + "name": "Wilcox Mitchell", + "gender": "male", + "company": "NETUR", + "email": "wilcoxmitchell@netur.com", + "phone": "+1 (920) 454-3229", + "address": "776 Burnett Street, Brady, South Dakota, 5248", + "about": "Duis voluptate amet ut dolore pariatur excepteur eu. Magna qui excepteur voluptate velit adipisicing. Nisi cupidatat sunt ipsum mollit enim. Incididunt commodo cillum sint enim cillum dolore minim ullamco proident eiusmod id. Magna voluptate commodo labore labore elit anim reprehenderit sunt id eu pariatur non occaecat. Laborum nulla occaecat ipsum exercitation enim esse nulla.\r\n", + "registered": "2019-04-11T06:11:59 -08:00", + "latitude": -43.907845, + "longitude": 7.955436, + "tags": [ + "officia", + "amet", + "laboris", + "sunt", + "ea", + "minim", + "tempor" + ], + "friends": [ + { + "id": 0, + "name": "Hess Simmons" + }, + { + "id": 1, + "name": "Hill Cruz" + }, + { + "id": 2, + "name": "Gardner Walls" + } + ], + "greeting": "Hello, Wilcox Mitchell! You have 2 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e5af2db43ad2ed198", + "index": 188, + "guid": "da5b1746-bce5-48d5-a4e7-6f7034b456a2", + "isActive": false, + "balance": "$2,941.86", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "blue", + "name": "Walsh Wright", + "gender": "male", + "company": "COMVEYER", + "email": "walshwright@comveyer.com", + "phone": "+1 (828) 509-2760", + "address": "615 Amboy Street, Cucumber, Alaska, 4817", + "about": "Aute velit anim laborum quis fugiat culpa dolore nostrud. Dolore ad ad ullamco esse sunt labore. Quis commodo consequat occaecat aliqua reprehenderit fugiat do sit do nulla do anim amet velit. Labore proident velit adipisicing incididunt elit sunt nisi consectetur ea laboris sunt labore nulla. Sint id labore qui amet eiusmod minim commodo ea.\r\n", + "registered": "2018-06-30T04:43:26 -08:00", + "latitude": -32.92391, + "longitude": -82.127925, + "tags": [ + "ad", + "pariatur", + "Lorem", + "deserunt", + "sit", + "anim", + "dolore" + ], + "friends": [ + { + "id": 0, + "name": "Houston Sanchez" + }, + { + "id": 1, + "name": "Barrett Espinoza" + }, + { + "id": 2, + "name": "Pruitt Christensen" + } + ], + "greeting": "Hello, Walsh Wright! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e84a99dd051ecd17b", + "index": 189, + "guid": "bf8ee2b2-1ce4-461f-adb7-6009a7864702", + "isActive": true, + "balance": "$2,777.42", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "blue", + "name": "Kirsten Pacheco", + "gender": "female", + "company": "KOFFEE", + "email": "kirstenpacheco@koffee.com", + "phone": "+1 (831) 526-2117", + "address": "415 Jefferson Avenue, Dalton, Arizona, 208", + "about": "Excepteur labore cillum consequat dolor amet laborum minim id ipsum consectetur non Lorem deserunt. Laborum laboris ea non irure esse ut in velit commodo adipisicing aliqua dolore labore. Lorem ad aliquip ut aliqua qui nisi et reprehenderit non nulla aute ea est duis.\r\n", + "registered": "2018-01-07T04:10:25 -08:00", + "latitude": -3.55267, + "longitude": 5.355419, + "tags": [ + "commodo", + "duis", + "sit", + "consequat", + "eu", + "aliquip", + "Lorem" + ], + "friends": [ + { + "id": 0, + "name": "Simmons Shannon" + }, + { + "id": 1, + "name": "Kirk Small" + }, + { + "id": 2, + "name": "Meredith Gould" + } + ], + "greeting": "Hello, Kirsten Pacheco! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e785d1a4dd9028625", + "index": 190, + "guid": "44ea3b54-9917-4582-866d-c2fda5abc03c", + "isActive": false, + "balance": "$3,280.54", + "picture": "http://placehold.it/32x32", + "age": 30, + "eyeColor": "brown", + "name": "Witt Pollard", + "gender": "male", + "company": "DIGINETIC", + "email": "wittpollard@diginetic.com", + "phone": "+1 (873) 434-2053", + "address": "727 Douglass Street, Heil, Louisiana, 9781", + "about": "Lorem dolore in anim duis minim id id non dolor laborum commodo id excepteur. Commodo deserunt nostrud non mollit deserunt adipisicing pariatur deserunt irure cupidatat in minim nulla. Reprehenderit Lorem amet velit Lorem mollit in nostrud do culpa commodo aliquip dolore. Est voluptate culpa sint in quis incididunt veniam quis. Non sunt nulla ut excepteur. Sit laboris officia commodo sint commodo voluptate consectetur enim do enim ad duis tempor. Duis adipisicing reprehenderit ullamco velit et culpa ad ullamco.\r\n", + "registered": "2016-04-03T11:58:14 -08:00", + "latitude": 76.516779, + "longitude": -105.93569, + "tags": [ + "laboris", + "tempor", + "do", + "velit", + "proident", + "mollit", + "sunt" + ], + "friends": [ + { + "id": 0, + "name": "Hayes Mckee" + }, + { + "id": 1, + "name": "Dolly Riggs" + }, + { + "id": 2, + "name": "Castaneda Hopkins" + } + ], + "greeting": "Hello, Witt Pollard! You have 5 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e152b8fa7daf527f6", + "index": 191, + "guid": "51c79db7-2d59-4aae-8db3-8754ccba3d50", + "isActive": true, + "balance": "$2,353.95", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "brown", + "name": "Frances Velasquez", + "gender": "female", + "company": "MINGA", + "email": "francesvelasquez@minga.com", + "phone": "+1 (861) 448-2067", + "address": "693 Jamison Lane, Duryea, Hawaii, 8187", + "about": "Incididunt nulla proident tempor laborum enim velit est culpa veniam est. Excepteur proident consectetur sit adipisicing sit duis est cillum dolore in exercitation adipisicing irure Lorem. Nostrud reprehenderit fugiat ad ad id magna velit minim commodo ex elit consectetur est Lorem. Elit in reprehenderit commodo voluptate fugiat. Anim velit officia nulla magna exercitation. Non consequat deserunt tempor dolor irure deserunt non sint in exercitation culpa consequat reprehenderit ullamco.\r\n", + "registered": "2015-02-07T02:11:54 -08:00", + "latitude": -16.8896, + "longitude": -145.150496, + "tags": [ + "nisi", + "id", + "nisi", + "amet", + "eu", + "laboris", + "nulla" + ], + "friends": [ + { + "id": 0, + "name": "Laura Bass" + }, + { + "id": 1, + "name": "Leach Fry" + }, + { + "id": 2, + "name": "Pittman Rich" + } + ], + "greeting": "Hello, Frances Velasquez! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e814dcd013a10b085", + "index": 192, + "guid": "6404cba6-cdc3-469f-866c-55eb78fdd4ce", + "isActive": false, + "balance": "$2,363.40", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "green", + "name": "Pena Lewis", + "gender": "male", + "company": "CONJURICA", + "email": "penalewis@conjurica.com", + "phone": "+1 (873) 458-3482", + "address": "156 Nassau Avenue, Bartonsville, Ohio, 2980", + "about": "Voluptate mollit esse ex ut culpa cupidatat sunt ex. Eiusmod adipisicing ipsum proident eiusmod do cupidatat labore ex culpa. Laboris ipsum sint consequat esse adipisicing. Id voluptate excepteur ad adipisicing duis id elit aute quis quis commodo. Amet pariatur sunt eiusmod minim officia sunt aliqua. Consectetur Lorem nostrud culpa consequat cillum ipsum reprehenderit eiusmod commodo consequat aute.\r\n", + "registered": "2019-02-26T09:46:27 -08:00", + "latitude": 67.870277, + "longitude": -3.109592, + "tags": [ + "anim", + "irure", + "velit", + "eiusmod", + "reprehenderit", + "ex", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "Mara Nelson" + }, + { + "id": 1, + "name": "Maribel Scott" + }, + { + "id": 2, + "name": "Burke Schultz" + } + ], + "greeting": "Hello, Pena Lewis! You have 6 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e8f73a09a0e0da98f", + "index": 193, + "guid": "750a8e1c-06a8-4c9a-a450-0125cb264d39", + "isActive": true, + "balance": "$3,357.08", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "green", + "name": "Charmaine Gallagher", + "gender": "female", + "company": "OPTICALL", + "email": "charmainegallagher@opticall.com", + "phone": "+1 (847) 412-2089", + "address": "255 Canda Avenue, Barrelville, Palau, 5635", + "about": "Qui incididunt ipsum est fugiat tempor non occaecat qui do nisi aliqua officia nisi. Excepteur commodo eu amet sint. Elit aliqua fugiat ex adipisicing aliquip ea proident.\r\n", + "registered": "2017-02-21T08:10:57 -08:00", + "latitude": 79.030489, + "longitude": 27.906814, + "tags": [ + "eiusmod", + "velit", + "nostrud", + "occaecat", + "tempor", + "culpa", + "fugiat" + ], + "friends": [ + { + "id": 0, + "name": "Kellie Watson" + }, + { + "id": 1, + "name": "Wiley Graves" + }, + { + "id": 2, + "name": "Nell Church" + } + ], + "greeting": "Hello, Charmaine Gallagher! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eb2eafaff64ccb35f", + "index": 194, + "guid": "20fb246e-0797-43f2-abbf-d4ff7f284c48", + "isActive": false, + "balance": "$1,318.81", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "green", + "name": "Shannon Camacho", + "gender": "male", + "company": "PULZE", + "email": "shannoncamacho@pulze.com", + "phone": "+1 (910) 467-3732", + "address": "350 Box Street, Oneida, North Dakota, 3692", + "about": "Officia eu pariatur consectetur nisi mollit dolore minim cillum aute in anim ad sint eu. Excepteur ullamco laboris aliqua laboris. Id fugiat tempor amet fugiat reprehenderit in. Nostrud anim velit elit commodo. Sint pariatur quis fugiat anim nostrud qui in. Et ipsum fugiat aliqua ipsum ut ad pariatur elit.\r\n", + "registered": "2016-06-03T01:56:25 -08:00", + "latitude": 51.299672, + "longitude": 66.438232, + "tags": [ + "mollit", + "occaecat", + "labore", + "consectetur", + "consequat", + "duis", + "amet" + ], + "friends": [ + { + "id": 0, + "name": "Rogers Clark" + }, + { + "id": 1, + "name": "Francis Bird" + }, + { + "id": 2, + "name": "Copeland Love" + } + ], + "greeting": "Hello, Shannon Camacho! You have 10 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839e2655eda50bf02887", + "index": 195, + "guid": "8018addc-309c-425a-8b77-9123ccc1141b", + "isActive": false, + "balance": "$3,385.75", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "green", + "name": "Brandi Schroeder", + "gender": "female", + "company": "ORBIN", + "email": "brandischroeder@orbin.com", + "phone": "+1 (966) 452-3765", + "address": "837 Aurelia Court, Allentown, California, 5224", + "about": "Enim et in non eu. Enim veniam laborum duis ex qui amet consequat culpa exercitation sint et adipisicing ex. Consequat ea in et sint adipisicing laborum eu cupidatat mollit id tempor sint commodo. Exercitation pariatur cillum cupidatat ipsum anim esse deserunt cupidatat elit dolore ea reprehenderit occaecat amet. Ullamco cillum ea pariatur elit eiusmod. Nostrud veniam adipisicing qui veniam eu ipsum voluptate nisi. In minim esse do dolor dolor consequat.\r\n", + "registered": "2018-07-04T10:19:44 -08:00", + "latitude": 58.041078, + "longitude": -154.107136, + "tags": [ + "ad", + "consequat", + "irure", + "ut", + "amet", + "aute", + "mollit" + ], + "friends": [ + { + "id": 0, + "name": "Mercer Rojas" + }, + { + "id": 1, + "name": "Bentley Lane" + }, + { + "id": 2, + "name": "Stevens Preston" + } + ], + "greeting": "Hello, Brandi Schroeder! You have 3 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5e25839eaa4e7bf1747c7560", + "index": 196, + "guid": "4838212e-6a64-48e9-bbe9-413b3e316f03", + "isActive": false, + "balance": "$1,831.20", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "green", + "name": "Viola Glover", + "gender": "female", + "company": "MOTOVATE", + "email": "violaglover@motovate.com", + "phone": "+1 (844) 591-2196", + "address": "476 Hubbard Place, Brandermill, Nevada, 6252", + "about": "Aliquip in fugiat nulla quis exercitation irure ex id exercitation cupidatat occaecat occaecat nisi incididunt. Ut esse veniam cupidatat pariatur pariatur cillum deserunt enim. Velit est et labore fugiat sint quis proident reprehenderit aliqua. Esse reprehenderit labore do ullamco ex velit voluptate laboris veniam duis.\r\n", + "registered": "2015-09-07T03:24:47 -08:00", + "latitude": -39.499299, + "longitude": -74.488452, + "tags": [ + "qui", + "ipsum", + "minim", + "veniam", + "eu", + "nostrud", + "officia" + ], + "friends": [ + { + "id": 0, + "name": "Richardson Pitts" + }, + { + "id": 1, + "name": "Spencer Matthews" + }, + { + "id": 2, + "name": "Dollie Solomon" + } + ], + "greeting": "Hello, Viola Glover! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839e0098d9a65653e882", + "index": 197, + "guid": "94746e0a-2853-48cf-8ac6-b1c966ad1b05", + "isActive": false, + "balance": "$3,850.94", + "picture": "http://placehold.it/32x32", + "age": 21, + "eyeColor": "brown", + "name": "Rhea Farley", + "gender": "female", + "company": "MEMORA", + "email": "rheafarley@memora.com", + "phone": "+1 (842) 433-2264", + "address": "116 Jefferson Street, Caspar, Washington, 3511", + "about": "Irure ullamco excepteur est ex quis. Aliquip ut aute excepteur nisi tempor aliquip. Ipsum ullamco commodo dolore consequat velit cillum ex nostrud laboris. Cupidatat aliquip Lorem ad ad officia nisi. Enim nisi consectetur proident dolore sunt. Voluptate excepteur dolore eiusmod laboris ea nisi Lorem adipisicing aliquip deserunt sint irure nulla.\r\n", + "registered": "2018-09-10T10:38:23 -08:00", + "latitude": -42.472067, + "longitude": 86.883197, + "tags": [ + "ex", + "velit", + "consectetur", + "velit", + "Lorem", + "sit", + "culpa" + ], + "friends": [ + { + "id": 0, + "name": "Carol Bolton" + }, + { + "id": 1, + "name": "Elizabeth Fowler" + }, + { + "id": 2, + "name": "Johnston Nielsen" + } + ], + "greeting": "Hello, Rhea Farley! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5e25839ee536ac5ca5f1f3c0", + "index": 198, + "guid": "f04901b0-9254-41b5-a11e-fbd8af31a999", + "isActive": false, + "balance": "$2,418.85", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "blue", + "name": "Mcclure Whitley", + "gender": "male", + "company": "PHORMULA", + "email": "mcclurewhitley@phormula.com", + "phone": "+1 (889) 563-2189", + "address": "335 Bay Street, Coyote, Arkansas, 4403", + "about": "Id eiusmod minim cillum fugiat proident commodo. Pariatur laborum ullamco fugiat nisi eiusmod occaecat nulla reprehenderit ut esse reprehenderit fugiat mollit labore. Aliquip exercitation eiusmod veniam fugiat reprehenderit non sint nostrud dolore ea cillum consectetur. Dolor ea ea quis enim ea cillum elit. Nulla reprehenderit pariatur sunt ipsum. Qui laborum pariatur aute occaecat ea sunt irure irure Lorem.\r\n", + "registered": "2014-09-16T05:23:19 -08:00", + "latitude": -41.660527, + "longitude": 165.291138, + "tags": [ + "enim", + "occaecat", + "quis", + "laboris", + "id", + "nulla", + "labore" + ], + "friends": [ + { + "id": 0, + "name": "Perkins Oneill" + }, + { + "id": 1, + "name": "Kerri Holloway" + }, + { + "id": 2, + "name": "Hanson Case" + } + ], + "greeting": "Hello, Mcclure Whitley! You have 10 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5e25839ed261c3fae830f033", + "index": 199, + "guid": "30b5d426-5640-409d-a44e-191baa0895e4", + "isActive": false, + "balance": "$3,663.24", + "picture": "http://placehold.it/32x32", + "age": 25, + "eyeColor": "brown", + "name": "King Mullins", + "gender": "male", + "company": "KROG", + "email": "kingmullins@krog.com", + "phone": "+1 (993) 479-2472", + "address": "728 Lorimer Street, Alderpoint, Wisconsin, 4749", + "about": "Nostrud incididunt cillum deserunt quis exercitation occaecat. Laborum aliqua aliquip adipisicing est aliqua esse mollit aliquip est Lorem dolore incididunt. In adipisicing commodo qui in duis ea cupidatat fugiat nostrud. Est et mollit veniam occaecat excepteur mollit labore laborum voluptate cillum sint nulla Lorem esse. Culpa tempor laboris cillum proident nostrud anim do voluptate magna consectetur culpa adipisicing duis quis. Deserunt ut fugiat ex eiusmod voluptate incididunt irure occaecat tempor exercitation.\r\n", + "registered": "2018-04-18T03:18:43 -08:00", + "latitude": 59.972237, + "longitude": 53.645751, + "tags": [ + "occaecat", + "qui", + "Lorem", + "minim", + "non", + "non", + "consectetur" + ], + "friends": [ + { + "id": 0, + "name": "Martinez Hanson" + }, + { + "id": 1, + "name": "Dale Chase" + }, + { + "id": 2, + "name": "Johnnie Lara" + } + ], + "greeting": "Hello, King Mullins! You have 6 unread messages.", + "favoriteFruit": "apple" + } +] diff --git a/Tests/Shared/RTHelper.h b/Tests/Shared/RTHelper.h new file mode 100644 index 0000000..10746a7 --- /dev/null +++ b/Tests/Shared/RTHelper.h @@ -0,0 +1,21 @@ +// +// RTHelper.h +// RFAPI +// +// Created by BB9z on 2020/1/9. +// Copyright © 2020 RFUI. All rights reserved. +// + +#import + +/** +Helper methods for unit test. +*/ +@interface RTHelper : NSObject + +/** + Help test cases written in Swift to catch NSException. + */ ++ (BOOL)catchException:(NS_NOESCAPE void(^__nonnull)(void))tryBlock error:(NSError *__nullable __autoreleasing *__nullable)error; + +@end diff --git a/Tests/Shared/RTHelper.m b/Tests/Shared/RTHelper.m new file mode 100644 index 0000000..3af7a81 --- /dev/null +++ b/Tests/Shared/RTHelper.m @@ -0,0 +1,24 @@ +// +// RTHelper.m +// RFAPI +// +// Created by BB9z on 2020/1/9. +// Copyright © 2020 RFUI. All rights reserved. +// + +#import "RTHelper.h" + +@implementation RTHelper + ++ (BOOL)catchException:(NS_NOESCAPE void(^)(void))tryBlock error:(NSError *__autoreleasing*)error { + @try { + tryBlock(); + return YES; + } + @catch (NSException *exception) { + *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo]; + return NO; + } +} + +@end diff --git a/Tests/Shared/TestConvention.swift b/Tests/Shared/TestConvention.swift new file mode 100644 index 0000000..c265cc0 --- /dev/null +++ b/Tests/Shared/TestConvention.swift @@ -0,0 +1,126 @@ +// +// TestConvention.swift +// RFAPI +// +// Created by BB9z on 2020/1/17. +// Copyright © 2020 RFUI. All rights reserved. +// + +import XCTest + +class TestConvention: XCTestCase { + + // Has default base url + lazy var api: TestAPI = { + let api = TestAPI() + api.baseURL = URL(string: "https://httpbin.org") + let defineConfigURL = Bundle(for: type(of: self)).url(forResource: "test_defines", withExtension: "plist")! + let defineConfig = NSDictionary(contentsOf: defineConfigURL) as! [String: [String: Any]] + api.defineManager.setDefinesWithRulesInfo(defineConfig) + return api + }() + + func testCallbacksOrder() { + let expSuccess1 = expectation(description: "success") + let expFinsh1 = expectation(description: "finish") + let expComplete1 = expectation(description: "complate") + api.request(name: "Status") { c in + c.parameters = ["code": 200] + c.success { _, _ in + expSuccess1.fulfill() + } + c.failure { _, _ in + XCTAssert(false, "This request should success.") + } + c.finished { _, _ in + expFinsh1.fulfill() + } + c.complation { _, _, _ in + expComplete1.fulfill() + } + } + wait(for: [expSuccess1, expFinsh1, expComplete1], timeout: 10, enforceOrder: true) + + let expFail2 = expectation(description: "fail") + let expFinsh2 = expectation(description: "finish") + let expComplete2 = expectation(description: "complate") + api.request(name: "Status") { c in + c.parameters = ["code": 500] + c.success { _, _ in + XCTAssert(false, "This request should fail.") + } + c.failure { _, _ in + expFail2.fulfill() + } + c.finished { _, _ in + expFinsh2.fulfill() + } + c.complation { _, _, _ in + expComplete2.fulfill() + } + } + wait(for: [expFail2, expFinsh2, expComplete2], timeout: 10, enforceOrder: true) + } + + func testTaskObjectReturnsWhenSessionTaskMake() { + let cannotMakeDefine = RFAPIDefine() + cannotMakeDefine.name = RFAPIName(rawValue: "") + var request = api.request(define: cannotMakeDefine) { c in + c.complation { task, _, _ in + XCTAssertNil(task) + } + } + XCTAssertNil(request) + + request = api.request(name: "Anything"){ c in + c.complation { task, _, _ in + XCTAssertNotNil(task) + } + } + XCTAssertNotNil(request) + request?.cancel() + } + + func testCallbacksAlwaysCalledAfterFunctionReturns() { + let cannotMakeDefine = RFAPIDefine() + let cannotMakeCallbackExpectation = expectation(description: "callback") + let cannotMakeEndExpectation = expectation(description: "request called") + let request = api.request(define: cannotMakeDefine) { c in + c.identifier = "" + c.failure { task, error in + cannotMakeCallbackExpectation.fulfill() + XCTAssertTrue(Thread.isMainThread) + XCTAssertNil(task) + XCTAssertNotNil(error) + } + } + XCTAssertNil(request) + cannotMakeEndExpectation.fulfill() + // Callbacks should always have a delay. + wait(for: [cannotMakeEndExpectation, cannotMakeCallbackExpectation], timeout: 0.1, enforceOrder: true) + } + + func testKeepInstanceBeforeTaskComplation() { + let requestComplateExpectation = expectation(description: "Request Complate") + let managerDeallocExpectation = expectation(description: "Manager Dealloc") + + weak var apiInstance: TestAPI? + autoreleasepool { + let define = RFAPIDefine() + define.path = "https://httpbin.org/delay/2" + + let api = TestAPI() + api.deallocExpectation = managerDeallocExpectation + api.request(define: define) { c in + c.identifier = "" + c.complation { _, _, error in + XCTAssertNil(error) + requestComplateExpectation.fulfill() + } + } + apiInstance = api + } + XCTAssertNotNil(apiInstance) + wait(for: [requestComplateExpectation, managerDeallocExpectation], timeout: 10, enforceOrder: true) + } +} diff --git a/Tests/Shared/TestDefine.swift b/Tests/Shared/TestDefine.swift new file mode 100644 index 0000000..5316c30 --- /dev/null +++ b/Tests/Shared/TestDefine.swift @@ -0,0 +1,106 @@ +// +// TestDefine.swift +// RFAPI +// +// Created by BB9z on 2020/1/7. +// Copyright © 2020 RFUI. All rights reserved. +// + +import XCTest + +class TestDefine: XCTestCase { + + func testMerge() { + let r1 = RFAPIDefine() + r1.name = RFAPIName(rawValue: "R1") + r1.path = "111" + r1.method = "m1" + + let r2 = RFAPIDefine() + r2.name = RFAPIName(rawValue: "R2") + r2.path = "222" + r2.cachePolicy = .cachePolicyNoCache + + let r3 = r1.newDefineMergedDefault(r2) + XCTAssertEqual(r3.name, r1.name) + XCTAssertEqual(r3.path, r1.path) + XCTAssertEqual(r3.method, r1.method) + XCTAssertEqual(r3.cachePolicy, r2.cachePolicy) + XCTAssertNotEqual(r3.cachePolicy, r1.cachePolicy) + } + + func testCopy() { + let r1 = RFAPIDefine() + r1.path = "111" + r1.cachePolicy = .cachePolicyAlways + + let r2 = r1.copy() as! RFAPIDefine + XCTAssertFalse(r1.path! as NSString !== r2.path! as NSString) + XCTAssertEqual(r1.path, r2.path) + r2.path = "222" + XCTAssertNotEqual(r1.path, r2.path) + + XCTAssertEqual(r1.cachePolicy, r2.cachePolicy) + r2.cachePolicy = .cachePolicyExpire + XCTAssertNotEqual(r1.cachePolicy, r2.cachePolicy) + } + + func testCoding() { + // Test load v1 data + let dataURL = Bundle(for: type(of: self)).url(forResource: "archived_define_v1", withExtension: "plist")! + let data = try! Data(contentsOf: dataURL) + let defineV1: RFAPIDefine = try! NSKeyedUnarchiver.unarchivedObject(ofClass: RFAPIDefine.self, from: data)! + XCTAssertNotNil(defineV1) + debugPrint(defineV1) + + // This define has same properties with the v1 one, except pathPrefix. + let define = RFAPIDefine() + define.name = RFAPIName(rawValue: "ArchiveTest") + define.baseURL = URL(string: "http://example.com") + define.pathPrefix = "api/v2/" + define.path = "do" + define.method = "PUT" + define.httpRequestHeaders = ["xxx-foo": "bar"] + define.defaultParameters = ["query": "text"] + define.needsAuthorization = true + define.requestSerializerClass = AFHTTPRequestSerializer.self + define.cachePolicy = .cachePolicyExpire + define.expire = 300 + define.offlinePolicy = .offlinePolicyLoadCache + define.responseSerializerClass = AFHTTPResponseSerializer.self + define.responseExpectType = .object + define.responseClass = "RFAPIDefine" + define.responseAcceptNull = true + define.userInfo = ["user": "info"] + define.notes = "This is a test" + + let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(NSUUID().uuidString) + XCTAssertFalse(FileManager.default.fileExists(atPath: fileURL.path)) + let dataV2 = try! NSKeyedArchiver.archivedData(withRootObject: define, requiringSecureCoding: true) + XCTAssert(dataV2.count > 100) + try! dataV2.write(to: fileURL, options: []) + XCTAssertTrue(FileManager.default.fileExists(atPath: fileURL.path)) + + let defineV2: RFAPIDefine = try! NSKeyedUnarchiver.unarchivedObject(ofClass: RFAPIDefine.self, from: dataV2)! + XCTAssertNotNil(defineV2) + XCTAssertEqual(defineV1.name, defineV2.name) + XCTAssertEqual(defineV1.baseURL, defineV2.baseURL) + XCTAssertNotEqual(defineV1.pathPrefix, defineV2.pathPrefix) + XCTAssertEqual(defineV1.path, defineV2.path) + XCTAssertEqual(defineV1.method, defineV2.method) + XCTAssertEqual(defineV1.httpRequestHeaders as! [String: String], defineV2.httpRequestHeaders as! [String: String]) + XCTAssertEqual(defineV1.defaultParameters as! [String: String], defineV2.defaultParameters as! [String: String]) + XCTAssertEqual(defineV1.needsAuthorization, defineV2.needsAuthorization) + XCTAssertTrue(defineV1.requestSerializerClass === defineV2.requestSerializerClass) + XCTAssertEqual(defineV1.cachePolicy, defineV2.cachePolicy) + XCTAssertEqual(defineV1.expire, defineV2.expire) + XCTAssertEqual(defineV1.offlinePolicy, defineV2.offlinePolicy) + XCTAssertTrue(defineV1.responseSerializerClass === defineV2.responseSerializerClass) + XCTAssertEqual(defineV1.responseExpectType, defineV2.responseExpectType) + XCTAssertEqual(defineV1.responseClass, defineV2.responseClass) + XCTAssertEqual(defineV1.responseExpectType, defineV2.responseExpectType) + XCTAssertEqual(defineV1.responseAcceptNull, defineV2.responseAcceptNull) + XCTAssertEqual(defineV1.userInfo as! [String: String], defineV2.userInfo as! [String: String]) + XCTAssertEqual(defineV1.notes, defineV2.notes) + } +} diff --git a/Tests/Shared/TestDefineManager.swift b/Tests/Shared/TestDefineManager.swift new file mode 100644 index 0000000..fbcf059 --- /dev/null +++ b/Tests/Shared/TestDefineManager.swift @@ -0,0 +1,131 @@ +// +// TestDefineManager.swift +// RFAPI +// +// Created by BB9z on 29/03/2018. +// Copyright © 2018 RFUI. All rights reserved. +// + +import XCTest + +@objc(TestRequestSerializer) +class TestRequestSerializer: AFHTTPRequestSerializer {} + +@objc(TestResponseSerializer) +class TestResponseSerializer: AFHTTPResponseSerializer {} + +class TestDefineManager: XCTestCase { + + lazy var manager = RFAPIDefineManager() + lazy var rawConfig: [String: [String: Any]] = { + let dataURL = Bundle(for: type(of: self)).url(forResource: "test_defines", withExtension: "plist")! + return NSDictionary(contentsOf: dataURL) as! [String: [String: Any]] + }() + + override func setUp() { + super.setUp() + manager.setDefinesWithRulesInfo(rawConfig) + } + + func testRuleLoad() { + XCTAssertNotNil(manager.defaultDefine) + } + + func testDefinesSetter() { + XCTAssert(manager.defines.count >= 2) + manager.defines = [] + XCTAssert(manager.defines.count == 0) + + let define = RFAPIDefine() + XCTAssertThrowsError(try RTHelper.catchException { + manager.defines = [ define ] + }) + XCTAssertEqual(manager.defines, []) + define.name = RFAPIName(rawValue: "test") + XCTAssertNoThrow(manager.defines = [ define ]) + XCTAssertEqual(manager.defines, [ define ]) + } + + func testSerializersGet() { + XCTAssert(type(of: manager.defaultRequestSerializer) === AFJSONRequestSerializer.self) + XCTAssert(type(of: manager.defaultResponseSerializer) === AFJSONResponseSerializer.self) + + let testSerializersDefine = manager.define(forName: RFAPIName(rawValue: "Serializers")) + XCTAssertNotNil(testSerializersDefine) + let testRequestS = manager.requestSerializer(for: testSerializersDefine) + XCTAssert(type(of: testRequestS) === TestRequestSerializer.self) + let testResponseS = manager.responseSerializer(for: testSerializersDefine) + XCTAssert(type(of: testResponseS) === TestResponseSerializer.self) + + let noSerializersDefine = manager.define(forName: RFAPIName(rawValue: "NoSerializers")) + let noRequestS = manager.requestSerializer(for: noSerializersDefine) + XCTAssert(noRequestS === manager.defaultRequestSerializer) + let noResponseS = manager.responseSerializer(for: noSerializersDefine) + XCTAssert(noResponseS === manager.defaultResponseSerializer) + } + + func testAllPropertiesLoad() { + let define2 = manager.define(forName: RFAPIName(rawValue: "AllProperties"))! + XCTAssertNotNil(define2) + XCTAssertEqual("AllProperties", define2.name!.rawValue) + XCTAssertEqual(URL(string: "http://example.com"), define2.baseURL) + XCTAssertEqual("api/v2/", define2.pathPrefix) + XCTAssertEqual("do", define2.path) + XCTAssertEqual("PUT", define2.method) + XCTAssertEqual(["xxx-foo": "bar"], define2.httpRequestHeaders as! [String: String]) + XCTAssertEqual(["query": "text"], define2.defaultParameters as! [String: String]) + XCTAssertEqual(true, define2.needsAuthorization) + XCTAssertTrue(AFHTTPRequestSerializer.self === define2.requestSerializerClass) + XCTAssertEqual(RFAPIDefineCachePolicy.cachePolicyExpire, define2.cachePolicy) + XCTAssertEqual(300, define2.expire) + XCTAssertEqual(RFAPIDefineOfflinePolicy.offlinePolicyLoadCache, define2.offlinePolicy) + XCTAssertTrue(AFHTTPResponseSerializer.self === define2.responseSerializerClass) + XCTAssertEqual(RFAPIDefineResponseExpectType.object, define2.responseExpectType) + XCTAssertEqual("RFAPIDefine", define2.responseClass) + XCTAssertEqual(true, define2.responseAcceptNull) + XCTAssertEqual(["user": "info"], define2.userInfo as! [String: String]) + XCTAssertEqual("This is a test", define2.notes) + } + + func testMakeURL() { + let define = RFAPIDefine() + define.path = "zz://?a=b" + + let p = [RFAPIRequestForceQuryStringParametersKey: ["a2": 123]] as NSMutableDictionary + let url = try! manager.requestURL(for: define, parameters: p) + XCTAssertEqual(url.absoluteString, "zz://?a=b&a2=123") + } + + func testMakeURLParametersInPath() { + let define = RFAPIDefine() + define.path = "zz://example.com:{port}/{path}/{no-exsist}?{q1}=b" + + let p = ["port": 8080, "path": "hello-word", "q1": "key1", "key2": "c" ] as NSMutableDictionary + let url = try! manager.requestURL(for: define, parameters: p) + // No key2 + XCTAssertEqual(url.absoluteString, "zz://example.com:8080/hello-word/?key1=b") + } + + func testMakeURLBaseAndPrefix() { + let define = RFAPIDefine() + define.baseURL = URL(string: "http://example.com/api/") + define.pathPrefix = "/root/" + define.path = "doit" + + var url = try! manager.requestURL(for: define, parameters: nil) + XCTAssertEqual(url.absoluteString, "http://example.com/root/doit") + + define.pathPrefix = "relative/" + url = try! manager.requestURL(for: define, parameters: nil) + XCTAssertEqual(url.absoluteString, "http://example.com/api/relative/doit") + + define.path = "/doit/" + url = try! manager.requestURL(for: define, parameters: nil) + XCTAssertEqual(url.absoluteString, "http://example.com/api/relative//doit/") + + define.pathPrefix = "foo" + define.path = "bar" + url = try! manager.requestURL(for: define, parameters: nil) + XCTAssertEqual(url.absoluteString, "http://example.com/api/foobar") + } +} diff --git a/Tests/Shared/TestRequest.swift b/Tests/Shared/TestRequest.swift new file mode 100644 index 0000000..30ef1b0 --- /dev/null +++ b/Tests/Shared/TestRequest.swift @@ -0,0 +1,149 @@ +// +// TestRequest.swift +// RFAPI +// +// Created by BB9z on 2020/1/10. +// Copyright © 2020 RFUI. All rights reserved. +// + +import XCTest + +class TestAPI: RFAPI { + var deallocExpectation: XCTestExpectation? + + deinit { + deallocExpectation?.fulfill() + } +} + +class TestRequest: XCTestCase { + + // No default base url + lazy var api: TestAPI = { + let api = TestAPI() + let uc = URLSessionConfiguration.default + uc.timeoutIntervalForRequest = 5 + api.sessionConfiguration = uc + let defineConfigURL = Bundle(for: type(of: self)).url(forResource: "test_defines", withExtension: "plist")! + let defineConfig = NSDictionary(contentsOf: defineConfigURL) as! [String: [String: Any]] + api.defineManager.setDefinesWithRulesInfo(defineConfig) + return api + }() + + func testV1Interface() { + let successExpectation = expectation(description: "Request Succeed") + let completeExpectation = expectation(description: "Request Completed") + let context = RFAPIRequestConext() + context.groupIdentifier = "v1" + api.request(withName: "Anything", parameters: ["path": "lookup"], controlInfo: context, success: { task, response in + guard let rsp = response as? [String: Any] else { + XCTAssert(false, "Response invalid.") + return + } + XCTAssertEqual(rsp["url"] as! String?, "https://httpbin.org/anything/lookup") + successExpectation.fulfill() + }, failure: { task, error in + XCTAssert(false, error.localizedDescription) + }) { _ in + completeExpectation.fulfill() + } + wait(for: [successExpectation, completeExpectation], timeout: 10, enforceOrder: true) + } + + func testIdentifierControl() { + + } + + func testFormUpload() { + + } + + func testTaskCancel() { + + } + + func testTimeout() { + let completeExpectation = expectation(description: "") + let request = api.request(name: "Delay") { c in + c.timeoutInterval = 1 + c.parameters = ["time": 10] + c.success { _, _ in + XCTAssert(false, "This request should fail.") + } + c.failure { task, error in + XCTAssertNotNil(task) + XCTAssertNotNil(error) + let e = error as NSError + XCTAssertEqual(e.code, NSURLErrorTimedOut) + XCTAssertEqual(e.domain, NSURLErrorDomain) + } + c.finished { task, s in + XCTAssertNotNil(task) + XCTAssertFalse(s) + } + c.complation { task, rsp, error in + debugPrint(error!) + XCTAssertNotNil(task) + XCTAssertNil(rsp) + XCTAssertNotNil(error) + completeExpectation.fulfill() + } + } + XCTAssertEqual(request?.originalRequest?.timeoutInterval, 1) + wait(for: [completeExpectation], timeout: 10) + XCTAssertNotNil(request?.error) + } + + func testHTTPStatusError() { + let completeExpectation = expectation(description: "") + let request = api.request(name: "404") { c in + c.success { _, _ in + XCTAssert(false, "This request should fail.") + } + c.failure { task, error in + XCTAssertNotNil(task) + XCTAssertNotNil(error) + } + c.finished { task, s in + XCTAssertNotNil(task) + XCTAssertFalse(s) + } + c.complation { task, rsp, error in + debugPrint(error!) + XCTAssertNotNil(task) + XCTAssertNil(rsp) + XCTAssertNotNil(error) + completeExpectation.fulfill() + } + } + wait(for: [completeExpectation], timeout: 10) + XCTAssertNotNil(request?.error) + guard let httpResponse = request?.response as? HTTPURLResponse else { + XCTAssert(false, "Response should be HTTPURLResponse") + return + } + XCTAssertEqual(httpResponse.statusCode, 404) + } + + func testRedirects() { + let successExpectation = expectation(description: "Request Succeed") + + let redirectTimesDefine = RFAPIDefine() + redirectTimesDefine.path = "https://httpbin.org/redirect/3" + let request = api.request(define: redirectTimesDefine) { c in + c.identifier = "" + c.success { task, response in + guard let rsp = response as? [String: Any] else { + XCTAssert(false, "Response invalid.") + return + } + print(rsp) + XCTAssertEqual(task.originalRequest?.url?.absoluteURL.absoluteString, "https://httpbin.org/redirect/3") + XCTAssertEqual(task.currentRequest?.url?.absoluteURL.absoluteString, "https://httpbin.org/get") + successExpectation.fulfill() + } + } + wait(for: [successExpectation], timeout: 15, enforceOrder: true) + XCTAssertNil(request?.error) + } +} diff --git a/Tests/Shared/TestsBridgingHeader.h b/Tests/Shared/TestsBridgingHeader.h new file mode 100644 index 0000000..21bd7f8 --- /dev/null +++ b/Tests/Shared/TestsBridgingHeader.h @@ -0,0 +1,11 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import +#import +#import +#import +#import +#import "RTHelper.h" diff --git a/Tests/Shared/archived_define_v1.plist b/Tests/Shared/archived_define_v1.plist new file mode 100644 index 0000000..db41b2d Binary files /dev/null and b/Tests/Shared/archived_define_v1.plist differ diff --git a/Tests/Shared/test_defines.plist b/Tests/Shared/test_defines.plist new file mode 100644 index 0000000..4d5a3a4 --- /dev/null +++ b/Tests/Shared/test_defines.plist @@ -0,0 +1,91 @@ + + + + + DEFAULT + + Authorization + + + AllProperties + + Base + http://example.com + Path Prefix + api/v2/ + Path + do + Method + PUT + Headers + + xxx-foo + bar + + Parameters + + query + text + + Authorization + + Serializer + AFHTTPRequestSerializer + Cache Policy + 3 + Expire + 300 + Offline Policy + 1 + Response Serializer + AFHTTPResponseSerializer + Response Type + 2 + Response Accept Null + + Response Class + RFAPIDefine + User Info + + user + info + + Notes + This is a test + + Serializers + + Serializer + TestRequestSerializer + Response Serializer + TestResponseSerializer + + NoSerializers + + @ HTTPBin + + Status + + Path + https://httpbin.org/status/{code} + + Anything + + Path + https://httpbin.org/anything/{path} + Method + POST + + 404 + + Path + https://httpbin.org/status/404 + + Delay + + Path + https://httpbin.org/delay/{time} + + + + diff --git a/Test/iOS/Info.plist b/Tests/iOS/Info.plist similarity index 100% rename from Test/iOS/Info.plist rename to Tests/iOS/Info.plist diff --git a/Test/macOS/Info.plist b/Tests/macOS/Info.plist similarity index 100% rename from Test/macOS/Info.plist rename to Tests/macOS/Info.plist diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..951b97b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "Tests" diff --git a/en.lproj/RFAPI.strings b/en.lproj/RFAPI.strings deleted file mode 100644 index f731183..0000000 --- a/en.lproj/RFAPI.strings +++ /dev/null @@ -1,7 +0,0 @@ -/* - RFAPI.strings - RFDemo - - Created by BB9z on 3/29/16. - Copyright © 2016 RFUI. All rights reserved. -*/ diff --git a/zh-Hans.lproj/RFAPI.strings b/zh-Hans.lproj/RFAPI.strings deleted file mode 100644 index f731183..0000000 --- a/zh-Hans.lproj/RFAPI.strings +++ /dev/null @@ -1,7 +0,0 @@ -/* - RFAPI.strings - RFDemo - - Created by BB9z on 3/29/16. - Copyright © 2016 RFUI. All rights reserved. -*/