Compare commits

..

149 Commits

Author SHA1 Message Date
Tanner Bennett d7d40e6d27 Fix #140, system log messages work
more progress
2019-03-30 16:56:46 -05:00
Tanner Bennett bec7e0c229 Correct header comments
Some headers were reading UICatalog inside the FLEX project
2019-03-30 15:12:55 -05:00
Tanner Bennett 82a19e41e7 Fix prettyArgumentComponentsForMethod: bug
#178 made methods with no arguments appear to take one argument by forcibly returning the selector name as the only argument. This is not desired behavior. Updating the test to reflect desired behavior reveals this.

This commit makes this method return an empty array when selectors consist of one component, and does some housekeeping on the tests added in #178.
2019-03-30 15:10:59 -05:00
Tanner Bennett 867ae614e5 Detect and unbox pointers to objects from void *
- Also unbox C strings into NSString
- Also adds return type encoding string to method calling view controller
2019-03-30 15:10:58 -05:00
Tanner Bennett 22b7c6ccc7 Add helper methods to FLEXRuntimeUtility
- Add FLEXTypeEncoding enum
- Can check whether arbitrary poiner is valid object
- Can get return type encoding for method
- Can unbox raw pointers from NSValue into actual objects, or unbox C strings into NSStrings

Code copied from the Objc runtime complies with ASPL
2019-03-30 13:35:39 -05:00
ThePantsThief 9e9704580a Fix #168 by restructuring try-catch branching 2019-03-30 13:19:17 -05:00
Tanner Bennett 1ef608cf8a Fix #245
`#if __arm64__` is not a sufficient check for whether a platform is 64 bit. `__LP64__` appears to be a better candidate.

`MAX_REALISTIC_ADDRESS` was wrongly being set to `INT_MAX` on some 64 bit platforms.
2018-11-23 01:20:55 -06:00
Tanner Bennett b64cd37ec6 Add "Get" to readwrite editor screens, fix #235
Previously you could only "Set" mutable ivars or properties. This commit adds a "Get" button to the same screen to allow you to view the current value instead. Also works in the user defaults explorer.

It may be worth considering other approaches to this entirely, such as an alert that asks you if you want to get or set the ivar/property before a new screen is even pushed, or maybe a "Get" button as an accessory view on the rows of mutable ivars/properties.
2018-11-23 00:00:30 -06:00
Tanner Bennett 44e9d55fb8 Fix crash surrounding SwiftObject subclasses
SwiftObject subclasses cannot be safely inspected with the Objc runtime, attempts to do so sometimes lead to crashes
2018-11-22 16:09:21 -06:00
Geor Kasapidi ab9515caaf Allow registration of content type viewers (#241) 2018-11-09 06:07:09 -06:00
Tanner Bennett 0dd0fc9418 Update to recommended project settings
Silence warning for "implementing deprecated method" in Apple's own sample code.
2018-11-04 02:34:19 -06:00
Tanner Bennett 24d5f3e9b2 Add FLEXColorExplorerViewController
Provides a visual of the color for all UIColor objects.
2018-11-04 02:34:19 -06:00
Terry Lewis 693f57eef7 Added view-color indicator to cells within view-hierarchy list. (#239) 2018-11-04 02:30:40 -06:00
Tanner Bennett 7c17ce0787 Fix Instances table view bug
Accidentally returned a FLEXObjectRef and not the object itself
2018-10-17 16:52:40 -05:00
Tanner Bennett 400a3ccd1c Add +[NSBundle mainBundle] to global list 2018-10-17 16:52:40 -05:00
Colin Humber a8cdac1872 Fixed strict prototype warnings (#231) 2018-08-23 14:25:31 -05:00
zhaogyrain dedac1f98d Fix #225
Fixes the SQL editor clipping under the navigation bar on iPhone X
2018-08-08 23:35:40 -05:00
Tanner Bennett efa317f0d1 Merge pull request #227 from NSExceptional/organize-refs-pr
Group similar "objects with ivars referencing this object"
2018-07-30 18:08:52 -05:00
Tanner Bennett 9b55bb10de Group similar "objects referencing this object" 2018-07-24 20:20:30 -05:00
Tanner Bennett 122fb41fa8 Merge pull request #228 from NSExceptional/inheritance-pr
Additional object explorer scopes
2018-07-19 16:29:48 -05:00
Tanner Bennett d6b5e8c77d Additional object explorer scopes
Scopes now include:
- No inheritance (base class)
- Base class with parent attributes
- Base class with all inherited attributes, except NSObject
- NSObject attributes only

It is unusual that you need to see anything but the parent's attributes alongside those of the base class, and you especially rarely need to see NSObject attributes.
2018-07-19 16:29:15 -05:00
Tanner Bennett a6ad98dd53 Catch exceptions thrown by [value description] 2018-07-12 22:33:07 -05:00
Tanner Bennett cc35f2086a Recommended project settings (9.2) 2018-07-12 22:32:05 -05:00
Tanner Bennett 7038aae6db ExplorerToolbar → Toolbar 2018-07-12 22:31:23 -05:00
Tanner Bennett f5433153d0 Only scroll log view if new messages have arrived
This fixes a bug where being scrolled to the bottom of the system log screen would make the table view stutter repeatedly because it was trying to scroll to the bottom while already being at the bottom.
2018-07-12 22:22:12 -05:00
Tanner Bennett 8b7c59d949 Fix #117: limit network response size
Limit cached network response size to 50 MB
2018-07-12 22:22:12 -05:00
Tanner Bennett faef524b6c Merge pull request #220 from bharat/master
Resolve Xcode 9.3 warnings and deprecations
2018-07-11 14:19:59 -05:00
Tanner Bennett a2bdc03684 Merge pull request #188 from NSExceptional/master
Simplify view controller presentation code
2018-07-10 20:19:36 -05:00
Tanner Bennett bd5f9740b7 Simplify view controller presentation code
Removes duplicate code related to presenting a view controller from the
FLEX window (usually triggered by a toolbar button).

This adds `-[FLEXExplorerViewController
presentOrDismissViewControllerFromToolbar:shouldDismiss:completion:]`
which also makes it easier for others to add their own toolbar buttons
to present some other kind of screen, or for FLEX itself to utilize
again in the future.
2018-07-10 17:57:04 -05:00
Ryan Olson 505bb2ca41 Merge pull request #198 from NSExceptional/exception-pr
Reveal exception reason in dialog
2018-07-10 10:07:37 -07:00
Ryan Olson 009711ab3f Merge pull request #199 from NSExceptional/log-pr
System log will cache messages
2018-07-10 10:06:10 -07:00
Ryan Olson 7ad7653cdf Merge pull request #226 from pujiaxin33/feature_AddShareToFileBrowser
add share  action to FLEXFileBrowserTableViewController
2018-07-10 10:02:35 -07:00
Ryan Olson e5f51e4dfa Merge pull request #222 from NSExceptional/heap-enum-fix
Fix heap enumeration crash
2018-07-10 09:49:08 -07:00
pjx 92029d2b43 add share action to FLEXFileBrowserTableViewController 2018-07-06 12:01:50 +08:00
Bharat Mediratta 7da059791e Further amendment to macro definition 2018-06-04 14:00:18 -07:00
Bharat Mediratta af57527961 Minor cleanups in macros and indentation 2018-06-04 13:57:41 -07:00
Bharat Mediratta 9a8f45663e Revert update to Xcode 9.3; Stay at Xcode 8 for now 2018-06-04 13:56:54 -07:00
Tanner Bennett 8528c8a1f6 System log will cache messages
fix paren
2018-05-21 06:51:17 -05:00
Tanner Bennett d682fd0ace Fix heap enumeration crash
We only need to enumerate the `DefaultMallocZone` zone to find objects we care about.
2018-05-20 02:16:22 -05:00
Tanner Bennett 31af87a81e Reveal exception reason in dialog
When a user-invoked method call fails, FLEX presents a dialog informing you that it failed.

In practice, it is more useful to see the exception name and reason than the (potentially crazy long) object description.
2018-05-07 20:15:55 -05:00
Bharat Mediratta 386d6ae06a UITouch.maximumForcePossible is only available in iOS 9.0+ 2018-05-03 12:33:00 -07:00
Bharat Mediratta df79ae7971 Fix deprecation warnings
Replace:
 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

With:
 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
2018-05-03 12:18:55 -07:00
Bharat Mediratta d30c642707 Adjust FLEX_AT_LEAST_IOS11_SDK to work with -Wexpansion-to-defined 2018-05-03 12:10:22 -07:00
Bharat Mediratta f463e2b43e Xcode 9.3 automated fixes 2018-05-03 12:07:46 -07:00
Shoaib Meenai ed49a4fc89 Use FOUNDATION_EXTERN for global variable
Global variables that can be accessed from both C (or Objective-C) and
C++ (or Objective-C++) source files should be marked `extern "C"` when
in C++ mode (which is what `FOUNDATION_EXTERN` does), to ensure
consistent access across languages.
2018-04-03 21:40:04 -07:00
Chaoshuai Lu b897250fde Fix generics 2018-03-22 16:41:55 -07:00
Greg Heo 06709a5afe Reorder to match order in Installation section 2018-01-23 23:07:47 -08:00
Greg Heo 6eee9e6080 Refactor Installation section 2018-01-23 23:07:47 -08:00
Greg Heo c50e6e51c5 Add Swift example 2018-01-23 23:07:47 -08:00
Marcus Ficner d1e9248695 Fix typo 2018-01-23 23:05:10 -08:00
Chaoshuai Lu a535f10d0c Add generics to foundation collection classes 2018-01-23 23:04:33 -08:00
Chaoshuai Lu 009aa5e3f9 Add iPhone X safe area handling to layout the toolbar correctly 2017-12-17 23:12:41 -08:00
Chaoshuai Lu 675f03fc71 Make Travis use xcodebuild (and xcpretty) instead of (deprecated) xctool 2017-12-17 22:48:15 -08:00
Ryan Olson 99eccdf4c3 Merge pull request #178 from tikoyesayan/master
Fix crash in FLEXRuntimeUtility
2017-08-27 17:22:00 -07:00
Ryan Olson 29afa5e80f Merge pull request #177 from CodeLife2012/networkbug
Should keep the same completion logic
2017-08-27 16:53:20 -07:00
Ryan Olson bf26bc6539 Fix formatting from 62ef95f 2017-08-27 15:56:08 -07:00
Ryan Olson f7b40646e2 Fix network search bar showing over pushed request detail pages 2017-08-27 15:52:22 -07:00
Ryan Olson 84c1fb159b Merge pull request #194 from adysart/xcode9
Xcode9
2017-08-27 15:29:13 -07:00
Aidan Dysart 62ef95ff93 satisfy the -Wstrict-prototypes clang warning 2017-08-08 11:01:10 -07:00
Aidan Dysart 731b729db7 Upgrade project and scheme for Xcode 9, enable default warnings 2017-08-08 11:00:48 -07:00
Tigran Yesayan a1c464d1a7 Add missing free function in testMethodListForClass: method in unit test 2017-07-11 12:48:05 +04:00
Tigran Yesayan eb2ecbf9b3 Added unit test to check the new way of getting method components 2017-07-09 14:56:56 +04:00
Tigran Yesayan 16fab66f7b Fix crash in FLEXRuntimeUtility 2017-07-09 14:52:41 +04:00
Ryan Olson b3e70ac491 Merge pull request #189 from NSExceptional/refactor-toolbar
Make FLEXExplorerToolbar more extensible
2017-07-05 08:58:24 -07:00
Tanner Bennett d5177bb049 Make FLEXExplorerToolbar more extensible
Exposes `FLEXExplorerToolbar.toolbarItems` as a public API so that others can more easily, safely, and reliably add their own items to the FLEX toolbar.
2017-07-03 23:50:46 -05:00
Ryan Olson bb0faeb3cf Merge pull request #184 from revolter/hotfix/code-style
Fix code style
2017-07-03 18:10:17 -07:00
Ryan Olson 761feef3c0 Merge pull request #190 from showbie/request-error-in-red
Highlight request name in red if status code is a 4xx or 5xx
2017-07-03 17:47:00 -07:00
Colin Humber 352bae03ea Highlight request name in red if status code is a 4xx or 5xx 2017-06-29 12:58:01 -06:00
Ryan Olson b0085cae7d Merge pull request #187 from nicked/patch-1
Fixed keyboard shortcuts in Xcode 9 simulator
2017-06-19 17:41:44 -07:00
nicked b2f93f1752 Fixed keyboard shortcuts in Xcode 9 simulator
Pressing Cmd+S in the Simulator normally just takes a screenshot, but in Xcode 9, it also triggers the default "s" keyboard shortcut which enables the FLEX Select tool. This is because the keyboard flags is ANDed with a logical NOT of UIKeyModifierShift instead of the bitwise inverse
2017-06-14 18:03:26 +02:00
Ryan Olson 354510f2c4 Merge pull request #185 from showbie/host-blacklist
Add ability to filter out noisy network requests from being recorded
2017-05-31 22:23:26 -07:00
Colin Humber b8c6175193 Added configuration to filter out network requests for particular hosts from being recorded 2017-05-31 14:18:46 -06:00
Iulian Onofrei d409b110f5 Fix code style 2017-05-23 10:31:02 +03:00
Ryan Olson 5c73220158 Merge pull request #182 from NSExceptional/fix-heap
Fix #119
2017-05-21 22:33:26 -07:00
Tanner Bennett 397721e7ea Fix #119 2017-05-21 18:20:42 -05:00
Ryan Olson 5714275bcd Merge pull request #176 from NSIRLConnection/master
Fix #175
2017-05-21 15:27:56 -07:00
Ryan Olson 833c584e41 Merge pull request #180 from revolter/feature/database-password
Add support for SQLCipher protected databases
2017-05-21 15:22:31 -07:00
Ryan Olson 49b24487c5 Merge pull request #181 from revolter/feature/toolbar-position
Add toolbar position persistence
2017-05-21 15:17:49 -07:00
Iulian Onofrei 6d4eb01a07 Add toolbar position persistence 2017-05-10 17:33:42 +03:00
Iulian Onofrei a752203ff9 Add support for SQLCipher protected databases 2017-05-10 17:12:24 +03:00
Karl Peng c69427613d should keep the same completion logic 2017-03-24 17:29:08 +08:00
Michael 5d75a83568 Fix #175 by checking if the objects respond to compare: 2017-03-09 14:06:47 -05:00
Ryan Olson 7642a0632d Merge pull request #160 from defagos/carthage-instructions-image
Add image for Carthage FLEX exclusion setup
2017-01-15 12:07:48 -08:00
Ryan Olson 841054a713 Merge pull request #161 from unixzii/master
Add a convenient action button in FLEXImagePreviewViewController
2017-01-15 12:05:37 -08:00
杨弘宇 49f368fd63 replace Copy button with Action button in FLEXImagePreviewViewController's navigation item 2016-11-08 20:58:57 +08:00
Samuel Défago 1dc99250c8 Add image for Carthage FLEX exclusion setup 2016-11-07 09:09:19 +01:00
Ryan Olson 00edccf326 Merge pull request #159 from defagos/carthage-instructions
Add Carthage instructions for FLEX exclusion from release builds
2016-11-04 16:54:32 -07:00
Samuel Défago 7f3af90645 Add Carthage instructions for FLEX exclusion from release builds 2016-11-04 07:43:09 +01:00
Ryan Olson 1a030f06cd Merge pull request #157 from froody/size
[FLEX] Add size to heap objects view
2016-10-20 19:48:28 -07:00
Tom Birch 0477858bed [FLEX] Add size to heap objects view
Change-Id: I757714b2570d400886959caa72e731ef65a925b5
2016-10-19 19:55:19 -07:00
Ryan Olson 000e061d00 Merge pull request #152 from rtyu128/rtyu128-FLEXNetworkObserver-Fixed
Fix #150.
2016-10-10 08:16:59 -07:00
Anchor 224978b31b Fixed #150.
Use @selector(URLSession:dataTask:didBecomeDownloadTask:) instead of @selector(URLSession:dataTask:didBecomeDownloadTask:delegate:) in method search.
2016-10-08 10:58:56 +08:00
Ryan Olson 7fd133f13b Bump version in podspec 2016-10-01 15:46:38 -07:00
Ryan Olson e40054ba1a Merge pull request #142 from shepting/patch-1
Fix typo pruged -> pruned
2016-09-30 16:42:58 -07:00
Ryan Olson 1761734447 Merge pull request #149 from c0diq/xcode8
Add Xcode8 support
2016-09-30 16:41:54 -07:00
Ryan Olson f23ee3cd95 Merge pull request #151 from Blankdlh/master
Fix method swizzling for NSURLSessionDelegate
2016-09-30 16:38:55 -07:00
kunka e455ac0c7d Fix swizzling for URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler: and URLSession:dataTask:didReceiveResponse:completionHandler: 2016-09-29 18:10:31 +08:00
Sylvain Rebaud 94f68c6dfe Add Xcode8 support 2016-09-26 15:15:36 -07:00
Steven Hepting a22f022014 Fix typo. 2016-08-15 08:17:07 -07:00
Ryan Olson 22e7edb698 Bring back the memory reader for heap enumeration
Some malloc replacements don't provide a default reader
2016-08-09 08:55:54 -07:00
Ryan Olson 52fcda53c5 Merge pull request #138 from iampgzp/master
Added curl logger for debugging request
2016-08-01 10:15:44 -07:00
Ji Pei be6c5d0e43 Removed curl log in recordRequestWillBeSentWithRequestID to avoid too much logging spew 2016-08-01 12:49:32 -04:00
Ryan Olson 6ed0037f50 Merge pull request #134 from ThePantsThief/master
Search for a specific class
2016-07-29 15:35:49 -07:00
Ryan Olson 81e3a5ff47 Merge pull request #132 from revolter/patch-1
Update FLEXRuntimeUtility.m
2016-07-29 15:27:32 -07:00
Ji Pei 2a6e28c9d0 Added curl logger for debugging request 2016-07-27 16:52:47 -04:00
ThePantsThief 9e928b0b09 Fix incorrect strcmp check 2016-07-22 12:49:17 -05:00
Tanner Bennett d5deaad628 Search for a specific class
Added a way to jump to a specific class from the System Libraries tab,
since it is sometimes difficult to find a class if you don’t know which
bundle it’s in.

Also fixed a few other things that bugged me, like manually getting the
last path component of a string instead of using the lastPathComponent
property…
2016-07-06 15:04:39 -05:00
Iulian Onofrei 232ae8a6fd Update FLEXRuntimeUtility.m
Fix typos
2016-06-21 10:46:57 +03:00
Ryan Olson c766e5d94a Merge pull request #128 from Flipboard/timoliver-realm-path-fix
Replaced deprecated 'path' property in Realm configuration
2016-05-20 06:15:30 -07:00
Tim Oliver 5f27a2304b Replaced 'path' property with 'fileURL' in Realm configuration 2016-05-20 15:21:24 +08:00
Ryan Olson 0cb0f44f18 Update version in .podspec 2016-02-28 22:18:38 -08:00
Ryan Olson d1c1aa0a26 Fix xcodebuild error from incorrect method resolution 2016-02-28 22:15:07 -08:00
Ryan Olson 832957f621 Merge pull request #114 from SoXeon/master
fix crash when object malloc_size no greater than zero
2016-02-28 21:52:30 -08:00
Ryan Olson 24985ac984 Fix crash on calling _keyCode on events from the simulator keyboard 2016-02-28 21:43:39 -08:00
dazi.dp 0b652c2f2a add malloc.h 2016-02-25 22:31:47 +08:00
dazi.dp 98d83bb438 fix crash when object malloc_size no greater than zero 2016-02-25 22:20:22 +08:00
Tim Oliver e13717b056 Merge pull request #112 from TimOliver/timoliver-realm-null-fix
Added handling for nil Realm property values.
2016-02-23 22:12:57 +08:00
Tim Oliver 552e687b9c Added handling for nil Realm property values 2016-02-23 22:08:51 +08:00
Ryan Olson fb29421644 Merge pull request #110 from TimOliver/patch-1
Updated database features in README.
2016-02-22 22:08:27 -08:00
Tim Oliver f5d930bd58 Updated database features in README. 2016-02-22 02:10:15 +08:00
Ryan Olson 26af4ef476 Merge pull request #105 from TimOliver/timoliver-realm-integration
Added Realm Database File Introspection Alongside SQLite
2016-02-21 09:39:55 -08:00
Ryan Olson ac8940da26 Merge pull request #107 from nin9tyfour/master
*Added the ability to determine a UIView's view controller without selection the view controller's view delegate.
2016-02-16 20:24:36 -08:00
Tim Oliver f597152a62 Cleaned up example project. 2016-02-17 11:35:39 +08:00
Tim Oliver 58f94f108c Made protocol required. 2016-02-17 11:34:54 +08:00
Tim Oliver a5e0bbd50e Added better extension-checking logic. 2016-02-17 11:34:36 +08:00
Tim Oliver ac273fbfc9 Added test Realm file and classes to the sample app. 2016-02-17 11:20:17 +08:00
Tim Oliver 548fd03bd5 Made Realm integration a dynamic runtime check. 2016-02-17 11:19:17 +08:00
nin9tyfour b564c25d2a *Added the ability to determine a UIView's view controller without selection the view controller's view delegate. 2016-02-16 15:29:52 +11:00
Ryan Olson bd821dc553 Restrict simulator force touch support to iOS 9+ 2016-02-13 13:44:00 -08:00
Ryan Olson e46a33417b Merge pull request #106 from ReadmeCritic/master
Correct the spelling of CocoaPods in README
2016-02-13 13:36:37 -08:00
ReadmeCritic a3419a841f Correct the spelling of CocoaPods in README 2016-02-11 15:46:43 -08:00
Tim Oliver cafc1ba0bd Reset UICatalog project. 2016-02-09 14:16:54 +08:00
Tim Oliver 0112c097d9 Updated Realm optional macros and cleaned project changes. 2016-02-09 14:10:52 +08:00
Tim Oliver 507d03fd90 Cleaned up project file, and added test Realm file. 2016-02-07 16:47:31 +08:00
Tim Oliver b6453ac360 Made references to Realm build optional. 2016-02-06 15:36:49 +08:00
Tim Oliver b693ceb20e Cleaned up protocol logic. 2016-02-06 14:42:19 +08:00
Ryan Olson 31e81a616d Merge pull request #104 from Flipboard/dzc-add-contributing
Add contributing stub
2016-01-30 15:13:42 -08:00
David Creemer 928f60b56f Add contributing stub 2016-01-28 13:19:10 -08:00
Tim Oliver 12a1900d75 Abstracted database parser logic and added framework for Realm parser. 2016-01-28 12:50:39 +08:00
Ryan Olson c72b6f7e5b Merge pull request #102 from pra85/patch-1
Update license year range to 2016
2016-01-18 11:43:12 -08:00
Prayag Verma 3cf9f72dcb Update license year range to 2016 2016-01-18 14:12:03 +05:30
Ryan Olson 9b1318e975 Project structure cleanup 2016-01-17 20:52:34 -08:00
Ryan Olson d3d0f04c23 Heap scan cleanup 2016-01-17 19:54:38 -08:00
Ryan Olson b606d04944 Fix keyboard shortcuts firing when typing into an alert view 2016-01-11 13:27:15 -08:00
Ryan Olson 7afd50d241 Remove asl hacks needed for iOS 7 2016-01-08 15:01:42 -08:00
Ryan Olson 94f06d5ff8 Fix heap search/enumeration crashes! 2016-01-06 17:43:01 -08:00
Ryan Olson 85424fd15e Support viewing AFNetworking request bodies 2016-01-05 15:10:10 -08:00
Ryan Olson cff391f78c Use NSKeyedArchiver for files with .coded extensions 2015-12-18 19:38:32 -05:00
Ryan Olson 3e420bb747 Bump version in .podspec 2015-12-14 08:56:10 -08:00
140 changed files with 4555 additions and 1365 deletions
+6 -2
View File
@@ -1,8 +1,12 @@
language: objective-c
xcode_workspace: FLEX.xcworkspace
xcode_sdk: iphonesimulator
before_install:
- gem install xcpretty
matrix:
include:
- xcode_scheme: UICatalog
xcode_sdk: iphonesimulator
- xcode_scheme: FLEX
xcode_sdk: iphonesimulator
script:
- set -o pipefail
- xcodebuild -workspace $TRAVIS_XCODE_WORKSPACE -scheme $TRAVIS_XCODE_SCHEME -sdk $TRAVIS_XCODE_SDK build | xcpretty
+3
View File
@@ -0,0 +1,3 @@
# Contributing to FLEX #
We welcome contributions! Please open a pull request with your changes.
@@ -228,7 +228,7 @@
CGFloat hexLabelOriginY = CGRectGetMaxY(self.colorPreviewBox.frame) - self.colorPreviewBox.layer.borderWidth - self.hexLabel.frame.size.height;
self.hexLabel.frame = CGRectMake(hexLabelOriginX, hexLabelOriginY, self.hexLabel.frame.size.width, self.hexLabel.frame.size.height);
NSArray *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
NSArray<FLEXColorComponentInputView *> *colorComponentInputViews = @[self.alphaInput, self.redInput, self.greenInput, self.blueInput];
for (FLEXColorComponentInputView *inputView in colorComponentInputViews) {
CGSize fitSize = [inputView sizeThatFits:constrainSize];
inputView.frame = CGRectMake(0, runningOriginY, fitSize.width, fitSize.height);
@@ -1,6 +1,6 @@
//
// FLEXArgumentInputFontsPickerView.h
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/7/27.
// Copyright (c) 2014年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXArgumentInputFontsPickerView.m
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/7/27.
// Copyright (c) 2014年 f. All rights reserved.
@@ -11,7 +11,7 @@
@interface FLEXArgumentInputFontsPickerView ()
@property (nonatomic, strong) NSMutableArray *availableFonts;
@property (nonatomic, strong) NSMutableArray<NSString *> *availableFonts;
@end
@@ -35,7 +35,7 @@
if ([self.availableFonts indexOfObject:inputValue] == NSNotFound) {
[self.availableFonts insertObject:inputValue atIndex:0];
}
[(UIPickerView*)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
[(UIPickerView *)self.inputTextView.inputView selectRow:[self.availableFonts indexOfObject:inputValue] inComponent:0 animated:NO];
}
- (id)inputValue
@@ -56,7 +56,7 @@
- (void)createAvailableFonts
{
NSMutableArray *unsortedFontsArray = [NSMutableArray array];
NSMutableArray<NSString *> *unsortedFontsArray = [NSMutableArray array];
for (NSString *eachFontFamily in [UIFont familyNames]) {
for (NSString *eachFontName in [UIFont fontNamesForFamilyName:eachFontFamily]) {
[unsortedFontsArray addObject:eachFontName];
@@ -90,7 +90,7 @@
fontLabel = (UILabel*)view;
}
UIFont *font = [UIFont fontWithName:self.availableFonts[row] size:15.0];
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
NSDictionary<NSString *, id> *attributesDictionary = [NSDictionary<NSString *, id> dictionaryWithObject:font forKey:NSFontAttributeName];
NSAttributedString *attributesString = [[NSAttributedString alloc] initWithString:self.availableFonts[row] attributes:attributesDictionary];
fontLabel.attributedText = attributesString;
[fontLabel sizeToFit];
@@ -8,6 +8,7 @@
#import "FLEXArgumentInputTextView.h"
#warning TODO This is never supported
@interface FLEXArgumentInputJSONObjectView : FLEXArgumentInputTextView
@end
@@ -35,7 +35,7 @@
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value
{
static NSArray *primitiveTypes = nil;
static NSArray<NSString *> *primitiveTypes = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
primitiveTypes = @[@(@encode(char)),
@@ -12,7 +12,7 @@
@interface FLEXArgumentInputStructView ()
@property (nonatomic, strong) NSArray *argumentInputViews;
@property (nonatomic, strong) NSArray<FLEXArgumentInputView *> *argumentInputViews;
@end
@@ -22,8 +22,8 @@
{
self = [super initWithArgumentTypeEncoding:typeEncoding];
if (self) {
NSMutableArray *inputViews = [NSMutableArray array];
NSArray *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
NSMutableArray<FLEXArgumentInputView *> *inputViews = [NSMutableArray array];
NSArray<NSString *> *customTitles = [[self class] customFieldTitlesForTypeEncoding:typeEncoding];
[FLEXRuntimeUtility enumerateTypesInStructEncoding:typeEncoding usingBlock:^(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset) {
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
@@ -179,9 +179,9 @@
return type && type[0] == '{';
}
+ (NSArray *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
+ (NSArray<NSString *> *)customFieldTitlesForTypeEncoding:(const char *)typeEncoding
{
NSArray *customTitles = nil;
NSArray<NSString *> *customTitles = nil;
if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
customTitles = @[@"CGPoint origin", @"CGSize size"];
} else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
@@ -22,7 +22,7 @@
{
self = [super initWithFrame:CGRectZero];
if (self) {
self.typeEncoding = @(typeEncoding);
self.typeEncoding = typeEncoding != NULL ? @(typeEncoding) : nil;
}
return self;
}
@@ -39,26 +39,23 @@
+ (Class)argumentInputViewSubclassForTypeEncoding:(const char *)typeEncoding currentValue:(id)currentValue
{
Class argumentInputViewSubclass = nil;
NSArray<Class> *inputViewClasses = @[[FLEXArgumentInputColorView class],
[FLEXArgumentInputFontView class],
[FLEXArgumentInputStringView class],
[FLEXArgumentInputStructView class],
[FLEXArgumentInputSwitchView class],
[FLEXArgumentInputDateView class],
[FLEXArgumentInputNumberView class],
[FLEXArgumentInputJSONObjectView class]];
// Note that order is important here since multiple subclasses may support the same type.
// An example is the number subclass and the bool subclass for the type @encode(BOOL).
// Both work, but we'd prefer to use the bool subclass.
if ([FLEXArgumentInputColorView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputColorView class];
} else if ([FLEXArgumentInputFontView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputFontView class];
} else if ([FLEXArgumentInputStringView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputStringView class];
} else if ([FLEXArgumentInputStructView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputStructView class];
} else if ([FLEXArgumentInputSwitchView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputSwitchView class];
} else if ([FLEXArgumentInputDateView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputDateView class];
} else if ([FLEXArgumentInputNumberView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputNumberView class];
} else if ([FLEXArgumentInputJSONObjectView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = [FLEXArgumentInputJSONObjectView class];
for (Class inputView in inputViewClasses) {
if ([inputView supportsObjCType:typeEncoding withCurrentValue:currentValue]) {
argumentInputViewSubclass = inputView;
break;
}
}
return argumentInputViewSubclass;
@@ -6,9 +6,9 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
@interface FLEXDefaultEditorViewController : FLEXFieldEditorViewController
@interface FLEXDefaultEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
@@ -64,6 +64,13 @@
self.firstInputView.inputValue = [self.defaults objectForKey:self.key];
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [self.defaults objectForKey:self.key];
[self exploreObjectOrPopViewController:returnedObject];
}
+ (BOOL)canEditDefaultWithValue:(id)currentValue
{
return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:@encode(id) currentValue:currentValue];
+3 -1
View File
@@ -8,11 +8,13 @@
#import <UIKit/UIKit.h>
@class FLEXArgumentInputView;
@interface FLEXFieldEditorView : UIView
@property (nonatomic, copy) NSString *targetDescription;
@property (nonatomic, copy) NSString *fieldDescription;
@property (nonatomic, strong) NSArray *argumentInputViews;
@property (nonatomic, strong) NSArray<FLEXArgumentInputView *> *argumentInputViews;
@end
+1 -1
View File
@@ -103,7 +103,7 @@
}
}
- (void)setArgumentInputViews:(NSArray *)argumentInputViews
- (void)setArgumentInputViews:(NSArray<FLEXArgumentInputView *> *)argumentInputViews
{
if (![_argumentInputViews isEqual:argumentInputViews]) {
@@ -22,7 +22,11 @@
@property (nonatomic, strong, readonly) id target;
@property (nonatomic, strong, readonly) FLEXFieldEditorView *fieldEditorView;
@property (nonatomic, strong, readonly) UIBarButtonItem *setterButton;
- (void)actionButtonPressed:(id)sender;
- (NSString *)titleForActionButton;
/// Pushes an explorer view controller for the given object
/// or pops the current view controller.
- (void)exploreObjectOrPopViewController:(id)objectOrNil;
@end
@@ -10,8 +10,10 @@
#import "FLEXFieldEditorView.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXUtility.h"
#import "FLEXObjectExplorerFactory.h"
#import "FLEXArgumentInputView.h"
#import "FLEXArgumentInputViewFactory.h"
#import "FLEXObjectExplorerViewController.h"
@interface FLEXFieldEditorViewController () <UIScrollViewDelegate>
@@ -114,4 +116,16 @@
return @"Set";
}
- (void)exploreObjectOrPopViewController:(id)objectOrNil {
if (objectOrNil) {
// For non-nil (or void) return types, push an explorer view controller to display the object
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:objectOrNil];
[self.navigationController pushViewController:explorerViewController animated:YES];
} else {
// If we didn't get a returned object but the method call succeeded,
// pop this view controller off the stack to indicate that the call went through.
[self.navigationController popViewControllerAnimated:YES];
}
}
@end
@@ -6,10 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
#import <objc/runtime.h>
@interface FLEXIvarEditorViewController : FLEXFieldEditorViewController
@interface FLEXIvarEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithTarget:(id)target ivar:(Ivar)ivar;
@@ -55,6 +55,17 @@
[FLEXRuntimeUtility setValue:self.firstInputView.inputValue forIvar:self.ivar onObject:self.target];
self.firstInputView.inputValue = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
// Pop view controller for consistency;
// property setters and method calls also pop on success.
[self.navigationController popViewControllerAnimated:YES];
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [FLEXRuntimeUtility valueForIvar:self.ivar onObject:self.target];
[self exploreObjectOrPopViewController:returnedObject];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
@@ -17,6 +17,7 @@
@interface FLEXMethodCallingViewController ()
@property (nonatomic, assign) Method method;
@property (nonatomic, assign) FLEXTypeEncoding *returnType;
@end
@@ -27,7 +28,8 @@
self = [super initWithTarget:target];
if (self) {
self.method = method;
self.title = [self isClassMethod] ? @"Class Method" : @"Method";
self.returnType = [FLEXRuntimeUtility returnTypeForMethod:method];
self.title = [self isClassMethod] ? @"Class Method" : @"Method";;
}
return self;
}
@@ -36,10 +38,14 @@
{
[super viewDidLoad];
self.fieldEditorView.fieldDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
NSString *returnType = @((const char *)self.returnType);
NSString *methodDescription = [FLEXRuntimeUtility prettyNameForMethod:self.method isClassMethod:[self isClassMethod]];
NSString *format = @"Signature:\n%@\n\nReturn Type:\n%@";
NSString *info = [NSString stringWithFormat:format, methodDescription, returnType];
self.fieldEditorView.fieldDescription = info;
NSArray *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
NSMutableArray *argumentInputViews = [NSMutableArray array];
NSArray<NSString *> *methodComponents = [FLEXRuntimeUtility prettyArgumentComponentsForMethod:self.method];
NSMutableArray<FLEXArgumentInputView *> *argumentInputViews = [NSMutableArray array];
unsigned int argumentIndex = kFLEXNumberOfImplicitArgs;
for (NSString *methodComponent in methodComponents) {
char *argumentTypeEncoding = method_copyArgumentType(self.method, argumentIndex);
@@ -54,6 +60,12 @@
self.fieldEditorView.argumentInputViews = argumentInputViews;
}
- (void)dealloc
{
free(self.returnType);
self.returnType = NULL;
}
- (BOOL)isClassMethod
{
return self.target && self.target == [self.target class];
@@ -88,12 +100,11 @@
[alert show];
} else if (returnedObject) {
// For non-nil (or void) return types, push an explorer view controller to display the returned object
returnedObject = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:returnedObject type:self.returnType];
FLEXObjectExplorerViewController *explorerViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:returnedObject];
[self.navigationController pushViewController:explorerViewController animated:YES];
} else {
// If we didn't get a returned object but the method call succeeded,
// pop this view controller off the stack to indicate that the call went through.
[self.navigationController popViewControllerAnimated:YES];
[self exploreObjectOrPopViewController:returnedObject];
}
}
@@ -0,0 +1,18 @@
//
// FLEXMutableFieldEditorViewController.h
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
@interface FLEXMutableFieldEditorViewController : FLEXFieldEditorViewController
@property (nonatomic, strong, readonly) UIBarButtonItem *getterButton;
- (void)getterButtonPressed:(id)sender;
- (NSString *)titleForGetterButton;
@end
@@ -0,0 +1,36 @@
//
// FLEXMutableFieldEditorViewController.m
// FLEX
//
// Created by Tanner on 11/22/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXMutableFieldEditorViewController.h"
#import "FLEXFieldEditorView.h"
@interface FLEXMutableFieldEditorViewController ()
@property (nonatomic, strong, readwrite) UIBarButtonItem *getterButton;
@end
@implementation FLEXMutableFieldEditorViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.getterButton = [[UIBarButtonItem alloc] initWithTitle:[self titleForGetterButton] style:UIBarButtonItemStyleDone target:self action:@selector(getterButtonPressed:)];
self.navigationItem.rightBarButtonItems = @[self.setterButton, self.getterButton];
}
- (void)getterButtonPressed:(id)sender {
// Subclasses can override
[self.fieldEditorView endEditing:YES];
}
- (NSString *)titleForGetterButton {
return @"Get";
}
@end
@@ -6,10 +6,10 @@
// Copyright (c) 2014 Flipboard. All rights reserved.
//
#import "FLEXFieldEditorViewController.h"
#import "FLEXMutableFieldEditorViewController.h"
#import <objc/runtime.h>
@interface FLEXPropertyEditorViewController : FLEXFieldEditorViewController
@interface FLEXPropertyEditorViewController : FLEXMutableFieldEditorViewController
- (id)initWithTarget:(id)target property:(objc_property_t)property;
@@ -76,6 +76,13 @@
}
}
- (void)getterButtonPressed:(id)sender
{
[super getterButtonPressed:sender];
id returnedObject = [FLEXRuntimeUtility valueForProperty:self.property onObject:self.target];
[self exploreObjectOrPopViewController:returnedObject];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView
{
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
@@ -17,6 +17,12 @@
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates;
- (BOOL)wantsWindowToBecomeKey;
/// @brief Used to present (or dismiss) a modal view controller ("tool"), typically triggered by pressing a button in the toolbar.
///
/// If a tool is already presented, this method simply dismisses it and calls the completion block.
/// If no tool is presented, @code future() @endcode is presented and the completion block is called.
- (void)toggleToolWithViewControllerProvider:(UIViewController *(^)(void))future completion:(void(^)(void))completion;
// Keyboard shortcut helpers
- (void)toggleSelectTool;
@@ -16,6 +16,8 @@
#import "FLEXObjectExplorerFactory.h"
#import "FLEXNetworkHistoryTableViewController.h"
static NSString *const kFLEXToolbarTopMarginDefaultsKey = @"com.flex.FLEXToolbar.topMargin";
typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
FLEXExplorerModeDefault,
FLEXExplorerModeSelect,
@@ -43,10 +45,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
/// Borders of all the visible views in the hierarchy at the selection point.
/// The keys are NSValues with the correponding view (nonretained).
@property (nonatomic, strong) NSDictionary *outlineViewsForVisibleViews;
@property (nonatomic, strong) NSDictionary<NSValue *, UIView *> *outlineViewsForVisibleViews;
/// The actual views at the selection point with the deepest view last.
@property (nonatomic, strong) NSArray *viewsAtTapPoint;
@property (nonatomic, strong) NSArray<UIView *> *viewsAtTapPoint;
/// The view that we're currently highlighting with an overlay and displaying details for.
@property (nonatomic, strong) UIView *selectedView;
@@ -66,7 +68,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
@property (nonatomic, assign) UIStatusBarStyle previousStatusBarStyle;
/// All views that we're KVOing. Used to help us clean up properly.
@property (nonatomic, strong) NSMutableSet *observedViews;
@property (nonatomic, strong) NSMutableSet<UIView *> *observedViews;
@end
@@ -94,10 +96,14 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// Toolbar
self.explorerToolbar = [[FLEXExplorerToolbar alloc] init];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:self.view.bounds.size];
// Start the toolbar off below any bars that may be at the top of the view.
CGFloat toolbarOriginY = 100.0;
self.explorerToolbar.frame = CGRectMake(0.0, toolbarOriginY, toolbarSize.width, toolbarSize.height);
id toolbarOriginYDefault = [[NSUserDefaults standardUserDefaults] objectForKey:kFLEXToolbarTopMarginDefaultsKey];
CGFloat toolbarOriginY = toolbarOriginYDefault ? [toolbarOriginYDefault doubleValue] : 100;
CGRect safeArea = [self viewSafeArea];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea))];
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(CGRectGetMinX(safeArea), toolbarOriginY, toolbarSize.width, toolbarSize.height)];
self.explorerToolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
[self.view addSubview:self.explorerToolbar];
[self setupToolbarActions];
@@ -165,32 +171,30 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return shouldAutorotate;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
for (UIView *outlineView in [self.outlineViewsForVisibleViews allValues]) {
outlineView.hidden = YES;
}
self.selectedViewOverlay.hidden = YES;
}
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
for (UIView *outlineView in [self.outlineViewsForVisibleViews allValues]) {
outlineView.hidden = YES;
}
self.selectedViewOverlay.hidden = YES;
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
for (UIView *view in self.viewsAtTapPoint) {
NSValue *key = [NSValue valueWithNonretainedObject:view];
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.frame = [self frameInLocalCoordinatesForView:view];
if (self.currentMode == FLEXExplorerModeSelect) {
outlineView.hidden = NO;
}
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
for (UIView *view in self.viewsAtTapPoint) {
NSValue *key = [NSValue valueWithNonretainedObject:view];
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.frame = [self frameInLocalCoordinatesForView:view];
if (self.currentMode == FLEXExplorerModeSelect) {
outlineView.hidden = NO;
}
}
if (self.selectedView) {
self.selectedViewOverlay.frame = [self frameInLocalCoordinatesForView:self.selectedView];
self.selectedViewOverlay.hidden = NO;
}
if (self.selectedView) {
self.selectedViewOverlay.frame = [self frameInLocalCoordinatesForView:self.selectedView];
self.selectedViewOverlay.hidden = NO;
}
}];
}
#pragma mark - Setter Overrides
- (void)setSelectedView:(UIView *)selectedView
@@ -206,7 +210,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// Update the toolbar and selected overlay
self.explorerToolbar.selectedViewDescription = [FLEXUtility descriptionForView:selectedView includingFrame:YES];
self.explorerToolbar.selectedViewOverlayColor = [FLEXUtility consistentRandomColorForObject:selectedView];;
self.explorerToolbar.selectedViewOverlayColor = [FLEXUtility consistentRandomColorForObject:selectedView];
if (selectedView) {
if (!self.selectedViewOverlay) {
@@ -232,7 +236,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
}
- (void)setViewsAtTapPoint:(NSArray *)viewsAtTapPoint
- (void)setViewsAtTapPoint:(NSArray<UIView *> *)viewsAtTapPoint
{
if (![_viewsAtTapPoint isEqual:viewsAtTapPoint]) {
for (UIView *view in _viewsAtTapPoint) {
@@ -262,7 +266,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
case FLEXExplorerModeSelect:
// Make sure the outline views are unhidden in case we came from the move mode.
for (id key in self.outlineViewsForVisibleViews) {
for (NSValue *key in self.outlineViewsForVisibleViews) {
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.hidden = NO;
}
@@ -270,7 +274,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
case FLEXExplorerModeMove:
// Hide all the outline views to focus on the selected view, which is the only one that will move.
for (id key in self.outlineViewsForVisibleViews) {
for (NSValue *key in self.outlineViewsForVisibleViews) {
UIView *outlineView = self.outlineViewsForVisibleViews[key];
outlineView.hidden = YES;
}
@@ -311,9 +315,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[self.observedViews removeObject:view];
}
+ (NSArray *)viewKeyPathsToTrack
+ (NSArray<NSString *> *)viewKeyPathsToTrack
{
static NSArray *trackedViewKeyPaths = nil;
static NSArray<NSString *> *trackedViewKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *frameKeyPath = NSStringFromSelector(@selector(frame));
@@ -322,7 +326,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return trackedViewKeyPaths;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context
{
[self updateOverlayAndDescriptionForObjectIfNeeded:object];
}
@@ -376,10 +380,10 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
[self toggleViewsTool];
}
- (NSArray *)allViewsInHierarchy
- (NSArray<UIView *> *)allViewsInHierarchy
{
NSMutableArray *allViews = [NSMutableArray array];
NSArray *windows = [self allWindows];
NSMutableArray<UIView *> *allViews = [NSMutableArray array];
NSArray<UIWindow *> *windows = [FLEXUtility allWindows];
for (UIWindow *window in windows) {
if (window != self.view.window) {
[allViews addObject:window];
@@ -389,28 +393,6 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return allViews;
}
- (NSArray *)allWindows
{
BOOL includeInternalWindows = YES;
BOOL onlyVisibleWindows = NO;
NSArray *allWindowsComponents = @[@"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"];
SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocation.target = [UIWindow class];
invocation.selector = allWindowsSelector;
[invocation setArgument:&includeInternalWindows atIndex:2];
[invocation setArgument:&onlyVisibleWindows atIndex:3];
[invocation invoke];
__unsafe_unretained NSArray *windows = nil;
[invocation getReturnValue:&windows];
return windows;
}
- (UIWindow *)statusWindow
{
NSString *statusBarString = [NSString stringWithFormat:@"%@arWindow", @"_statusB"];
@@ -484,14 +466,24 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
CGRect newToolbarFrame = self.toolbarFrameBeforeDragging;
newToolbarFrame.origin.y += translation.y;
CGFloat maxY = CGRectGetMaxY(self.view.bounds) - newToolbarFrame.size.height;
if (newToolbarFrame.origin.y < 0.0) {
newToolbarFrame.origin.y = 0.0;
} else if (newToolbarFrame.origin.y > maxY) {
newToolbarFrame.origin.y = maxY;
[self updateToolbarPositionWithUnconstrainedFrame:newToolbarFrame];
}
- (void)updateToolbarPositionWithUnconstrainedFrame:(CGRect)unconstrainedFrame
{
CGRect safeArea = [self viewSafeArea];
// We only constrain the Y-axis because We want the toolbar to handle the X-axis safeArea layout by itself
CGFloat minY = CGRectGetMinY(safeArea);
CGFloat maxY = CGRectGetMaxY(safeArea) - unconstrainedFrame.size.height;
if (unconstrainedFrame.origin.y < minY) {
unconstrainedFrame.origin.y = minY;
} else if (unconstrainedFrame.origin.y > maxY) {
unconstrainedFrame.origin.y = maxY;
}
self.explorerToolbar.frame = newToolbarFrame;
self.explorerToolbar.frame = unconstrainedFrame;
[[NSUserDefaults standardUserDefaults] setDouble:unconstrainedFrame.origin.y forKey:kFLEXToolbarTopMarginDefaultsKey];
}
- (void)handleToolbarHintTapGesture:(UITapGestureRecognizer *)tapGR
@@ -548,8 +540,8 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// For outlined views and the selected view, only use visible views.
// Outlining hidden views adds clutter and makes the selection behavior confusing.
NSArray *visibleViewsAtTapPoint = [self viewsAtPoint:selectionPointInWindow skipHiddenViews:YES];
NSMutableDictionary *newOutlineViewsForVisibleViews = [NSMutableDictionary dictionary];
NSArray<UIView *> *visibleViewsAtTapPoint = [self viewsAtPoint:selectionPointInWindow skipHiddenViews:YES];
NSMutableDictionary<NSValue *, UIView *> *newOutlineViewsForVisibleViews = [NSMutableDictionary dictionary];
for (UIView *view in visibleViewsAtTapPoint) {
UIView *outlineView = [self outlineViewForView:view];
[self.view addSubview:outlineView];
@@ -577,17 +569,17 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)removeAndClearOutlineViews
{
for (id key in self.outlineViewsForVisibleViews) {
for (NSValue *key in self.outlineViewsForVisibleViews) {
UIView *outlineView = self.outlineViewsForVisibleViews[key];
[outlineView removeFromSuperview];
}
self.outlineViewsForVisibleViews = nil;
}
- (NSArray *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden
- (NSArray<UIView *> *)viewsAtPoint:(CGPoint)tapPointInWindow skipHiddenViews:(BOOL)skipHidden
{
NSMutableArray *views = [NSMutableArray array];
for (UIWindow *window in [self allWindows]) {
NSMutableArray<UIView *> *views = [NSMutableArray array];
for (UIWindow *window in [FLEXUtility allWindows]) {
// Don't include the explorer's own window or subviews.
if (window != self.view.window && [window pointInside:tapPointInWindow withEvent:nil]) {
[views addObject:window];
@@ -602,7 +594,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
// Select in the window that would handle the touch, but don't just use the result of hitTest:withEvent: so we can still select views with interaction disabled.
// Default to the the application's key window if none of the windows want the touch.
UIWindow *windowForSelection = [[UIApplication sharedApplication] keyWindow];
for (UIWindow *window in [[self allWindows] reverseObjectEnumerator]) {
for (UIWindow *window in [[FLEXUtility allWindows] reverseObjectEnumerator]) {
// Ignore the explorer's own window.
if (window != self.view.window) {
if ([window hitTest:tapPointInWindow withEvent:nil]) {
@@ -616,9 +608,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return [[self recursiveSubviewsAtPoint:tapPointInWindow inView:windowForSelection skipHiddenViews:YES] lastObject];
}
- (NSArray *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden
- (NSArray<UIView *> *)recursiveSubviewsAtPoint:(CGPoint)pointInView inView:(UIView *)view skipHiddenViews:(BOOL)skipHidden
{
NSMutableArray *subviewsAtPoint = [NSMutableArray array];
NSMutableArray<UIView *> *subviewsAtPoint = [NSMutableArray array];
for (UIView *subview in view.subviews) {
BOOL isHidden = subview.hidden || subview.alpha < 0.01;
if (skipHidden && isHidden) {
@@ -640,9 +632,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return subviewsAtPoint;
}
- (NSArray *)allRecursiveSubviewsInView:(UIView *)view
- (NSArray<UIView *> *)allRecursiveSubviewsInView:(UIView *)view
{
NSMutableArray *subviews = [NSMutableArray array];
NSMutableArray<UIView *> *subviews = [NSMutableArray array];
for (UIView *subview in view.subviews) {
[subviews addObject:subview];
[subviews addObjectsFromArray:[self allRecursiveSubviewsInView:subview]];
@@ -650,9 +642,9 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return subviews;
}
- (NSDictionary *)hierarchyDepthsForViews:(NSArray *)views
- (NSDictionary<NSValue *, NSNumber *> *)hierarchyDepthsForViews:(NSArray<UIView *> *)views
{
NSMutableDictionary *hierarchyDepths = [NSMutableDictionary dictionary];
NSMutableDictionary<NSValue *, NSNumber *> *hierarchyDepths = [NSMutableDictionary dictionary];
for (UIView *view in views) {
NSInteger depth = 0;
UIView *tryView = view;
@@ -696,6 +688,33 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
}
#pragma mark - Safe Area Handling
- (CGRect)viewSafeArea
{
CGRect safeArea = self.view.bounds;
#if FLEX_AT_LEAST_IOS11_SDK
if (@available(iOS 11, *)) {
safeArea = UIEdgeInsetsInsetRect(self.view.bounds, self.view.safeAreaInsets);
}
#endif
return safeArea;
}
#if FLEX_AT_LEAST_IOS11_SDK
- (void)viewSafeAreaInsetsDidChange
{
if (@available(iOS 11, *)) {
[super viewSafeAreaInsetsDidChange];
}
CGRect safeArea = [self viewSafeArea];
CGSize toolbarSize = [self.explorerToolbar sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(safeArea))];
[self updateToolbarPositionWithUnconstrainedFrame:CGRectMake(CGRectGetMinX(self.explorerToolbar.frame), CGRectGetMinY(self.explorerToolbar.frame), toolbarSize.width, toolbarSize.height)];
}
#endif
#pragma mark - Touch Handling
- (BOOL)shouldReceiveTouchAtWindowPoint:(CGPoint)pointInWindowCoordinates
@@ -734,7 +753,7 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
{
// Note that we need to wait until the view controller is dismissed to calculated the frame of the outline view.
// Otherwise the coordinate conversion doesn't give the correct result.
[self resignKeyAndDismissViewControllerAnimated:YES completion:^{
[self toggleViewsToolWithCompletion:^{
// If the selected view is outside of the tap point array (selected from "Full Hierarchy"),
// then clear out the tap point array and remove all the outline views.
if (![self.viewsAtTapPoint containsObject:selectedView]) {
@@ -814,6 +833,15 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
return self.previousKeyWindow != nil;
}
- (void)toggleToolWithViewControllerProvider:(UIViewController *(^)(void))future completion:(void(^)(void))completion
{
if (self.presentedViewController) {
[self resignKeyAndDismissViewControllerAnimated:YES completion:completion];
} else {
[self makeKeyAndPresentViewController:future() animated:YES completion:completion];
}
}
#pragma mark - Keyboard Shortcut Helpers
- (void)toggleSelectTool
@@ -836,49 +864,32 @@ typedef NS_ENUM(NSUInteger, FLEXExplorerMode) {
- (void)toggleViewsTool
{
BOOL viewsModalShown = [[self presentedViewController] isKindOfClass:[UINavigationController class]];
viewsModalShown = viewsModalShown && [[[(UINavigationController *)[self presentedViewController] viewControllers] firstObject] isKindOfClass:[FLEXHierarchyTableViewController class]];
if (viewsModalShown) {
[self resignKeyAndDismissViewControllerAnimated:YES completion:nil];
} else {
void (^presentBlock)() = ^{
NSArray *allViews = [self allViewsInHierarchy];
NSDictionary *depthsForViews = [self hierarchyDepthsForViews:allViews];
FLEXHierarchyTableViewController *hierarchyTVC = [[FLEXHierarchyTableViewController alloc] initWithViews:allViews viewsAtTap:self.viewsAtTapPoint selectedView:self.selectedView depths:depthsForViews];
hierarchyTVC.delegate = self;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
};
if (self.presentedViewController) {
[self resignKeyAndDismissViewControllerAnimated:NO completion:presentBlock];
} else {
presentBlock();
[self toggleViewsToolWithCompletion:nil];
}
- (void)toggleViewsToolWithCompletion:(void(^)(void))completion
{
[self toggleToolWithViewControllerProvider:^UIViewController *{
NSArray<UIView *> *allViews = [self allViewsInHierarchy];
NSDictionary *depthsForViews = [self hierarchyDepthsForViews:allViews];
FLEXHierarchyTableViewController *hierarchyTVC = [[FLEXHierarchyTableViewController alloc] initWithViews:allViews viewsAtTap:self.viewsAtTapPoint selectedView:self.selectedView depths:depthsForViews];
hierarchyTVC.delegate = self;
return [[UINavigationController alloc] initWithRootViewController:hierarchyTVC];
} completion:^{
if (completion) {
completion();
}
}
}];
}
- (void)toggleMenuTool
{
BOOL menuModalShown = [[self presentedViewController] isKindOfClass:[UINavigationController class]];
menuModalShown = menuModalShown && [[[(UINavigationController *)[self presentedViewController] viewControllers] firstObject] isKindOfClass:[FLEXGlobalsTableViewController class]];
if (menuModalShown) {
[self resignKeyAndDismissViewControllerAnimated:YES completion:nil];
} else {
void (^presentBlock)() = ^{
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
globalsViewController.delegate = self;
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:globalsViewController];
[self makeKeyAndPresentViewController:navigationController animated:YES completion:nil];
};
if (self.presentedViewController) {
[self resignKeyAndDismissViewControllerAnimated:NO completion:presentBlock];
} else {
presentBlock();
}
}
[self toggleToolWithViewControllerProvider:^UIViewController *{
FLEXGlobalsTableViewController *globalsViewController = [[FLEXGlobalsTableViewController alloc] init];
globalsViewController.delegate = self;
[FLEXGlobalsTableViewController setApplicationWindow:[[UIApplication sharedApplication] keyWindow]];
return [[UINavigationController alloc] initWithRootViewController:globalsViewController];
} completion:nil];
}
- (void)handleDownArrowKeyPressed
-8
View File
@@ -6,12 +6,4 @@
// 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>
@@ -9,6 +9,8 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef UIViewController *(^FLEXCustomContentViewerFuture)(NSData *data);
@interface FLEXManager : NSObject
+ (instancetype)sharedManager;
@@ -23,13 +25,19 @@
/// If this property is set to YES, FLEX will swizzle NSURLConnection*Delegate and NSURLSession*Delegate methods
/// on classes that conform to the protocols. This allows you to view network activity history from the main FLEX menu.
/// Full responses are kept temporarily in a size limited cache and may be pruged under memory pressure.
/// Full responses are kept temporarily in a size-limited cache and may be pruned under memory pressure.
@property (nonatomic, assign, getter=isNetworkDebuggingEnabled) BOOL networkDebuggingEnabled;
/// Defaults to 25 MB if never set. Values set here are presisted across launches of the app.
/// The response cache uses an NSCache, so it may purge prior to hitting the limit when the app is under memory pressure.
@property (nonatomic, assign) NSUInteger networkResponseCacheByteLimit;
/// Requests whose host ends with one of the blacklisted entries in this array will be not be recorded (eg. google.com).
/// Wildcard or subdomain entries are not required (eg. google.com will match any subdomain under google.com).
/// Useful to remove requests that are typically noisy, such as analytics requests that you aren't interested in tracking.
@property (nonatomic, copy) NSArray<NSString *> *networkRequestHostBlacklist;
#pragma mark - Keyboard Shortcuts
/// Simulator keyboard shortcuts are enabled by default.
@@ -49,6 +57,10 @@
#pragma mark - Extensions
/// Default database password is @c nil by default.
/// Set this to the password you want the databases to open with.
@property (copy, nonatomic) NSString *defaultSqliteDatabasePassword;
/// Adds an entry at the bottom of the list of Global State items. Call this method before this view controller is displayed.
/// @param entryName The string to be displayed in the cell.
/// @param objectFutureBlock When you tap on the row, information about the object returned by this block will be displayed.
@@ -67,4 +79,13 @@
- (void)registerGlobalEntryWithName:(NSString *)entryName
viewControllerFutureBlock:(UIViewController * (^)(void))viewControllerFutureBlock;
/// Sets custom viewer for specific content type.
/// @param contentType Mime type like application/json
/// @param viewControllerFutureBlock Viewer (view controller) creation block
/// @note This method must be called from the main thread.
/// The viewControllerFutureBlock will be invoked from the main thread and may not return nil.
/// @note The passed block will be copied and retain for the duration of the application, you may want to use __weak references.
- (void)setCustomViewerForContentType:(NSString *)contentType
viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock;
@end
@@ -13,14 +13,14 @@
#import <Foundation/Foundation.h>
@interface FLEXDatabaseManager : NSObject
@protocol FLEXDatabaseManager <NSObject>
- (instancetype)initWithPath:(NSString*)aPath;
@required
- (instancetype)initWithPath:(NSString*)path;
- (BOOL)open;
- (NSArray *)queryAllTables;
- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName;
- (NSArray *)queryAllDataWithTableName:(NSString *)tableName;
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables;
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName;
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName;
@end
@@ -19,7 +19,7 @@
@property (nonatomic, strong) UITableView *contentTableView;
@property (nonatomic, strong) UIView *leftHeader;
@property (nonatomic, strong) NSDictionary *sortStatusDict;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *sortStatusDict;
@property (nonatomic, strong) NSArray *rowData;
@end
@@ -135,7 +135,7 @@ static const CGFloat kColumnMargin = 1;
- (void)loadHeaderData
{
NSArray *subviews = self.headerScrollView.subviews;
NSArray<UIView *> *subviews = self.headerScrollView.subviews;
for (UIView *subview in subviews) {
[subview removeFromSuperview];
@@ -0,0 +1,14 @@
//
// FLEXRealmDatabaseManager.h
// FLEX
//
// Created by Tim Oliver on 28/01/2016.
// Copyright © 2016 Realm. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FLEXDatabaseManager.h"
@interface FLEXRealmDatabaseManager : NSObject <FLEXDatabaseManager>
@end
@@ -0,0 +1,114 @@
//
// FLEXRealmDatabaseManager.m
// FLEX
//
// Created by Tim Oliver on 28/01/2016.
// Copyright © 2016 Realm. All rights reserved.
//
#import "FLEXRealmDatabaseManager.h"
#if __has_include(<Realm/Realm.h>)
#import <Realm/Realm.h>
#import <Realm/RLMRealm_Dynamic.h>
#else
#import "FLEXRealmDefines.h"
#endif
@interface FLEXRealmDatabaseManager ()
@property (nonatomic, copy) NSString *path;
@property (nonatomic, strong) RLMRealm * realm;
@end
//#endif
@implementation FLEXRealmDatabaseManager
- (instancetype)initWithPath:(NSString*)aPath
{
Class realmClass = NSClassFromString(@"RLMRealm");
if (realmClass == nil) {
return nil;
}
self = [super init];
if (self) {
_path = aPath;
}
return self;
}
- (BOOL)open
{
Class realmClass = NSClassFromString(@"RLMRealm");
Class configurationClass = NSClassFromString(@"RLMRealmConfiguration");
if (realmClass == nil || configurationClass == nil) {
return NO;
}
NSError *error = nil;
id configuration = [[configurationClass alloc] init];
[(RLMRealmConfiguration *)configuration setFileURL:[NSURL fileURLWithPath:self.path]];
self.realm = [realmClass realmWithConfiguration:configuration error:&error];
return (error == nil);
}
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables
{
NSMutableArray<NSDictionary<NSString *, id> *> *allTables = [NSMutableArray array];
RLMSchema *schema = [self.realm schema];
for (RLMObjectSchema *objectSchema in schema.objectSchema) {
if (objectSchema.className == nil) {
continue;
}
NSDictionary<NSString *, id> *dictionary = @{@"name":objectSchema.className};
[allTables addObject:dictionary];
}
return allTables;
}
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName
{
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
if (objectSchema == nil) {
return nil;
}
NSMutableArray<NSString *> *columnNames = [NSMutableArray array];
for (RLMProperty *property in objectSchema.properties) {
[columnNames addObject:property.name];
}
return columnNames;
}
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName
{
RLMObjectSchema *objectSchema = [[self.realm schema] schemaForClassName:tableName];
RLMResults *results = [self.realm allObjects:tableName];
if (results.count == 0 || objectSchema == nil) {
return nil;
}
NSMutableArray<NSDictionary<NSString *, id> *> *allDataEntries = [NSMutableArray array];
for (RLMObject *result in results) {
NSMutableDictionary<NSString *, id> *entry = [NSMutableDictionary dictionary];
for (RLMProperty *property in objectSchema.properties) {
id value = [result valueForKey:property.name];
entry[property.name] = (value) ? (value) : [NSNull null];
}
[allDataEntries addObject:entry];
}
return allDataEntries;
}
@end
@@ -0,0 +1,46 @@
//
// Realm.h
// FLEX
//
// Created by Tim Oliver on 16/02/2016.
// Copyright © 2016 Realm. All rights reserved.
//
#if __has_include(<Realm/Realm.h>)
#else
@class RLMObject, RLMResults, RLMRealm, RLMRealmConfiguration, RLMSchema, RLMObjectSchema, RLMProperty;
@interface RLMRealmConfiguration : NSObject
@property (nonatomic, copy) NSURL *fileURL;
@end
@interface RLMRealm : NSObject
@property (nonatomic, readonly) RLMSchema *schema;
+ (RLMRealm *)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error;
- (RLMResults *)allObjects:(NSString *)className;
@end
@interface RLMSchema : NSObject
@property (nonatomic, readonly) NSArray<RLMObjectSchema *> *objectSchema;
- (RLMObjectSchema *)schemaForClassName:(NSString *)className;
@end
@interface RLMObjectSchema : NSObject
@property (nonatomic, readonly) NSString *className;
@property (nonatomic, readonly) NSArray<RLMProperty *> *properties;
@end
@interface RLMProperty : NSString
@property (nonatomic, readonly) NSString *name;
@end
@interface RLMResults : NSObject <NSFastEnumeration>
@property (nonatomic, readonly) NSInteger count;
@end
@interface RLMObject : NSObject
@end
#endif
@@ -0,0 +1,19 @@
//
// PTDatabaseManager.h
// Derived from:
//
// FMDatabase.h
// FMDB( https://github.com/ccgus/fmdb )
//
// Created by Peng Tao on 15/11/23.
//
// Licensed to Flying Meat Inc. under one or more contributor license agreements.
// See the LICENSE file distributed with this work for the terms under
// which Flying Meat Inc. licenses this file to you.
#import <Foundation/Foundation.h>
#import "FLEXDatabaseManager.h"
@interface FLEXSQLiteDatabaseManager : NSObject <FLEXDatabaseManager>
@end
@@ -6,22 +6,19 @@
// Copyright © 2015 Peng Tao. All rights reserved.
//
#import "FLEXDatabaseManager.h"
#import "FLEXSQLiteDatabaseManager.h"
#import "FLEXManager.h"
#import <sqlite3.h>
static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
@implementation FLEXDatabaseManager
@implementation FLEXSQLiteDatabaseManager
{
sqlite3* _db;
NSString* _databasePath;
}
- (instancetype)initWithPath:(NSString*)aPath
{
self = [super init];
@@ -32,12 +29,22 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
return self;
}
- (BOOL)open {
if (_db) {
return YES;
}
int err = sqlite3_open([_databasePath UTF8String], &_db);
#if SQLITE_HAS_CODEC
NSString *defaultSqliteDatabasePassword = [FLEXManager sharedManager].defaultSqliteDatabasePassword;
if (defaultSqliteDatabasePassword) {
const char *key = defaultSqliteDatabasePassword.UTF8String;
sqlite3_key(_db, key, (int)strlen(key));
}
#endif
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
@@ -79,23 +86,24 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
}
- (NSArray *)queryAllTables
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllTables
{
return [self executeQuery:QUERY_TABLENAMES_SQL];
}
- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName
- (NSArray<NSString *> *)queryAllColumnsWithTableName:(NSString *)tableName
{
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
NSArray *resultArray = [self executeQuery:sql];
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in resultArray) {
[array addObject:dict[@"name"]];
NSArray<NSDictionary<NSString *, id> *> *resultArray = [self executeQuery:sql];
NSMutableArray<NSString *> *array = [NSMutableArray array];
for (NSDictionary<NSString *, id> *dict in resultArray) {
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
[array addObject:columnName];
}
return array;
}
- (NSArray *)queryAllDataWithTableName:(NSString *)tableName
- (NSArray<NSDictionary<NSString *, id> *> *)queryAllDataWithTableName:(NSString *)tableName
{
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@",tableName];
return [self executeQuery:sql];
@@ -104,16 +112,16 @@ static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master W
#pragma mark -
#pragma mark - Private
- (NSArray *)executeQuery:(NSString *)sql
- (NSArray<NSDictionary<NSString *, id> *> *)executeQuery:(NSString *)sql
{
[self open];
NSMutableArray *resultArray = [NSMutableArray array];
NSMutableArray<NSDictionary<NSString *, id> *> *resultArray = [NSMutableArray array];
sqlite3_stmt *pstmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pstmt, 0) == SQLITE_OK) {
while (sqlite3_step(pstmt) == SQLITE_ROW) {
NSUInteger num_cols = (NSUInteger)sqlite3_data_count(pstmt);
if (num_cols > 0) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
NSMutableDictionary<NSString *, id> *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
int columnCount = sqlite3_column_count(pstmt);
@@ -1,6 +1,6 @@
//
// FLEXTableContentHeaderCell.h
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/26.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableContentHeaderCell.m
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/26.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableContentCell.h
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -18,9 +18,9 @@
@interface FLEXTableContentCell : UITableViewCell
@property (nonatomic, strong)NSArray *labels;
@property (nonatomic, strong) NSArray<UILabel *> *labels;
@property (nonatomic, weak) id<FLEXTableContentCellDelegate>delegate;
@property (nonatomic, weak) id<FLEXTableContentCellDelegate> delegate;
+ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
@@ -1,6 +1,6 @@
//
// FLEXTableContentCell.m
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -21,7 +21,7 @@
FLEXTableContentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell) {
cell = [[FLEXTableContentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
NSMutableArray *labels = [NSMutableArray array];
NSMutableArray<UILabel *> *labels = [NSMutableArray array];
for (int i = 0; i < number ; i++) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor whiteColor];
@@ -10,7 +10,7 @@
@interface FLEXTableContentViewController : UIViewController
@property (nonatomic, strong) NSArray *columnsArray;
@property (nonatomic, strong) NSArray *contentsArray;
@property (nonatomic, strong) NSArray<NSString *> *columnsArray;
@property (nonatomic, strong) NSArray<NSDictionary<NSString *, id> *> *contentsArray;
@end
+31 -32
View File
@@ -13,45 +13,39 @@
@interface FLEXTableContentViewController ()<FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate>
@property (nonatomic, strong)FLEXMultiColumnTableView *multiColumView;
@property (nonatomic, strong) FLEXMultiColumnTableView *multiColumView;
@end
@implementation FLEXTableContentViewController
- (instancetype)init
{
self = [super init];
if (self) {
CGRect rectStatus = [UIApplication sharedApplication].statusBarFrame;
CGFloat y = 64;
if (rectStatus.size.height == 0) {
y = 32;
}
_multiColumView = [[FLEXMultiColumnTableView alloc] initWithFrame:
CGRectMake(0, y, self.view.frame.size.width, self.view.frame.size.height - y)];
_multiColumView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_multiColumView.backgroundColor = [UIColor whiteColor];
_multiColumView.dataSource = self;
_multiColumView.delegate = self;
self.automaticallyAdjustsScrollViewInsets = NO;
[self.view addSubview:_multiColumView];
}
return self;
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
[self.view addSubview:self.multiColumView];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.multiColumView reloadData];
}
#pragma mark -
#pragma mark init SubView
- (FLEXMultiColumnTableView *)multiColumView {
if (!_multiColumView) {
_multiColumView = [[FLEXMultiColumnTableView alloc] initWithFrame:
CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
_multiColumView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;
_multiColumView.backgroundColor = [UIColor whiteColor];
_multiColumView.dataSource = self;
_multiColumView.delegate = self;
}
return _multiColumView;
}
#pragma mark MultiColumnTableView DataSource
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView
@@ -78,7 +72,7 @@
- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row
{
if (self.contentsArray.count > row) {
NSDictionary *dic = self.contentsArray[row];
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
if (self.contentsArray.count > column) {
return [NSString stringWithFormat:@"%@",[dic objectForKey:self.columnsArray[column]]];
}
@@ -90,11 +84,11 @@
{
NSMutableArray *result = [NSMutableArray array];
if (self.contentsArray.count > row) {
NSDictionary *dic = self.contentsArray[row];
NSDictionary<NSString *, id> *dic = self.contentsArray[row];
for (int i = 0; i < self.columnsArray.count; i ++) {
[result addObject:dic[self.columnsArray[i]]];
}
return result;
return result;
}
return nil;
}
@@ -119,7 +113,7 @@
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView
{
NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.contentsArray.count];
NSDictionary *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
NSDictionary<NSString *, id> *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
CGSize size = [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attrs context:nil].size;
@@ -139,7 +133,7 @@
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType
{
NSArray *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSArray<NSDictionary<NSString *, id> *> *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(NSDictionary<NSString *, id> * obj1, NSDictionary<NSString *, id> * obj2) {
if ([obj1 objectForKey:text] == [NSNull null]) {
return NSOrderedAscending;
@@ -147,6 +141,11 @@
if ([obj2 objectForKey:text] == [NSNull null]) {
return NSOrderedDescending;
}
if (![[obj1 objectForKey:text] respondsToSelector:@selector(compare:)] && ![[obj2 objectForKey:text] respondsToSelector:@selector(compare:)]) {
return NSOrderedSame;
}
NSComparisonResult result = [[obj1 objectForKey:text] compare:[obj2 objectForKey:text]];
return result;
@@ -171,10 +170,10 @@
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
_multiColumView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
self->_multiColumView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
}
else {
_multiColumView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
self->_multiColumView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
}
[self.view setNeedsLayout];
} completion:nil];
@@ -1,6 +1,6 @@
//
// FLEXTableLeftCell.h
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXTableLeftCell.m
// UICatalog
// FLEX
//
// Created by Peng Tao on 15/11/24.
// Copyright © 2015年 f. All rights reserved.
@@ -10,6 +10,7 @@
@interface FLEXTableListViewController : UITableViewController
+ (BOOL)supportsExtension:(NSString *)extension;
- (instancetype)initWithPath:(NSString *)path;
@end
@@ -7,40 +7,64 @@
//
#import "FLEXTableListViewController.h"
#import "FLEXDatabaseManager.h"
#import "FLEXSQLiteDatabaseManager.h"
#import "FLEXRealmDatabaseManager.h"
#import "FLEXTableContentViewController.h"
@interface FLEXTableListViewController ()
{
FLEXDatabaseManager *_dbm;
id<FLEXDatabaseManager> _dbm;
NSString *_databasePath;
}
@property (nonatomic, strong) NSArray *tables;
@property (nonatomic, strong) NSArray<NSString *> *tables;
+ (NSArray<NSString *> *)supportedSQLiteExtensions;
+ (NSArray<NSString *> *)supportedRealmExtensions;
@end
@implementation FLEXTableListViewController
- (instancetype)initWithPath:(NSString *)path
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
_databasePath = [path copy];
_dbm = [[FLEXDatabaseManager alloc] initWithPath:path];
_dbm = [self databaseManagerForFileAtPath:_databasePath];
[_dbm open];
[self getAllTables];
}
return self;
}
- (id<FLEXDatabaseManager>)databaseManagerForFileAtPath:(NSString *)path
{
NSString *pathExtension = path.pathExtension.lowercaseString;
NSArray<NSString *> *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
if ([sqliteExtensions indexOfObject:pathExtension] != NSNotFound) {
return [[FLEXSQLiteDatabaseManager alloc] initWithPath:path];
}
NSArray<NSString *> *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
if (realmExtensions != nil && [realmExtensions indexOfObject:pathExtension] != NSNotFound) {
return [[FLEXRealmDatabaseManager alloc] initWithPath:path];
}
return nil;
}
- (void)getAllTables
{
NSArray *resultArray = [_dbm queryAllTables];
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in resultArray) {
[array addObject:dict[@"name"]];
NSArray<NSDictionary<NSString *, id> *> *resultArray = [_dbm queryAllTables];
NSMutableArray<NSString *> *array = [NSMutableArray array];
for (NSDictionary<NSString *, id> *dict in resultArray) {
NSString *columnName = (NSString *)dict[@"name"] ?: @"";
[array addObject:columnName];
}
self.tables = array;
}
@@ -79,4 +103,35 @@
return [NSString stringWithFormat:@"%lu tables", (unsigned long)self.tables.count];
}
+ (BOOL)supportsExtension:(NSString *)extension
{
extension = extension.lowercaseString;
NSArray<NSString *> *sqliteExtensions = [FLEXTableListViewController supportedSQLiteExtensions];
if (sqliteExtensions.count > 0 && [sqliteExtensions indexOfObject:extension] != NSNotFound) {
return YES;
}
NSArray<NSString *> *realmExtensions = [FLEXTableListViewController supportedRealmExtensions];
if (realmExtensions.count > 0 && [realmExtensions indexOfObject:extension] != NSNotFound) {
return YES;
}
return NO;
}
+ (NSArray<NSString *> *)supportedSQLiteExtensions
{
return @[@"db", @"sqlite", @"sqlite3"];
}
+ (NSArray<NSString *> *)supportedRealmExtensions
{
if (NSClassFromString(@"RLMRealm") == nil) {
return nil;
}
return @[@"realm"];
}
@end
@@ -14,8 +14,8 @@
@interface FLEXClassesTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *classNames;
@property (nonatomic, strong) NSArray *filteredClassNames;
@property (nonatomic, strong) NSArray<NSString *> *classNames;
@property (nonatomic, strong) NSArray<NSString *> *filteredClassNames;
@property (nonatomic, strong) UISearchBar *searchBar;
@end
@@ -42,7 +42,7 @@
}
}
- (void)setClassNames:(NSArray *)classNames
- (void)setClassNames:(NSArray<NSString *> *)classNames
{
_classNames = classNames;
self.filteredClassNames = classNames;
@@ -53,7 +53,7 @@
unsigned int classNamesCount = 0;
const char **classNames = objc_copyClassNamesForImage([self.binaryImageName UTF8String], &classNamesCount);
if (classNames) {
NSMutableArray *classNameStrings = [NSMutableArray array];
NSMutableArray<NSString *> *classNameStrings = [NSMutableArray array];
for (unsigned int i = 0; i < classNamesCount; i++) {
const char *className = classNames[i];
NSString *classNameString = [NSString stringWithUTF8String:className];
@@ -68,7 +68,7 @@
- (void)updateTitle
{
NSString *shortImageName = [[self.binaryImageName componentsSeparatedByString:@"/"] lastObject];
NSString *shortImageName = self.binaryImageName.lastPathComponent;
self.title = [NSString stringWithFormat:@"%@ Classes (%lu)", shortImageName, (unsigned long)[self.filteredClassNames count]];
}
@@ -12,7 +12,7 @@
@interface FLEXCookiesTableViewController ()
@property (nonatomic, strong) NSArray *cookies;
@property (nonatomic, strong) NSArray<NSHTTPCookie *> *cookies;
@end
@@ -25,7 +25,7 @@
self.title = @"Cookies";
NSSortDescriptor *nameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
_cookies =[[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies sortedArrayUsingDescriptors:@[nameSortDescriptor]];
_cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies sortedArrayUsingDescriptors:@[nameSortDescriptor]];
}
return self;
@@ -1,6 +1,6 @@
//
// FLEXFileBrowserSearchOperation.h
// UICatalog
// FLEX
//
// Created by 啟倫 陳 on 2014/8/4.
// Copyright (c) 2014年 f. All rights reserved.
@@ -20,6 +20,6 @@
@protocol FLEXFileBrowserSearchOperationDelegate <NSObject>
- (void)fileBrowserSearchOperationResult:(NSArray *)searchResult size:(uint64_t)size;
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size;
@end
@@ -1,6 +1,6 @@
//
// FLEXFileBrowserSearchOperation.m
// UICatalog
// FLEX
//
// Created by on 2014/8/4.
// Copyright (c) 2014 f. All rights reserved.
@@ -38,7 +38,7 @@
- (uint64_t)totalSizeAtPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
uint64_t totalSize = [attributes fileSize];
for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
@@ -65,16 +65,16 @@
- (void)main
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSMutableArray *searchPaths = [NSMutableArray array];
NSMutableDictionary *sizeMapping = [NSMutableDictionary dictionary];
NSMutableArray<NSString *> *searchPaths = [NSMutableArray array];
NSMutableDictionary<NSString *, NSNumber *> *sizeMapping = [NSMutableDictionary dictionary];
uint64_t totalSize = 0;
NSMutableArray *stack = [NSMutableArray array];
NSMutableArray<NSString *> *stack = [NSMutableArray array];
[stack flex_push:self.path];
//recursive found all match searchString paths, and precomputing there size
while ([stack count]) {
NSString *currentPath = [stack flex_pop];
NSArray *directoryPath = [fileManager contentsOfDirectoryAtPath:currentPath error:nil];
NSArray<NSString *> *directoryPath = [fileManager contentsOfDirectoryAtPath:currentPath error:nil];
for (NSString *subPath in directoryPath) {
NSString *fullPath = [currentPath stringByAppendingPathComponent:subPath];
@@ -99,7 +99,7 @@
}
//sort
NSArray *sortedArray = [searchPaths sortedArrayUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
NSArray<NSString *> *sortedArray = [searchPaths sortedArrayUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) {
uint64_t pathSize1 = [sizeMapping[path1] unsignedLongLongValue];
uint64_t pathSize2 = [sizeMapping[path2] unsignedLongLongValue];
if (pathSize1 < pathSize2) {
@@ -19,8 +19,8 @@
@interface FLEXFileBrowserTableViewController () <FLEXFileBrowserFileOperationControllerDelegate, FLEXFileBrowserSearchOperationDelegate, UISearchResultsUpdating, UISearchControllerDelegate>
@property (nonatomic, copy) NSString *path;
@property (nonatomic, copy) NSArray *childPaths;
@property (nonatomic, strong) NSArray *searchPaths;
@property (nonatomic, copy) NSArray<NSString *> *childPaths;
@property (nonatomic, strong) NSArray<NSString *> *searchPaths;
@property (nonatomic, strong) NSNumber *recursiveSize;
@property (nonatomic, strong) NSNumber *searchPathsSize;
@property (nonatomic, strong) UISearchController *searchController;
@@ -44,30 +44,30 @@
self.path = path;
self.title = [path lastPathComponent];
self.operationQueue = [NSOperationQueue new];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.delegate = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.tableView.tableHeaderView = self.searchController.searchBar;
//computing path size
FLEXFileBrowserTableViewController *__weak weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
NSDictionary<NSString *, id> *attributes = [fileManager attributesOfItemAtPath:path error:NULL];
uint64_t totalSize = [attributes fileSize];
for (NSString *fileName in [fileManager enumeratorAtPath:path]) {
attributes = [fileManager attributesOfItemAtPath:[path stringByAppendingPathComponent:fileName] error:NULL];
totalSize += [attributes fileSize];
// Bail if the interested view controller has gone away.
if (!weakSelf) {
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
FLEXFileBrowserTableViewController *__strong strongSelf = weakSelf;
strongSelf.recursiveSize = @(totalSize);
@@ -86,14 +86,11 @@
{
[super viewDidLoad];
UIMenuItem *renameMenuItem = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
UIMenuItem *deleteMenuItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
[UIMenuController sharedMenuController].menuItems = @[renameMenuItem, deleteMenuItem];
}
#pragma mark - FLEXFileBrowserSearchOperationDelegate
- (void)fileBrowserSearchOperationResult:(NSArray *)searchResult size:(uint64_t)size
- (void)fileBrowserSearchOperationResult:(NSArray<NSString *> *)searchResult size:(uint64_t)size
{
self.searchPaths = searchResult;
self.searchPathsSize = @(size);
@@ -133,22 +130,22 @@
{
BOOL isSearchActive = self.searchController.isActive;
NSNumber *currentSize = isSearchActive ? self.searchPathsSize : self.recursiveSize;
NSArray *currentPaths = isSearchActive ? self.searchPaths : self.childPaths;
NSArray<NSString *> *currentPaths = isSearchActive ? self.searchPaths : self.childPaths;
NSString *sizeString = nil;
if (!currentSize) {
sizeString = @"Computing size…";
} else {
sizeString = [NSByteCountFormatter stringFromByteCount:[currentSize longLongValue] countStyle:NSByteCountFormatterCountStyleFile];
}
return [NSString stringWithFormat:@"%lu files (%@)", (unsigned long)[currentPaths count], sizeString];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
NSDictionary<NSString *, id> *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:NULL];
BOOL isDirectory = [[attributes fileType] isEqual:NSFileTypeDirectory];
NSString *subtitle = nil;
if (isDirectory) {
@@ -158,15 +155,15 @@
NSString *sizeString = [NSByteCountFormatter stringFromByteCount:[attributes fileSize] countStyle:NSByteCountFormatterCountStyleFile];
subtitle = [NSString stringWithFormat:@"%@ - %@", sizeString, [attributes fileModificationDate]];
}
static NSString *textCellIdentifier = @"textCell";
static NSString *imageCellIdentifier = @"imageCell";
UITableViewCell *cell = nil;
// Separate image and text only cells because otherwise the separator lines get out-of-whack on image cells reused with text only.
BOOL showImagePreview = [FLEXUtility isImagePathExtension:[fullPath pathExtension]];
NSString *cellIdentifier = showImagePreview ? imageCellIdentifier : textCellIdentifier;
if (!cell) {
cell = [[FLEXFileBrowserTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
@@ -177,12 +174,12 @@
NSString *cellTitle = [fullPath lastPathComponent];
cell.textLabel.text = cellTitle;
cell.detailTextLabel.text = subtitle;
if (showImagePreview) {
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.image = [UIImage imageWithContentsOfFile:fullPath];
}
return cell;
}
@@ -190,34 +187,35 @@
{
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSString *subpath = [fullPath lastPathComponent];
NSString *pathExtension = [subpath pathExtension];
BOOL isDirectory = NO;
BOOL stillExists = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
if (stillExists) {
UIViewController *drillInViewController = nil;
if (isDirectory) {
drillInViewController = [[[self class] alloc] initWithPath:fullPath];
} else if ([FLEXUtility isImagePathExtension:[fullPath pathExtension]]) {
} else if ([FLEXUtility isImagePathExtension:pathExtension]) {
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];
drillInViewController = [[FLEXImagePreviewViewController alloc] initWithImage:image];
} else {
// Special case keyed archives, json, and plists to get more readable data.
NSString *prettyString = nil;
if ([[subpath pathExtension] isEqual:@"archive"]) {
if ([pathExtension isEqual:@"archive"] || [pathExtension isEqual:@"coded"]) {
prettyString = [[NSKeyedUnarchiver unarchiveObjectWithFile:fullPath] description];
} else if ([[subpath pathExtension] isEqualToString:@"json"]) {
} else if ([pathExtension isEqualToString:@"json"]) {
prettyString = [FLEXUtility prettyJSONStringFromData:[NSData dataWithContentsOfFile:fullPath]];
} else if ([[subpath pathExtension] isEqualToString:@"plist"]) {
} else if ([pathExtension isEqualToString:@"plist"]) {
NSData *fileData = [NSData dataWithContentsOfFile:fullPath];
prettyString = [[NSPropertyListSerialization propertyListWithData:fileData options:0 format:NULL error:NULL] description];
}
if ([prettyString length] > 0) {
drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
} else if ([FLEXWebViewController supportsPathExtension:[subpath pathExtension]]) {
} else if ([FLEXWebViewController supportsPathExtension:pathExtension]) {
drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
} else if ([[subpath pathExtension] isEqualToString:@"db"]) {
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
} else if ([FLEXTableListViewController supportsExtension:subpath.pathExtension]) {
drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
}
else {
NSString *fileString = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:NULL];
@@ -226,7 +224,7 @@
}
}
}
if (drillInViewController) {
drillInViewController.title = [subpath lastPathComponent];
[self.navigationController pushViewController:drillInViewController animated:YES];
@@ -242,12 +240,25 @@
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIMenuItem *renameMenuItem = [[UIMenuItem alloc] initWithTitle:@"Rename" action:@selector(fileBrowserRename:)];
UIMenuItem *deleteMenuItem = [[UIMenuItem alloc] initWithTitle:@"Delete" action:@selector(fileBrowserDelete:)];
NSMutableArray *menus = [NSMutableArray arrayWithObjects:renameMenuItem, deleteMenuItem, nil];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
NSError *error = nil;
NSDictionary *attributes = [NSFileManager.defaultManager attributesOfItemAtPath:fullPath error:&error];
if (error == nil && [attributes fileType] != NSFileTypeDirectory) {
UIMenuItem *shareMenuItem = [[UIMenuItem alloc] initWithTitle:@"Share" action:@selector(fileBrowserShare:)];
[menus addObject:shareMenuItem];
}
[UIMenuController sharedMenuController].menuItems = menus;
return YES;
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
return action == @selector(fileBrowserDelete:) || action == @selector(fileBrowserRename:);
return action == @selector(fileBrowserDelete:) || action == @selector(fileBrowserRename:) || action == @selector(fileBrowserShare:);
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
@@ -287,12 +298,21 @@
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
self.fileOperationController = [[FLEXFileBrowserFileDeleteOperationController alloc] initWithPath:fullPath];
self.fileOperationController.delegate = self;
[self.fileOperationController show];
}
- (void)fileBrowserShare:(UITableViewCell *)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *fullPath = [self filePathAtIndexPath:indexPath];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[fullPath] applicationActivities:nil];
[self presentViewController:activityViewController animated:true completion:nil];
}
- (void)reloadDisplayedPaths
{
if (self.searchController.isActive) {
@@ -305,8 +325,8 @@
- (void)reloadChildPaths
{
NSMutableArray *childPaths = [NSMutableArray array];
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:NULL];
NSMutableArray<NSString *> *childPaths = [NSMutableArray array];
NSArray<NSString *> *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.path error:NULL];
for (NSString *subpath in subpaths) {
[childPaths addObject:[self.path stringByAppendingPathComponent:subpath]];
}
@@ -347,4 +367,10 @@
[[UIApplication sharedApplication] sendAction:_cmd to:target from:self forEvent:nil];
}
- (void)fileBrowserShare:(UIMenuController *)sender
{
id target = [self.nextResponder targetForAction:_cmd withSender:sender];
[[UIApplication sharedApplication] sendAction:_cmd to:target from:self forEvent:nil];
}
@end
@@ -33,6 +33,7 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
FLEXGlobalsRowAppDelegate,
FLEXGlobalsRowRootViewController,
FLEXGlobalsRowUserDefaults,
FLEXGlobalsRowMainBundle,
FLEXGlobalsRowApplication,
FLEXGlobalsRowKeyWindow,
FLEXGlobalsRowMainScreen,
@@ -42,17 +43,15 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
@interface FLEXGlobalsTableViewController ()
/// [FLEXGlobalsTableViewControllerEntry *]
@property (nonatomic, readonly, copy) NSArray *entries;
@property (nonatomic, readonly, copy) NSArray<FLEXGlobalsTableViewControllerEntry *> *entries;
@end
@implementation FLEXGlobalsTableViewController
/// [FLEXGlobalsTableViewControllerEntry *]
+ (NSArray *)defaultGlobalEntries
+ (NSArray<FLEXGlobalsTableViewControllerEntry *> *)defaultGlobalEntries
{
NSMutableArray *defaultGlobalEntries = [NSMutableArray array];
NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *defaultGlobalEntries = [NSMutableArray array];
for (FLEXGlobalsRow defaultRowIndex = 0; defaultRowIndex < FLEXGlobalsRowCount; defaultRowIndex++) {
FLEXGlobalsTableViewControllerEntryNameFuture titleFuture = nil;
@@ -125,6 +124,16 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
};
break;
case FLEXGlobalsRowMainBundle:
titleFuture = ^NSString *{
return @"📦 +[NSBundle mainBundle]";
};
viewControllerFuture = ^UIViewController *{
NSBundle *mainBundle = [NSBundle mainBundle];
return [FLEXObjectExplorerFactory explorerViewControllerForObject:mainBundle];
};
break;
case FLEXGlobalsRowApplication:
titleFuture = ^NSString *{
return @"💾 +[UIApplication sharedApplication]";
@@ -12,16 +12,49 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXUtility.h"
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectRef.h"
#import <malloc/malloc.h>
@interface FLEXInstancesTableViewController ()
@property (nonatomic, strong) NSArray *instances;
@property (nonatomic, strong) NSArray *fieldNames;
/// Array of [[section], [section], ...]
/// where [section] is [["row title", instance], ["row title", instance], ...]
@property (nonatomic) NSArray<FLEXObjectRef *> *instances;
@property (nonatomic) NSArray<NSArray<FLEXObjectRef*>*> *sections;
@property (nonatomic) NSArray<NSString *> *sectionTitles;
@property (nonatomic) NSArray<NSPredicate *> *predicates;
@property (nonatomic, readonly) NSInteger maxSections;
@end
@implementation FLEXInstancesTableViewController
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references {
return [self initWithReferences:references predicates:nil sectionTitles:nil];
}
- (id)initWithReferences:(NSArray<FLEXObjectRef *> *)references
predicates:(NSArray<NSPredicate *> *)predicates
sectionTitles:(NSArray<NSString *> *)sectionTitles {
NSParameterAssert(predicates.count == sectionTitles.count);
self = [super init];
if (self) {
self.instances = references;
self.predicates = predicates;
self.sectionTitles = sectionTitles;
if (predicates.count) {
[self buildSections];
} else {
self.sections = @[references];
}
}
return self;
}
+ (instancetype)instancesTableViewControllerForClassName:(NSString *)className
{
const char *classNameCString = [className UTF8String];
@@ -31,20 +64,26 @@
// Note: objects of certain classes crash when retain is called. It is up to the user to avoid tapping into instance lists for these classes.
// Ex. OS_dispatch_queue_specific_queue
// In the future, we could provide some kind of warning for classes that are known to be problematic.
[instances addObject:object];
if (malloc_size((__bridge const void *)(object)) > 0) {
[instances addObject:object];
}
}
}];
FLEXInstancesTableViewController *instancesViewController = [[self alloc] init];
instancesViewController.instances = instances;
instancesViewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)[instances count]];
return instancesViewController;
NSArray<FLEXObjectRef *> *references = [FLEXObjectRef referencingAll:instances];
FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:references];
viewController.title = [NSString stringWithFormat:@"%@ (%lu)", className, (unsigned long)[instances count]];
return viewController;
}
+ (instancetype)instancesTableViewControllerForInstancesReferencingObject:(id)object
{
NSMutableArray *instances = [NSMutableArray array];
NSMutableArray *fieldNames = [NSMutableArray array];
NSMutableArray<FLEXObjectRef *> *instances = [NSMutableArray array];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
// Skip Swift objects
if ([actualClass isKindOfClass:NSClassFromString(@"SwiftObject")]) {
return;
}
// Get all the ivars on the object. Start with the class and and travel up the inheritance chain.
// Once we find a match, record it and move on to the next object. There's no reason to find multiple matches within the same object.
Class tryClass = actualClass;
@@ -58,8 +97,7 @@
ptrdiff_t offset = ivar_getOffset(ivar);
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
[instances addObject:tryObject];
[fieldNames addObject:@(ivar_getName(ivar))];
[instances addObject:[FLEXObjectRef referencing:tryObject ivar:@(ivar_getName(ivar))]];
return;
}
}
@@ -67,11 +105,85 @@
tryClass = class_getSuperclass(tryClass);
}
}];
FLEXInstancesTableViewController *instancesViewController = [[self alloc] init];
instancesViewController.instances = instances;
instancesViewController.fieldNames = fieldNames;
instancesViewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
return instancesViewController;
NSArray<NSPredicate *> *predicates = [self defaultPredicates];
NSArray<NSString *> *sectionTitles = [self defaultSectionTitles];
FLEXInstancesTableViewController *viewController = [[self alloc] initWithReferences:instances
predicates:predicates
sectionTitles:sectionTitles];
viewController.title = [NSString stringWithFormat:@"Referencing %@ %p", NSStringFromClass(object_getClass(object)), object];
return viewController;
}
+ (NSPredicate *)defaultPredicateForSection:(NSInteger)section
{
// These are the types of references that we typically don't care about.
// We want this list of "object-ivar pairs" split into two sections.
BOOL(^isObserver)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
NSString *row = ref.reference;
return [row isEqualToString:@"__NSObserver object"] ||
[row isEqualToString:@"_CFXNotificationObjcObserverRegistration _object"];
};
/// These are common AutoLayout related references we also rarely care about.
BOOL(^isConstraintRelated)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
static NSSet *ignored = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ignored = [NSSet setWithArray:@[
@"NSLayoutConstraint _container",
@"NSContentSizeLayoutConstraint _container",
@"NSAutoresizingMaskLayoutConstraint _container",
@"MASViewConstraint _installedView",
@"MASLayoutConstraint _container",
@"MASViewAttribute _view"
]];
});
NSString *row = ref.reference;
return ([row hasPrefix:@"NSLayout"] && [row hasSuffix:@" _referenceItem"]) ||
([row hasPrefix:@"NSIS"] && [row hasSuffix:@" _delegate"]) ||
([row hasPrefix:@"_NSAutoresizingMask"] && [row hasSuffix:@" _referenceItem"]) ||
[ignored containsObject:row];
};
BOOL(^isEssential)(FLEXObjectRef *, NSDictionary *) = ^BOOL(FLEXObjectRef *ref, NSDictionary *bindings) {
return !(isObserver(ref, bindings) || isConstraintRelated(ref, bindings));
};
switch (section) {
case 0: return [NSPredicate predicateWithBlock:isEssential];
case 1: return [NSPredicate predicateWithBlock:isConstraintRelated];
case 2: return [NSPredicate predicateWithBlock:isObserver];
default: return nil;
}
}
+ (NSArray<NSPredicate *> *)defaultPredicates {
return @[[self defaultPredicateForSection:0],
[self defaultPredicateForSection:1],
[self defaultPredicateForSection:2]];
}
+ (NSArray<NSString *> *)defaultSectionTitles {
return @[@"", @"AutoLayout", @"Trivial"];
}
- (void)buildSections
{
NSInteger maxSections = self.maxSections;
NSMutableArray *sections = [NSMutableArray array];
for (NSInteger i = 0; i < maxSections; i++) {
NSPredicate *predicate = self.predicates[i];
[sections addObject:[self.instances filteredArrayUsingPredicate:predicate]];
}
self.sections = sections;
}
- (NSInteger)maxSections {
return self.predicates.count ?: 1;
}
@@ -79,12 +191,12 @@
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
return self.maxSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.instances count];
return self.sections[section].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -99,26 +211,33 @@
cell.detailTextLabel.font = cellFont;
cell.detailTextLabel.textColor = [UIColor grayColor];
}
id instance = self.instances[indexPath.row];
NSString *title = nil;
if ((NSInteger)[self.fieldNames count] > indexPath.row) {
title = [NSString stringWithFormat:@"%@ %@", NSStringFromClass(object_getClass(instance)), self.fieldNames[indexPath.row]];
} else {
title = [NSString stringWithFormat:@"%@ %p", NSStringFromClass(object_getClass(instance)), instance];
}
cell.textLabel.text = title;
cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:instance];
FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
cell.textLabel.text = row.reference;
cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:row.object];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (self.sectionTitles.count) {
// Return nil instead of empty strings
NSString *title = self.sectionTitles[section];
if (title.length) {
return title;
}
}
return nil;
}
#pragma mark - Table View Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
id instance = self.instances[indexPath.row];
id instance = self.instances[indexPath.row].object;
FLEXObjectExplorerViewController *drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:instance];
[self.navigationController pushViewController:drillInViewController animated:YES];
}
@@ -9,14 +9,16 @@
#import "FLEXLibrariesTableViewController.h"
#import "FLEXUtility.h"
#import "FLEXClassesTableViewController.h"
#import "FLEXClassExplorerViewController.h"
#import <objc/runtime.h>
@interface FLEXLibrariesTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *imageNames;
@property (nonatomic, strong) NSArray *filteredImageNames;
@property (nonatomic, strong) NSArray<NSString *> *imageNames;
@property (nonatomic, strong) NSArray<NSString *> *filteredImageNames;
@property (nonatomic, strong) UISearchBar *searchBar;
@property (nonatomic, strong) Class foundClass;
@end
@@ -50,7 +52,7 @@
unsigned int imageNamesCount = 0;
const char **imageNames = objc_copyImageNames(&imageNamesCount);
if (imageNames) {
NSMutableArray *imageNameStrings = [NSMutableArray array];
NSMutableArray<NSString *> *imageNameStrings = [NSMutableArray array];
NSString *appImageName = [FLEXUtility applicationImageName];
for (unsigned int i = 0; i < imageNamesCount; i++) {
const char *imageName = imageNames[i];
@@ -63,9 +65,9 @@
// Sort alphabetically
self.imageNames = [imageNameStrings sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(NSString *name1, NSString *name2) {
NSString *shortName1 = [self shortNameForImageName:name1];
NSString *shortName2 = [self shortNameForImageName:name2];
return [shortName1 caseInsensitiveCompare:shortName2];
NSString *shortName1 = [self shortNameForImageName:name1];
NSString *shortName2 = [self shortNameForImageName:name2];
return [shortName1 caseInsensitiveCompare:shortName2];
}];
free(imageNames);
@@ -74,16 +76,14 @@
- (NSString *)shortNameForImageName:(NSString *)imageName
{
NSString *shortName = nil;
NSArray *components = [imageName componentsSeparatedByString:@"/"];
NSUInteger componentsCount = [components count];
if (componentsCount >= 2) {
shortName = [NSString stringWithFormat:@"%@/%@", components[componentsCount - 2], components[componentsCount - 1]];
NSArray<NSString *> *components = [imageName componentsSeparatedByString:@"/"];
if (components.count >= 2) {
return [NSString stringWithFormat:@"%@/%@", components[components.count - 2], components[components.count - 1]];
}
return shortName;
return imageName.lastPathComponent;
}
- (void)setImageNames:(NSArray *)imageNames
- (void)setImageNames:(NSArray<NSString *> *)imageNames
{
if (![_imageNames isEqual:imageNames]) {
_imageNames = imageNames;
@@ -97,7 +97,7 @@
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if ([searchText length] > 0) {
NSPredicate *searchPreidcate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
NSPredicate *searchPreidcate = [NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary<NSString *, id> *bindings) {
BOOL matches = NO;
NSString *shortName = [self shortNameForImageName:evaluatedObject];
if ([shortName rangeOfString:searchText options:NSCaseInsensitiveSearch].length > 0) {
@@ -109,6 +109,8 @@
} else {
self.filteredImageNames = self.imageNames;
}
self.foundClass = NSClassFromString(searchText);
[self.tableView reloadData];
}
@@ -127,22 +129,32 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.filteredImageNames count];
return self.filteredImageNames.count + (self.foundClass ? 1 : 0);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
static NSString *cellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
}
NSString *fullImageName = self.filteredImageNames[indexPath.row];
cell.textLabel.text = [self shortNameForImageName:fullImageName];
NSString *executablePath;
if (self.foundClass) {
if (indexPath.row == 0) {
cell.textLabel.text = [NSString stringWithFormat:@"Class \"%@\"", self.searchBar.text];
return cell;
} else {
executablePath = self.filteredImageNames[indexPath.row-1];
}
} else {
executablePath = self.filteredImageNames[indexPath.row];
}
cell.textLabel.text = [self shortNameForImageName:executablePath];
return cell;
}
@@ -151,9 +163,15 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXClassesTableViewController *classesViewController = [[FLEXClassesTableViewController alloc] init];
classesViewController.binaryImageName = self.filteredImageNames[indexPath.row];
[self.navigationController pushViewController:classesViewController animated:YES];
if (indexPath.row == 0 && self.foundClass) {
FLEXClassExplorerViewController *objectExplorer = [FLEXClassExplorerViewController new];
objectExplorer.object = self.foundClass;
[self.navigationController pushViewController:objectExplorer animated:YES];
} else {
FLEXClassesTableViewController *classesViewController = [[FLEXClassesTableViewController alloc] init];
classesViewController.binaryImageName = self.filteredImageNames[self.foundClass ? indexPath.row-1 : indexPath.row];
[self.navigationController pushViewController:classesViewController animated:YES];
}
}
@end
@@ -14,12 +14,14 @@
static const NSInteger kFLEXLiveObjectsSortAlphabeticallyIndex = 0;
static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
static const NSInteger kFLEXLiveObjectsSortBySizeIndex = 2;
@interface FLEXLiveObjectsTableViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSDictionary *instanceCountsForClassNames;
@property (nonatomic, readonly) NSArray *allClassNames;
@property (nonatomic, strong) NSArray *filteredClassNames;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *instanceCountsForClassNames;
@property (nonatomic, strong) NSDictionary<NSString *, NSNumber *> *instanceSizesForClassNames;
@property (nonatomic, readonly) NSArray<NSString *> *allClassNames;
@property (nonatomic, strong) NSArray<NSString *> *filteredClassNames;
@property (nonatomic, strong) UISearchBar *searchBar;
@end
@@ -34,7 +36,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
self.searchBar.delegate = self;
self.searchBar.showsScopeBar = YES;
self.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count"];
self.searchBar.scopeButtonTitles = @[@"Sort Alphabetically", @"Sort by Count", @"Sort by Size"];
[self.searchBar sizeToFit];
self.tableView.tableHeaderView = self.searchBar;
@@ -44,7 +46,7 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
[self reloadTableData];
}
- (NSArray *)allClassNames
- (NSArray<NSString *> *)allClassNames
{
return [self.instanceCountsForClassNames allKeys];
}
@@ -72,18 +74,21 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
}];
// Convert our CF primitive dictionary into a nicer mapping of class name strings to counts that we will use as the table's model.
NSMutableDictionary *mutableCountsForClassNames = [NSMutableDictionary dictionary];
NSMutableDictionary<NSString *, NSNumber *> *mutableCountsForClassNames = [NSMutableDictionary dictionary];
NSMutableDictionary<NSString *, NSNumber *> *mutableSizesForClassNames = [NSMutableDictionary dictionary];
for (unsigned int i = 0; i < classCount; i++) {
Class class = classes[i];
NSUInteger instanceCount = (NSUInteger)CFDictionaryGetValue(mutableCountsForClasses, (__bridge const void *)(class));
NSString *className = @(class_getName(class));
if (instanceCount > 0) {
NSString *className = @(class_getName(class));
[mutableCountsForClassNames setObject:@(instanceCount) forKey:className];
}
[mutableSizesForClassNames setObject:@(class_getInstanceSize(class)) forKey:className];
}
free(classes);
self.instanceCountsForClassNames = mutableCountsForClassNames;
self.instanceSizesForClassNames = mutableSizesForClassNames;
[self updateTableDataForSearchFilter];
}
@@ -99,19 +104,27 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
NSString *title = @"Live Objects";
NSUInteger totalCount = 0;
NSUInteger totalSize = 0;
for (NSString *className in self.allClassNames) {
totalCount += [self.instanceCountsForClassNames[className] unsignedIntegerValue];
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
totalCount += count;
totalSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
}
NSUInteger filteredCount = 0;
NSUInteger filteredSize = 0;
for (NSString *className in self.filteredClassNames) {
filteredCount += [self.instanceCountsForClassNames[className] unsignedIntegerValue];
NSUInteger count = [self.instanceCountsForClassNames[className] unsignedIntegerValue];
filteredCount += count;
filteredSize += count * [self.instanceSizesForClassNames[className] unsignedIntegerValue];
}
if (filteredCount == totalCount) {
// Unfiltered
title = [title stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
title = [title stringByAppendingFormat:@" (%lu, %@)", (unsigned long)totalCount,
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
} else {
title = [title stringByAppendingFormat:@" (filtered, %lu)", (unsigned long)filteredCount];
title = [title stringByAppendingFormat:@" (filtered, %lu, %@)", (unsigned long)filteredCount,
[NSByteCountFormatter stringFromByteCount:filteredSize countStyle:NSByteCountFormatterCountStyleFile]];
}
self.title = title;
@@ -159,6 +172,15 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
// Reversed for descending counts.
return [count2 compare:count1];
}];
} else if (self.searchBar.selectedScopeButtonIndex == kFLEXLiveObjectsSortBySizeIndex) {
self.filteredClassNames = [self.filteredClassNames sortedArrayUsingComparator:^NSComparisonResult(NSString *className1, NSString *className2) {
NSNumber *count1 = self.instanceCountsForClassNames[className1];
NSNumber *count2 = self.instanceCountsForClassNames[className2];
NSNumber *size1 = self.instanceSizesForClassNames[className1];
NSNumber *size2 = self.instanceSizesForClassNames[className2];
// Reversed for descending sizes.
return [@(count2.integerValue * size2.integerValue) compare:@(count1.integerValue * size1.integerValue)];
}];
}
[self updateTitle];
@@ -190,7 +212,10 @@ static const NSInteger kFLEXLiveObjectsSortByCountIndex = 1;
NSString *className = self.filteredClassNames[indexPath.row];
NSNumber *count = self.instanceCountsForClassNames[className];
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld)", className, (long)[count integerValue]];
NSNumber *size = self.instanceSizesForClassNames[className];
unsigned long totalSize = count.unsignedIntegerValue * size.unsignedIntegerValue;
cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld, %@)", className, (long)[count integerValue],
[NSByteCountFormatter stringFromByteCount:totalSize countStyle:NSByteCountFormatterCountStyleFile]];
return cell;
}
@@ -0,0 +1,22 @@
//
// FLEXObjectRef.h
// FLEX
//
// Created by Tanner Bennett on 7/24/18.
// Copyright (c) 2018 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface FLEXObjectRef : NSObject
+ (instancetype)referencing:(id)object;
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName;
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects;
/// For example, "NSString 0x1d4085d0" or "NSLayoutConstraint _object"
@property (nonatomic, readonly) NSString *reference;
@property (nonatomic, readonly) id object;
@end
@@ -0,0 +1,47 @@
//
// FLEXObjectRef.m
// FLEX
//
// Created by Tanner Bennett on 7/24/18.
// Copyright (c) 2018 Flipboard. All rights reserved.
//
#import "FLEXObjectRef.h"
#import <objc/runtime.h>
@implementation FLEXObjectRef
+ (instancetype)referencing:(id)object {
return [[self alloc] initWithObject:object ivarName:nil];
}
+ (instancetype)referencing:(id)object ivar:(NSString *)ivarName {
return [[self alloc] initWithObject:object ivarName:ivarName];
}
+ (NSArray<FLEXObjectRef *> *)referencingAll:(NSArray *)objects {
NSMutableArray<FLEXObjectRef *> *refs = [NSMutableArray array];
for (id obj in objects) {
[refs addObject:[self referencing:obj]];
}
return refs;
}
- (id)initWithObject:(id)object ivarName:(NSString *)ivar {
self = [super init];
if (self) {
_object = object;
NSString *class = NSStringFromClass(object_getClass(object));
if (ivar) {
_reference = [NSString stringWithFormat:@"%@ %@", class, ivar];
} else {
_reference = [NSString stringWithFormat:@"%@ %p", class, object];
}
}
return self;
}
@end
@@ -102,23 +102,23 @@
+ (BOOL)supportsPathExtension:(NSString *)extension
{
BOOL supported = NO;
NSSet *supportedExtensions = [self webViewSupportedPathExtensions];
NSSet<NSString *> *supportedExtensions = [self webViewSupportedPathExtensions];
if ([supportedExtensions containsObject:[extension lowercaseString]]) {
supported = YES;
}
return supported;
}
+ (NSSet *)webViewSupportedPathExtensions
+ (NSSet<NSString *> *)webViewSupportedPathExtensions
{
static NSSet *pathExtenstions = nil;
static NSSet<NSString *> *pathExtenstions = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Note that this is not exhaustive, but all these extensions should work well in the web view.
// See https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/CreatingContentforSafarioniPhone/CreatingContentforSafarioniPhone.html#//apple_ref/doc/uid/TP40006482-SW7
pathExtenstions = [NSSet setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
pathExtenstions = [NSSet<NSString *> setWithArray:@[@"jpg", @"jpeg", @"png", @"gif", @"pdf", @"svg", @"tiff", @"3gp", @"3gpp", @"3g2",
@"3gp2", @"aiff", @"aif", @"aifc", @"cdda", @"amr", @"mp3", @"swa", @"mp4", @"mpeg",
@"mpg", @"mp3", @"wav", @"bwf", @"m4a", @"m4b", @"m4p", @"mov", @"qt", @"mqv", @"m4v"]];
});
return pathExtenstions;
@@ -0,0 +1,205 @@
//
// Taken from https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h
// by Tanner Bennett on 03/03/2019 with minimal modifications.
//
//===-- ActivityStreamAPI.h -------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef ActivityStreamSPI_h
#define ActivityStreamSPI_h
#include <sys/time.h>
// #include <xpc/xpc.h>
/* By default, XPC objects are declared as Objective-C types when building with
* an Objective-C compiler. This allows them to participate in ARC, in RR
* management by the Blocks runtime and in leaks checking by the static
* analyzer, and enables them to be added to Cocoa collections.
*
* See <os/object.h> for details.
*/
#if OS_OBJECT_USE_OBJC
OS_OBJECT_DECL(xpc_object);
#else
typedef void * xpc_object_t;
#endif
#define OS_ACTIVITY_MAX_CALLSTACK 32
// Enums
typedef NS_ENUM(uint32_t, os_activity_stream_flag_t) {
OS_ACTIVITY_STREAM_PROCESS_ONLY = 0x00000001,
OS_ACTIVITY_STREAM_SKIP_DECODE = 0x00000002,
OS_ACTIVITY_STREAM_PAYLOAD = 0x00000004,
OS_ACTIVITY_STREAM_HISTORICAL = 0x00000008,
OS_ACTIVITY_STREAM_CALLSTACK = 0x00000010,
OS_ACTIVITY_STREAM_DEBUG = 0x00000020,
OS_ACTIVITY_STREAM_BUFFERED = 0x00000040,
OS_ACTIVITY_STREAM_NO_SENSITIVE = 0x00000080,
OS_ACTIVITY_STREAM_INFO = 0x00000100,
OS_ACTIVITY_STREAM_PROMISCUOUS = 0x00000200,
OS_ACTIVITY_STREAM_PRECISE_TIMESTAMPS = 0x00000200
};
typedef NS_ENUM(uint32_t, os_activity_stream_type_t) {
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE = 0x0201,
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION = 0x0202,
OS_ACTIVITY_STREAM_TYPE_ACTIVITY_USERACTION = 0x0203,
OS_ACTIVITY_STREAM_TYPE_TRACE_MESSAGE = 0x0300,
OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE = 0x0400,
OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE = 0x0480,
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_BEGIN = 0x0601,
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_END = 0x0602,
OS_ACTIVITY_STREAM_TYPE_SIGNPOST_EVENT = 0x0603,
OS_ACTIVITY_STREAM_TYPE_STATEDUMP_EVENT = 0x0A00,
};
typedef NS_ENUM(uint32_t, os_activity_stream_event_t) {
OS_ACTIVITY_STREAM_EVENT_STARTED = 1,
OS_ACTIVITY_STREAM_EVENT_STOPPED = 2,
OS_ACTIVITY_STREAM_EVENT_FAILED = 3,
OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED = 4,
OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED = 5,
};
// Types
typedef uint64_t os_activity_id_t;
typedef struct os_activity_stream_s *os_activity_stream_t;
typedef struct os_activity_stream_entry_s *os_activity_stream_entry_t;
#define OS_ACTIVITY_STREAM_COMMON() \
uint64_t trace_id; \
uint64_t timestamp; \
uint64_t thread; \
const uint8_t *image_uuid; \
const char *image_path; \
struct timeval tv_gmt; \
struct timezone tz; \
uint32_t offset
typedef struct os_activity_stream_common_s {
OS_ACTIVITY_STREAM_COMMON();
} * os_activity_stream_common_t;
struct os_activity_create_s {
OS_ACTIVITY_STREAM_COMMON();
const char *name;
os_activity_id_t creator_aid;
uint64_t unique_pid;
};
struct os_activity_transition_s {
OS_ACTIVITY_STREAM_COMMON();
os_activity_id_t transition_id;
};
typedef struct os_log_message_s {
OS_ACTIVITY_STREAM_COMMON();
const char *format;
const uint8_t *buffer;
size_t buffer_sz;
const uint8_t *privdata;
size_t privdata_sz;
const char *subsystem;
const char *category;
uint32_t oversize_id;
uint8_t ttl;
bool persisted;
} * os_log_message_t;
typedef struct os_trace_message_v2_s {
OS_ACTIVITY_STREAM_COMMON();
const char *format;
const void *buffer;
size_t bufferLen;
xpc_object_t __unsafe_unretained payload;
} * os_trace_message_v2_t;
typedef struct os_activity_useraction_s {
OS_ACTIVITY_STREAM_COMMON();
const char *action;
bool persisted;
} * os_activity_useraction_t;
typedef struct os_signpost_s {
OS_ACTIVITY_STREAM_COMMON();
const char *format;
const uint8_t *buffer;
size_t buffer_sz;
const uint8_t *privdata;
size_t privdata_sz;
const char *subsystem;
const char *category;
uint64_t duration_nsec;
uint32_t callstack_depth;
uint64_t callstack[OS_ACTIVITY_MAX_CALLSTACK];
} * os_signpost_t;
typedef struct os_activity_statedump_s {
OS_ACTIVITY_STREAM_COMMON();
char *message;
size_t message_size;
char image_path_buffer[PATH_MAX];
} * os_activity_statedump_t;
struct os_activity_stream_entry_s {
os_activity_stream_type_t type;
// information about the process streaming the data
pid_t pid;
uint64_t proc_id;
const uint8_t *proc_imageuuid;
const char *proc_imagepath;
// the activity associated with this streamed event
os_activity_id_t activity_id;
os_activity_id_t parent_id;
union {
struct os_activity_stream_common_s common;
struct os_activity_create_s activity_create;
struct os_activity_transition_s activity_transition;
struct os_log_message_s log_message;
struct os_trace_message_v2_s trace_message;
struct os_activity_useraction_s useraction;
struct os_signpost_s signpost;
struct os_activity_statedump_s statedump;
};
};
// Blocks
typedef bool (^os_activity_stream_block_t)(os_activity_stream_entry_t entry,
int error);
typedef void (^os_activity_stream_event_block_t)(
os_activity_stream_t stream, os_activity_stream_event_t event);
// SPI entry point prototypes
typedef os_activity_stream_t (*os_activity_stream_for_pid_t)(
pid_t pid, os_activity_stream_flag_t flags,
os_activity_stream_block_t stream_block);
typedef void (*os_activity_stream_resume_t)(os_activity_stream_t stream);
typedef void (*os_activity_stream_cancel_t)(os_activity_stream_t stream);
typedef char *(*os_log_copy_formatted_message_t)(os_log_message_t log_message);
typedef void (*os_activity_stream_set_event_handler_t)(
os_activity_stream_t stream, os_activity_stream_event_block_t block);
#endif /* ActivityStreamSPI_h */
@@ -0,0 +1,18 @@
//
// FLEXASLLogController.h
// FLEX
//
// Created by Tanner on 3/14/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXLogController.h"
@interface FLEXASLLogController : NSObject <FLEXLogController>
/// Guaranteed to call back on the main thread.
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
- (BOOL)startMonitoring;
@end
@@ -0,0 +1,154 @@
//
// FLEXASLLogController.m
// FLEX
//
// Created by Tanner on 3/14/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXASLLogController.h"
#import <asl.h>
// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
#if TARGET_IPHONE_SIMULATOR
#define updateInterval 5.0
#else
#define updateInterval 1.0
#endif
@interface FLEXASLLogController ()
@property (nonatomic, readonly) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
@property (nonatomic, strong) NSTimer *logUpdateTimer;
@property (nonatomic, readonly) NSMutableIndexSet *logMessageIdentifiers;
// ASL stuff
@property (nonatomic) NSUInteger heapSize;
@property (nonatomic) dispatch_queue_t logQueue;
@property (nonatomic) dispatch_io_t io;
@property (nonatomic) NSString *remaining;
@property (nonatomic) int stderror;
@property (nonatomic) NSString *lastTimestamp;
@end
@implementation FLEXASLLogController
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
{
return [[self alloc] initWithUpdateHandler:newMessagesHandler];
}
- (id)initWithUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
{
NSParameterAssert(newMessagesHandler);
self = [super init];
if (self) {
_updateHandler = newMessagesHandler;
_logMessageIdentifiers = [NSMutableIndexSet indexSet];
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval
target:self
selector:@selector(updateLogMessages)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)dealloc
{
[self.logUpdateTimer invalidate];
}
- (BOOL)startMonitoring {
[self.logUpdateTimer fire];
return YES;
}
- (void)updateLogMessages
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<FLEXSystemLogMessage *> *newMessages;
@synchronized (self) {
newMessages = [self newLogMessagesForCurrentProcess];
if (!newMessages.count) {
return;
}
for (FLEXSystemLogMessage *message in newMessages) {
[self.logMessageIdentifiers addIndex:(NSUInteger)message.messageID];
}
self.lastTimestamp = @(asl_get(newMessages.lastObject.aslMessage, ASL_KEY_TIME));
}
dispatch_async(dispatch_get_main_queue(), ^{
self.updateHandler(newMessages);
});
});
}
#pragma mark - Log Message Fetching
- (NSArray<FLEXSystemLogMessage *> *)newLogMessagesForCurrentProcess
{
if (!self.logMessageIdentifiers.count) {
return [self allLogMessagesForCurrentProcess];
}
aslresponse response = [self ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *newMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
NSUInteger messageID = (NSUInteger)atoll(asl_get(aslMessage, ASL_KEY_MSG_ID));
if (![self.logMessageIdentifiers containsIndex:messageID]) {
[newMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
}
asl_release(response);
return newMessages;
}
- (aslresponse)ASLMessageListForCurrentProcess
{
static NSString *pidString = nil;
if (!pidString) {
pidString = @([[NSProcessInfo processInfo] processIdentifier]).stringValue;
}
// Create system log query object.
asl_object_t query = asl_new(ASL_TYPE_QUERY);
// Filter for messages from the current process.
// Note that this appears to happen by default on device, but is required in the simulator.
asl_set_query(query, ASL_KEY_PID, pidString.UTF8String, ASL_QUERY_OP_EQUAL);
// Filter for messages after the last retreived message.
if (self.lastTimestamp) {
asl_set_query(query, ASL_KEY_TIME, self.lastTimestamp.UTF8String, ASL_QUERY_OP_GREATER);
}
return asl_search(NULL, query);
}
- (NSArray<FLEXSystemLogMessage *> *)allLogMessagesForCurrentProcess
{
aslresponse response = [self ASLMessageListForCurrentProcess];
aslmsg aslMessage = NULL;
NSMutableArray<FLEXSystemLogMessage *> *logMessages = [NSMutableArray array];
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
asl_release(response);
return logMessages;
}
@end
@@ -0,0 +1,19 @@
//
// FLEXLogController.h
// FLEX
//
// Created by Tanner on 3/17/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FLEXSystemLogMessage.h"
@protocol FLEXLogController <NSObject>
/// Guaranteed to call back on the main thread.
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
- (BOOL)startMonitoring;
@end
@@ -0,0 +1,29 @@
//
// FLEXOSLogController.h
// FLEX
//
// Created by Tanner on 12/19/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXLogController.h"
#define FLEXOSLogAvailable() ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion >= 10)
extern NSString * const kFLEXiOSPersistentOSLogKey;
/// The log controller used for iOS 10 and up.
@interface FLEXOSLogController : NSObject <FLEXLogController>
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler;
- (BOOL)startMonitoring;
/// Whether log messages are to be recorded and kept in-memory in the background.
/// You do not need to initialize this value, only change it.
@property (nonatomic) BOOL persistent;
/// Used mostly internally, but also used by the log VC to persist messages
/// that were created prior to enabling persistence.
@property (nonatomic) NSMutableArray<FLEXSystemLogMessage *> *messages;
@end
@@ -0,0 +1,218 @@
//
// FLEXOSLogController.m
// FLEX
//
// Created by Tanner on 12/19/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXOSLogController.h"
#include <dlfcn.h>
#include "ActivityStreamAPI.h"
NSString * const kFLEXiOSPersistentOSLogKey = @"com.flex.enablePersistentOSLogLogging";
static os_activity_stream_for_pid_t OSActivityStreamForPID;
static os_activity_stream_resume_t OSActivityStreamResume;
static os_activity_stream_cancel_t OSActivityStreamCancel;
static os_log_copy_formatted_message_t OSLogCopyFormattedMessage;
static os_activity_stream_set_event_handler_t OSActivityStreamSetEventHandler;
static int (*proc_name)(int, char *, unsigned int);
static int (*proc_listpids)(uint32_t, uint32_t, void*, int);
static uint8_t (*OSLogGetType)(void *);
@interface FLEXOSLogController ()
+ (FLEXOSLogController *)sharedLogController;
@property (nonatomic) void (^updateHandler)(NSArray<FLEXSystemLogMessage *> *);
@property (nonatomic) BOOL canPrint;
@property (nonatomic) int filterPid;
@property (nonatomic) BOOL levelInfo;
@property (nonatomic) BOOL subsystemInfo;
@property (nonatomic) os_activity_stream_t stream;
@end
@implementation FLEXOSLogController
+ (void)load
{
// Persist logs when the app launches on iOS 10 if we have persitent logs turned on
if (FLEXOSLogAvailable()) {
BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
if (persistent) {
[self sharedLogController].persistent = YES;
[[self sharedLogController] startMonitoring];
}
}
}
+ (instancetype)sharedLogController {
static FLEXOSLogController *shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [self new];
});
return shared;
}
+ (instancetype)withUpdateHandler:(void(^)(NSArray<FLEXSystemLogMessage *> *newMessages))newMessagesHandler
{
FLEXOSLogController *shared = [self sharedLogController];
shared.updateHandler = newMessagesHandler;
return shared;
}
- (id)init
{
NSAssert(FLEXOSLogAvailable(), @"os_log is only available on iOS 10 and up");
self = [super init];
if (self) {
_filterPid = [NSProcessInfo processInfo].processIdentifier;
_levelInfo = NO;
_subsystemInfo = NO;
}
return self;
}
- (void)dealloc {
OSActivityStreamCancel(self.stream);
_stream = nil;
}
- (void)setPersistent:(BOOL)persistent {
if (_persistent == persistent) return;
_persistent = persistent;
self.messages = persistent ? [NSMutableArray array] : nil;
}
- (BOOL)startMonitoring {
if (![self lookupSPICalls]) {
// >= iOS 10 is required
return NO;
}
// Are we already monitoring?
if (self.stream) {
// Should we send out the "persisted" messages?
if (self.updateHandler && self.messages.count) {
dispatch_async(dispatch_get_main_queue(), ^{
self.updateHandler(self.messages);
});
}
return YES;
}
// Stream entry handler
os_activity_stream_block_t block = ^bool(os_activity_stream_entry_t entry, int error) {
return [self handleStreamEntry:entry error:error];
};
// Controls which types of messages we see
// 'Historical' appears to just show NSLog stuff
uint32_t activity_stream_flags = OS_ACTIVITY_STREAM_HISTORICAL;
activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
// activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
self.stream = OSActivityStreamForPID(self.filterPid, activity_stream_flags, block);
// Specify the stream-related event handler
OSActivityStreamSetEventHandler(self.stream, [self streamEventHandlerBlock]);
// Start the stream
OSActivityStreamResume(self.stream);
return YES;
}
- (BOOL)lookupSPICalls {
static BOOL hasSPI = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen("/System/Library/PrivateFrameworks/LoggingSupport.framework/LoggingSupport", RTLD_NOW);
OSActivityStreamForPID = (os_activity_stream_for_pid_t)dlsym(handle, "os_activity_stream_for_pid");
OSActivityStreamResume = (os_activity_stream_resume_t)dlsym(handle, "os_activity_stream_resume");
OSActivityStreamCancel = (os_activity_stream_cancel_t)dlsym(handle, "os_activity_stream_cancel");
OSLogCopyFormattedMessage = (os_log_copy_formatted_message_t)dlsym(handle, "os_log_copy_formatted_message");
OSActivityStreamSetEventHandler = (os_activity_stream_set_event_handler_t)dlsym(handle, "os_activity_stream_set_event_handler");
proc_name = (int(*)(int, char *, unsigned int))dlsym(handle, "proc_name");
proc_listpids = (int(*)(uint32_t, uint32_t, void*, int))dlsym(handle, "proc_listpids");
OSLogGetType = (uint8_t(*)(void *))dlsym(handle, "os_log_get_type");
hasSPI = (OSActivityStreamForPID != NULL) &&
(OSActivityStreamResume != NULL) &&
(OSActivityStreamCancel != NULL) &&
(OSLogCopyFormattedMessage != NULL) &&
(OSActivityStreamSetEventHandler != NULL) &&
(OSLogGetType != NULL) &&
(proc_name != NULL);
});
return hasSPI;
}
- (BOOL)handleStreamEntry:(os_activity_stream_entry_t)entry error:(int)error {
if (!self.canPrint || (self.filterPid != -1 && entry->pid != self.filterPid)) {
return YES;
}
if (!error && entry) {
if (entry->type == OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE ||
entry->type == OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE) {
os_log_message_t log_message = &entry->log_message;
// Get date
NSDate *date = [NSDate dateWithTimeIntervalSince1970:log_message->tv_gmt.tv_sec];
// Get log message text
const char *messageText = OSLogCopyFormattedMessage(log_message);
// https://github.com/limneos/oslog/issues/1
if (entry->log_message.format && !(strcmp(entry->log_message.format, messageText))) {
messageText = (char *)entry->log_message.format;
}
dispatch_async(dispatch_get_main_queue(), ^{
FLEXSystemLogMessage *message = [FLEXSystemLogMessage logMessageFromDate:date text:@(messageText)];
if (self.persistent) {
[self.messages addObject:message];
}
if (self.updateHandler) {
self.updateHandler(@[message]);
}
});
}
}
return YES;
}
- (os_activity_stream_event_block_t)streamEventHandlerBlock {
return [^void(os_activity_stream_t stream, os_activity_stream_event_t event) {
switch (event) {
case OS_ACTIVITY_STREAM_EVENT_STARTED:
self.canPrint = YES;
break;
case OS_ACTIVITY_STREAM_EVENT_STOPPED:
break;
case OS_ACTIVITY_STREAM_EVENT_FAILED:
break;
case OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED:
break;
case OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED:
break;
default:
printf("=== Unhandled case ===\n");
break;
}
} copy];
}
@end
@@ -1,6 +1,6 @@
//
// FLEXSystemLogMessage.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -8,14 +8,24 @@
#import <Foundation/Foundation.h>
#import <asl.h>
#import "ActivityStreamAPI.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXSystemLogMessage : NSObject
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage;
//+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage;
+ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, copy) NSString *sender;
@property (nonatomic, copy) NSString *messageText;
@property (nonatomic, assign) long long messageID;
// ASL specific properties
@property (nonatomic, readonly, nullable) NSString *sender;
@property (nonatomic, readonly, nullable) aslmsg aslMessage;
@property (nonatomic, readonly) NSDate *date;
@property (nonatomic, readonly) NSString *messageText;
@property (nonatomic, readonly) long long messageID;
@end
NS_ASSUME_NONNULL_END
@@ -1,6 +1,6 @@
//
// FLEXSystemLogMessage.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -10,9 +10,11 @@
@implementation FLEXSystemLogMessage
+(instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
+ (instancetype)logMessageFromASLMessage:(aslmsg)aslMessage
{
FLEXSystemLogMessage *logMessage = [[FLEXSystemLogMessage alloc] init];
NSDate *date = nil;
NSString *sender = nil, *text = nil;
long long identifier = 0;
const char *timestamp = asl_get(aslMessage, ASL_KEY_TIME);
if (timestamp) {
@@ -21,30 +23,67 @@
if (nanoseconds) {
timeInterval += [@(nanoseconds) doubleValue] / NSEC_PER_SEC;
}
logMessage.date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
}
const char *sender = asl_get(aslMessage, ASL_KEY_SENDER);
if (sender) {
logMessage.sender = @(sender);
const char *s = asl_get(aslMessage, ASL_KEY_SENDER);
if (s) {
sender = @(s);
}
const char *messageText = asl_get(aslMessage, ASL_KEY_MSG);
if (messageText) {
logMessage.messageText = @(messageText);
text = @(messageText);
}
const char *messageID = asl_get(aslMessage, ASL_KEY_MSG_ID);
if (messageID) {
logMessage.messageID = [@(messageID) longLongValue];
identifier = [@(messageID) longLongValue];
}
return logMessage;
FLEXSystemLogMessage *message = [[self alloc] initWithDate:date sender:sender text:text messageID:identifier];
message->_aslMessage = aslMessage;
return message;
}
+ (instancetype)logMessageFromOSLog:(os_log_message_t)logMessage
{
abort();
return nil;
}
+ (instancetype)logMessageFromDate:(NSDate *)date text:(NSString *)text
{
return [[self alloc] initWithDate:date sender:nil text:text messageID:0];
}
- (id)initWithDate:(NSDate *)date sender:(NSString *)sender text:(NSString *)text messageID:(long long)identifier
{
self = [super init];
if (self) {
_date = date;
_sender = sender;
_messageText = text;
_messageID = identifier;
}
return self;
}
- (BOOL)isEqual:(id)object
{
return [object isKindOfClass:[FLEXSystemLogMessage class]] && self.messageID == [object messageID];
if ([object isKindOfClass:[self class]]) {
if (self.messageID) {
// Only ASL uses messageID, otherwise it is 0
return self.messageID == [object messageID];
} else {
// Test message texts and dates for OS Log
return [self.messageText isEqual:[object messageText]] &&
[self.date isEqualToDate:[object date]];
}
}
return NO;
}
- (NSUInteger)hash
@@ -52,4 +91,10 @@
return (NSUInteger)self.messageID;
}
- (NSString *)description
{
NSString *escaped = [self.messageText stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
return [NSString stringWithFormat:@"(%lu) %@", self.messageText.length, escaped];
}
@end
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewCell.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewCell.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/25/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -74,12 +74,12 @@ static const UIEdgeInsets kFLEXLogMessageCellInsets = {10.0, 10.0, 10.0, 10.0};
+ (NSAttributedString *)attributedTextForLogMessage:(FLEXSystemLogMessage *)logMessage highlightedText:(NSString *)highlightedText
{
NSString *text = [self displayedTextForLogMessage:logMessage];
NSDictionary *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
NSDictionary<NSString *, id> *attributes = @{ NSFontAttributeName : [UIFont fontWithName:@"CourierNewPSMT" size:12.0] };
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:attributes];
if ([highlightedText length] > 0) {
NSMutableAttributedString *mutableAttributedText = [attributedText mutableCopy];
NSMutableDictionary *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
NSMutableDictionary<NSString *, id> *highlightAttributes = [@{ NSBackgroundColorAttributeName : [UIColor yellowColor] } mutableCopy];
[highlightAttributes addEntriesFromDictionary:attributes];
NSRange remainingSearchRange = NSMakeRange(0, text.length);
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewController.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/19/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXSystemLogTableViewController.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 1/19/15.
// Copyright (c) 2015 f. All rights reserved.
@@ -8,16 +8,16 @@
#import "FLEXSystemLogTableViewController.h"
#import "FLEXUtility.h"
#import "FLEXSystemLogMessage.h"
#import "FLEXASLLogController.h"
#import "FLEXOSLogController.h"
#import "FLEXSystemLogTableViewCell.h"
#import <asl.h>
@interface FLEXSystemLogTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
@property (nonatomic, strong) UISearchController *searchController;
@property (nonatomic, copy) NSArray *logMessages;
@property (nonatomic, copy) NSArray *filteredLogMessages;
@property (nonatomic, strong) NSTimer *logUpdateTimer;
@property (nonatomic, readonly) id<FLEXLogController> logController;
@property (nonatomic, readonly) NSMutableArray<FLEXSystemLogMessage *> *logMessages;
@property (nonatomic, copy) NSArray<FLEXSystemLogMessage *> *filteredLogMessages;
@end
@@ -27,57 +27,56 @@
{
[super viewDidLoad];
id logHandler = ^(NSArray<FLEXSystemLogMessage *> *newMessages) {
self.title = @"System Log";
[self.logMessages addObjectsFromArray:newMessages];
// "Follow" the log as new messages stream in if we were previously near the bottom.
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
[self.tableView reloadData];
if (wasNearBottom) {
[self scrollToLastRow];
}
};
_logMessages = [NSMutableArray array];
if ([NSProcessInfo processInfo].operatingSystemVersion.majorVersion <= 9) {
_logController = [FLEXASLLogController withUpdateHandler:logHandler];
} else {
_logController = [FLEXOSLogController withUpdateHandler:logHandler];
}
[self.tableView registerClass:[FLEXSystemLogTableViewCell class] forCellReuseIdentifier:kFLEXSystemLogTableViewCellIdentifier];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.title = @"Loading...";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ " style:UIBarButtonItemStylePlain target:self action:@selector(scrollToLastRow)];
UIBarButtonItem *scrollDown = [[UIBarButtonItem alloc] initWithTitle:@" ⬇︎ "
style:UIBarButtonItemStylePlain
target:self
action:@selector(scrollToLastRow)];
UIBarButtonItem *settings = [[UIBarButtonItem alloc] initWithTitle:@"Settings"
style:UIBarButtonItemStylePlain
target:self
action:@selector(showLogSettings)];
if (FLEXOSLogAvailable()) {
self.navigationItem.rightBarButtonItems = @[scrollDown, settings];
} else {
self.navigationItem.rightBarButtonItem = scrollDown;
}
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.delegate = self;
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.tableView.tableHeaderView = self.searchController.searchBar;
[self updateLogMessages];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSTimeInterval updateInterval = 1.0;
#if TARGET_IPHONE_SIMULATOR
// Querrying the ASL is much slower in the simulator. We need a longer polling interval to keep things repsonsive.
updateInterval = 5.0;
#endif
self.logUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:updateInterval target:self selector:@selector(updateLogMessages) userInfo:nil repeats:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.logUpdateTimer invalidate];
}
- (void)updateLogMessages
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *logMessages = [[self class] allLogMessagesForCurrentProcess];
dispatch_async(dispatch_get_main_queue(), ^{
self.title = @"System Log";
self.logMessages = logMessages;
// "Follow" the log as new messages stream in if we were previously near the bottom.
BOOL wasNearBottom = self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.frame.size.height - 100.0;
[self.tableView reloadData];
if (wasNearBottom) {
[self scrollToLastRow];
}
});
});
[self.logController startMonitoring];
}
- (void)scrollToLastRow
@@ -89,6 +88,27 @@
}
}
- (void)showLogSettings
{
FLEXOSLogController *logController = (FLEXOSLogController *)self.logController;
BOOL persistent = [[NSUserDefaults standardUserDefaults] boolForKey:kFLEXiOSPersistentOSLogKey];
NSString *toggle = persistent ? @"Disable" : @"Enable";
NSString *title = [@"Persistent logging: " stringByAppendingString:persistent ? @"ON" : @"OFF"];
NSString *body = @"In iOS 10 and up, ASL is gone. The OS Log API is much more limited. "
"To get as close to the old behavior as possible, logs must be collected manually at launch and stored.\n\n"
"Turn this feature on only when you need it.";
UIAlertController *settings = [UIAlertController alertControllerWithTitle:title message:body preferredStyle:UIAlertControllerStyleAlert];
[settings addAction:[UIAlertAction actionWithTitle:toggle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[[NSUserDefaults standardUserDefaults] setBool:!persistent forKey:kFLEXiOSPersistentOSLogKey];
logController.persistent = !persistent;
[logController.messages addObjectsFromArray:self.logMessages];
}]];
[settings addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:settings animated:YES completion:nil];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
@@ -98,7 +118,7 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.searchController.isActive ? [self.filteredLogMessages count] : [self.logMessages count];
return self.searchController.isActive ? self.filteredLogMessages.count : self.logMessages.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
@@ -137,9 +157,8 @@
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
if (action == @selector(copy:)) {
FLEXSystemLogMessage *logMessage = [self logMessageAtIndexPath:indexPath];
NSString *stringToCopy = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage] ?: @"";
[[UIPasteboard generalPasteboard] setString:stringToCopy];
// We usually only want to copy the log message itself, not any metadata associated with it.
[UIPasteboard generalPasteboard].string = [self logMessageAtIndexPath:indexPath].messageText;
}
}
@@ -154,7 +173,7 @@
{
NSString *searchString = searchController.searchBar.text;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary *bindings) {
NSArray<FLEXSystemLogMessage *> *filteredLogMessages = [self.logMessages filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXSystemLogMessage *logMessage, NSDictionary<NSString *, id> *bindings) {
NSString *displayedText = [FLEXSystemLogTableViewCell displayedTextForLogMessage:logMessage];
return [displayedText rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
}]];
@@ -167,43 +186,4 @@
});
}
#pragma mark - Log Message Fetching
// Due to a mistake in asl.h, things get a little messy. We need to mark these symbols as weak since they won't exist on iOS 7 despite the compiler thinking otherwise.
// asl.h in the iOS 8.1 SDK claims that asl_next() and asl_release() were introduced in iOS 7 to replace aslresponse_next() and aslresponse_free(). However, they were actually added in iOS 8.0.
extern aslmsg asl_next(asl_object_t obj) __attribute__((weak_import));
extern void asl_release(asl_object_t obj) __attribute__((weak_import));
+ (NSArray *)allLogMessagesForCurrentProcess
{
asl_object_t query = asl_new(ASL_TYPE_QUERY);
// Filter for messages from the current process. Note that this appears to happen by default on device, but is required in the simulator.
NSString *pidString = [NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]];
asl_set_query(query, ASL_KEY_PID, [pidString UTF8String], ASL_QUERY_OP_EQUAL);
aslresponse response = asl_search(NULL, query);
aslmsg aslMessage = NULL;
NSMutableArray *logMessages = [NSMutableArray array];
if (&asl_next != NULL && &asl_release != NULL) {
while ((aslMessage = asl_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
asl_release(response);
} else {
// Mute incorrect deprecated warnings. We'll need the "deprecated" functions on iOS 7, where their replacements don't yet exist.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
while ((aslMessage = aslresponse_next(response))) {
[logMessages addObject:[FLEXSystemLogMessage logMessageFromASLMessage:aslMessage]];
}
aslresponse_free(response);
#pragma clang diagnostic pop
}
return logMessages;
}
@end
@@ -0,0 +1,276 @@
==============================================================================
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
==============================================================================
Software from third parties included in the LLVM Project:
==============================================================================
The LLVM Project contains third party software which is under different license
terms. All such code will be identified clearly using at least one of two
mechanisms:
1) It will be in a separate directory tree with its own `LICENSE.txt` or
`LICENSE` file at the top containing the specific license and restrictions
which apply to that software, or
2) It will contain specific license and restriction terms at the top of every
file.
==============================================================================
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
==============================================================================
University of Illinois/NCSA
Open Source License
Copyright (c) 2010 Apple Inc.
All rights reserved.
Developed by:
LLDB Team
http://lldb.llvm.org/
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of the LLDB Team, copyright holders, nor the names of
its contributors may be used to endorse or promote products derived from
this Software without specific prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
@@ -8,9 +8,13 @@
#import "FLEXManager.h"
@class FLEXGlobalsTableViewControllerEntry;
@interface FLEXManager ()
/// An array of FLEXGlobalsTableViewControllerEntry objects that have been registered by the user.
@property (nonatomic, readonly, strong) NSArray *userGlobalEntries;
@property (nonatomic, readonly, strong) NSArray<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
@property (nonatomic, readonly, strong) NSDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
@end
@@ -24,7 +24,8 @@
@property (nonatomic, strong) FLEXWindow *explorerWindow;
@property (nonatomic, strong) FLEXExplorerViewController *explorerViewController;
@property (nonatomic, readonly, strong) NSMutableArray *userGlobalEntries;
@property (nonatomic, readonly, strong) NSMutableArray<FLEXGlobalsTableViewControllerEntry *> *userGlobalEntries;
@property (nonatomic, readonly, strong) NSMutableDictionary<NSString *, FLEXCustomContentViewerFuture> *customContentTypeViewers;
@end
@@ -44,7 +45,8 @@
{
self = [super init];
if (self) {
_userGlobalEntries = [[NSMutableArray alloc] init];
_userGlobalEntries = [NSMutableArray array];
_customContentTypeViewers = [NSMutableDictionary dictionary];
}
return self;
}
@@ -115,6 +117,16 @@
[[FLEXNetworkRecorder defaultRecorder] setResponseCacheByteLimit:networkResponseCacheByteLimit];
}
- (void)setNetworkRequestHostBlacklist:(NSArray<NSString *> *)networkRequestHostBlacklist
{
[FLEXNetworkRecorder defaultRecorder].hostBlacklist = networkRequestHostBlacklist;
}
- (NSArray<NSString *> *)hostBlacklist
{
return [FLEXNetworkRecorder defaultRecorder].hostBlacklist;
}
#pragma mark - FLEXWindowEventDelegate
- (BOOL)shouldHandleTouchAtPoint:(CGPoint)pointInWindow
@@ -278,6 +290,15 @@
[self.userGlobalEntries addObject:entry];
}
- (void)setCustomViewerForContentType:(NSString *)contentType viewControllerFutureBlock:(FLEXCustomContentViewerFuture)viewControllerFutureBlock
{
NSParameterAssert(contentType.length);
NSParameterAssert(viewControllerFutureBlock);
NSAssert([NSThread isMainThread], @"This method must be called from the main thread.");
self.customContentTypeViewers[contentType.lowercaseString] = viewControllerFutureBlock;
}
- (void)tryScrollDown
{
UIScrollView *firstScrollView = [self firstScrollView];
@@ -302,7 +323,7 @@
- (UIScrollView *)firstScrollView
{
NSMutableArray *views = [[[[UIApplication sharedApplication] keyWindow] subviews] mutableCopy];
NSMutableArray<UIView *> *views = [[[[UIApplication sharedApplication] keyWindow] subviews] mutableCopy];
UIScrollView *scrollView = nil;
while ([views count] > 0) {
UIView *view = [views firstObject];
+19
View File
@@ -0,0 +1,19 @@
//
// FLEXCurlLogger.h
//
//
// Created by Ji Pei on 07/27/16
//
#import <Foundation/Foundation.h>
@interface FLEXNetworkCurlLogger : NSObject
/**
* Generates a cURL command equivalent to the given request.
*
* @param request The request to be translated
*/
+ (NSString *)curlCommandString:(NSURLRequest *)request;
@end
+42
View File
@@ -0,0 +1,42 @@
//
// FLEXCurlLogger.m
//
//
// Created by Ji Pei on 07/27/16
//
#import "FLEXNetworkCurlLogger.h"
@implementation FLEXNetworkCurlLogger
+ (NSString *)curlCommandString:(NSURLRequest *)request {
__block NSMutableString *curlCommandString = [NSMutableString stringWithFormat:@"curl -v -X %@ ", request.HTTPMethod];
[curlCommandString appendFormat:@"\'%@\' ", request.URL.absoluteString];
[request.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *val, BOOL *stop) {
[curlCommandString appendFormat:@"-H \'%@: %@\' ", key, val];
}];
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
if (cookies) {
[curlCommandString appendFormat:@"-H \'Cookie:"];
for (NSHTTPCookie *cookie in cookies) {
[curlCommandString appendFormat:@" %@=%@;", cookie.name, cookie.value];
}
[curlCommandString appendFormat:@"\' "];
}
if (request.HTTPBody) {
if ([request.allHTTPHeaderFields[@"Content-Length"] intValue] < 1024) {
[curlCommandString appendFormat:@"-d \'%@\'",
[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]];
} else {
[curlCommandString appendFormat:@"[TOO MUCH DATA TO INCLUDE]"];
}
}
return curlCommandString;
}
@end
@@ -17,9 +17,9 @@
@interface FLEXNetworkHistoryTableViewController () <UISearchResultsUpdating, UISearchControllerDelegate>
/// Backing model
@property (nonatomic, copy) NSArray *networkTransactions;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *networkTransactions;
@property (nonatomic, assign) long long bytesReceived;
@property (nonatomic, copy) NSArray *filteredNetworkTransactions;
@property (nonatomic, copy) NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions;
@property (nonatomic, assign) long long filteredBytesReceived;
@property (nonatomic, assign) BOOL rowInsertInProgress;
@@ -41,6 +41,10 @@
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkObserverEnabledStateChangedNotification:) name:kFLEXNetworkObserverEnabledStateChangedNotification object:nil];
self.title = @"📡 Network";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Settings" style:UIBarButtonItemStylePlain target:self action:@selector(settingsButtonTapped:)];
// Needed to avoid search bar showing over detail pages pushed on the nav stack
// see http://asciiwwdc.com/2014/sessions/228
self.definesPresentationContext = YES;
}
return self;
}
@@ -86,7 +90,7 @@
self.networkTransactions = [[FLEXNetworkRecorder defaultRecorder] networkTransactions];
}
- (void)setNetworkTransactions:(NSArray *)networkTransactions
- (void)setNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)networkTransactions
{
if (![_networkTransactions isEqual:networkTransactions]) {
_networkTransactions = networkTransactions;
@@ -105,7 +109,7 @@
[self updateFirstSectionHeader];
}
- (void)setFilteredNetworkTransactions:(NSArray *)filteredNetworkTransactions
- (void)setFilteredNetworkTransactions:(NSArray<FLEXNetworkTransaction *> *)filteredNetworkTransactions
{
if (![_filteredNetworkTransactions isEqual:filteredNetworkTransactions]) {
_filteredNetworkTransactions = filteredNetworkTransactions;
@@ -192,7 +196,7 @@
[self tryUpdateTransactions];
}];
NSMutableArray *indexPathsToReload = [NSMutableArray array];
NSMutableArray<NSIndexPath *> *indexPathsToReload = [NSMutableArray array];
for (NSInteger row = 0; row < addedRowCount; row++) {
[indexPathsToReload addObject:[NSIndexPath indexPathForRow:row inSection:0]];
}
@@ -328,7 +332,7 @@
{
NSString *searchString = self.searchController.searchBar.text;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredNetworkTransactions = [self.networkTransactions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXNetworkTransaction *transaction, NSDictionary *bindings) {
NSArray<FLEXNetworkTransaction *> *filteredNetworkTransactions = [self.networkTransactions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FLEXNetworkTransaction *transaction, NSDictionary<NSString *, id> *bindings) {
return [[transaction.request.URL absoluteString] rangeOfString:searchString options:NSCaseInsensitiveSearch].length > 0;
}]];
dispatch_async(dispatch_get_main_queue(), ^{
+4 -1
View File
@@ -27,10 +27,13 @@ extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification;
/// If NO, the recorder not cache will not cache response for content types with an "image", "video", or "audio" prefix.
@property (nonatomic, assign) BOOL shouldCacheMediaResponses;
@property (nonatomic, copy) NSArray<NSString *> *hostBlacklist;
// Accessing recorded network activity
/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first.
- (NSArray *)networkTransactions;
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions;
/// The full response data IFF it hasn't been purged due to memory pressure.
- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction;
+14 -7
View File
@@ -7,6 +7,7 @@
//
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkCurlLogger.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXUtility.h"
#import "FLEXResources.h"
@@ -21,8 +22,8 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
@interface FLEXNetworkRecorder ()
@property (nonatomic, strong) NSCache *responseCache;
@property (nonatomic, strong) NSMutableArray *orderedTransactions;
@property (nonatomic, strong) NSMutableDictionary *networkTransactionsForRequestIdentifiers;
@property (nonatomic, strong) NSMutableArray<FLEXNetworkTransaction *> *orderedTransactions;
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXNetworkTransaction *> *networkTransactionsForRequestIdentifiers;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@@ -73,9 +74,9 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
[[NSUserDefaults standardUserDefaults] setObject:@(responseCacheByteLimit) forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey];
}
- (NSArray *)networkTransactions
- (NSArray<FLEXNetworkTransaction *> *)networkTransactions
{
__block NSArray *transactions = nil;
__block NSArray<FLEXNetworkTransaction *> *transactions = nil;
dispatch_sync(self.queue, ^{
transactions = [self.orderedTransactions copy];
});
@@ -103,6 +104,12 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
for (NSString *host in self.hostBlacklist) {
if ([request.URL.host hasSuffix:host]) {
return;
}
}
NSDate *startDate = [NSDate date];
if (redirectResponse) {
@@ -168,7 +175,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
BOOL shouldCache = [responseBody length] > 0;
if (!self.shouldCacheMediaResponses) {
NSArray *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
NSArray<NSString *> *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ];
for (NSString *ignoredPrefix in ignoredMIMETypePrefixes) {
shouldCache = shouldCache && ![transaction.response.MIMEType hasPrefix:ignoredPrefix];
}
@@ -245,7 +252,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)postNewTransactionNotificationWithTransaction:(FLEXNetworkTransaction *)transaction
{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
NSDictionary<NSString *, id> *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderNewTransactionNotification object:self userInfo:userInfo];
});
}
@@ -253,7 +260,7 @@ NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.r
- (void)postUpdateNotificationForTransaction:(FLEXNetworkTransaction *)transaction
{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
NSDictionary<NSString *, id> *userInfo = @{ kFLEXNetworkRecorderUserInfoTransactionKey : transaction };
[[NSNotificationCenter defaultCenter] postNotificationName:kFLEXNetworkRecorderTransactionUpdatedNotification object:self userInfo:userInfo];
});
}
@@ -13,7 +13,7 @@
@interface FLEXNetworkSettingsTableViewController () <UIActionSheetDelegate>
@property (nonatomic, copy) NSArray *cells;
@property (nonatomic, copy) NSArray<UITableViewCell *> *cells;
@property (nonatomic, strong) UITableViewCell *cacheLimitCell;
@@ -34,7 +34,7 @@
{
[super viewDidLoad];
NSMutableArray *mutableCells = [NSMutableArray array];
NSMutableArray<UITableViewCell *> *mutableCells = [NSMutableArray array];
UITableViewCell *networkDebuggingCell = [self switchCellWithTitle:@"Network Debugging" toggleAction:@selector(networkDebuggingToggled:) isOn:[FLEXNetworkObserver isEnabled]];
[mutableCells addObject:networkDebuggingCell];
@@ -136,7 +136,7 @@
if (isDestructive) {
actionButton.tintColor = [UIColor redColor];
}
actionButton.titleLabel.font = [[self class] cellTitleFont];;
actionButton.titleLabel.font = [[self class] cellTitleFont];
[actionButton addTarget:self action:@selector(clearRequestsTapped:) forControlEvents:UIControlEventTouchUpInside];
[buttonCell.contentView addSubview:actionButton];
+3
View File
@@ -36,6 +36,9 @@ typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) {
/// Only applicable for image downloads. A small thumbnail to preview the full response.
@property (nonatomic, strong) UIImage *responseThumbnail;
/// Populated lazily. Handles both normal HTTPBody data and HTTPBodyStreams.
@property (nonatomic, strong, readonly) NSData *cachedRequestBody;
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state;
@end
+28
View File
@@ -8,6 +8,12 @@
#import "FLEXNetworkTransaction.h"
@interface FLEXNetworkTransaction ()
@property (nonatomic, strong, readwrite) NSData *cachedRequestBody;
@end
@implementation FLEXNetworkTransaction
- (NSString *)description
@@ -22,6 +28,28 @@
return description;
}
- (NSData *)cachedRequestBody {
if (!_cachedRequestBody) {
if (self.request.HTTPBody != nil) {
_cachedRequestBody = self.request.HTTPBody;
} else if ([self.request.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
NSInputStream *bodyStream = [self.request.HTTPBodyStream copy];
const NSUInteger bufferSize = 1024;
uint8_t buffer[bufferSize];
NSMutableData *data = [NSMutableData data];
[bodyStream open];
NSInteger readBytes = 0;
do {
readBytes = [bodyStream read:buffer maxLength:bufferSize];
[data appendBytes:buffer length:readBytes];
} while (readBytes > 0);
[bodyStream close];
_cachedRequestBody = data;
}
}
return _cachedRequestBody;
}
+ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state
{
NSString *readableString = nil;
@@ -7,23 +7,14 @@
//
#import "FLEXNetworkTransactionDetailTableViewController.h"
#import "FLEXNetworkCurlLogger.h"
#import "FLEXNetworkRecorder.h"
#import "FLEXNetworkTransaction.h"
#import "FLEXWebViewController.h"
#import "FLEXImagePreviewViewController.h"
#import "FLEXMultilineTableViewCell.h"
#import "FLEXUtility.h"
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray *rows;
@end
@implementation FLEXNetworkDetailSection
@end
#import "FLEXManager+Private.h"
typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@@ -39,9 +30,20 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
@end
@interface FLEXNetworkDetailSection : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailRow *> *rows;
@end
@implementation FLEXNetworkDetailSection
@end
@interface FLEXNetworkTransactionDetailTableViewController ()
@property (nonatomic, copy) NSArray *sections;
@property (nonatomic, copy) NSArray<FLEXNetworkDetailSection *> *sections;
@end
@@ -53,7 +55,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTransactionUpdatedNotification:) name:kFLEXNetworkRecorderTransactionUpdatedNotification object:nil];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Copy curl" style:UIBarButtonItemStylePlain target:self action:@selector(copyButtonPressed:)];
}
return self;
}
@@ -74,7 +76,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
}
}
- (void)setSections:(NSArray *)sections
- (void)setSections:(NSArray<FLEXNetworkDetailSection *> *)sections
{
if (![_sections isEqual:sections]) {
_sections = [sections copy];
@@ -84,7 +86,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
- (void)rebuildTableSections
{
NSMutableArray *sections = [NSMutableArray array];
NSMutableArray<FLEXNetworkDetailSection *> *sections = [NSMutableArray array];
FLEXNetworkDetailSection *generalSection = [[self class] generalSectionForTransaction:self.transaction];
if ([generalSection.rows count] > 0) {
@@ -120,26 +122,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
- (void)copyButtonPressed:(id)sender
{
NSMutableString *requestDetailString = [NSMutableString string];
for (FLEXNetworkDetailSection *section in self.sections) {
if ([section.rows count] > 0) {
if ([section.title length] > 0) {
[requestDetailString appendString:section.title];
[requestDetailString appendString:@"\n\n"];
}
for (FLEXNetworkDetailRow *row in section.rows) {
NSString *rowDescription = [[[self class] attributedTextForRow:row] string];
if ([rowDescription length] > 0) {
[requestDetailString appendString:rowDescription];
[requestDetailString appendString:@"\n"];
}
}
[requestDetailString appendString:@"\n\n"];
}
}
[[UIPasteboard generalPasteboard] setString:requestDetailString];
[[UIPasteboard generalPasteboard] setString:[FLEXNetworkCurlLogger curlCommandString:_transaction.request]];
}
#pragma mark - Table view data source
@@ -228,10 +211,10 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (NSAttributedString *)attributedTextForRow:(FLEXNetworkDetailRow *)row
{
NSDictionary *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
NSDictionary *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSDictionary<NSString *, id> *titleAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Medium" size:12.0],
NSForegroundColorAttributeName : [UIColor colorWithWhite:0.5 alpha:1.0] };
NSDictionary<NSString *, id> *detailAttributes = @{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSString *title = [NSString stringWithFormat:@"%@: ", row.title];
NSString *detailText = row.detailText ?: @"";
@@ -246,7 +229,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (FLEXNetworkDetailSection *)generalSectionForTransaction:(FLEXNetworkTransaction *)transaction
{
NSMutableArray *rows = [NSMutableArray array];
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray array];
FLEXNetworkDetailRow *requestURLRow = [[FLEXNetworkDetailRow alloc] init];
requestURLRow.title = @"Request URL";
@@ -264,10 +247,10 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
requestMethodRow.detailText = transaction.request.HTTPMethod;
[rows addObject:requestMethodRow];
if ([transaction.request.HTTPBody length] > 0) {
if ([transaction.cachedRequestBody length] > 0) {
FLEXNetworkDetailRow *postBodySizeRow = [[FLEXNetworkDetailRow alloc] init];
postBodySizeRow.title = @"Request Body Size";
postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:[transaction.request.HTTPBody length] countStyle:NSByteCountFormatterCountStyleBinary];
postBodySizeRow.detailText = [NSByteCountFormatter stringFromByteCount:[transaction.cachedRequestBody length] countStyle:NSByteCountFormatterCountStyleBinary];
[rows addObject:postBodySizeRow];
FLEXNetworkDetailRow *postBodyRow = [[FLEXNetworkDetailRow alloc] init];
@@ -396,7 +379,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
{
FLEXNetworkDetailSection *postBodySection = [[FLEXNetworkDetailSection alloc] init];
postBodySection.title = @"Request Body Parameters";
if ([transaction.request.HTTPBody length] > 0) {
if ([transaction.cachedRequestBody length] > 0) {
NSString *contentType = [transaction.request valueForHTTPHeaderField:@"Content-Type"];
if ([contentType hasPrefix:@"application/x-www-form-urlencoded"]) {
NSString *bodyString = [[NSString alloc] initWithData:[self postBodyDataForTransaction:transaction] encoding:NSUTF8StringEncoding];
@@ -408,7 +391,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (FLEXNetworkDetailSection *)queryParametersSectionForTransaction:(FLEXNetworkTransaction *)transaction
{
NSDictionary *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
NSDictionary<NSString *, id> *queryDictionary = [FLEXUtility dictionaryFromQuery:transaction.request.URL.query];
FLEXNetworkDetailSection *querySection = [[FLEXNetworkDetailSection alloc] init];
querySection.title = @"Query Parameters";
querySection.rows = [self networkDetailRowsFromDictionary:queryDictionary];
@@ -427,12 +410,12 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
return responseHeadersSection;
}
+ (NSArray *)networkDetailRowsFromDictionary:(NSDictionary *)dictionary
+ (NSArray<FLEXNetworkDetailRow *> *)networkDetailRowsFromDictionary:(NSDictionary<NSString *, id> *)dictionary
{
NSMutableArray *rows = [NSMutableArray arrayWithCapacity:[dictionary count]];
NSArray *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
NSMutableArray<FLEXNetworkDetailRow *> *rows = [NSMutableArray arrayWithCapacity:[dictionary count]];
NSArray<NSString *> *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
NSString *value = dictionary[key];
id value = dictionary[key];
FLEXNetworkDetailRow *row = [[FLEXNetworkDetailRow alloc] init];
row.title = key;
row.detailText = [value description];
@@ -443,6 +426,16 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (UIViewController *)detailViewControllerForMIMEType:(NSString *)mimeType data:(NSData *)data
{
FLEXCustomContentViewerFuture makeCustomViewer = [FLEXManager sharedManager].customContentTypeViewers[mimeType.lowercaseString];
if (makeCustomViewer) {
UIViewController *viewer = makeCustomViewer(data);
if (viewer) {
return viewer;
}
}
// FIXME (RKO): Don't rely on UTF8 string encoding
UIViewController *detailViewController = nil;
if ([FLEXUtility isValidJSONData:data]) {
@@ -470,7 +463,7 @@ typedef UIViewController *(^FLEXNetworkDetailRowSelectionFuture)(void);
+ (NSData *)postBodyDataForTransaction:(FLEXNetworkTransaction *)transaction
{
NSData *bodyData = transaction.request.HTTPBody;
NSData *bodyData = transaction.cachedRequestBody;
if ([bodyData length] > 0) {
NSString *contentEncoding = [transaction.request valueForHTTPHeaderField:@"Content-Encoding"];
if ([contentEncoding rangeOfString:@"deflate" options:NSCaseInsensitiveSearch].length > 0 || [contentEncoding rangeOfString:@"gzip" options:NSCaseInsensitiveSearch].length > 0) {
@@ -79,7 +79,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
self.nameLabel.text = [self nameLabelText];
CGSize nameLabelPreferredSize = [self.nameLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
self.nameLabel.frame = CGRectMake(textOriginX, kVerticalPadding, availableTextWidth, nameLabelPreferredSize.height);
self.nameLabel.textColor = self.transaction.error ? [UIColor redColor] : [UIColor blackColor];
self.nameLabel.textColor = (self.transaction.error || [FLEXUtility isErrorStatusCodeFromURLResponse:self.transaction.response]) ? [UIColor redColor] : [UIColor blackColor];
self.pathLabel.text = [self pathLabelText];
CGSize pathLabelPreferredSize = [self.pathLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
@@ -111,7 +111,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
- (NSString *)pathLabelText
{
NSURL *url = self.transaction.request.URL;
NSMutableArray *mutablePathComponents = [[url pathComponents] mutableCopy];
NSMutableArray<NSString *> *mutablePathComponents = [[url pathComponents] mutableCopy];
if ([mutablePathComponents count] > 0) {
[mutablePathComponents removeLastObject];
}
@@ -124,7 +124,7 @@ NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactio
- (NSString *)transactionDetailsLabelText
{
NSMutableArray *detailComponents = [NSMutableArray array];
NSMutableArray<NSString *> *detailComponents = [NSMutableArray array];
NSString *timestamp = [[self class] timestampStringFromRequestDate:self.transaction.startTime];
if ([timestamp length] > 0) {
@@ -14,7 +14,7 @@
#import <Foundation/Foundation.h>
extern NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
FOUNDATION_EXTERN NSString *const kFLEXNetworkObserverEnabledStateChangedNotification;
/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system.
/// High level network events are sent to the default FLEXNetworkRecorder instance which maintains the request history and caches response bodies.
@@ -68,7 +68,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
@interface FLEXNetworkObserver ()
@property (nonatomic, strong) NSMutableDictionary *requestStatesForRequestIDs;
@property (nonatomic, strong) NSMutableDictionary<NSString *, FLEXInternalRequestState *> *requestStatesForRequestIDs;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@@ -172,7 +172,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
@selector(URLSession:dataTask:didReceiveData:),
@selector(URLSession:dataTask:didReceiveResponse:completionHandler:),
@selector(URLSession:task:didCompleteWithError:),
@selector(URLSession:dataTask:didBecomeDownloadTask:delegate:),
@selector(URLSession:dataTask:didBecomeDownloadTask:),
@selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:),
@selector(URLSession:downloadTask:didFinishDownloadingToURL:)
};
@@ -318,7 +318,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
if ([FLEXNetworkObserver isEnabled]) {
NSString *requestID = [self nextRequestID];
[[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
[[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
NSURLConnectionAsyncCompletion completionWrapper = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
[[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response];
@@ -357,7 +357,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
if ([FLEXNetworkObserver isEnabled]) {
NSString *requestID = [self nextRequestID];
[[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
[[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID];
NSError *temporaryError = nil;
NSURLResponse *temporaryResponse = nil;
@@ -420,7 +420,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
// with nil completion block.
if ([FLEXNetworkObserver isEnabled] && completion) {
NSString *requestID = [self nextRequestID];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper);
[self setRequestID:requestID forConnectionOrTask:task];
@@ -462,9 +462,9 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) {
NSURLSessionUploadTask *task = nil;
if ([FLEXNetworkObserver isEnabled]) {
if ([FLEXNetworkObserver isEnabled] && completion) {
NSString *requestID = [self nextRequestID];
NSString *mechanism = [self mechansimFromClassMethod:selector onClass:class];
NSString *mechanism = [self mechanismFromClassMethod:selector onClass:class];
NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion];
task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper);
[self setRequestID:requestID forConnectionOrTask:task];
@@ -479,7 +479,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
});
}
+ (NSString *)mechansimFromClassMethod:(SEL)selector onClass:(Class)class
+ (NSString *)mechanismFromClassMethod:(SEL)selector onClass:(Class)class
{
return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(class), NSStringFromSelector(selector)];
}
@@ -667,13 +667,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
NSURLSessionWillPerformHTTPRedirectionBlock undefinedBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) {
[[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf];
completionHandler(newRequest);
};
NSURLSessionWillPerformHTTPRedirectionBlock implementationBlock = ^(id <NSURLSessionTaskDelegate> slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) {
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
undefinedBlock(slf, session, task, response, newRequest, completionHandler);
[[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf];
} originalImplementationBlock:^{
((id(*)(id, SEL, id, id, id, id, void(^)()))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler);
((id(*)(id, SEL, id, id, id, id, void(^)(NSURLRequest *)))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler);
}];
};
@@ -747,13 +748,14 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id <NSU
NSURLSessionDidReceiveResponseBlock undefinedBlock = ^(id <NSURLSessionDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) {
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf];
completionHandler(NSURLSessionResponseAllow);
};
NSURLSessionDidReceiveResponseBlock implementationBlock = ^(id <NSURLSessionDelegate> slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) {
[self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{
undefinedBlock(slf, session, dataTask, response, completionHandler);
[[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf];
} originalImplementationBlock:^{
((void(*)(id, SEL, id, id, id, void(^)()))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler);
((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler);
}];
};
@@ -942,7 +944,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
NSMutableData *dataAccumulator = nil;
if (response.expectedContentLength < 0) {
dataAccumulator = [[NSMutableData alloc] init];
} else {
} else if (response.expectedContentLength < 52428800) {
dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength];
}
requestState.dataAccumulator = dataAccumulator;
@@ -993,7 +995,7 @@ static char const * const kFLEXRequestIDKey = "kFLEXRequestIDKey";
{
[self performBlock:^{
// Mimic the behavior of NSURLSession which is to create an error on cancellation.
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" };
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
[self connection:connection didFailWithError:error delegate:nil];
}];
@@ -35,11 +35,11 @@ typedef NS_ENUM(NSUInteger, FLEXClassExplorerRow) {
#pragma mark - Superclass Overrides
- (NSArray *)possibleExplorerSections
- (NSArray<NSNumber *> *)possibleExplorerSections
{
// Move class methods to between our custom section and the properties section since
// we are more interested in the class sections than in the instance level sections.
NSMutableArray *mutableSections = [[super possibleExplorerSections] mutableCopy];
NSMutableArray<NSNumber *> *mutableSections = [[super possibleExplorerSections] mutableCopy];
[mutableSections removeObject:@(FLEXObjectExplorerSectionClassMethods)];
[mutableSections insertObject:@(FLEXObjectExplorerSectionClassMethods) atIndex:[mutableSections indexOfObject:@(FLEXObjectExplorerSectionProperties)]];
return mutableSections;
@@ -0,0 +1,13 @@
//
// FLEXColorExplorerViewController.h
// Flipboard
//
// Created by Tanner on 10/18/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXObjectExplorerViewController.h"
@interface FLEXColorExplorerViewController : FLEXObjectExplorerViewController
@end
@@ -0,0 +1,49 @@
//
// FLEXColorExplorerViewController.m
// Flipboard
//
// Created by Tanner on 10/18/18.
// Copyright © 2018 Flipboard. All rights reserved.
//
#import "FLEXColorExplorerViewController.h"
@interface FLEXColorExplorerViewController ()
@end
@implementation FLEXColorExplorerViewController
- (BOOL)shouldShowDescription
{
return NO;
}
- (NSString *)customSectionTitle
{
return @"Color";
}
- (NSArray *)customSectionRowCookies
{
return @[@0];
}
- (UIView *)customViewForRowCookie:(id)rowCookie
{
CGFloat width = [UIScreen mainScreen].bounds.size.width;
UIView *square = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
square.backgroundColor = (UIColor *)self.object;
return square;
}
//- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
// if (indexPath.section == 0 && indexPath.row == 0) {
// cell.contentView.backgroundColor = (UIColor *)self.object;
// }
//
// return cell;
//}
@end
@@ -1,6 +1,6 @@
//
// FLEXGlobalsTableViewControllerEntry.h
// UICatalog
// FLEX
//
// Created by Javier Soto on 7/26/14.
// Copyright (c) 2014 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXGlobalsTableViewControllerEntry.m
// UICatalog
// FLEX
//
// Created by Javier Soto on 7/26/14.
// Copyright (c) 2014 f. All rights reserved.
@@ -22,4 +22,4 @@
return entry;
}
@end
@end
@@ -1,6 +1,6 @@
//
// FLEXLayerExplorerViewController.h
// UICatalog
// FLEX
//
// Created by Ryan Olson on 12/14/14.
// Copyright (c) 2014 f. All rights reserved.
@@ -1,6 +1,6 @@
//
// FLEXLayerExplorerViewController.m
// UICatalog
// FLEX
//
// Created by Ryan Olson on 12/14/14.
// Copyright (c) 2014 f. All rights reserved.
@@ -17,6 +17,7 @@
#import "FLEXImageExplorerViewController.h"
#import "FLEXClassExplorerViewController.h"
#import "FLEXLayerExplorerViewController.h"
#import "FLEXColorExplorerViewController.h"
#import <objc/runtime.h>
@implementation FLEXObjectExplorerFactory
@@ -28,7 +29,7 @@
return nil;
}
static NSDictionary *explorerSubclassesForObjectTypeStrings = nil;
static NSDictionary<NSString *, Class> *explorerSubclassesForObjectTypeStrings = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
explorerSubclassesForObjectTypeStrings = @{NSStringFromClass([NSArray class]) : [FLEXArrayExplorerViewController class],
@@ -38,7 +39,9 @@
NSStringFromClass([UIViewController class]) : [FLEXViewControllerExplorerViewController class],
NSStringFromClass([UIView class]) : [FLEXViewExplorerViewController class],
NSStringFromClass([UIImage class]) : [FLEXImageExplorerViewController class],
NSStringFromClass([CALayer class]) : [FLEXLayerExplorerViewController class]};
NSStringFromClass([CALayer class]) : [FLEXLayerExplorerViewController class],
NSStringFromClass([UIColor class]) : [FLEXColorExplorerViewController class]
};
});
Class explorerClass = nil;
@@ -33,6 +33,7 @@ typedef NS_ENUM(NSUInteger, FLEXObjectExplorerSection) {
- (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie;
- (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie;
- (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie;
- (UIView *)customViewForRowCookie:(id)rowCookie;
// More subclass configuration hooks.
@@ -17,6 +17,20 @@
#import "FLEXInstancesTableViewController.h"
#import <objc/runtime.h>
typedef NS_ENUM(NSUInteger, FLEXObjectExplorerScope) {
FLEXObjectExplorerScopeNoInheritance,
FLEXObjectExplorerScopeWithParent,
FLEXObjectExplorerScopeAllButNSObject,
FLEXObjectExplorerScopeNSObjectOnly
};
typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
FLEXMetadataKindProperties,
FLEXMetadataKindIvars,
FLEXMetadataKindMethods,
FLEXMetadataKindClassMethods
};
// Convenience boxes to keep runtime properties, ivars, and methods in foundation collections.
@interface FLEXPropertyBox : NSObject
@property (nonatomic, assign) objc_property_t property;
@@ -36,36 +50,41 @@
@implementation FLEXMethodBox
@end
static const NSInteger kFLEXObjectExplorerScopeNoInheritanceIndex = 0;
static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
@interface FLEXObjectExplorerViewController () <UISearchBarDelegate>
@property (nonatomic, strong) NSArray *properties;
@property (nonatomic, strong) NSArray *inheritedProperties;
@property (nonatomic, strong) NSArray *filteredProperties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *properties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *propertiesWithParent;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *inheritedProperties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *NSObjectProperties;
@property (nonatomic, strong) NSArray<FLEXPropertyBox *> *filteredProperties;
@property (nonatomic, strong) NSArray *ivars;
@property (nonatomic, strong) NSArray *inheritedIvars;
@property (nonatomic, strong) NSArray *filteredIvars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivarsWithParent;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *inheritedIvars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *NSObjectIvars;
@property (nonatomic, strong) NSArray<FLEXIvarBox *> *filteredIvars;
@property (nonatomic, strong) NSArray *methods;
@property (nonatomic, strong) NSArray *inheritedMethods;
@property (nonatomic, strong) NSArray *filteredMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *methodsWithParent;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredMethods;
@property (nonatomic, strong) NSArray *classMethods;
@property (nonatomic, strong) NSArray *inheritedClassMethods;
@property (nonatomic, strong) NSArray *filteredClassMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethodsWithParent;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedClassMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectClassMethods;
@property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredClassMethods;
@property (nonatomic, strong) NSArray *superclasses;
@property (nonatomic, strong) NSArray *filteredSuperclasses;
@property (nonatomic, strong) NSArray<Class> *superclasses;
@property (nonatomic, strong) NSArray<Class> *filteredSuperclasses;
@property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
@property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
@property (nonatomic, strong) UISearchBar *searchBar;
@property (nonatomic, strong) NSString *filterText;
@property (nonatomic, assign) BOOL includeInheritance;
@property (nonatomic, assign) FLEXObjectExplorerScope scope;
@end
@@ -85,8 +104,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
self.searchBar.delegate = self;
self.searchBar.showsScopeBar = YES;
self.searchBar.scopeButtonTitles = @[@"No Inheritance", @"Include Inheritance"];
[self.searchBar sizeToFit];
[self refreshScopeTitles];
self.tableView.tableHeaderView = self.searchBar;
self.refreshControl = [[UIRefreshControl alloc] init];
@@ -116,6 +134,28 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
#pragma mark - Search
- (void)refreshScopeTitles {
if (!self.searchBar) return;
Class parent = [self.object superclass];
Class parentSuper = [parent superclass];
NSMutableArray *scopes = [NSMutableArray arrayWithObject:@"Base"];
if (parent) {
[scopes addObject:@"+ Parent"];
}
if (parentSuper && parentSuper != [NSObject class]) {
[scopes addObject:@"+ Inherited"];
}
if ([self.object isKindOfClass:[NSObject class]]) {
[scopes addObject:@"NSObject"];
}
self.searchBar.scopeButtonTitles = scopes;
[self.searchBar sizeToFit];
[self updateTableData];
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.filterText = searchText;
@@ -128,13 +168,62 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
{
if (selectedScope == kFLEXObjectExplorerScopeIncludeInheritanceIndex) {
self.includeInheritance = YES;
} else if (selectedScope == kFLEXObjectExplorerScopeNoInheritanceIndex) {
self.includeInheritance = NO;
self.scope = selectedScope;
[self updateDisplayedData];
}
- (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
switch (metadataKind) {
case FLEXMetadataKindProperties:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.properties;
case FLEXObjectExplorerScopeWithParent:
return self.propertiesWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedProperties;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectProperties;
}
case FLEXMetadataKindIvars:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.ivars;
case FLEXObjectExplorerScopeWithParent:
return self.ivarsWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedIvars;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectIvars;
}
case FLEXMetadataKindMethods:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.methods;
case FLEXObjectExplorerScopeWithParent:
return self.methodsWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedMethods;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectMethods;
}
case FLEXMetadataKindClassMethods:
switch (self.scope) {
case FLEXObjectExplorerScopeNoInheritance:
return self.classMethods;
case FLEXObjectExplorerScopeWithParent:
return self.classMethodsWithParent;
case FLEXObjectExplorerScopeAllButNSObject:
return self.inheritedClassMethods;
case FLEXObjectExplorerScopeNSObjectOnly:
return self.NSObjectClassMethods;
}
}
}
- (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope {
return [self metadata:metadataKind forScope:scope].count;
}
#pragma mark - Setter overrides
@@ -143,15 +232,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
_object = object;
// Use [object class] here rather than object_getClass because we don't want to show the KVO prefix for observed objects.
self.title = [[object class] description];
[self updateTableData];
}
- (void)setIncludeInheritance:(BOOL)includeInheritance
{
if (_includeInheritance != includeInheritance) {
_includeInheritance = includeInheritance;
[self updateDisplayedData];
}
[self refreshScopeTitles];
}
- (void)setFilterText:(NSString *)filterText
@@ -215,12 +296,18 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
{
Class class = [self.object class];
self.properties = [[self class] propertiesForClass:class];
self.inheritedProperties = [[self class] inheritedPropertiesForClass:class];
self.propertiesWithParent = [self.properties arrayByAddingObjectsFromArray:[[self class] propertiesForClass:[class superclass]]];
self.inheritedProperties = [self.properties arrayByAddingObjectsFromArray:[[self class] inheritedPropertiesForClass:class]];
self.NSObjectProperties = [[self class] propertiesForClass:[NSObject class]];
}
+ (NSArray *)propertiesForClass:(Class)class
+ (NSArray<FLEXPropertyBox *> *)propertiesForClass:(Class)class
{
NSMutableArray *boxedProperties = [NSMutableArray array];
if (!class) {
return @[];
}
NSMutableArray<FLEXPropertyBox *> *boxedProperties = [NSMutableArray array];
unsigned int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
if (propertyList) {
@@ -234,10 +321,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return boxedProperties;
}
+ (NSArray *)inheritedPropertiesForClass:(Class)class
/// Skips NSObject
+ (NSArray<FLEXPropertyBox *> *)inheritedPropertiesForClass:(Class)class
{
NSMutableArray *inheritedProperties = [NSMutableArray array];
while ((class = [class superclass])) {
NSMutableArray<FLEXPropertyBox *> *inheritedProperties = [NSMutableArray array];
while ((class = [class superclass]) && class != [NSObject class]) {
[inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
}
return inheritedProperties;
@@ -245,14 +333,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)updateFilteredProperties
{
NSArray *candidateProperties = self.properties;
if (self.includeInheritance) {
candidateProperties = [candidateProperties arrayByAddingObjectsFromArray:self.inheritedProperties];
}
NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forScope:self.scope];
NSArray *unsortedFilteredProperties = nil;
NSArray<FLEXPropertyBox *> *unsortedFilteredProperties = nil;
if ([self.filterText length] > 0) {
NSMutableArray *mutableUnsortedFilteredProperties = [NSMutableArray array];
NSMutableArray<FLEXPropertyBox *> *mutableUnsortedFilteredProperties = [NSMutableArray array];
for (FLEXPropertyBox *propertyBox in candidateProperties) {
NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
@@ -282,7 +367,10 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
id value = nil;
if ([self canHaveInstanceState]) {
FLEXPropertyBox *propertyBox = self.filteredProperties[index];
NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
}
return value;
}
@@ -294,12 +382,17 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
{
Class class = [self.object class];
self.ivars = [[self class] ivarsForClass:class];
self.inheritedIvars = [[self class] inheritedIvarsForClass:class];
self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
}
+ (NSArray *)ivarsForClass:(Class)class
+ (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
{
NSMutableArray *boxedIvars = [NSMutableArray array];
if (!class) {
return @[];
}
NSMutableArray<FLEXIvarBox *> *boxedIvars = [NSMutableArray array];
unsigned int ivarCount = 0;
Ivar *ivarList = class_copyIvarList(class, &ivarCount);
if (ivarList) {
@@ -313,10 +406,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return boxedIvars;
}
+ (NSArray *)inheritedIvarsForClass:(Class)class
/// Skips NSObject
+ (NSArray<FLEXIvarBox *> *)inheritedIvarsForClass:(Class)class
{
NSMutableArray *inheritedIvars = [NSMutableArray array];
while ((class = [class superclass])) {
NSMutableArray<FLEXIvarBox *> *inheritedIvars = [NSMutableArray array];
while ((class = [class superclass]) && class != [NSObject class]) {
[inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
}
return inheritedIvars;
@@ -324,14 +418,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)updateFilteredIvars
{
NSArray *candidateIvars = self.ivars;
if (self.includeInheritance) {
candidateIvars = [candidateIvars arrayByAddingObjectsFromArray:self.inheritedIvars];
}
NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
NSArray *unsortedFilteredIvars = nil;
NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
if ([self.filterText length] > 0) {
NSMutableArray *mutableUnsortedFilteredIvars = [NSMutableArray array];
NSMutableArray<FLEXIvarBox *> *mutableUnsortedFilteredIvars = [NSMutableArray array];
for (FLEXIvarBox *ivarBox in candidateIvars) {
NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
@@ -361,7 +452,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
id value = nil;
if ([self canHaveInstanceState]) {
FLEXIvarBox *ivarBox = self.filteredIvars[index];
const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
}
return value;
}
@@ -373,12 +466,15 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
{
Class class = [self.object class];
self.methods = [[self class] methodsForClass:class];
self.inheritedMethods = [[self class] inheritedMethodsForClass:class];
self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
}
- (void)updateFilteredMethods
{
self.filteredMethods = [self filteredMethodsFromMethods:self.methods inheritedMethods:self.inheritedMethods areClassMethods:NO];
NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
}
- (void)updateClassMethods
@@ -386,17 +482,24 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
const char *className = [NSStringFromClass([self.object class]) UTF8String];
Class metaClass = objc_getMetaClass(className);
self.classMethods = [[self class] methodsForClass:metaClass];
self.inheritedClassMethods = [[self class] inheritedMethodsForClass:metaClass];
self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
}
- (void)updateFilteredClassMethods
{
self.filteredClassMethods = [self filteredMethodsFromMethods:self.classMethods inheritedMethods:self.inheritedClassMethods areClassMethods:YES];
NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
}
+ (NSArray *)methodsForClass:(Class)class
+ (NSArray<FLEXMethodBox *> *)methodsForClass:(Class)class
{
NSMutableArray *boxedMethods = [NSMutableArray array];
if (!class) {
return @[];
}
NSMutableArray<FLEXMethodBox *> *boxedMethods = [NSMutableArray array];
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList(class, &methodCount);
if (methodList) {
@@ -410,25 +513,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return boxedMethods;
}
+ (NSArray *)inheritedMethodsForClass:(Class)class
/// Skips NSObject
+ (NSArray<FLEXMethodBox *> *)inheritedMethodsForClass:(Class)class
{
NSMutableArray *inheritedMethods = [NSMutableArray array];
while ((class = [class superclass])) {
NSMutableArray<FLEXMethodBox *> *inheritedMethods = [NSMutableArray array];
while ((class = [class superclass]) && class != [NSObject class]) {
[inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
}
return inheritedMethods;
}
- (NSArray *)filteredMethodsFromMethods:(NSArray *)methods inheritedMethods:(NSArray *)inheritedMethods areClassMethods:(BOOL)areClassMethods
- (NSArray<FLEXMethodBox *> *)filteredMethodsFromMethods:(NSArray<FLEXMethodBox *> *)methods areClassMethods:(BOOL)areClassMethods
{
NSArray *candidateMethods = methods;
if (self.includeInheritance) {
candidateMethods = [candidateMethods arrayByAddingObjectsFromArray:inheritedMethods];
}
NSArray *unsortedFilteredMethods = nil;
NSArray<FLEXMethodBox *> *candidateMethods = methods;
NSArray<FLEXMethodBox *> *unsortedFilteredMethods = nil;
if ([self.filterText length] > 0) {
NSMutableArray *mutableUnsortedFilteredMethods = [NSMutableArray array];
NSMutableArray<FLEXMethodBox *> *mutableUnsortedFilteredMethods = [NSMutableArray array];
for (FLEXMethodBox *methodBox in candidateMethods) {
NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
@@ -440,7 +540,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
unsortedFilteredMethods = candidateMethods;
}
NSArray *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
NSArray<FLEXMethodBox *> *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
return [name1 caseInsensitiveCompare:name2];
@@ -464,9 +564,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
#pragma mark - Superclasses
+ (NSArray *)superclassesForClass:(Class)class
+ (NSArray<Class> *)superclassesForClass:(Class)class
{
NSMutableArray *superClasses = [NSMutableArray array];
NSMutableArray<Class> *superClasses = [NSMutableArray array];
while ((class = [class superclass])) {
[superClasses addObject:class];
}
@@ -481,7 +581,7 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (void)updateFilteredSuperclasses
{
if ([self.filterText length] > 0) {
NSMutableArray *filteredSuperclasses = [NSMutableArray array];
NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
for (Class superclass in self.superclasses) {
if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
[filteredSuperclasses addObject:superclass];
@@ -496,9 +596,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
#pragma mark - Table View Data Helpers
- (NSArray *)possibleExplorerSections
- (NSArray<NSNumber *> *)possibleExplorerSections
{
static NSArray *possibleSections = nil;
static NSArray<NSNumber *> *possibleSections = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
possibleSections = @[@(FLEXObjectExplorerSectionDescription),
@@ -513,9 +613,9 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return possibleSections;
}
- (NSArray *)visibleExplorerSections
- (NSArray<NSNumber *> *)visibleExplorerSections
{
NSMutableArray *visibleSections = [NSMutableArray array];
NSMutableArray<NSNumber *> *visibleSections = [NSMutableArray array];
for (NSNumber *possibleSection in [self possibleExplorerSections]) {
FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
@@ -736,34 +836,22 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
} break;
case FLEXObjectExplorerSectionProperties: {
NSUInteger totalCount = [self.properties count];
if (self.includeInheritance) {
totalCount += [self.inheritedProperties count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
} break;
case FLEXObjectExplorerSectionIvars: {
NSUInteger totalCount = [self.ivars count];
if (self.includeInheritance) {
totalCount += [self.inheritedIvars count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
} break;
case FLEXObjectExplorerSectionMethods: {
NSUInteger totalCount = [self.methods count];
if (self.includeInheritance) {
totalCount += [self.inheritedMethods count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
} break;
case FLEXObjectExplorerSectionClassMethods: {
NSUInteger totalCount = [self.classMethods count];
if (self.includeInheritance) {
totalCount += [self.inheritedClassMethods count];
}
NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
} break;
@@ -858,7 +946,8 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
@@ -874,7 +963,16 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
cell.detailTextLabel.textColor = [UIColor grayColor];
}
}
UIView *customView;
if (isCustomSection) {
customView = [self customViewForRowCookie:[self customSectionRowCookieForVisibleRow:indexPath.row]];
if (customView) {
[cell.contentView addSubview:customView];
}
}
cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
@@ -891,7 +989,11 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont] }];
CGFloat preferredHeight = [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.frame.size.width style:tableView.style showsAccessory:NO];
height = MAX(height, preferredHeight);
} else if (explorerSection == FLEXObjectExplorerSectionCustom) {
id cookie = [self customSectionRowCookieForVisibleRow:indexPath.row];
height = [self heightForCustomViewRowForRowCookie:cookie];
}
return height;
}
@@ -1020,6 +1122,16 @@ static const NSInteger kFLEXObjectExplorerScopeIncludeInheritanceIndex = 1;
return nil;
}
- (UIView *)customViewForRowCookie:(id)rowCookie
{
return nil;
}
- (CGFloat)heightForCustomViewRowForRowCookie:(id)rowCookie
{
return self.tableView.rowHeight;
}
- (BOOL)canHaveInstanceState
{
return YES;
@@ -15,7 +15,8 @@
typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
FLEXViewExplorerRowViewController,
FLEXViewExplorerRowPreview
FLEXViewExplorerRowPreview,
FLEXViewExplorerRowViewControllerForAncestor
};
@interface FLEXViewExplorerViewController ()
@@ -46,6 +47,8 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
if ([FLEXUtility viewControllerForView:self.viewToExplore]) {
[rowCookies addObject:@(FLEXViewExplorerRowViewController)];
}else{
[rowCookies addObject:@(FLEXViewExplorerRowViewControllerForAncestor)];
}
[rowCookies addObject:@(FLEXViewExplorerRowPreview)];
@@ -54,9 +57,9 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
return rowCookies;
}
- (NSArray *)shortcutPropertyNames
- (NSArray<NSString *> *)shortcutPropertyNames
{
NSArray *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
NSArray<NSString *> *propertyNames = @[@"frame", @"bounds", @"center", @"transform", @"backgroundColor", @"alpha", @"opaque", @"hidden", @"clipsToBounds", @"userInteractionEnabled", @"layer"];
if ([self.viewToExplore isKindOfClass:[UILabel class]]) {
propertyNames = [@[@"text", @"font", @"textColor"] arrayByAddingObjectsFromArray:propertyNames];
@@ -79,6 +82,10 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
case FLEXViewExplorerRowPreview:
title = @"Preview Image";
break;
case FLEXViewExplorerRowViewControllerForAncestor:
title = @"View Controller For Ancestor";
break;
}
} else if ([rowCookie isKindOfClass:[NSString class]]) {
objc_property_t property = [self viewPropertyForName:rowCookie];
@@ -105,6 +112,10 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
case FLEXViewExplorerRowPreview:
break;
case FLEXViewExplorerRowViewControllerForAncestor:
subtitle = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[FLEXUtility viewControllerForAncestralView:self.viewToExplore]];
break;
}
} else if ([rowCookie isKindOfClass:[NSString class]]) {
objc_property_t property = [self viewPropertyForName:rowCookie];
@@ -141,6 +152,10 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
case FLEXViewExplorerRowPreview:
drillInViewController = [[self class] imagePreviewViewControllerForView:self.viewToExplore];
break;
case FLEXViewExplorerRowViewControllerForAncestor:
drillInViewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:[FLEXUtility viewControllerForAncestralView:self.viewToExplore]];
break;
}
} else if ([rowCookie isKindOfClass:[NSString class]]) {
objc_property_t property = [self viewPropertyForName:rowCookie];
@@ -180,22 +195,22 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
// We add these properties to the class at runtime if they haven't been added yet.
// This way, we can use our property editor to access and change them.
// The property attributes match the declared attributes in UIView.h
NSDictionary *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
NSDictionary<NSString *, NSString *> *frameAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGRect)), kFLEXUtilityAttributeNonAtomic : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"frame" attributes:frameAttributes toClass:[UIView class]];
NSDictionary *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
NSDictionary<NSString *, NSString *> *alphaAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(CGFloat)), kFLEXUtilityAttributeNonAtomic : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"alpha" attributes:alphaAttributes toClass:[UIView class]];
NSDictionary *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
NSDictionary<NSString *, NSString *> *clipsAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"clipsToBounds" attributes:clipsAttributes toClass:[UIView class]];
NSDictionary *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
NSDictionary<NSString *, NSString *> *opaqueAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isOpaque"};
[FLEXRuntimeUtility tryAddPropertyWithName:"opaque" attributes:opaqueAttributes toClass:[UIView class]];
NSDictionary *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
NSDictionary<NSString *, NSString *> *hiddenAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(@encode(BOOL)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCustomGetter : @"isHidden"};
[FLEXRuntimeUtility tryAddPropertyWithName:"hidden" attributes:hiddenAttributes toClass:[UIView class]];
NSDictionary *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
NSDictionary<NSString *, NSString *> *backgroundColorAttributes = @{kFLEXUtilityAttributeTypeEncoding : @(FLEXEncodeClass(UIColor)), kFLEXUtilityAttributeNonAtomic : @"", kFLEXUtilityAttributeCopy : @""};
[FLEXRuntimeUtility tryAddPropertyWithName:"backgroundColor" attributes:backgroundColorAttributes toClass:[UIView class]];
}
@@ -12,6 +12,10 @@
@interface FLEXExplorerToolbar : UIView
/// The items to be displayed in the toolbar. Defaults to:
/// globalsItem, hierarchyItem, selectItem, moveItem, closeItem
@property (nonatomic, copy) NSArray<FLEXToolbarItem *> *toolbarItems;
/// Toolbar item for selecting views.
/// Users of the toolbar can configure the enabled/selected state and event targets/actions.
@property (nonatomic, strong, readonly) FLEXToolbarItem *selectItem;
@@ -22,12 +22,13 @@
@property (nonatomic, strong) UIImageView *dragHandleImageView;
@property (nonatomic, strong) NSArray *toolbarItems;
@property (nonatomic, strong) UIView *selectedViewDescriptionContainer;
@property (nonatomic, strong) UIView *selectedViewDescriptionSafeAreaContainer;
@property (nonatomic, strong) UIView *selectedViewColorIndicator;
@property (nonatomic, strong) UILabel *selectedViewDescriptionLabel;
@property (nonatomic, strong,readwrite) UIView *backgroundView;
@end
@implementation FLEXExplorerToolbar
@@ -36,10 +37,12 @@
{
self = [super initWithFrame:frame];
if (self) {
NSMutableArray *toolbarItems = [NSMutableArray array];
self.backgroundView = [[UIView alloc] init];
self.backgroundView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.95];
[self addSubview:self.backgroundView];
self.dragHandle = [[UIView alloc] init];
self.dragHandle.backgroundColor = [FLEXToolbarItem defaultBackgroundColor];
self.dragHandle.backgroundColor = [UIColor clearColor];
[self addSubview:self.dragHandle];
UIImage *dragHandle = [FLEXResources dragHandle];
@@ -48,56 +51,52 @@
UIImage *globalsIcon = [FLEXResources globeIcon];
self.globalsItem = [FLEXToolbarItem toolbarItemWithTitle:@"menu" image:globalsIcon];
[self addSubview:self.globalsItem];
[toolbarItems addObject:self.globalsItem];
UIImage *listIcon = [FLEXResources listIcon];
self.hierarchyItem = [FLEXToolbarItem toolbarItemWithTitle:@"views" image:listIcon];
[self addSubview:self.hierarchyItem];
[toolbarItems addObject:self.hierarchyItem];
UIImage *selectIcon = [FLEXResources selectIcon];
self.selectItem = [FLEXToolbarItem toolbarItemWithTitle:@"select" image:selectIcon];
[self addSubview:self.selectItem];
[toolbarItems addObject:self.selectItem];
UIImage *moveIcon = [FLEXResources moveIcon];
self.moveItem = [FLEXToolbarItem toolbarItemWithTitle:@"move" image:moveIcon];
[self addSubview:self.moveItem];
[toolbarItems addObject:self.moveItem];
UIImage *closeIcon = [FLEXResources closeIcon];
self.closeItem = [FLEXToolbarItem toolbarItemWithTitle:@"close" image:closeIcon];
[self addSubview:self.closeItem];
[toolbarItems addObject:self.closeItem];
self.toolbarItems = toolbarItems;
self.backgroundColor = [UIColor clearColor];
self.selectedViewDescriptionContainer = [[UIView alloc] init];
self.selectedViewDescriptionContainer.backgroundColor = [UIColor colorWithWhite:0.9 alpha:0.95];
self.selectedViewDescriptionContainer.hidden = YES;
[self addSubview:self.selectedViewDescriptionContainer];
self.selectedViewDescriptionSafeAreaContainer = [[UIView alloc] init];
self.selectedViewDescriptionSafeAreaContainer.backgroundColor = [UIColor clearColor];
[self.selectedViewDescriptionContainer addSubview:self.selectedViewDescriptionSafeAreaContainer];
self.selectedViewColorIndicator = [[UIView alloc] init];
self.selectedViewColorIndicator.backgroundColor = [UIColor redColor];
[self.selectedViewDescriptionContainer addSubview:self.selectedViewColorIndicator];
[self.selectedViewDescriptionSafeAreaContainer addSubview:self.selectedViewColorIndicator];
self.selectedViewDescriptionLabel = [[UILabel alloc] init];
self.selectedViewDescriptionLabel.backgroundColor = [UIColor clearColor];
self.selectedViewDescriptionLabel.font = [[self class] descriptionLabelFont];
[self.selectedViewDescriptionContainer addSubview:self.selectedViewDescriptionLabel];
[self.selectedViewDescriptionSafeAreaContainer addSubview:self.selectedViewDescriptionLabel];
self.toolbarItems = @[_globalsItem, _hierarchyItem, _selectItem, _moveItem, _closeItem];
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect safeArea = [self safeArea];
// Drag Handle
const CGFloat kToolbarItemHeight = [[self class] toolbarItemHeight];
self.dragHandle.frame = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, [[self class] dragHandleWidth], kToolbarItemHeight);
self.dragHandle.frame = CGRectMake(CGRectGetMinX(safeArea), CGRectGetMinY(safeArea), [[self class] dragHandleWidth], kToolbarItemHeight);
CGRect dragHandleImageFrame = self.dragHandleImageView.frame;
dragHandleImageFrame.origin.x = FLEXFloor((self.dragHandle.frame.size.width - dragHandleImageFrame.size.width) / 2.0);
dragHandleImageFrame.origin.y = FLEXFloor((self.dragHandle.frame.size.height - dragHandleImageFrame.size.height) / 2.0);
@@ -106,9 +105,9 @@
// Toolbar Items
CGFloat originX = CGRectGetMaxX(self.dragHandle.frame);
CGFloat originY = self.bounds.origin.y;
CGFloat originY = CGRectGetMinY(safeArea);
CGFloat height = kToolbarItemHeight;
CGFloat width = FLEXFloor((CGRectGetMaxX(self.bounds) - originX) / [self.toolbarItems count]);
CGFloat width = FLEXFloor((CGRectGetWidth(safeArea) - CGRectGetWidth(self.dragHandle.frame)) / [self.toolbarItems count]);
for (UIView *toolbarItem in self.toolbarItems) {
toolbarItem.frame = CGRectMake(originX, originY, width, height);
originX = CGRectGetMaxX(toolbarItem.frame);
@@ -117,8 +116,10 @@
// Make sure the last toolbar item goes to the edge to account for any accumulated rounding effects.
UIView *lastToolbarItem = [self.toolbarItems lastObject];
CGRect lastToolbarItemFrame = lastToolbarItem.frame;
lastToolbarItemFrame.size.width = CGRectGetMaxX(self.bounds) - lastToolbarItemFrame.origin.x;
lastToolbarItemFrame.size.width = CGRectGetMaxX(safeArea) - lastToolbarItemFrame.origin.x;
lastToolbarItem.frame = lastToolbarItemFrame;
self.backgroundView.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), kToolbarItemHeight);
const CGFloat kSelectedViewColorDiameter = [[self class] selectedViewColorIndicatorDiameter];
const CGFloat kDescriptionLabelHeight = [[self class] descriptionLabelHeight];
@@ -127,11 +128,19 @@
const CGFloat kDescriptionContainerHeight = [[self class] descriptionContainerHeight];
CGRect descriptionContainerFrame = CGRectZero;
descriptionContainerFrame.size.width = CGRectGetWidth(self.bounds);
descriptionContainerFrame.size.height = kDescriptionContainerHeight;
descriptionContainerFrame.origin.x = CGRectGetMinX(self.bounds);
descriptionContainerFrame.origin.y = CGRectGetMaxY(self.bounds) - kDescriptionContainerHeight;
descriptionContainerFrame.size.width = self.bounds.size.width;
self.selectedViewDescriptionContainer.frame = descriptionContainerFrame;
CGRect descriptionSafeAreaContainerFrame = CGRectZero;
descriptionSafeAreaContainerFrame.size.width = CGRectGetWidth(safeArea);
descriptionSafeAreaContainerFrame.size.height = kDescriptionContainerHeight;
descriptionSafeAreaContainerFrame.origin.x = CGRectGetMinX(safeArea);
descriptionSafeAreaContainerFrame.origin.y = CGRectGetMinY(safeArea);
self.selectedViewDescriptionSafeAreaContainer.frame = descriptionSafeAreaContainerFrame;
// Selected View Color
CGRect selectedViewColorFrame = CGRectZero;
selectedViewColorFrame.size.width = kSelectedViewColorDiameter;
@@ -150,10 +159,36 @@
descriptionLabelFrame.size.width = CGRectGetMaxX(self.selectedViewDescriptionContainer.bounds) - kHorizontalPadding - descriptionOriginX;
self.selectedViewDescriptionLabel.frame = descriptionLabelFrame;
}
#pragma mark - Setter Overrides
- (void)setToolbarItems:(NSArray<FLEXToolbarItem *> *)toolbarItems {
if (_toolbarItems == toolbarItems) {
return;
}
// Remove old toolbar items, if any
for (FLEXToolbarItem *item in _toolbarItems) {
[item removeFromSuperview];
}
// Trim to 5 items if necessary
if (toolbarItems.count > 5) {
toolbarItems = [toolbarItems subarrayWithRange:NSMakeRange(0, 5)];
}
for (FLEXToolbarItem *item in toolbarItems) {
[self addSubview:item];
}
_toolbarItems = toolbarItems.copy;
// Lay out new items
[self setNeedsLayout];
[self layoutIfNeeded];
}
- (void)setSelectedViewOverlayColor:(UIColor *)selectedViewOverlayColor
{
if (![_selectedViewOverlayColor isEqual:selectedViewOverlayColor]) {
@@ -223,4 +258,15 @@
return CGSizeMake(size.width, height);
}
- (CGRect)safeArea
{
CGRect safeArea = self.bounds;
#if FLEX_AT_LEAST_IOS11_SDK
if (@available(iOS 11, *)) {
safeArea = UIEdgeInsetsInsetRect(self.bounds, self.safeAreaInsets);
}
#endif
return safeArea;
}
@end
@@ -12,6 +12,4 @@
+ (instancetype)toolbarItemWithTitle:(NSString *)title image:(UIImage *)image;
+ (UIColor *)defaultBackgroundColor;
@end
@@ -43,7 +43,7 @@
#pragma mark - Display Defaults
+ (NSDictionary *)titleAttributes
+ (NSDictionary<NSString *, id> *)titleAttributes
{
return @{NSFontAttributeName : [FLEXUtility defaultFontOfSize:12.0]};
}
@@ -70,7 +70,7 @@
+ (UIColor *)defaultBackgroundColor
{
return [UIColor colorWithWhite:1.0 alpha:0.95];
return [UIColor clearColor];
}
+ (CGFloat)topMargin

Some files were not shown because too many files have changed in this diff Show More