Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bb44925c8 | |||
| 320aeb815b | |||
| ab4b678498 | |||
| 52bf2071a5 | |||
| f0bb931a64 | |||
| 30f1fecc54 | |||
| 85a424a824 | |||
| 6405bf40e3 | |||
| b79fd26ca4 | |||
| caadcce7f1 | |||
| c250200d03 | |||
| 51c05087e7 | |||
| 7a2e65f292 | |||
| 0489c09ba3 | |||
| 70038d244d | |||
| a9e0dedd31 | |||
| f6ad51219d | |||
| a42af79040 | |||
| 792634527a | |||
| 5627219c56 | |||
| 4e81d4b476 | |||
| 001d58cd89 | |||
| 03c96d8fdb | |||
| b5423192fb | |||
| 1efb40a07e | |||
| 6c6023dc84 | |||
| a6dc4b010c | |||
| dd87da4134 | |||
| 627ff6cbe2 | |||
| 9cc8435cae | |||
| 3d977450ca | |||
| f7c482ceed | |||
| c38b90ee60 | |||
| 70491431fa | |||
| 6bc055911e | |||
| 9b1e13b963 | |||
| 74a73893d4 | |||
| 4a3ab17851 | |||
| 5b8efe71a7 | |||
| b7f2d9bcbe | |||
| f4efc6dbbf | |||
| efab760253 | |||
| 48826e2160 | |||
| 0b4e231814 | |||
| 6f2d811338 | |||
| 9c9ce5e2e1 | |||
| 08b4559b26 | |||
| 913ad5e2c6 | |||
| 7649dc616c | |||
| efabb29a52 | |||
| e3612e31d7 | |||
| 2575d2eaee |
@@ -0,0 +1,8 @@
|
||||
language: objective-c
|
||||
xcode_workspace: FLEX.xcworkspace
|
||||
matrix:
|
||||
include:
|
||||
- xcode_scheme: UICatalog
|
||||
xcode_sdk: iphonesimulator
|
||||
- xcode_scheme: FLEX
|
||||
xcode_sdk: iphonesimulator
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// FLEXWindow.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/13/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXWindow.h"
|
||||
|
||||
@implementation FLEXWindow
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
// Some apps have windows at UIWindowLevelStatusBar + n.
|
||||
// If we make the window level too high, we block out UIAlertViews.
|
||||
// There's a balance between staying above the app's windows and staying below alerts.
|
||||
// UIWindowLevelStatusBar + 100 seems to hit that balance.
|
||||
self.windowLevel = UIWindowLevelStatusBar + 100.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
BOOL pointInside = NO;
|
||||
if ([self.eventDelegate shouldHandleTouchAtPoint:point]) {
|
||||
pointInside = [super pointInside:point withEvent:event];
|
||||
}
|
||||
return pointInside;
|
||||
}
|
||||
|
||||
@end
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
@property (nonatomic, weak) id <FLEXExplorerViewControllerDelegate> delegate;
|
||||
|
||||
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
|
||||
- (BOOL)wantsWindowToBecomeKey;
|
||||
|
||||
@end
|
||||
|
||||
+27
-71
@@ -120,77 +120,27 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Status Bar Wrangling for iOS 7
|
||||
|
||||
// Try to get the preferred status bar properties from the app's root view controller (not us).
|
||||
// In general, our window shouldn't be the key window when this view controller is asked about the status bar.
|
||||
// However, we guard against infinite recursion and provide a reasonable default for status bar behavior in case our window is the keyWindow.
|
||||
|
||||
- (UIViewController *)viewControllerForStatusBarAndOrientationProperties
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [[[UIApplication sharedApplication] keyWindow] rootViewController];
|
||||
|
||||
// On iPhone, modal view controllers get asked
|
||||
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
|
||||
while (viewControllerToAsk.presentedViewController) {
|
||||
viewControllerToAsk = viewControllerToAsk.presentedViewController;
|
||||
}
|
||||
}
|
||||
|
||||
return viewControllerToAsk;
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
UIStatusBarStyle preferredStyle = UIStatusBarStyleDefault;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
// We might need to foward to a child
|
||||
UIViewController *childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarStyle];
|
||||
while (childViewControllerToAsk && childViewControllerToAsk != viewControllerToAsk) {
|
||||
viewControllerToAsk = childViewControllerToAsk;
|
||||
childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarStyle];
|
||||
}
|
||||
|
||||
preferredStyle = [viewControllerToAsk preferredStatusBarStyle];
|
||||
}
|
||||
return preferredStyle;
|
||||
}
|
||||
|
||||
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
UIStatusBarAnimation preferredAnimation = UIStatusBarAnimationFade;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
preferredAnimation = [viewControllerToAsk preferredStatusBarUpdateAnimation];
|
||||
}
|
||||
return preferredAnimation;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
BOOL prefersHidden = NO;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
// Again, we might need to forward to a child
|
||||
UIViewController *childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarHidden];
|
||||
while (childViewControllerToAsk && childViewControllerToAsk != viewControllerToAsk) {
|
||||
viewControllerToAsk = childViewControllerToAsk;
|
||||
childViewControllerToAsk = [viewControllerToAsk childViewControllerForStatusBarHidden];
|
||||
}
|
||||
|
||||
prefersHidden = [viewControllerToAsk prefersStatusBarHidden];
|
||||
}
|
||||
return prefersHidden;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Rotation
|
||||
|
||||
- (NSUInteger)supportedInterfaceOrientations
|
||||
- (UIViewController *)viewControllerForRotationAndOrientation
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
NSUInteger supportedOrientations = [FLEXUtility infoPlistSupportedInterfaceOrientationsMask];
|
||||
UIWindow *window = self.previousKeyWindow ?: [[UIApplication sharedApplication] keyWindow];
|
||||
UIViewController *viewController = window.rootViewController;
|
||||
NSString *viewControllerSelectorString = [@[@"_vie", @"wContro", @"llerFor", @"Supported", @"Interface", @"Orientations"] componentsJoinedByString:@""];
|
||||
SEL viewControllerSelector = NSSelectorFromString(viewControllerSelectorString);
|
||||
if ([viewController respondsToSelector:viewControllerSelector]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
viewController = [viewController performSelector:viewControllerSelector];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
return viewController;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForRotationAndOrientation];
|
||||
UIInterfaceOrientationMask supportedOrientations = [FLEXUtility infoPlistSupportedInterfaceOrientationsMask];
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
supportedOrientations = [viewControllerToAsk supportedInterfaceOrientations];
|
||||
}
|
||||
@@ -206,7 +156,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForStatusBarAndOrientationProperties];
|
||||
UIViewController *viewControllerToAsk = [self viewControllerForRotationAndOrientation];
|
||||
BOOL shouldAutorotate = YES;
|
||||
if (viewControllerToAsk && viewControllerToAsk != self) {
|
||||
shouldAutorotate = [viewControllerToAsk shouldAutorotate];
|
||||
@@ -860,9 +810,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
|
||||
- (void)resignKeyAndDismissViewControllerAnimated:(BOOL)animated completion:(void (^)(void))completion
|
||||
{
|
||||
[self.previousKeyWindow makeKeyWindow];
|
||||
|
||||
UIWindow *previousKeyWindow = self.previousKeyWindow;
|
||||
self.previousKeyWindow = nil;
|
||||
[previousKeyWindow makeKeyWindow];
|
||||
[[previousKeyWindow rootViewController] setNeedsStatusBarAppearanceUpdate];
|
||||
|
||||
// Restore the status bar window's normal window level.
|
||||
// We want it above FLEX while a modal is presented for scroll to top, but below FLEX otherwise for exploration.
|
||||
@@ -874,4 +825,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
|
||||
[self dismissViewControllerAnimated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (BOOL)wantsWindowToBecomeKey
|
||||
{
|
||||
return self.previousKeyWindow != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXManager : NSObject
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
return [self.explorerViewController shouldReceiveTouchAtWindowPoint:pointInWindow];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
// Only when the explorer view controller wants it because it needs to accept key input & affect the status bar.
|
||||
return [self.explorerViewController wantsWindowToBecomeKey];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - FLEXExplorerViewControllerDelegate
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
@protocol FLEXWindowEventDelegate <NSObject>
|
||||
|
||||
- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow;
|
||||
- (BOOL)canBecomeKeyWindow;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// FLEXWindow.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Ryan Olson on 4/13/14.
|
||||
// Copyright (c) 2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXWindow.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@implementation FLEXWindow
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
// Some apps have windows at UIWindowLevelStatusBar + n.
|
||||
// If we make the window level too high, we block out UIAlertViews.
|
||||
// There's a balance between staying above the app's windows and staying below alerts.
|
||||
// UIWindowLevelStatusBar + 100 seems to hit that balance.
|
||||
self.windowLevel = UIWindowLevelStatusBar + 100.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
BOOL pointInside = NO;
|
||||
if ([self.eventDelegate shouldHandleTouchAtPoint:point]) {
|
||||
pointInside = [super pointInside:point withEvent:event];
|
||||
}
|
||||
return pointInside;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAffectStatusBarAppearance
|
||||
{
|
||||
return [self isKeyWindow];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
return [self.eventDelegate canBecomeKeyWindow];
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
// This adds a method (superclass override) at runtime which gives us the status bar behavior we want.
|
||||
// The FLEX window is intended to be an overlay that generally doesn't affect the app underneath.
|
||||
// Most of the time, we want the app's main window(s) to be in control of status bar behavior.
|
||||
// Done at runtime with an obfuscated selector because it is private API. But you shoudn't ship this to the App Store anyways...
|
||||
NSString *canAffectSelectorString = [@[@"_can", @"Affect", @"Status", @"Bar", @"Appearance"] componentsJoinedByString:@""];
|
||||
SEL canAffectSelector = NSSelectorFromString(canAffectSelectorString);
|
||||
Method shouldAffectMethod = class_getInstanceMethod(self, @selector(shouldAffectStatusBarAppearance));
|
||||
IMP canAffectImplementation = method_getImplementation(shouldAffectMethod);
|
||||
class_addMethod(self, canAffectSelector, canAffectImplementation, method_getTypeEncoding(shouldAffectMethod));
|
||||
|
||||
// One more...
|
||||
NSString *canBecomeKeySelectorString = [NSString stringWithFormat:@"_%@", NSStringFromSelector(@selector(canBecomeKeyWindow))];
|
||||
SEL canBecomeKeySelector = NSSelectorFromString(canBecomeKeySelectorString);
|
||||
Method canBecomeKeyMethod = class_getInstanceMethod(self, @selector(canBecomeKeyWindow));
|
||||
IMP canBecomeKeyImplementation = method_getImplementation(canBecomeKeyMethod);
|
||||
class_addMethod(self, canBecomeKeySelector, canBecomeKeyImplementation, method_getTypeEncoding(canBecomeKeyMethod));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// FLEX.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Eric Horacek on 7/18/15.
|
||||
// Copyright (c) 2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for FLEX.
|
||||
FOUNDATION_EXPORT double FLEXVersionNumber;
|
||||
|
||||
//! Project version string for FLEX.
|
||||
FOUNDATION_EXPORT const unsigned char FLEXVersionString[];
|
||||
|
||||
#import <FLEX/FLEXManager.h>
|
||||
+5
@@ -7,11 +7,14 @@
|
||||
//
|
||||
|
||||
#import "FLEXFileBrowserFileOperationController.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXFileBrowserFileDeleteOperationController () <UIAlertViewDelegate>
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *path;
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXFileBrowserFileDeleteOperationController
|
||||
@@ -73,6 +76,8 @@
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *path;
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXFileBrowserFileRenameOperationController
|
||||
+4
-4
@@ -316,7 +316,7 @@
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForCell:sender];
|
||||
indexPath = [self.searchController.searchResultsTableView indexPathForCell:sender];
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
NSString *subpath = [self.childPaths objectAtIndex:indexPath.row];
|
||||
fullPath = [self.path stringByAppendingPathComponent:subpath];
|
||||
} else {
|
||||
indexPath = [self.searchDisplayController.searchResultsTableView indexPathForCell:sender];
|
||||
indexPath = [self.searchController.searchResultsTableView indexPathForCell:sender];
|
||||
fullPath = [self.searchPaths objectAtIndex:indexPath.row];
|
||||
}
|
||||
|
||||
@@ -345,9 +345,9 @@
|
||||
|
||||
- (void)reloadDisplayedPaths
|
||||
{
|
||||
if (self.searchDisplayController.isActive) {
|
||||
if (self.searchController.isActive) {
|
||||
[self reloadSearchPaths];
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
} else {
|
||||
[self reloadChildPaths];
|
||||
[self.tableView reloadData];
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -62,7 +62,10 @@
|
||||
|
||||
UISearchBar *searchBar = [[UISearchBar alloc] init];
|
||||
[searchBar sizeToFit];
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
|
||||
#pragma clang diagnostic pop
|
||||
self.searchController.delegate = self;
|
||||
self.searchController.searchResultsDataSource = self;
|
||||
self.searchController.searchResultsDelegate = self;
|
||||
@@ -229,10 +232,10 @@
|
||||
for (UITableView *tableView in tableViews) {
|
||||
for (FLEXNetworkTransactionTableViewCell *cell in [tableView visibleCells]) {
|
||||
if ([cell.transaction isEqual:transaction]) {
|
||||
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
|
||||
if (indexPath) {
|
||||
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
}
|
||||
// Using -[UITableView reloadRowsAtIndexPaths:withRowAnimation:] is overkill here and kicks off a lot of
|
||||
// work that can make the table view somewhat unresponseive when lots of updates are streaming in.
|
||||
// We just need to tell the cell that it needs to re-layout.
|
||||
[cell setNeedsLayout];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -358,9 +361,9 @@
|
||||
return [[transaction.request.URL absoluteString] rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
|
||||
}]];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self.searchDisplayController.searchBar.text isEqual:searchString]) {
|
||||
if ([self.searchController.searchBar.text isEqual:searchString]) {
|
||||
self.filteredNetworkTransactions = filteredNetworkTransactions;
|
||||
[self.searchDisplayController.searchResultsTableView reloadData];
|
||||
[self.searchController.searchResultsTableView reloadData];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,6 +103,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
|
||||
{
|
||||
NSDate *startDate = [NSDate date];
|
||||
|
||||
if (redirectResponse) {
|
||||
[self recordResponseReceivedWithRequestID:requestID response:redirectResponse];
|
||||
[self recordLoadingFinishedWithRequestID:requestID responseBody:nil];
|
||||
@@ -112,7 +114,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
FLEXNetworkTransaction *transaction = [[FLEXNetworkTransaction alloc] init];
|
||||
transaction.requestID = requestID;
|
||||
transaction.request = request;
|
||||
transaction.startTime = [NSDate date];
|
||||
transaction.startTime = startDate;
|
||||
|
||||
[self.orderedTransactions insertObject:transaction atIndex:0];
|
||||
[self.networkTransactionsForRequestIdentifiers setObject:transaction forKey:requestID];
|
||||
@@ -124,6 +126,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response
|
||||
{
|
||||
NSDate *responseDate = [NSDate date];
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
if (!transaction) {
|
||||
@@ -131,7 +135,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
}
|
||||
transaction.response = response;
|
||||
transaction.transactionState = FLEXNetworkTransactionStateReceivingData;
|
||||
transaction.latency = -[transaction.startTime timeIntervalSinceNow];
|
||||
transaction.latency = -[transaction.startTime timeIntervalSinceDate:responseDate];
|
||||
|
||||
[self postUpdateNotificationForTransaction:transaction];
|
||||
});
|
||||
@@ -152,13 +156,15 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
|
||||
|
||||
- (void)recordLoadingFinishedWithRequestID:(NSString *)requestID responseBody:(NSData *)responseBody
|
||||
{
|
||||
NSDate *finishedDate = [NSDate date];
|
||||
|
||||
dispatch_async(self.queue, ^{
|
||||
FLEXNetworkTransaction *transaction = [self.networkTransactionsForRequestIdentifiers objectForKey:requestID];
|
||||
if (!transaction) {
|
||||
return;
|
||||
}
|
||||
transaction.transactionState = FLEXNetworkTransactionStateFinished;
|
||||
transaction.duration = -[transaction.startTime timeIntervalSinceNow];
|
||||
transaction.duration = -[transaction.startTime timeIntervalSinceDate:finishedDate];
|
||||
|
||||
BOOL shouldCache = [responseBody length] > 0;
|
||||
if (!self.shouldCacheMediaResponses) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "UIKit/UIKit.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
|
||||
FLEXNetworkTransactionStateUnstarted,
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// which Square, Inc. licenses this file to you.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
|
||||
|
||||
/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system.
|
||||
|
||||
@@ -102,7 +102,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
+ (void)setShouldEnableOnLaunch:(BOOL)shouldEnableOnLaunch
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:kFLEXNetworkObserverEnableOnLaunchDefaultsKey];
|
||||
[[NSUserDefaults standardUserDefaults] setBool:shouldEnableOnLaunch forKey:kFLEXNetworkObserverEnableOnLaunchDefaultsKey];
|
||||
}
|
||||
|
||||
+ (BOOL)shouldEnableOnLaunch
|
||||
@@ -149,6 +149,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
/// The superclass implementation (and implementations in classes above that) will be executed without inteference if called from the original implementation.
|
||||
+ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock
|
||||
{
|
||||
// If we don't have an object to detect nested calls on, just run the original implmentation and bail.
|
||||
// This case can happen if someone besides the URL loading system calls the delegate methods directly.
|
||||
// See https://github.com/Flipboard/FLEX/issues/61 for an example.
|
||||
if (!object) {
|
||||
originalImplementationBlock();
|
||||
return;
|
||||
}
|
||||
|
||||
const void *key = selector;
|
||||
|
||||
// Don't run the sniffing block if we're inside a nested call
|
||||
@@ -344,15 +352,20 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = [NSURLSessionTask class];
|
||||
// In iOS 7 resume lives in __NSCFLocalSessionTask
|
||||
// In iOS 8 resume lives in NSURLSessionTask
|
||||
// In iOS 9 resume lives in __NSCFURLSessionTask
|
||||
Class class = Nil;
|
||||
if (![[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
|
||||
class = NSClassFromString([@[@"__", @"NSC", @"FLocalS", @"ession", @"Task"] componentsJoinedByString:@""]);
|
||||
} else if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 9) {
|
||||
class = [NSURLSessionTask class];
|
||||
} else {
|
||||
class = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]);
|
||||
}
|
||||
SEL selector = @selector(resume);
|
||||
SEL swizzledSelector = [self swizzledSelectorForSelector:selector];
|
||||
|
||||
if ([self instanceRespondsButDoesNotImplementSelector:selector class:class]) {
|
||||
// Dummy NSURLSessionTask to get the actual class, needed for iOS 7 (__NSCFURLSessionTask)
|
||||
class = [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"about:blank"]] superclass];
|
||||
}
|
||||
|
||||
Method originalResume = class_getInstanceMethod(class, selector);
|
||||
|
||||
void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) {
|
||||
@@ -457,7 +470,6 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
// The method signatures here are close enough that we can use the same logic to inject into all of them.
|
||||
const SEL selectors[] = {
|
||||
@selector(dataTaskWithHTTPGetRequest:completionHandler:),
|
||||
@selector(dataTaskWithRequest:completionHandler:),
|
||||
@selector(dataTaskWithURL:completionHandler:),
|
||||
@selector(downloadTaskWithRequest:completionHandler:),
|
||||
@@ -479,7 +491,10 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
|
||||
NSURLSessionTask *(^asyncDataOrDownloadSwizzleBlock)(Class, id, NSURLSessionAsyncCompletion) = ^NSURLSessionTask *(Class slf, id argument, NSURLSessionAsyncCompletion completion) {
|
||||
NSURLSessionTask *task = nil;
|
||||
if ([FLEXNetworkObserver isEnabled]) {
|
||||
// If completion block was not provided sender expect to receive delegated methods or does not
|
||||
// interested in callback at all. In this case we should just call original method implementation
|
||||
// with nil completion block.
|
||||
if ([FLEXNetworkObserver isEnabled] && completion) {
|
||||
NSString *requestID = [self nextRequestID];
|
||||
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
|
||||
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
|
||||
@@ -857,8 +872,6 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
|
||||
Method method = class_getInstanceMethod(cls, selector);
|
||||
struct objc_method_description methodDescription = *method_getDescription(method);
|
||||
|
||||
typedef void (^NSURLSessionTaskDidCompleteWithErrorBlock)(id slf, SEL sel);
|
||||
|
||||
BOOL (^undefinedBlock)(id <NSURLSessionTaskDelegate>, SEL) = ^(id slf, SEL sel) {
|
||||
return YES;
|
||||
};
|
||||
@@ -1143,7 +1156,8 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
|
||||
FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID];
|
||||
|
||||
if (!requestState.dataAccumulator) {
|
||||
requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)totalBytesExpectedToWrite];
|
||||
NSUInteger unsignedBytesExpectedToWrite = totalBytesExpectedToWrite > 0 ? totalBytesExpectedToWrite : 0;
|
||||
requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:unsignedBytesExpectedToWrite];
|
||||
[[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:downloadTask.response];
|
||||
|
||||
NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDownloadTask (delegate: %@)", [delegate class]];
|
||||
|
||||
@@ -206,6 +206,13 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
|
||||
{
|
||||
id value = nil;
|
||||
const char *type = ivar_getTypeEncoding(ivar);
|
||||
#ifdef __arm64__
|
||||
// See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
|
||||
const char *name = ivar_getName(ivar);
|
||||
if (type[0] == @encode(Class)[0] && strcmp(name, "isa") != 0) {
|
||||
value = object_getClass(object);
|
||||
} else
|
||||
#endif
|
||||
if (type[0] == @encode(id)[0] || type[0] == @encode(Class)[0]) {
|
||||
value = object_getIvar(object, ivar);
|
||||
} else {
|
||||
@@ -583,7 +590,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
|
||||
if (encodingCString[0] == '@') {
|
||||
NSString *class = [encodingString substringFromIndex:1];
|
||||
class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
|
||||
if ([class length] == 0) {
|
||||
if ([class length] == 0 || [class isEqual:@"?"]) {
|
||||
class = @"id";
|
||||
} else {
|
||||
class = [class stringByAppendingString:@" *"];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user