├── ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs ├── Pods │ ├── Target Support Files │ │ ├── Aspects │ │ │ ├── Aspects.xcconfig │ │ │ ├── Aspects-prefix.pch │ │ │ ├── Aspects-dummy.m │ │ │ └── Aspects-Private.xcconfig │ │ └── Pods │ │ │ ├── Pods-dummy.m │ │ │ ├── Pods.debug.xcconfig │ │ │ ├── Pods.release.xcconfig │ │ │ ├── Pods-acknowledgements.markdown │ │ │ ├── Pods-acknowledgements.plist │ │ │ └── Pods-resources.sh │ ├── Headers │ │ ├── Private │ │ │ └── Aspects │ │ │ │ └── Aspects.h │ │ └── Public │ │ │ └── Aspects │ │ │ └── Aspects.h │ ├── Manifest.lock │ ├── Aspects │ │ ├── LICENSE │ │ ├── Aspects.h │ │ ├── README.md │ │ └── Aspects.m │ └── Pods.xcodeproj │ │ └── project.pbxproj ├── Podfile ├── Podfile.lock ├── Headers │ ├── UIDevice+Private.h │ ├── UITapticEngine.h │ └── UIDevice+Private.m ├── ViewControllerPreview.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── Taptic │ ├── ViewControllerPreview-Bridging-Header.h │ ├── TapticEngine.h │ └── TapticEngine.m ├── ViewControllerPreview.xcworkspace │ └── contents.xcworkspacedata ├── ViewControllerPreview │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── MasterViewController+UIViewControllerPreviewing.swift │ ├── DetailViewController.swift │ └── MasterViewController.swift └── LICENSE.txt ├── .gitignore ├── LICENSE └── README.md /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Aspects/Aspects.xcconfig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Headers/Private/Aspects/Aspects.h: -------------------------------------------------------------------------------- 1 | ../../../Aspects/Aspects.h -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Headers/Public/Aspects/Aspects.h: -------------------------------------------------------------------------------- 1 | ../../../Aspects/Aspects.h -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | inhibit_all_warnings! 3 | 4 | pod 'Aspects' -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Aspects/Aspects-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Pods/Pods-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods : NSObject 3 | @end 4 | @implementation PodsDummy_Pods 5 | @end 6 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Aspects/Aspects-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Aspects : NSObject 3 | @end 4 | @implementation PodsDummy_Aspects 5 | @end 6 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Aspects (1.4.1) 3 | 4 | DEPENDENCIES: 5 | - Aspects 6 | 7 | SPEC CHECKSUMS: 8 | Aspects: 7595ba96a6727a58ebcbfc954497fc5d2fdde546 9 | 10 | COCOAPODS: 0.38.2 11 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Aspects (1.4.1) 3 | 4 | DEPENDENCIES: 5 | - Aspects 6 | 7 | SPEC CHECKSUMS: 8 | Aspects: 7595ba96a6727a58ebcbfc954497fc5d2fdde546 9 | 10 | COCOAPODS: 0.38.2 11 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Headers/UIDevice+Private.h: -------------------------------------------------------------------------------- 1 | @import UIKit; 2 | 3 | #import "UITapticEngine.h" 4 | 5 | @interface UIDevice (Private) 6 | 7 | - (UITapticEngine *)tapticEngine; 8 | 9 | - (UITapticEngine *)_tapticEngine; 10 | 11 | @end -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Taptic/ViewControllerPreview-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "TapticEngine.h" 6 | #import "UITapticEngine.h" 7 | #import "UIDevice+Private.h" -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Taptic/TapticEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // TapticEngine.h 3 | // ViewControllerPreview 4 | // 5 | // Created by Dal Rupnik on 28/09/15. 6 | // Copyright © 2015 Apple Inc. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | @interface TapticEngine : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Headers/UITapticEngine.h: -------------------------------------------------------------------------------- 1 | static int const UITapticEngineFeedbackPeek = 1001; 2 | static int const UITapticEngineFeedbackPop = 1002; 3 | 4 | @interface UITapticEngine : NSObject 5 | 6 | - (void)actuateFeedback:(int)arg1; 7 | - (void)endUsingFeedback:(int)arg1; 8 | - (void)prepareUsingFeedback:(int)arg1; 9 | 10 | @end -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Aspects/Aspects-Private.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Aspects.xcconfig" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/Aspects" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Aspects" 4 | PODS_ROOT = ${SRCROOT} 5 | SKIP_INSTALL = YES -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Pods/Pods.debug.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Aspects" 3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Aspects" 4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"Aspects" 5 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Pods/Pods.release.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/Aspects" 3 | OTHER_CFLAGS = $(inherited) -isystem "${PODS_ROOT}/Headers/Public" -isystem "${PODS_ROOT}/Headers/Public/Aspects" 4 | OTHER_LDFLAGS = $(inherited) -ObjC -l"Aspects" 5 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | 21 | # AppCode 22 | .idea/ 23 | 24 | # Dominus 25 | dominus.cfg 26 | 27 | # CocoaPods 28 | #Pods 29 | 30 | # Carthage 31 | Carthage/Build 32 | 33 | # KZBootstrap 34 | KZBootstrapUserMacros.h 35 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Headers/UIDevice+Private.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITapticEngine.m 3 | // ViewControllerPreview 4 | // 5 | // Created by Dal Rupnik on 28/09/15. 6 | // Copyright © 2015 Apple Inc. All rights reserved. 7 | // 8 | 9 | #import "UIDevice+Private.h" 10 | #pragma clang diagnostic push 11 | #pragma clang diagnostic ignored "-Wincomplete-implementation" 12 | 13 | @implementation UIDevice (Private) 14 | 15 | - (UITapticEngine *)tapticEngine 16 | { 17 | return [self _tapticEngine]; 18 | } 19 | 20 | @end 21 | 22 | #pragma clang diagnostic pop 23 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Unified Sense 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Taptic/TapticEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // TapticEngine.m 3 | // ViewControllerPreview 4 | // 5 | // Created by Dal Rupnik on 28/09/15. 6 | // Copyright © 2015 Apple Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UITapticEngine.h" 11 | #import "UIDevice+Private.h" 12 | 13 | #import "TapticEngine.h" 14 | 15 | @implementation TapticEngine 16 | 17 | + (void)load 18 | { 19 | UITapticEngine *tapticEngine = [UIDevice currentDevice]._tapticEngine; 20 | 21 | [tapticEngine aspect_hookSelector:@selector(actuateFeedback:) withOptions:AspectPositionAfter usingBlock:^(id arg1){ 22 | NSLog(@"actuateFeedback: %@", arg1.arguments); 23 | }error:nil]; 24 | 25 | [tapticEngine aspect_hookSelector:@selector(endUsingFeedback:) withOptions:AspectPositionAfter usingBlock:^(id arg1){ 26 | NSLog(@"endUsingFeedback: %@", arg1.arguments); 27 | }error:nil]; 28 | 29 | [tapticEngine aspect_hookSelector:@selector(prepareUsingFeedback:) withOptions:AspectPositionAfter usingBlock:^(id arg1){ 30 | NSLog(@"prepareUsingFeedback: %@", arg1.arguments); 31 | }error:nil]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Aspects/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Peter Steinberger, steipete@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Pods/Pods-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Aspects 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2014 Peter Steinberger, steipete@gmail.com 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | Generated by CocoaPods - http://cocoapods.org 28 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | The application delegate. Creates and holds onto the main window, and implements the split view controller delegate. 7 | */ 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 13 | // MARK: Properties 14 | 15 | var window: UIWindow? 16 | 17 | // MARK: App Delegate 18 | 19 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 20 | let splitViewController = self.window!.rootViewController as! UISplitViewController 21 | splitViewController.delegate = self 22 | 23 | return true 24 | } 25 | 26 | // MARK: Split View Delegate 27 | 28 | func splitViewController(splitViewController: UISplitViewController, collapseSecondaryViewController secondaryViewController: UIViewController, ontoPrimaryViewController primaryViewController: UIViewController) -> Bool { 29 | 30 | guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } 31 | guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } 32 | 33 | // Return true if the `detailItemTitle` has not been set, collapsing the secondary controller. 34 | return topAsDetailController.detailItemTitle == nil 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/MasterViewController+UIViewControllerPreviewing.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | Demonstrates the implementation of the previewing delegate's "Peek" and "Pop" callbacks. 7 | */ 8 | 9 | import UIKit 10 | 11 | extension MasterViewController: UIViewControllerPreviewingDelegate { 12 | // MARK: UIViewControllerPreviewingDelegate 13 | 14 | /// Create a previewing view controller to be shown at "Peek". 15 | func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { 16 | // Obtain the index path and the cell that was pressed. 17 | guard let indexPath = tableView.indexPathForRowAtPoint(location), 18 | cell = tableView.cellForRowAtIndexPath(indexPath) else { return nil } 19 | 20 | // Create a detail view controller and set its properties. 21 | guard let detailViewController = storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController else { return nil } 22 | 23 | let previewDetail = sampleData[indexPath.row] 24 | detailViewController.detailItemTitle = previewDetail.title 25 | 26 | /* 27 | Set the height of the preview by setting the preferred content size of the detail view controller. 28 | Width should be zero, because it's not used in portrait. 29 | */ 30 | detailViewController.preferredContentSize = CGSize(width: 0.0, height: previewDetail.preferredHeight) 31 | 32 | // Set the source rect to the cell frame, so surrounding elements are blurred. 33 | previewingContext.sourceRect = cell.frame 34 | 35 | return detailViewController 36 | } 37 | 38 | /// Present the view controller for the "Pop" action. 39 | func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) { 40 | // Reuse the "Peek" view controller for presentation. 41 | showViewController(viewControllerToCommit, sender: self) 42 | } 43 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taptic Engine Playground 2 | 3 | This example was used to discover the Apple's usage of Taptic Engine functions. It's actuation methods are hidden in `UIDevice` and `_UITapticEngine` classes and are called by `UIPreviewInteractionController` when appropriate. 4 | 5 | This original sample displays a UITableViewController that triggers view controller previews, using preferredContentSize to show previews of different sizes. The sample also includes single-item and grouped action items. 6 | 7 | ## Disclaimer 8 | 9 | **When you are using Apple Private API's you are taking all responsibility for any damage done to your device. That includes running and testing all code, contained in this repository.** 10 | 11 | ## Research 12 | 13 | The methods and classes for Taptic Engine can be found using [**Runtime Headers**](https://github.com/nst/iOS-Runtime-Headers), look for `UIDevice` and `_UITapticEngine` classes. 14 | 15 | The values for peek and pop were discovered using [Aspects](https://github.com/steipete/Aspects) and this is an example output: 16 | 17 | ``` 18 | // Cell touch 19 | 2015-09-28 01:32:25.322 ViewControllerPreview[656:143905] prepareUsingFeedback: ( 20 | 0 21 | ) 22 | 2015-09-28 01:32:25.325 ViewControllerPreview[656:143905] prepareUsingFeedback: ( 23 | 1 24 | ) 25 | // Peek 26 | 2015-09-28 01:32:26.443 ViewControllerPreview[656:143905] actuateFeedback: ( 27 | 1001 28 | ) 29 | 2015-09-28 01:32:27.519 ViewControllerPreview[656:143905] endUsingFeedback: ( 30 | 0 31 | ) 32 | 2015-09-28 01:32:27.520 ViewControllerPreview[656:143905] endUsingFeedback: ( 33 | 1 34 | ) 35 | // Pop 36 | 2015-09-28 01:32:27.538 ViewControllerPreview[656:143905] actuateFeedback: ( 37 | 1002 38 | ) 39 | ``` 40 | 41 | ## Requirements 42 | 43 | ### Build 44 | 45 | iOS 9 SDK, Xcode 7 46 | 47 | ### Runtime 48 | 49 | iOS 9 50 | 51 | # Thanks 52 | 53 | A big thanks goes to **Peter Steinberger** for all his work on **Aspects**. 54 | 55 | Contact 56 | ====== 57 | 58 | Dal Rupnik 59 | 60 | - [legoless](https://github.com/legoless) on **GitHub** 61 | - [@thelegoless](https://twitter.com/thelegoless) on **Twitter** 62 | - [dal@unifiedsense.com](mailto:dal@unifiedsense.com) 63 | 64 | License 65 | ====== 66 | 67 | **TapticPlayground** is released under the **MIT** license. See [LICENSE](https://github.com/Legoless/TapticPlayground/blob/master/LICENSE) file for more information. 68 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Pods/Pods-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2014 Peter Steinberger, steipete@gmail.com 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | Title 39 | Aspects 40 | Type 41 | PSGroupSpecifier 42 | 43 | 44 | FooterText 45 | Generated by CocoaPods - http://cocoapods.org 46 | Title 47 | 48 | Type 49 | PSGroupSpecifier 50 | 51 | 52 | StringsTable 53 | Acknowledgements 54 | Title 55 | Acknowledgements 56 | 57 | 58 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | The detail view controller. 7 | */ 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | // MARK: Properties 13 | 14 | @IBOutlet weak var detailDescriptionLabel: UILabel! 15 | 16 | // Property to hold the detail item's title. 17 | var detailItemTitle: String? 18 | 19 | // Preview action items. 20 | lazy var previewActions: [UIPreviewActionItem] = { 21 | func previewActionForTitle(title: String, style: UIPreviewActionStyle = .Default) -> UIPreviewAction { 22 | return UIPreviewAction(title: title, style: style) { previewAction, viewController in 23 | guard let detailViewController = viewController as? DetailViewController, 24 | item = detailViewController.detailItemTitle else { return } 25 | 26 | print("\(previewAction.title) triggered from `DetailViewController` for item: \(item)") 27 | } 28 | } 29 | 30 | let action1 = previewActionForTitle("Default Action") 31 | let action2 = previewActionForTitle("Destructive Action", style: .Destructive) 32 | 33 | let subAction1 = previewActionForTitle("Sub Action 1") 34 | let subAction2 = previewActionForTitle("Sub Action 2") 35 | let groupedActions = UIPreviewActionGroup(title: "Sub Actions…", style: .Default, actions: [subAction1, subAction2] ) 36 | 37 | return [action1, action2, groupedActions] 38 | }() 39 | 40 | // MARK: Life cycle 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | 45 | // Update the user interface for the detail item. 46 | if let detail = detailItemTitle { 47 | detailDescriptionLabel.text = detail 48 | } 49 | 50 | // Set up the detail view's `navigationItem`. 51 | navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem() 52 | navigationItem.leftItemsSupplementBackButton = true 53 | } 54 | 55 | // MARK: Preview actions 56 | 57 | override func previewActionItems() -> [UIPreviewActionItem] { 58 | return previewActions 59 | } 60 | } -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Sample code project: ViewControllerPreviews: Using the UIViewController previewing APIs 2 | Version: 1.1 3 | 4 | IMPORTANT: This Apple software is supplied to you by Apple 5 | Inc. ("Apple") in consideration of your agreement to the following 6 | terms, and your use, installation, modification or redistribution of 7 | this Apple software constitutes acceptance of these terms. If you do 8 | not agree with these terms, please do not use, install, modify or 9 | redistribute this Apple software. 10 | 11 | In consideration of your agreement to abide by the following terms, and 12 | subject to these terms, Apple grants you a personal, non-exclusive 13 | license, under Apple's copyrights in this original Apple software (the 14 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 15 | Software, with or without modifications, in source and/or binary forms; 16 | provided that if you redistribute the Apple Software in its entirety and 17 | without modifications, you must retain this notice and the following 18 | text and disclaimers in all such redistributions of the Apple Software. 19 | Neither the name, trademarks, service marks or logos of Apple Inc. may 20 | be used to endorse or promote products derived from the Apple Software 21 | without specific prior written permission from Apple. Except as 22 | expressly stated in this notice, no other rights or licenses, express or 23 | implied, are granted by Apple herein, including but not limited to any 24 | patent rights that may be infringed by your derivative works or by other 25 | works in which the Apple Software may be incorporated. 26 | 27 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 28 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 29 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 30 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 31 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 32 | 33 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 37 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 38 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 39 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 40 | POSSIBILITY OF SUCH DAMAGE. 41 | 42 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 43 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Aspects/Aspects.h: -------------------------------------------------------------------------------- 1 | // 2 | // Aspects.h 3 | // Aspects - A delightful, simple library for aspect oriented programming. 4 | // 5 | // Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_OPTIONS(NSUInteger, AspectOptions) { 11 | AspectPositionAfter = 0, /// Called after the original implementation (default) 12 | AspectPositionInstead = 1, /// Will replace the original implementation. 13 | AspectPositionBefore = 2, /// Called before the original implementation. 14 | 15 | AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution. 16 | }; 17 | 18 | /// Opaque Aspect Token that allows to deregister the hook. 19 | @protocol AspectToken 20 | 21 | /// Deregisters an aspect. 22 | /// @return YES if deregistration is successful, otherwise NO. 23 | - (BOOL)remove; 24 | 25 | @end 26 | 27 | /// The AspectInfo protocol is the first parameter of our block syntax. 28 | @protocol AspectInfo 29 | 30 | /// The instance that is currently hooked. 31 | - (id)instance; 32 | 33 | /// The original invocation of the hooked method. 34 | - (NSInvocation *)originalInvocation; 35 | 36 | /// All method arguments, boxed. This is lazily evaluated. 37 | - (NSArray *)arguments; 38 | 39 | @end 40 | 41 | /** 42 | Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second. 43 | 44 | Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe. 45 | */ 46 | @interface NSObject (Aspects) 47 | 48 | /// Adds a block of code before/instead/after the current `selector` for a specific class. 49 | /// 50 | /// @param block Aspects replicates the type signature of the method being hooked. 51 | /// The first parameter will be `id`, followed by all parameters of the method. 52 | /// These parameters are optional and will be filled to match the block signature. 53 | /// You can even use an empty block, or one that simple gets `id`. 54 | /// 55 | /// @note Hooking static methods is not supported. 56 | /// @return A token which allows to later deregister the aspect. 57 | + (id)aspect_hookSelector:(SEL)selector 58 | withOptions:(AspectOptions)options 59 | usingBlock:(id)block 60 | error:(NSError **)error; 61 | 62 | /// Adds a block of code before/instead/after the current `selector` for a specific instance. 63 | - (id)aspect_hookSelector:(SEL)selector 64 | withOptions:(AspectOptions)options 65 | usingBlock:(id)block 66 | error:(NSError **)error; 67 | 68 | @end 69 | 70 | 71 | typedef NS_ENUM(NSUInteger, AspectErrorCode) { 72 | AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted. 73 | AspectErrorDoesNotRespondToSelector, /// Selector could not be found. 74 | AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed. 75 | AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed. 76 | AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair. 77 | AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called. 78 | AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large. 79 | 80 | AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated. 81 | }; 82 | 83 | extern NSString *const AspectErrorDomain; 84 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2015 Apple Inc. All Rights Reserved. 3 | See LICENSE.txt for this sample’s licensing information 4 | 5 | Abstract: 6 | The master view controller. 7 | */ 8 | 9 | import UIKit 10 | 11 | class MasterViewController: UITableViewController { 12 | // MARK: Types 13 | 14 | /// A simple data structure to populate the table view. 15 | struct PreviewDetail { 16 | let title: String 17 | let preferredHeight: Double 18 | } 19 | 20 | var timer : NSTimer? 21 | 22 | // MARK: Properties 23 | 24 | let sampleData = [ 25 | PreviewDetail(title: "Small", preferredHeight: 160.0), 26 | PreviewDetail(title: "Medium", preferredHeight: 320.0), 27 | PreviewDetail(title: "Large", preferredHeight: 0.0) // 0.0 to get the default height. 28 | ] 29 | 30 | /// An alert controller used to notify the user if 3D touch is not available. 31 | var alertController: UIAlertController? 32 | 33 | // MARK: View Life Cycle 34 | @IBAction func vibrateButtonTap(sender: UIBarButtonItem) 35 | { 36 | if sender.title != "Stop" { 37 | sender.title = "Stop" 38 | 39 | self.timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "vibrate", userInfo: nil, repeats: true) 40 | } 41 | else { 42 | sender.title = "Vibrate" 43 | 44 | self.timer!.invalidate() 45 | self.timer = nil 46 | } 47 | } 48 | 49 | func vibrate () { 50 | UIDevice.currentDevice().tapticEngine().actuateFeedback(UITapticEngineFeedbackPeek) 51 | } 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | 56 | // Check for force touch feature, and add force touch/previewing capability. 57 | if traitCollection.forceTouchCapability == .Available { 58 | /* 59 | Register for `UIViewControllerPreviewingDelegate` to enable 60 | "Peek" and "Pop". 61 | (see: MasterViewController+UIViewControllerPreviewing.swift) 62 | 63 | The view controller will be automatically unregistered when it is 64 | deallocated. 65 | */ 66 | registerForPreviewingWithDelegate(self, sourceView: view) 67 | } 68 | else { 69 | // Create an alert to display to the user. 70 | alertController = UIAlertController(title: "3D Touch Not Available", message: "Unsupported device.", preferredStyle: .Alert) 71 | } 72 | } 73 | 74 | override func viewWillAppear(animated: Bool) { 75 | // Clear the selection if the split view is only showing one view controller. 76 | clearsSelectionOnViewWillAppear = splitViewController!.collapsed 77 | 78 | super.viewWillAppear(animated) 79 | } 80 | 81 | override func viewDidAppear(animated: Bool) { 82 | super.viewDidAppear(animated) 83 | 84 | // Present the alert if necessary. 85 | if let alertController = alertController { 86 | alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) 87 | presentViewController(alertController, animated: true, completion: nil) 88 | 89 | // Clear the `alertController` to ensure it's not presented multiple times. 90 | self.alertController = nil 91 | } 92 | } 93 | 94 | // MARK: UIStoryboardSegue Handling 95 | 96 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 97 | if segue.identifier == "showDetail", let indexPath = tableView.indexPathForSelectedRow { 98 | let previewDetail = sampleData[indexPath.row] 99 | 100 | let detailViewController = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController 101 | 102 | // Pass the `title` to the `detailViewController`. 103 | detailViewController.detailItemTitle = previewDetail.title 104 | } 105 | } 106 | 107 | // MARK: Table View 108 | 109 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 110 | // Return the number of items in the sample data structure. 111 | return sampleData.count 112 | } 113 | 114 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 115 | let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 116 | 117 | let previewDetail = sampleData[indexPath.row] 118 | cell.textLabel!.text = previewDetail.title 119 | 120 | return cell 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Target Support Files/Pods/Pods-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | realpath() { 12 | DIRECTORY="$(cd "${1%/*}" && pwd)" 13 | FILENAME="${1##*/}" 14 | echo "$DIRECTORY/$FILENAME" 15 | } 16 | 17 | install_resource() 18 | { 19 | case $1 in 20 | *.storyboard) 21 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 22 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 23 | ;; 24 | *.xib) 25 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 26 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 27 | ;; 28 | *.framework) 29 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 30 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 31 | echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 32 | rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 33 | ;; 34 | *.xcdatamodel) 35 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" 36 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" 37 | ;; 38 | *.xcdatamodeld) 39 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" 40 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" 41 | ;; 42 | *.xcmappingmodel) 43 | echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" 44 | xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" 45 | ;; 46 | *.xcassets) 47 | ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") 48 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 49 | ;; 50 | /*) 51 | echo "$1" 52 | echo "$1" >> "$RESOURCES_TO_COPY" 53 | ;; 54 | *) 55 | echo "${PODS_ROOT}/$1" 56 | echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" 57 | ;; 58 | esac 59 | } 60 | 61 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 62 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 63 | if [[ "${ACTION}" == "install" ]]; then 64 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 65 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 66 | fi 67 | rm -f "$RESOURCES_TO_COPY" 68 | 69 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 70 | then 71 | case "${TARGETED_DEVICE_FAMILY}" in 72 | 1,2) 73 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 74 | ;; 75 | 1) 76 | TARGET_DEVICE_ARGS="--target-device iphone" 77 | ;; 78 | 2) 79 | TARGET_DEVICE_ARGS="--target-device ipad" 80 | ;; 81 | *) 82 | TARGET_DEVICE_ARGS="--target-device mac" 83 | ;; 84 | esac 85 | 86 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 87 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 88 | while read line; do 89 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 90 | XCASSET_FILES+=("$line") 91 | fi 92 | done <<<"$OTHER_XCASSETS" 93 | 94 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 95 | fi 96 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Aspects/README.md: -------------------------------------------------------------------------------- 1 | Aspects v1.4.1 [![Build Status](https://travis-ci.org/steipete/Aspects.svg?branch=master)](https://travis-ci.org/steipete/Aspects) 2 | ============== 3 | 4 | Delightful, simple library for aspect oriented programming by [@steipete](http://twitter.com/steipete). 5 | 6 | **Think of Aspects as method swizzling on steroids. It allows you to add code to existing methods per class or per instance**, whilst thinking of the insertion point e.g. before/instead/after. Aspects automatically deals with calling super and is easier to use than regular method swizzling. 7 | 8 | This is stable and used in hundreds of apps since it's part of [PSPDFKit, an iOS PDF framework that ships with apps like Dropbox or Evernote](http://pspdfkit.com), and now I finally made it open source. 9 | 10 | Aspects extends `NSObject` with the following methods: 11 | 12 | ``` objc 13 | /// Adds a block of code before/instead/after the current `selector` for a specific class. 14 | /// 15 | /// @param block Aspects replicates the type signature of the method being hooked. 16 | /// The first parameter will be `id`, followed by all parameters of the method. 17 | /// These parameters are optional and will be filled to match the block signature. 18 | /// You can even use an empty block, or one that simple gets `id`. 19 | /// 20 | /// @note Hooking static methods is not supported. 21 | /// @return A token which allows to later deregister the aspect. 22 | + (id)aspect_hookSelector:(SEL)selector 23 | withOptions:(AspectOptions)options 24 | usingBlock:(id)block 25 | error:(NSError **)error; 26 | 27 | /// Adds a block of code before/instead/after the current `selector` for a specific instance. 28 | - (id)aspect_hookSelector:(SEL)selector 29 | withOptions:(AspectOptions)options 30 | usingBlock:(id)block 31 | error:(NSError **)error; 32 | 33 | /// Deregister an aspect. 34 | /// @return YES if deregistration is successful, otherwise NO. 35 | id aspect = ...; 36 | [aspect remove]; 37 | ``` 38 | 39 | Adding aspects returns an opaque token of type `AspectToken` which can be used to deregister again. All calls are thread-safe. 40 | 41 | Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called 1000 times per second. 42 | 43 | Aspects calls and matches block arguments. Blocks without arguments are supported as well. The first block argument will be of type `id`. 44 | 45 | When to use Aspects 46 | ------------------- 47 | Aspect-oritented programming (AOP) is used to encapsulate "cross-cutting" concerns. These are the kind of requirements that *cut-accross* many modules in your system, and so cannot be encapsulated using normal Object Oriented programming. Some examples of these kinds of requirements: 48 | 49 | * *Whenever* a user invokes a method on the service client, security should be checked. 50 | * *Whenever* a useer interacts with the store, a genius suggestion should be presented, based on their interaction. 51 | * *All* calls should be logged. 52 | 53 | If we implemented the above requirements using regular OO there'd be some drawbacks: 54 | 55 | 56 | Good OO says a class should have a single responsibility, however adding on extra *cross-cutting* requirements means a class that is taking on other responsibilites. For example you might have a **StoreClient** that supposed to be all about making purchases from an online store. Add in some cross-cutting requirements and it might also have to take on the roles of logging, security and recommendations. This is not great: 57 | 58 | * Our StoreClient is now harder to understand and maintain. 59 | * These cross-cutting requirements are duplicated and spreading throughout our app. 60 | 61 | AOP lets us modularize these cross-cutting requirements, and then cleanly identify all of the places they should be applied. As shown in the examples above cross-cutting requirements can be eithe technical or business focused in nature. 62 | 63 | ## Here are some concrete examples: 64 | 65 | 66 | Aspects can be used to **dynamically add logging** for debug builds only: 67 | 68 | ``` objc 69 | [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo, BOOL animated) { 70 | NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated); 71 | } error:NULL]; 72 | ``` 73 | 74 | ------------------- 75 | It can be used to greatly simplify your analytics setup: 76 | https://github.com/orta/ARAnalytics 77 | 78 | ------------------- 79 | You can check if methods are really being called in your test cases: 80 | ``` objc 81 | - (void)testExample { 82 | TestClass *testClass = [TestClass new]; 83 | TestClass *testClass2 = [TestClass new]; 84 | 85 | __block BOOL testCallCalled = NO; 86 | [testClass aspect_hookSelector:@selector(testCall) withOptions:AspectPositionAfter usingBlock:^{ 87 | testCallCalled = YES; 88 | } error:NULL]; 89 | 90 | [testClass2 testCallAndExecuteBlock:^{ 91 | [testClass testCall]; 92 | } error:NULL]; 93 | XCTAssertTrue(testCallCalled, @"Calling testCallAndExecuteBlock must call testCall"); 94 | } 95 | ``` 96 | ------------------- 97 | It can be really useful for debugging. Here I was curious when exactly the tap gesture changed state: 98 | 99 | ``` objc 100 | [_singleTapGesture aspect_hookSelector:@selector(setState:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo) { 101 | NSLog(@"%@: %@", aspectInfo.instance, aspectInfo.arguments); 102 | } error:NULL]; 103 | ``` 104 | 105 | ------------------- 106 | Another convenient use case is adding handlers for classes that you don't own. I've written it for use in [PSPDFKit](http://pspdfkit.com), where we require notifications when a view controller is being dismissed modally. This includes UIKit view controllers like `MFMailComposeViewController` or `UIImagePickerController`. We could have created subclasses for each of these controllers, but this would be quite a lot of unnecessary code. Aspects gives you a simpler solution for this problem: 107 | 108 | ``` objc 109 | @implementation UIViewController (DismissActionHook) 110 | 111 | // Will add a dismiss action once the controller gets dismissed. 112 | - (void)pspdf_addWillDismissAction:(void (^)(void))action { 113 | PSPDFAssert(action != NULL); 114 | 115 | [self aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo) { 116 | if ([aspectInfo.instance isBeingDismissed]) { 117 | action(); 118 | } 119 | } error:NULL]; 120 | } 121 | 122 | @end 123 | ``` 124 | 125 | Debugging 126 | --------- 127 | Aspects identifies itself nicely in the stack trace, so it's easy to see if a method has been hooked: 128 | 129 | 130 | 131 | Using Aspects with non-void return types 132 | ---------------------------------------- 133 | 134 | You can use the invocation object to customize the return value: 135 | 136 | ``` objc 137 | [PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^(id info, NSSet *touches, UIEvent *event) { 138 | // Call original implementation. 139 | BOOL processTouches; 140 | NSInvocation *invocation = info.originalInvocation; 141 | [invocation invoke]; 142 | [invocation getReturnValue:&processTouches]; 143 | 144 | if (processTouches) { 145 | processTouches = pspdf_stylusShouldProcessTouches(touches, event); 146 | [invocation setReturnValue:&processTouches]; 147 | } 148 | } error:NULL]; 149 | ``` 150 | 151 | Installation 152 | ------------ 153 | The simplest option is to use `pod "Aspects"`. 154 | 155 | You can also add the two files `Aspects.h/m`. There are no further requirements. 156 | 157 | Compatibility and Limitations 158 | ----------------------------- 159 | Aspects uses quite some runtime trickery to achieve what it does. You can mostly mix this with regular method swizzling. 160 | 161 | An important limitation is that for class-based hooking, a method can only be hooked once within the subclass hierarchy. [See #2](https://github.com/steipete/Aspects/issues/2) 162 | This does not apply for objects that are hooked. Aspects creates a dynamic subclass here and has full control. 163 | 164 | KVO works if observers are created after your calls `aspect_hookSelector:` It most likely will crash the other way around. 165 | Still looking for workarounds here - any help apprechiated. 166 | 167 | Because of ugly implementation details on the ObjC runtime, methods that return unions that also contain structs might not work correctly unless this code runs on the arm64 runtime. 168 | 169 | Credits 170 | ------- 171 | The idea to use `_objc_msgForward` and parts of the `NSInvocation` argument selection is from the excellent [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) from the GitHub guys. [This article](http://codeshaker.blogspot.co.at/2012/01/aop-delivered.html) explains how it works under the hood. 172 | 173 | 174 | Supported iOS & SDK Versions 175 | ----------------------------- 176 | 177 | * Aspects requires ARC. 178 | * Aspects is tested with iOS 6+ and OS X 10.7 or higher. 179 | 180 | License 181 | ------- 182 | MIT licensed, Copyright (c) 2014 Peter Steinberger, steipete@gmail.com, [@steipete](http://twitter.com/steipete) 183 | 184 | 185 | Release Notes 186 | ----------------- 187 | 188 | Version 1.4.1 189 | 190 | - Rename error codes. 191 | 192 | Version 1.4.0 193 | 194 | - Add support for block signatures that match method signatures. (thanks to @nickynick) 195 | 196 | Version 1.3.1 197 | 198 | - Add support for OS X 10.7 or higher. (thanks to @ashfurrow) 199 | 200 | Version 1.3.0 201 | 202 | - Add automatic deregistration. 203 | - Checks if the selector exists before trying to hook. 204 | - Improved dealloc hooking. (no more unsafe_unretained needed) 205 | - Better examples. 206 | - Always log errors. 207 | 208 | Version 1.2.0 209 | 210 | - Adds error parameter. 211 | - Improvements in subclassing registration tracking. 212 | 213 | Version 1.1.0 214 | 215 | - Renamed the files from NSObject+Aspects.m/h to just Aspects.m/h. 216 | - Removing now works via calling `remove` on the aspect token. 217 | - Allow hooking dealloc. 218 | - Fixes infinite loop if the same method is hooked for multiple classes. Hooking will only work for one class in the hierarchy. 219 | - Additional checks to prevent things like hooking retain/release/autorelease or forwardInvocation: 220 | - The original implementation of forwardInvocation is now correctly preserved. 221 | - Classes are properly cleaned up and restored to the original state after the last hook is deregistered. 222 | - Lots and lots of new test cases! 223 | 224 | Version 1.0.1 225 | 226 | - Minor tweaks and documentation improvements. 227 | 228 | Version 1.0.0 229 | 230 | - Initial release 231 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Pods.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 335445D0B008598C71060E3913329C52 /* Aspects.h in Headers */ = {isa = PBXBuildFile; fileRef = 95D546B51E581DE401E42F9905A6F36E /* Aspects.h */; }; 11 | 3B4168F33F6906D62CF479A4E7AC9053 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8B352A49CEEFCFFE9A49DC1A2513813 /* Foundation.framework */; }; 12 | A237F65E6F8BFD4E1A901AD2AC767D71 /* Aspects.m in Sources */ = {isa = PBXBuildFile; fileRef = 77BE6D4EC2B31DCB5B9BE10FD52DE304 /* Aspects.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; 13 | B627DCCF6888DFD5E9E9D4161B594BF0 /* Aspects-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 556D358B1127E77A7AF8B7C9B9422091 /* Aspects-dummy.m */; }; 14 | C09211C2917CEADA360B48BBFA0C3DE0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B8B352A49CEEFCFFE9A49DC1A2513813 /* Foundation.framework */; }; 15 | E9F1F9DAB6255FE47B35B183DCE660B6 /* Pods-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 820584B2529C1AC0C4FCE6FF2A3192AE /* Pods-dummy.m */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | 11CCC0738BA7FBE6088BCDCED5D1577D /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = E8F3060ABAD4F0616637250692D6232F; 24 | remoteInfo = Aspects; 25 | }; 26 | /* End PBXContainerItemProxy section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 02BA764A94765EAD4B7C20AA4436EDC3 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Pods.release.xcconfig; sourceTree = ""; }; 30 | 15A529C27057E4A57D259CBC6E6CE49C /* Pods-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-acknowledgements.markdown"; sourceTree = ""; }; 31 | 332BEE3AFFC449B6E2641E6BA5CC3CEF /* Aspects-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Aspects-prefix.pch"; sourceTree = ""; }; 32 | 556D358B1127E77A7AF8B7C9B9422091 /* Aspects-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Aspects-dummy.m"; sourceTree = ""; }; 33 | 641AE05DD55E5E6AC1590CD7B4A18F97 /* Pods-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-resources.sh"; sourceTree = ""; }; 34 | 6675C4C0A5147F1A2AC0E63FF71E14D1 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 77BE6D4EC2B31DCB5B9BE10FD52DE304 /* Aspects.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = Aspects.m; sourceTree = ""; }; 36 | 820584B2529C1AC0C4FCE6FF2A3192AE /* Pods-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-dummy.m"; sourceTree = ""; }; 37 | 95D546B51E581DE401E42F9905A6F36E /* Aspects.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = Aspects.h; sourceTree = ""; }; 38 | A9E9173F0DB62BD5B6231EAC37D3B0F3 /* Aspects-Private.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Aspects-Private.xcconfig"; sourceTree = ""; }; 39 | AE075DA55A6D791CB4D92584F640E569 /* libAspects.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAspects.a; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | B8B352A49CEEFCFFE9A49DC1A2513813 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 41 | BA6428E9F66FD5A23C0A2E06ED26CD2F /* Podfile */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 42 | BF59BC15D23E1E1912C8F334E7236813 /* Pods-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-acknowledgements.plist"; sourceTree = ""; }; 43 | E8F9F4AEA8BD0B7DA86C6B15EA5AF6BD /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Pods.debug.xcconfig; sourceTree = ""; }; 44 | FB47F105EC5A2FC48D9859829F4C192D /* Aspects.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Aspects.xcconfig; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 6E9BA070ED196FCB52EF93CD1D41C109 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 3B4168F33F6906D62CF479A4E7AC9053 /* Foundation.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | A590E3C189F1BFAD2CF0E144B030AEFB /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | C09211C2917CEADA360B48BBFA0C3DE0 /* Foundation.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 53F661C0CA7190D2CF05023FB33D61E4 /* iOS */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | B8B352A49CEEFCFFE9A49DC1A2513813 /* Foundation.framework */, 71 | ); 72 | name = iOS; 73 | sourceTree = ""; 74 | }; 75 | 7DB346D0F39D3F0E887471402A8071AB = { 76 | isa = PBXGroup; 77 | children = ( 78 | BA6428E9F66FD5A23C0A2E06ED26CD2F /* Podfile */, 79 | BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */, 80 | F44F05A8F527D3FE55781A7396EFE39E /* Pods */, 81 | CCA510CFBEA2D207524CDA0D73C3B561 /* Products */, 82 | D2411A5FE7F7A004607BED49990C37F4 /* Targets Support Files */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 952EEBFAF8F7E620423C9F156F25A506 /* Pods */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 15A529C27057E4A57D259CBC6E6CE49C /* Pods-acknowledgements.markdown */, 90 | BF59BC15D23E1E1912C8F334E7236813 /* Pods-acknowledgements.plist */, 91 | 820584B2529C1AC0C4FCE6FF2A3192AE /* Pods-dummy.m */, 92 | 641AE05DD55E5E6AC1590CD7B4A18F97 /* Pods-resources.sh */, 93 | E8F9F4AEA8BD0B7DA86C6B15EA5AF6BD /* Pods.debug.xcconfig */, 94 | 02BA764A94765EAD4B7C20AA4436EDC3 /* Pods.release.xcconfig */, 95 | ); 96 | name = Pods; 97 | path = "Target Support Files/Pods"; 98 | sourceTree = ""; 99 | }; 100 | AAF8345DD2F9ED27DB31CE726E9831AE /* Support Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | FB47F105EC5A2FC48D9859829F4C192D /* Aspects.xcconfig */, 104 | A9E9173F0DB62BD5B6231EAC37D3B0F3 /* Aspects-Private.xcconfig */, 105 | 556D358B1127E77A7AF8B7C9B9422091 /* Aspects-dummy.m */, 106 | 332BEE3AFFC449B6E2641E6BA5CC3CEF /* Aspects-prefix.pch */, 107 | ); 108 | name = "Support Files"; 109 | path = "../Target Support Files/Aspects"; 110 | sourceTree = ""; 111 | }; 112 | BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 53F661C0CA7190D2CF05023FB33D61E4 /* iOS */, 116 | ); 117 | name = Frameworks; 118 | sourceTree = ""; 119 | }; 120 | CCA510CFBEA2D207524CDA0D73C3B561 /* Products */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | AE075DA55A6D791CB4D92584F640E569 /* libAspects.a */, 124 | 6675C4C0A5147F1A2AC0E63FF71E14D1 /* libPods.a */, 125 | ); 126 | name = Products; 127 | sourceTree = ""; 128 | }; 129 | D2411A5FE7F7A004607BED49990C37F4 /* Targets Support Files */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 952EEBFAF8F7E620423C9F156F25A506 /* Pods */, 133 | ); 134 | name = "Targets Support Files"; 135 | sourceTree = ""; 136 | }; 137 | F44F05A8F527D3FE55781A7396EFE39E /* Pods */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | FAE2C80AB9ABC4DC15EFD3048CDC534B /* Aspects */, 141 | ); 142 | name = Pods; 143 | sourceTree = ""; 144 | }; 145 | FAE2C80AB9ABC4DC15EFD3048CDC534B /* Aspects */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 95D546B51E581DE401E42F9905A6F36E /* Aspects.h */, 149 | 77BE6D4EC2B31DCB5B9BE10FD52DE304 /* Aspects.m */, 150 | AAF8345DD2F9ED27DB31CE726E9831AE /* Support Files */, 151 | ); 152 | path = Aspects; 153 | sourceTree = ""; 154 | }; 155 | /* End PBXGroup section */ 156 | 157 | /* Begin PBXHeadersBuildPhase section */ 158 | 42658B36D2A9671D1AE88A071A4A4D03 /* Headers */ = { 159 | isa = PBXHeadersBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 335445D0B008598C71060E3913329C52 /* Aspects.h in Headers */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXHeadersBuildPhase section */ 167 | 168 | /* Begin PBXNativeTarget section */ 169 | 6EAC72D7913EE8A7E6A7E105BE1D31E8 /* Pods */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = 59C9F4EAE93BD40DA23328F7801F58AC /* Build configuration list for PBXNativeTarget "Pods" */; 172 | buildPhases = ( 173 | 67E7D8E5FAEFE6FEB1905BF20E3536C8 /* Sources */, 174 | A590E3C189F1BFAD2CF0E144B030AEFB /* Frameworks */, 175 | ); 176 | buildRules = ( 177 | ); 178 | dependencies = ( 179 | E9B4C7544E37F59BB492C43E128E1C6C /* PBXTargetDependency */, 180 | ); 181 | name = Pods; 182 | productName = Pods; 183 | productReference = 6675C4C0A5147F1A2AC0E63FF71E14D1 /* libPods.a */; 184 | productType = "com.apple.product-type.library.static"; 185 | }; 186 | E8F3060ABAD4F0616637250692D6232F /* Aspects */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = 2093D827DB0BD1C7822D59D761D002AD /* Build configuration list for PBXNativeTarget "Aspects" */; 189 | buildPhases = ( 190 | DB2FAC966B9D27D57C7ADA59CDF3B6F7 /* Sources */, 191 | 6E9BA070ED196FCB52EF93CD1D41C109 /* Frameworks */, 192 | 42658B36D2A9671D1AE88A071A4A4D03 /* Headers */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = Aspects; 199 | productName = Aspects; 200 | productReference = AE075DA55A6D791CB4D92584F640E569 /* libAspects.a */; 201 | productType = "com.apple.product-type.library.static"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastSwiftUpdateCheck = 0700; 210 | LastUpgradeCheck = 0700; 211 | }; 212 | buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; 213 | compatibilityVersion = "Xcode 3.2"; 214 | developmentRegion = English; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | ); 219 | mainGroup = 7DB346D0F39D3F0E887471402A8071AB; 220 | productRefGroup = CCA510CFBEA2D207524CDA0D73C3B561 /* Products */; 221 | projectDirPath = ""; 222 | projectRoot = ""; 223 | targets = ( 224 | E8F3060ABAD4F0616637250692D6232F /* Aspects */, 225 | 6EAC72D7913EE8A7E6A7E105BE1D31E8 /* Pods */, 226 | ); 227 | }; 228 | /* End PBXProject section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | 67E7D8E5FAEFE6FEB1905BF20E3536C8 /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | E9F1F9DAB6255FE47B35B183DCE660B6 /* Pods-dummy.m in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | DB2FAC966B9D27D57C7ADA59CDF3B6F7 /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | B627DCCF6888DFD5E9E9D4161B594BF0 /* Aspects-dummy.m in Sources */, 244 | A237F65E6F8BFD4E1A901AD2AC767D71 /* Aspects.m in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | /* End PBXSourcesBuildPhase section */ 249 | 250 | /* Begin PBXTargetDependency section */ 251 | E9B4C7544E37F59BB492C43E128E1C6C /* PBXTargetDependency */ = { 252 | isa = PBXTargetDependency; 253 | name = Aspects; 254 | target = E8F3060ABAD4F0616637250692D6232F /* Aspects */; 255 | targetProxy = 11CCC0738BA7FBE6088BCDCED5D1577D /* PBXContainerItemProxy */; 256 | }; 257 | /* End PBXTargetDependency section */ 258 | 259 | /* Begin XCBuildConfiguration section */ 260 | 0ACEFE6FFA92A9980D684D340CB00C09 /* Debug */ = { 261 | isa = XCBuildConfiguration; 262 | baseConfigurationReference = A9E9173F0DB62BD5B6231EAC37D3B0F3 /* Aspects-Private.xcconfig */; 263 | buildSettings = { 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | GCC_PREFIX_HEADER = "Target Support Files/Aspects/Aspects-prefix.pch"; 266 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 267 | MTL_ENABLE_DEBUG_INFO = YES; 268 | OTHER_LDFLAGS = ""; 269 | OTHER_LIBTOOLFLAGS = ""; 270 | PRODUCT_NAME = "$(TARGET_NAME)"; 271 | SDKROOT = iphoneos; 272 | SKIP_INSTALL = YES; 273 | }; 274 | name = Debug; 275 | }; 276 | 48F87BA423376546576D000525808B6B /* Release */ = { 277 | isa = XCBuildConfiguration; 278 | baseConfigurationReference = 02BA764A94765EAD4B7C20AA4436EDC3 /* Pods.release.xcconfig */; 279 | buildSettings = { 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 282 | MTL_ENABLE_DEBUG_INFO = NO; 283 | OTHER_LDFLAGS = ""; 284 | OTHER_LIBTOOLFLAGS = ""; 285 | PODS_ROOT = "$(SRCROOT)"; 286 | PRODUCT_NAME = "$(TARGET_NAME)"; 287 | SDKROOT = iphoneos; 288 | SKIP_INSTALL = YES; 289 | }; 290 | name = Release; 291 | }; 292 | 5CE5176205D06FF3FFE3DDDA9291E44B /* Debug */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ALWAYS_SEARCH_USER_PATHS = NO; 296 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 297 | CLANG_CXX_LIBRARY = "libc++"; 298 | CLANG_ENABLE_MODULES = YES; 299 | CLANG_ENABLE_OBJC_ARC = YES; 300 | CLANG_WARN_BOOL_CONVERSION = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; 303 | CLANG_WARN_EMPTY_BODY = YES; 304 | CLANG_WARN_ENUM_CONVERSION = YES; 305 | CLANG_WARN_INT_CONVERSION = YES; 306 | CLANG_WARN_OBJC_ROOT_CLASS = YES; 307 | CLANG_WARN_UNREACHABLE_CODE = YES; 308 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 309 | COPY_PHASE_STRIP = NO; 310 | GCC_C_LANGUAGE_STANDARD = gnu99; 311 | GCC_DYNAMIC_NO_PIC = NO; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 325 | ONLY_ACTIVE_ARCH = YES; 326 | STRIP_INSTALLED_PRODUCT = NO; 327 | SYMROOT = "${SRCROOT}/../build"; 328 | }; 329 | name = Debug; 330 | }; 331 | 6191DC981AB6393BC0F95C5335866E71 /* Release */ = { 332 | isa = XCBuildConfiguration; 333 | baseConfigurationReference = A9E9173F0DB62BD5B6231EAC37D3B0F3 /* Aspects-Private.xcconfig */; 334 | buildSettings = { 335 | ENABLE_STRICT_OBJC_MSGSEND = YES; 336 | GCC_PREFIX_HEADER = "Target Support Files/Aspects/Aspects-prefix.pch"; 337 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 338 | MTL_ENABLE_DEBUG_INFO = NO; 339 | OTHER_LDFLAGS = ""; 340 | OTHER_LIBTOOLFLAGS = ""; 341 | PRODUCT_NAME = "$(TARGET_NAME)"; 342 | SDKROOT = iphoneos; 343 | SKIP_INSTALL = YES; 344 | }; 345 | name = Release; 346 | }; 347 | 627A0B4FAD3611E4FE733A1F420506C2 /* Debug */ = { 348 | isa = XCBuildConfiguration; 349 | baseConfigurationReference = E8F9F4AEA8BD0B7DA86C6B15EA5AF6BD /* Pods.debug.xcconfig */; 350 | buildSettings = { 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 353 | MTL_ENABLE_DEBUG_INFO = YES; 354 | OTHER_LDFLAGS = ""; 355 | OTHER_LIBTOOLFLAGS = ""; 356 | PODS_ROOT = "$(SRCROOT)"; 357 | PRODUCT_NAME = "$(TARGET_NAME)"; 358 | SDKROOT = iphoneos; 359 | SKIP_INSTALL = YES; 360 | }; 361 | name = Debug; 362 | }; 363 | 74857149DC1E0D599B8A01A78349A926 /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 368 | CLANG_CXX_LIBRARY = "libc++"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_WARN_BOOL_CONVERSION = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES; 374 | CLANG_WARN_EMPTY_BODY = YES; 375 | CLANG_WARN_ENUM_CONVERSION = YES; 376 | CLANG_WARN_INT_CONVERSION = YES; 377 | CLANG_WARN_OBJC_ROOT_CLASS = YES; 378 | CLANG_WARN_UNREACHABLE_CODE = YES; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | COPY_PHASE_STRIP = YES; 381 | ENABLE_NS_ASSERTIONS = NO; 382 | GCC_C_LANGUAGE_STANDARD = gnu99; 383 | GCC_PREPROCESSOR_DEFINITIONS = "RELEASE=1"; 384 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 385 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 386 | GCC_WARN_UNDECLARED_SELECTOR = YES; 387 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 388 | GCC_WARN_UNUSED_FUNCTION = YES; 389 | GCC_WARN_UNUSED_VARIABLE = YES; 390 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 391 | STRIP_INSTALLED_PRODUCT = NO; 392 | SYMROOT = "${SRCROOT}/../build"; 393 | VALIDATE_PRODUCT = YES; 394 | }; 395 | name = Release; 396 | }; 397 | /* End XCBuildConfiguration section */ 398 | 399 | /* Begin XCConfigurationList section */ 400 | 2093D827DB0BD1C7822D59D761D002AD /* Build configuration list for PBXNativeTarget "Aspects" */ = { 401 | isa = XCConfigurationList; 402 | buildConfigurations = ( 403 | 0ACEFE6FFA92A9980D684D340CB00C09 /* Debug */, 404 | 6191DC981AB6393BC0F95C5335866E71 /* Release */, 405 | ); 406 | defaultConfigurationIsVisible = 0; 407 | defaultConfigurationName = Release; 408 | }; 409 | 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { 410 | isa = XCConfigurationList; 411 | buildConfigurations = ( 412 | 5CE5176205D06FF3FFE3DDDA9291E44B /* Debug */, 413 | 74857149DC1E0D599B8A01A78349A926 /* Release */, 414 | ); 415 | defaultConfigurationIsVisible = 0; 416 | defaultConfigurationName = Release; 417 | }; 418 | 59C9F4EAE93BD40DA23328F7801F58AC /* Build configuration list for PBXNativeTarget "Pods" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | 627A0B4FAD3611E4FE733A1F420506C2 /* Debug */, 422 | 48F87BA423376546576D000525808B6B /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | /* End XCConfigurationList section */ 428 | }; 429 | rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; 430 | } 431 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/ViewControllerPreview.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A8A70021B9526D8004F4789 /* MasterViewController+UIViewControllerPreviewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8A70011B9526D8004F4789 /* MasterViewController+UIViewControllerPreviewing.swift */; settings = {ASSET_TAGS = (); }; }; 11 | 385418381BB8A9F400757E98 /* TapticEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 385418371BB8A9F400757E98 /* TapticEngine.m */; settings = {ASSET_TAGS = (); }; }; 12 | 38D67EA91BB8AF6A000CFC2D /* UIDevice+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 38D67EA81BB8AF6A000CFC2D /* UIDevice+Private.m */; settings = {ASSET_TAGS = (); }; }; 13 | 4FB6B1A0C93A465494750B03 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 39533B101DDEE88B4A233426 /* libPods.a */; }; 14 | E0C18F5D1B8CFBEF00D2C512 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0C18F5C1B8CFBEF00D2C512 /* AppDelegate.swift */; }; 15 | E0C18F5F1B8CFBEF00D2C512 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0C18F5E1B8CFBEF00D2C512 /* MasterViewController.swift */; }; 16 | E0C18F611B8CFBEF00D2C512 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0C18F601B8CFBEF00D2C512 /* DetailViewController.swift */; }; 17 | E0C18F641B8CFBEF00D2C512 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0C18F621B8CFBEF00D2C512 /* Main.storyboard */; }; 18 | E0C18F661B8CFBEF00D2C512 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E0C18F651B8CFBEF00D2C512 /* Assets.xcassets */; }; 19 | E0C18F691B8CFBEF00D2C512 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0C18F671B8CFBEF00D2C512 /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 05B1D0682C685C777FE6612B /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 24 | 0A8A70011B9526D8004F4789 /* MasterViewController+UIViewControllerPreviewing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MasterViewController+UIViewControllerPreviewing.swift"; sourceTree = ""; }; 25 | 16EB40041BA1FF1D001544BE /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 26 | 385418311BB8A90F00757E98 /* UITapticEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UITapticEngine.h; sourceTree = ""; }; 27 | 385418321BB8A92600757E98 /* UIDevice+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIDevice+Private.h"; sourceTree = ""; }; 28 | 385418351BB8A9F300757E98 /* ViewControllerPreview-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ViewControllerPreview-Bridging-Header.h"; sourceTree = ""; }; 29 | 385418361BB8A9F400757E98 /* TapticEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TapticEngine.h; sourceTree = ""; }; 30 | 385418371BB8A9F400757E98 /* TapticEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TapticEngine.m; sourceTree = ""; }; 31 | 38D67EA81BB8AF6A000CFC2D /* UIDevice+Private.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+Private.m"; sourceTree = ""; }; 32 | 39533B101DDEE88B4A233426 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | AC321300F863FAF7FE099CF0 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 34 | E0C18F591B8CFBEF00D2C512 /* ViewControllerPreview.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ViewControllerPreview.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | E0C18F5C1B8CFBEF00D2C512 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | E0C18F5E1B8CFBEF00D2C512 /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 37 | E0C18F601B8CFBEF00D2C512 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 38 | E0C18F631B8CFBEF00D2C512 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | E0C18F651B8CFBEF00D2C512 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | E0C18F681B8CFBEF00D2C512 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | E0C18F6A1B8CFBEF00D2C512 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | E0C18F561B8CFBEF00D2C512 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 4FB6B1A0C93A465494750B03 /* libPods.a in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 0A8A70031B952849004F4789 /* Supporting Files */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | E0C18F671B8CFBEF00D2C512 /* LaunchScreen.storyboard */, 60 | E0C18F651B8CFBEF00D2C512 /* Assets.xcassets */, 61 | E0C18F6A1B8CFBEF00D2C512 /* Info.plist */, 62 | ); 63 | name = "Supporting Files"; 64 | sourceTree = ""; 65 | }; 66 | 2FB8DDEA447EEC16C3B0A182 /* Frameworks */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 39533B101DDEE88B4A233426 /* libPods.a */, 70 | ); 71 | name = Frameworks; 72 | sourceTree = ""; 73 | }; 74 | 385418301BB8A8F700757E98 /* Headers */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 385418311BB8A90F00757E98 /* UITapticEngine.h */, 78 | 385418321BB8A92600757E98 /* UIDevice+Private.h */, 79 | 38D67EA81BB8AF6A000CFC2D /* UIDevice+Private.m */, 80 | ); 81 | path = Headers; 82 | sourceTree = ""; 83 | }; 84 | 385418341BB8A9BB00757E98 /* Taptic */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 385418361BB8A9F400757E98 /* TapticEngine.h */, 88 | 385418371BB8A9F400757E98 /* TapticEngine.m */, 89 | 385418351BB8A9F300757E98 /* ViewControllerPreview-Bridging-Header.h */, 90 | ); 91 | path = Taptic; 92 | sourceTree = ""; 93 | }; 94 | C2F844D2381A6925019B25DB /* Pods */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 05B1D0682C685C777FE6612B /* Pods.debug.xcconfig */, 98 | AC321300F863FAF7FE099CF0 /* Pods.release.xcconfig */, 99 | ); 100 | name = Pods; 101 | sourceTree = ""; 102 | }; 103 | E0C18F501B8CFBEF00D2C512 = { 104 | isa = PBXGroup; 105 | children = ( 106 | 16EB40041BA1FF1D001544BE /* README.md */, 107 | 385418301BB8A8F700757E98 /* Headers */, 108 | E0C18F5B1B8CFBEF00D2C512 /* ViewControllerPreview */, 109 | 385418341BB8A9BB00757E98 /* Taptic */, 110 | E0C18F5A1B8CFBEF00D2C512 /* Products */, 111 | C2F844D2381A6925019B25DB /* Pods */, 112 | 2FB8DDEA447EEC16C3B0A182 /* Frameworks */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | E0C18F5A1B8CFBEF00D2C512 /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | E0C18F591B8CFBEF00D2C512 /* ViewControllerPreview.app */, 120 | ); 121 | name = Products; 122 | sourceTree = ""; 123 | }; 124 | E0C18F5B1B8CFBEF00D2C512 /* ViewControllerPreview */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | E0C18F5C1B8CFBEF00D2C512 /* AppDelegate.swift */, 128 | E0C18F5E1B8CFBEF00D2C512 /* MasterViewController.swift */, 129 | 0A8A70011B9526D8004F4789 /* MasterViewController+UIViewControllerPreviewing.swift */, 130 | E0C18F601B8CFBEF00D2C512 /* DetailViewController.swift */, 131 | E0C18F621B8CFBEF00D2C512 /* Main.storyboard */, 132 | 0A8A70031B952849004F4789 /* Supporting Files */, 133 | ); 134 | path = ViewControllerPreview; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | E0C18F581B8CFBEF00D2C512 /* ViewControllerPreview */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = E0C18F831B8CFBEF00D2C512 /* Build configuration list for PBXNativeTarget "ViewControllerPreview" */; 143 | buildPhases = ( 144 | C685654280B7B6334AA9ABC5 /* Check Pods Manifest.lock */, 145 | E0C18F551B8CFBEF00D2C512 /* Sources */, 146 | E0C18F561B8CFBEF00D2C512 /* Frameworks */, 147 | E0C18F571B8CFBEF00D2C512 /* Resources */, 148 | 494E415E440EA0F3C0CFC9EC /* Copy Pods Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | ); 154 | name = ViewControllerPreview; 155 | productName = ViewControllerPreview; 156 | productReference = E0C18F591B8CFBEF00D2C512 /* ViewControllerPreview.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | E0C18F511B8CFBEF00D2C512 /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | LastSwiftUpdateCheck = 0700; 166 | LastUpgradeCheck = 0700; 167 | ORGANIZATIONNAME = "Apple Inc."; 168 | TargetAttributes = { 169 | E0C18F581B8CFBEF00D2C512 = { 170 | CreatedOnToolsVersion = 7.0; 171 | }; 172 | }; 173 | }; 174 | buildConfigurationList = E0C18F541B8CFBEF00D2C512 /* Build configuration list for PBXProject "ViewControllerPreview" */; 175 | compatibilityVersion = "Xcode 3.2"; 176 | developmentRegion = English; 177 | hasScannedForEncodings = 0; 178 | knownRegions = ( 179 | en, 180 | Base, 181 | ); 182 | mainGroup = E0C18F501B8CFBEF00D2C512; 183 | productRefGroup = E0C18F5A1B8CFBEF00D2C512 /* Products */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | E0C18F581B8CFBEF00D2C512 /* ViewControllerPreview */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | E0C18F571B8CFBEF00D2C512 /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | E0C18F691B8CFBEF00D2C512 /* LaunchScreen.storyboard in Resources */, 198 | E0C18F661B8CFBEF00D2C512 /* Assets.xcassets in Resources */, 199 | E0C18F641B8CFBEF00D2C512 /* Main.storyboard in Resources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXShellScriptBuildPhase section */ 206 | 494E415E440EA0F3C0CFC9EC /* Copy Pods Resources */ = { 207 | isa = PBXShellScriptBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | inputPaths = ( 212 | ); 213 | name = "Copy Pods Resources"; 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; 219 | showEnvVarsInLog = 0; 220 | }; 221 | C685654280B7B6334AA9ABC5 /* Check Pods Manifest.lock */ = { 222 | isa = PBXShellScriptBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | inputPaths = ( 227 | ); 228 | name = "Check Pods Manifest.lock"; 229 | outputPaths = ( 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | shellPath = /bin/sh; 233 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 234 | showEnvVarsInLog = 0; 235 | }; 236 | /* End PBXShellScriptBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | E0C18F551B8CFBEF00D2C512 /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 38D67EA91BB8AF6A000CFC2D /* UIDevice+Private.m in Sources */, 244 | E0C18F611B8CFBEF00D2C512 /* DetailViewController.swift in Sources */, 245 | 0A8A70021B9526D8004F4789 /* MasterViewController+UIViewControllerPreviewing.swift in Sources */, 246 | E0C18F5F1B8CFBEF00D2C512 /* MasterViewController.swift in Sources */, 247 | 385418381BB8A9F400757E98 /* TapticEngine.m in Sources */, 248 | E0C18F5D1B8CFBEF00D2C512 /* AppDelegate.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXVariantGroup section */ 255 | E0C18F621B8CFBEF00D2C512 /* Main.storyboard */ = { 256 | isa = PBXVariantGroup; 257 | children = ( 258 | E0C18F631B8CFBEF00D2C512 /* Base */, 259 | ); 260 | name = Main.storyboard; 261 | sourceTree = ""; 262 | }; 263 | E0C18F671B8CFBEF00D2C512 /* LaunchScreen.storyboard */ = { 264 | isa = PBXVariantGroup; 265 | children = ( 266 | E0C18F681B8CFBEF00D2C512 /* Base */, 267 | ); 268 | name = LaunchScreen.storyboard; 269 | sourceTree = ""; 270 | }; 271 | /* End PBXVariantGroup section */ 272 | 273 | /* Begin XCBuildConfiguration section */ 274 | E0C18F811B8CFBEF00D2C512 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 279 | CLANG_CXX_LIBRARY = "libc++"; 280 | CLANG_ENABLE_MODULES = YES; 281 | CLANG_ENABLE_OBJC_ARC = YES; 282 | CLANG_WARN_BOOL_CONVERSION = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INT_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_UNREACHABLE_CODE = YES; 290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 291 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 292 | COPY_PHASE_STRIP = NO; 293 | DEBUG_INFORMATION_FORMAT = dwarf; 294 | ENABLE_STRICT_OBJC_MSGSEND = YES; 295 | ENABLE_TESTABILITY = YES; 296 | GCC_C_LANGUAGE_STANDARD = gnu99; 297 | GCC_DYNAMIC_NO_PIC = NO; 298 | GCC_NO_COMMON_BLOCKS = YES; 299 | GCC_OPTIMIZATION_LEVEL = 0; 300 | GCC_PREPROCESSOR_DEFINITIONS = ( 301 | "DEBUG=1", 302 | "$(inherited)", 303 | ); 304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 306 | GCC_WARN_UNDECLARED_SELECTOR = YES; 307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 308 | GCC_WARN_UNUSED_FUNCTION = YES; 309 | GCC_WARN_UNUSED_VARIABLE = YES; 310 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 311 | MTL_ENABLE_DEBUG_INFO = YES; 312 | ONLY_ACTIVE_ARCH = YES; 313 | SDKROOT = iphoneos; 314 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 315 | }; 316 | name = Debug; 317 | }; 318 | E0C18F821B8CFBEF00D2C512 /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 323 | CLANG_CXX_LIBRARY = "libc++"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_CONSTANT_CONVERSION = YES; 328 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 329 | CLANG_WARN_EMPTY_BODY = YES; 330 | CLANG_WARN_ENUM_CONVERSION = YES; 331 | CLANG_WARN_INT_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | GCC_C_LANGUAGE_STANDARD = gnu99; 341 | GCC_NO_COMMON_BLOCKS = YES; 342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 344 | GCC_WARN_UNDECLARED_SELECTOR = YES; 345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 346 | GCC_WARN_UNUSED_FUNCTION = YES; 347 | GCC_WARN_UNUSED_VARIABLE = YES; 348 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 349 | MTL_ENABLE_DEBUG_INFO = NO; 350 | SDKROOT = iphoneos; 351 | VALIDATE_PRODUCT = YES; 352 | }; 353 | name = Release; 354 | }; 355 | E0C18F841B8CFBEF00D2C512 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | baseConfigurationReference = 05B1D0682C685C777FE6612B /* Pods.debug.xcconfig */; 358 | buildSettings = { 359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 360 | CLANG_ENABLE_MODULES = YES; 361 | CODE_SIGN_IDENTITY = "iPhone Developer"; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 363 | INFOPLIST_FILE = ViewControllerPreview/Info.plist; 364 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 365 | PRODUCT_BUNDLE_IDENTIFIER = com.apple.samplecode.ViewControllerPreview; 366 | PRODUCT_NAME = "$(TARGET_NAME)"; 367 | PROVISIONING_PROFILE = ""; 368 | SWIFT_OBJC_BRIDGING_HEADER = "Taptic/ViewControllerPreview-Bridging-Header.h"; 369 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 370 | }; 371 | name = Debug; 372 | }; 373 | E0C18F851B8CFBEF00D2C512 /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | baseConfigurationReference = AC321300F863FAF7FE099CF0 /* Pods.release.xcconfig */; 376 | buildSettings = { 377 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 378 | CLANG_ENABLE_MODULES = YES; 379 | CODE_SIGN_IDENTITY = "iPhone Developer"; 380 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 381 | INFOPLIST_FILE = ViewControllerPreview/Info.plist; 382 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 383 | PRODUCT_BUNDLE_IDENTIFIER = com.apple.samplecode.ViewControllerPreview; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | PROVISIONING_PROFILE = ""; 386 | SWIFT_OBJC_BRIDGING_HEADER = "Taptic/ViewControllerPreview-Bridging-Header.h"; 387 | }; 388 | name = Release; 389 | }; 390 | /* End XCBuildConfiguration section */ 391 | 392 | /* Begin XCConfigurationList section */ 393 | E0C18F541B8CFBEF00D2C512 /* Build configuration list for PBXProject "ViewControllerPreview" */ = { 394 | isa = XCConfigurationList; 395 | buildConfigurations = ( 396 | E0C18F811B8CFBEF00D2C512 /* Debug */, 397 | E0C18F821B8CFBEF00D2C512 /* Release */, 398 | ); 399 | defaultConfigurationIsVisible = 0; 400 | defaultConfigurationName = Release; 401 | }; 402 | E0C18F831B8CFBEF00D2C512 /* Build configuration list for PBXNativeTarget "ViewControllerPreview" */ = { 403 | isa = XCConfigurationList; 404 | buildConfigurations = ( 405 | E0C18F841B8CFBEF00D2C512 /* Debug */, 406 | E0C18F851B8CFBEF00D2C512 /* Release */, 407 | ); 408 | defaultConfigurationIsVisible = 0; 409 | defaultConfigurationName = Release; 410 | }; 411 | /* End XCConfigurationList section */ 412 | }; 413 | rootObject = E0C18F511B8CFBEF00D2C512 /* Project object */; 414 | } 415 | -------------------------------------------------------------------------------- /ViewControllerPreviewsUsingtheUIViewControllerpreviewingAPIs/Pods/Aspects/Aspects.m: -------------------------------------------------------------------------------- 1 | // 2 | // Aspects.m 3 | // Aspects - A delightful, simple library for aspect oriented programming. 4 | // 5 | // Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license. 6 | // 7 | 8 | #import "Aspects.h" 9 | #import 10 | #import 11 | #import 12 | 13 | #define AspectLog(...) 14 | //#define AspectLog(...) do { NSLog(__VA_ARGS__); }while(0) 15 | #define AspectLogError(...) do { NSLog(__VA_ARGS__); }while(0) 16 | 17 | // Block internals. 18 | typedef NS_OPTIONS(int, AspectBlockFlags) { 19 | AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25), 20 | AspectBlockFlagsHasSignature = (1 << 30) 21 | }; 22 | typedef struct _AspectBlock { 23 | __unused Class isa; 24 | AspectBlockFlags flags; 25 | __unused int reserved; 26 | void (__unused *invoke)(struct _AspectBlock *block, ...); 27 | struct { 28 | unsigned long int reserved; 29 | unsigned long int size; 30 | // requires AspectBlockFlagsHasCopyDisposeHelpers 31 | void (*copy)(void *dst, const void *src); 32 | void (*dispose)(const void *); 33 | // requires AspectBlockFlagsHasSignature 34 | const char *signature; 35 | const char *layout; 36 | } *descriptor; 37 | // imported variables 38 | } *AspectBlockRef; 39 | 40 | @interface AspectInfo : NSObject 41 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation; 42 | @property (nonatomic, unsafe_unretained, readonly) id instance; 43 | @property (nonatomic, strong, readonly) NSArray *arguments; 44 | @property (nonatomic, strong, readonly) NSInvocation *originalInvocation; 45 | @end 46 | 47 | // Tracks a single aspect. 48 | @interface AspectIdentifier : NSObject 49 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error; 50 | - (BOOL)invokeWithInfo:(id)info; 51 | @property (nonatomic, assign) SEL selector; 52 | @property (nonatomic, strong) id block; 53 | @property (nonatomic, strong) NSMethodSignature *blockSignature; 54 | @property (nonatomic, weak) id object; 55 | @property (nonatomic, assign) AspectOptions options; 56 | @end 57 | 58 | // Tracks all aspects for an object/class. 59 | @interface AspectsContainer : NSObject 60 | - (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition; 61 | - (BOOL)removeAspect:(id)aspect; 62 | - (BOOL)hasAspects; 63 | @property (atomic, copy) NSArray *beforeAspects; 64 | @property (atomic, copy) NSArray *insteadAspects; 65 | @property (atomic, copy) NSArray *afterAspects; 66 | @end 67 | 68 | @interface AspectTracker : NSObject 69 | - (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent; 70 | @property (nonatomic, strong) Class trackedClass; 71 | @property (nonatomic, strong) NSMutableSet *selectorNames; 72 | @property (nonatomic, weak) AspectTracker *parentEntry; 73 | @end 74 | 75 | @interface NSInvocation (Aspects) 76 | - (NSArray *)aspects_arguments; 77 | @end 78 | 79 | #define AspectPositionFilter 0x07 80 | 81 | #define AspectError(errorCode, errorDescription) do { \ 82 | AspectLogError(@"Aspects: %@", errorDescription); \ 83 | if (error) { *error = [NSError errorWithDomain:AspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0) 84 | 85 | NSString *const AspectErrorDomain = @"AspectErrorDomain"; 86 | static NSString *const AspectsSubclassSuffix = @"_Aspects_"; 87 | static NSString *const AspectsMessagePrefix = @"aspects_"; 88 | 89 | @implementation NSObject (Aspects) 90 | 91 | /////////////////////////////////////////////////////////////////////////////////////////// 92 | #pragma mark - Public Aspects API 93 | 94 | + (id)aspect_hookSelector:(SEL)selector 95 | withOptions:(AspectOptions)options 96 | usingBlock:(id)block 97 | error:(NSError **)error { 98 | return aspect_add((id)self, selector, options, block, error); 99 | } 100 | 101 | /// @return A token which allows to later deregister the aspect. 102 | - (id)aspect_hookSelector:(SEL)selector 103 | withOptions:(AspectOptions)options 104 | usingBlock:(id)block 105 | error:(NSError **)error { 106 | return aspect_add(self, selector, options, block, error); 107 | } 108 | 109 | /////////////////////////////////////////////////////////////////////////////////////////// 110 | #pragma mark - Private Helper 111 | 112 | static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) { 113 | NSCParameterAssert(self); 114 | NSCParameterAssert(selector); 115 | NSCParameterAssert(block); 116 | 117 | __block AspectIdentifier *identifier = nil; 118 | aspect_performLocked(^{ 119 | if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) { 120 | AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector); 121 | identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error]; 122 | if (identifier) { 123 | [aspectContainer addAspect:identifier withOptions:options]; 124 | 125 | // Modify the class to allow message interception. 126 | aspect_prepareClassAndHookSelector(self, selector, error); 127 | } 128 | } 129 | }); 130 | return identifier; 131 | } 132 | 133 | static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) { 134 | NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type."); 135 | 136 | __block BOOL success = NO; 137 | aspect_performLocked(^{ 138 | id self = aspect.object; // strongify 139 | if (self) { 140 | AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector); 141 | success = [aspectContainer removeAspect:aspect]; 142 | 143 | aspect_cleanupHookedClassAndSelector(self, aspect.selector); 144 | // destroy token 145 | aspect.object = nil; 146 | aspect.block = nil; 147 | aspect.selector = NULL; 148 | }else { 149 | NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect]; 150 | AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc); 151 | } 152 | }); 153 | return success; 154 | } 155 | 156 | static void aspect_performLocked(dispatch_block_t block) { 157 | static OSSpinLock aspect_lock = OS_SPINLOCK_INIT; 158 | OSSpinLockLock(&aspect_lock); 159 | block(); 160 | OSSpinLockUnlock(&aspect_lock); 161 | } 162 | 163 | static SEL aspect_aliasForSelector(SEL selector) { 164 | NSCParameterAssert(selector); 165 | return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]); 166 | } 167 | 168 | static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) { 169 | AspectBlockRef layout = (__bridge void *)block; 170 | if (!(layout->flags & AspectBlockFlagsHasSignature)) { 171 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block]; 172 | AspectError(AspectErrorMissingBlockSignature, description); 173 | return nil; 174 | } 175 | void *desc = layout->descriptor; 176 | desc += 2 * sizeof(unsigned long int); 177 | if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) { 178 | desc += 2 * sizeof(void *); 179 | } 180 | if (!desc) { 181 | NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block]; 182 | AspectError(AspectErrorMissingBlockSignature, description); 183 | return nil; 184 | } 185 | const char *signature = (*(const char **)desc); 186 | return [NSMethodSignature signatureWithObjCTypes:signature]; 187 | } 188 | 189 | static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) { 190 | NSCParameterAssert(blockSignature); 191 | NSCParameterAssert(object); 192 | NSCParameterAssert(selector); 193 | 194 | BOOL signaturesMatch = YES; 195 | NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector]; 196 | if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) { 197 | signaturesMatch = NO; 198 | }else { 199 | if (blockSignature.numberOfArguments > 1) { 200 | const char *blockType = [blockSignature getArgumentTypeAtIndex:1]; 201 | if (blockType[0] != '@') { 202 | NSLog(@"asds"); 203 | signaturesMatch = NO; 204 | } 205 | } 206 | // Argument 0 is self/block, argument 1 is SEL or id. We start comparing at argument 2. 207 | // The block can have less arguments than the method, that's ok. 208 | if (signaturesMatch) { 209 | for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) { 210 | const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; 211 | const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; 212 | // Only compare parameter, not the optional type data. 213 | if (!methodType || !blockType || methodType[0] != blockType[0]) { 214 | NSLog(@"Method Type: %s, Block type: %s", methodType, blockType); 215 | 216 | signaturesMatch = NO; break; 217 | } 218 | } 219 | } 220 | } 221 | 222 | if (!signaturesMatch) { 223 | NSString *description = [NSString stringWithFormat:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature]; 224 | AspectError(AspectErrorIncompatibleBlockSignature, description); 225 | return NO; 226 | } 227 | return YES; 228 | } 229 | 230 | /////////////////////////////////////////////////////////////////////////////////////////// 231 | #pragma mark - Class + Selector Preparation 232 | 233 | static BOOL aspect_isMsgForwardIMP(IMP impl) { 234 | return impl == _objc_msgForward 235 | #if !defined(__arm64__) 236 | || impl == (IMP)_objc_msgForward_stret 237 | #endif 238 | ; 239 | } 240 | 241 | static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) { 242 | IMP msgForwardIMP = _objc_msgForward; 243 | #if !defined(__arm64__) 244 | // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id. 245 | // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html 246 | // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783 247 | // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4) 248 | Method method = class_getInstanceMethod(self.class, selector); 249 | const char *encoding = method_getTypeEncoding(method); 250 | BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B; 251 | if (methodReturnsStructValue) { 252 | @try { 253 | NSUInteger valueSize = 0; 254 | NSGetSizeAndAlignment(encoding, &valueSize, NULL); 255 | 256 | if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) { 257 | methodReturnsStructValue = NO; 258 | } 259 | } @catch (NSException *e) {} 260 | } 261 | if (methodReturnsStructValue) { 262 | msgForwardIMP = (IMP)_objc_msgForward_stret; 263 | } 264 | #endif 265 | return msgForwardIMP; 266 | } 267 | 268 | static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { 269 | NSCParameterAssert(selector); 270 | Class klass = aspect_hookClass(self, error); 271 | Method targetMethod = class_getInstanceMethod(klass, selector); 272 | IMP targetMethodIMP = method_getImplementation(targetMethod); 273 | if (!aspect_isMsgForwardIMP(targetMethodIMP)) { 274 | // Make a method alias for the existing method implementation, it not already copied. 275 | const char *typeEncoding = method_getTypeEncoding(targetMethod); 276 | SEL aliasSelector = aspect_aliasForSelector(selector); 277 | if (![klass instancesRespondToSelector:aliasSelector]) { 278 | __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding); 279 | NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); 280 | } 281 | 282 | // We use forwardInvocation to hook in. 283 | class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); 284 | AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); 285 | } 286 | } 287 | 288 | // Will undo the runtime changes made. 289 | static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) { 290 | NSCParameterAssert(self); 291 | NSCParameterAssert(selector); 292 | 293 | Class klass = object_getClass(self); 294 | BOOL isMetaClass = class_isMetaClass(klass); 295 | if (isMetaClass) { 296 | klass = (Class)self; 297 | } 298 | 299 | // Check if the method is marked as forwarded and undo that. 300 | Method targetMethod = class_getInstanceMethod(klass, selector); 301 | IMP targetMethodIMP = method_getImplementation(targetMethod); 302 | if (aspect_isMsgForwardIMP(targetMethodIMP)) { 303 | // Restore the original method implementation. 304 | const char *typeEncoding = method_getTypeEncoding(targetMethod); 305 | SEL aliasSelector = aspect_aliasForSelector(selector); 306 | Method originalMethod = class_getInstanceMethod(klass, aliasSelector); 307 | IMP originalIMP = method_getImplementation(originalMethod); 308 | NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); 309 | 310 | class_replaceMethod(klass, selector, originalIMP, typeEncoding); 311 | AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); 312 | } 313 | 314 | // Deregister global tracked selector 315 | aspect_deregisterTrackedSelector(self, selector); 316 | 317 | // Get the aspect container and check if there are any hooks remaining. Clean up if there are not. 318 | AspectsContainer *container = aspect_getContainerForObject(self, selector); 319 | if (!container.hasAspects) { 320 | // Destroy the container 321 | aspect_destroyContainerForObject(self, selector); 322 | 323 | // Figure out how the class was modified to undo the changes. 324 | NSString *className = NSStringFromClass(klass); 325 | if ([className hasSuffix:AspectsSubclassSuffix]) { 326 | Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]); 327 | NSCAssert(originalClass != nil, @"Original class must exist"); 328 | object_setClass(self, originalClass); 329 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass)); 330 | 331 | // We can only dispose the class pair if we can ensure that no instances exist using our subclass. 332 | // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around. 333 | //objc_disposeClassPair(object.class); 334 | }else { 335 | // Class is most likely swizzled in place. Undo that. 336 | if (isMetaClass) { 337 | aspect_undoSwizzleClassInPlace((Class)self); 338 | } 339 | } 340 | } 341 | } 342 | 343 | /////////////////////////////////////////////////////////////////////////////////////////// 344 | #pragma mark - Hook Class 345 | 346 | static Class aspect_hookClass(NSObject *self, NSError **error) { 347 | NSCParameterAssert(self); 348 | Class statedClass = self.class; 349 | Class baseClass = object_getClass(self); 350 | NSString *className = NSStringFromClass(baseClass); 351 | 352 | // Already subclassed 353 | if ([className hasSuffix:AspectsSubclassSuffix]) { 354 | return baseClass; 355 | 356 | // We swizzle a class object, not a single object. 357 | }else if (class_isMetaClass(baseClass)) { 358 | return aspect_swizzleClassInPlace((Class)self); 359 | // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place. 360 | }else if (statedClass != baseClass) { 361 | return aspect_swizzleClassInPlace(baseClass); 362 | } 363 | 364 | // Default case. Create dynamic subclass. 365 | const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String; 366 | Class subclass = objc_getClass(subclassName); 367 | 368 | if (subclass == nil) { 369 | subclass = objc_allocateClassPair(baseClass, subclassName, 0); 370 | if (subclass == nil) { 371 | NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName]; 372 | AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc); 373 | return nil; 374 | } 375 | 376 | aspect_swizzleForwardInvocation(subclass); 377 | aspect_hookedGetClass(subclass, statedClass); 378 | aspect_hookedGetClass(object_getClass(subclass), statedClass); 379 | objc_registerClassPair(subclass); 380 | } 381 | 382 | object_setClass(self, subclass); 383 | return subclass; 384 | } 385 | 386 | static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:"; 387 | static void aspect_swizzleForwardInvocation(Class klass) { 388 | NSCParameterAssert(klass); 389 | // If there is no method, replace will act like class_addMethod. 390 | IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@"); 391 | if (originalImplementation) { 392 | class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); 393 | } 394 | AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass)); 395 | } 396 | 397 | static void aspect_undoSwizzleForwardInvocation(Class klass) { 398 | NSCParameterAssert(klass); 399 | Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName)); 400 | Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:)); 401 | // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy. 402 | IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod); 403 | class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@"); 404 | 405 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass)); 406 | } 407 | 408 | static void aspect_hookedGetClass(Class class, Class statedClass) { 409 | NSCParameterAssert(class); 410 | NSCParameterAssert(statedClass); 411 | Method method = class_getInstanceMethod(class, @selector(class)); 412 | IMP newIMP = imp_implementationWithBlock(^(id self) { 413 | return statedClass; 414 | }); 415 | class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method)); 416 | } 417 | 418 | /////////////////////////////////////////////////////////////////////////////////////////// 419 | #pragma mark - Swizzle Class In Place 420 | 421 | static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) { 422 | static NSMutableSet *swizzledClasses; 423 | static dispatch_once_t pred; 424 | dispatch_once(&pred, ^{ 425 | swizzledClasses = [NSMutableSet new]; 426 | }); 427 | @synchronized(swizzledClasses) { 428 | block(swizzledClasses); 429 | } 430 | } 431 | 432 | static Class aspect_swizzleClassInPlace(Class klass) { 433 | NSCParameterAssert(klass); 434 | NSString *className = NSStringFromClass(klass); 435 | 436 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { 437 | if (![swizzledClasses containsObject:className]) { 438 | aspect_swizzleForwardInvocation(klass); 439 | [swizzledClasses addObject:className]; 440 | } 441 | }); 442 | return klass; 443 | } 444 | 445 | static void aspect_undoSwizzleClassInPlace(Class klass) { 446 | NSCParameterAssert(klass); 447 | NSString *className = NSStringFromClass(klass); 448 | 449 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { 450 | if ([swizzledClasses containsObject:className]) { 451 | aspect_undoSwizzleForwardInvocation(klass); 452 | [swizzledClasses removeObject:className]; 453 | } 454 | }); 455 | } 456 | 457 | /////////////////////////////////////////////////////////////////////////////////////////// 458 | #pragma mark - Aspect Invoke Point 459 | 460 | // This is a macro so we get a cleaner stack trace. 461 | #define aspect_invoke(aspects, info) \ 462 | for (AspectIdentifier *aspect in aspects) {\ 463 | [aspect invokeWithInfo:info];\ 464 | if (aspect.options & AspectOptionAutomaticRemoval) { \ 465 | aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \ 466 | } \ 467 | } 468 | 469 | // This is the swizzled forwardInvocation: method. 470 | static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { 471 | NSCParameterAssert(self); 472 | NSCParameterAssert(invocation); 473 | SEL originalSelector = invocation.selector; 474 | SEL aliasSelector = aspect_aliasForSelector(invocation.selector); 475 | invocation.selector = aliasSelector; 476 | AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector); 477 | AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector); 478 | AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; 479 | NSArray *aspectsToRemove = nil; 480 | 481 | // Before hooks. 482 | aspect_invoke(classContainer.beforeAspects, info); 483 | aspect_invoke(objectContainer.beforeAspects, info); 484 | 485 | // Instead hooks. 486 | BOOL respondsToAlias = YES; 487 | if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) { 488 | aspect_invoke(classContainer.insteadAspects, info); 489 | aspect_invoke(objectContainer.insteadAspects, info); 490 | }else { 491 | Class klass = object_getClass(invocation.target); 492 | do { 493 | if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) { 494 | [invocation invoke]; 495 | break; 496 | } 497 | }while (!respondsToAlias && (klass = class_getSuperclass(klass))); 498 | } 499 | 500 | // After hooks. 501 | aspect_invoke(classContainer.afterAspects, info); 502 | aspect_invoke(objectContainer.afterAspects, info); 503 | 504 | // If no hooks are installed, call original implementation (usually to throw an exception) 505 | if (!respondsToAlias) { 506 | invocation.selector = originalSelector; 507 | SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName); 508 | if ([self respondsToSelector:originalForwardInvocationSEL]) { 509 | ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation); 510 | }else { 511 | [self doesNotRecognizeSelector:invocation.selector]; 512 | } 513 | } 514 | 515 | // Remove any hooks that are queued for deregistration. 516 | [aspectsToRemove makeObjectsPerformSelector:@selector(remove)]; 517 | } 518 | #undef aspect_invoke 519 | 520 | /////////////////////////////////////////////////////////////////////////////////////////// 521 | #pragma mark - Aspect Container Management 522 | 523 | // Loads or creates the aspect container. 524 | static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) { 525 | NSCParameterAssert(self); 526 | SEL aliasSelector = aspect_aliasForSelector(selector); 527 | AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector); 528 | if (!aspectContainer) { 529 | aspectContainer = [AspectsContainer new]; 530 | objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN); 531 | } 532 | return aspectContainer; 533 | } 534 | 535 | static AspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) { 536 | NSCParameterAssert(klass); 537 | AspectsContainer *classContainer = nil; 538 | do { 539 | classContainer = objc_getAssociatedObject(klass, selector); 540 | if (classContainer.hasAspects) break; 541 | }while ((klass = class_getSuperclass(klass))); 542 | 543 | return classContainer; 544 | } 545 | 546 | static void aspect_destroyContainerForObject(id self, SEL selector) { 547 | NSCParameterAssert(self); 548 | SEL aliasSelector = aspect_aliasForSelector(selector); 549 | objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN); 550 | } 551 | 552 | /////////////////////////////////////////////////////////////////////////////////////////// 553 | #pragma mark - Selector Blacklist Checking 554 | 555 | static NSMutableDictionary *aspect_getSwizzledClassesDict() { 556 | static NSMutableDictionary *swizzledClassesDict; 557 | static dispatch_once_t pred; 558 | dispatch_once(&pred, ^{ 559 | swizzledClassesDict = [NSMutableDictionary new]; 560 | }); 561 | return swizzledClassesDict; 562 | } 563 | 564 | static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) { 565 | static NSSet *disallowedSelectorList; 566 | static dispatch_once_t pred; 567 | dispatch_once(&pred, ^{ 568 | disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil]; 569 | }); 570 | 571 | // Check against the blacklist. 572 | NSString *selectorName = NSStringFromSelector(selector); 573 | if ([disallowedSelectorList containsObject:selectorName]) { 574 | NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName]; 575 | AspectError(AspectErrorSelectorBlacklisted, errorDescription); 576 | return NO; 577 | } 578 | 579 | // Additional checks. 580 | AspectOptions position = options&AspectPositionFilter; 581 | if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) { 582 | NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc."; 583 | AspectError(AspectErrorSelectorDeallocPosition, errorDesc); 584 | return NO; 585 | } 586 | 587 | if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) { 588 | NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName]; 589 | AspectError(AspectErrorDoesNotRespondToSelector, errorDesc); 590 | return NO; 591 | } 592 | 593 | // Search for the current class and the class hierarchy IF we are modifying a class object 594 | if (class_isMetaClass(object_getClass(self))) { 595 | Class klass = [self class]; 596 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); 597 | Class currentClass = [self class]; 598 | do { 599 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 600 | if ([tracker.selectorNames containsObject:selectorName]) { 601 | 602 | // Find the topmost class for the log. 603 | if (tracker.parentEntry) { 604 | AspectTracker *topmostEntry = tracker.parentEntry; 605 | while (topmostEntry.parentEntry) { 606 | topmostEntry = topmostEntry.parentEntry; 607 | } 608 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)]; 609 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); 610 | return NO; 611 | }else if (klass == currentClass) { 612 | // Already modified and topmost! 613 | return YES; 614 | } 615 | } 616 | }while ((currentClass = class_getSuperclass(currentClass))); 617 | 618 | // Add the selector as being modified. 619 | currentClass = klass; 620 | AspectTracker *parentTracker = nil; 621 | do { 622 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 623 | if (!tracker) { 624 | tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker]; 625 | swizzledClassesDict[(id)currentClass] = tracker; 626 | } 627 | [tracker.selectorNames addObject:selectorName]; 628 | // All superclasses get marked as having a subclass that is modified. 629 | parentTracker = tracker; 630 | }while ((currentClass = class_getSuperclass(currentClass))); 631 | } 632 | 633 | return YES; 634 | } 635 | 636 | static void aspect_deregisterTrackedSelector(id self, SEL selector) { 637 | if (!class_isMetaClass(object_getClass(self))) return; 638 | 639 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); 640 | NSString *selectorName = NSStringFromSelector(selector); 641 | Class currentClass = [self class]; 642 | do { 643 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 644 | if (tracker) { 645 | [tracker.selectorNames removeObject:selectorName]; 646 | if (tracker.selectorNames.count == 0) { 647 | [swizzledClassesDict removeObjectForKey:tracker]; 648 | } 649 | } 650 | }while ((currentClass = class_getSuperclass(currentClass))); 651 | } 652 | 653 | @end 654 | 655 | @implementation AspectTracker 656 | 657 | - (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent { 658 | if (self = [super init]) { 659 | _trackedClass = trackedClass; 660 | _parentEntry = parent; 661 | _selectorNames = [NSMutableSet new]; 662 | } 663 | return self; 664 | } 665 | - (NSString *)description { 666 | return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, parent:%p>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.parentEntry]; 667 | } 668 | 669 | @end 670 | 671 | /////////////////////////////////////////////////////////////////////////////////////////// 672 | #pragma mark - NSInvocation (Aspects) 673 | 674 | @implementation NSInvocation (Aspects) 675 | 676 | // Thanks to the ReactiveCocoa team for providing a generic solution for this. 677 | - (id)aspect_argumentAtIndex:(NSUInteger)index { 678 | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; 679 | // Skip const type qualifier. 680 | if (argType[0] == _C_CONST) argType++; 681 | 682 | #define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0) 683 | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { 684 | __autoreleasing id returnObj; 685 | [self getArgument:&returnObj atIndex:(NSInteger)index]; 686 | return returnObj; 687 | } else if (strcmp(argType, @encode(SEL)) == 0) { 688 | SEL selector = 0; 689 | [self getArgument:&selector atIndex:(NSInteger)index]; 690 | return NSStringFromSelector(selector); 691 | } else if (strcmp(argType, @encode(Class)) == 0) { 692 | __autoreleasing Class theClass = Nil; 693 | [self getArgument:&theClass atIndex:(NSInteger)index]; 694 | return theClass; 695 | // Using this list will box the number with the appropriate constructor, instead of the generic NSValue. 696 | } else if (strcmp(argType, @encode(char)) == 0) { 697 | WRAP_AND_RETURN(char); 698 | } else if (strcmp(argType, @encode(int)) == 0) { 699 | WRAP_AND_RETURN(int); 700 | } else if (strcmp(argType, @encode(short)) == 0) { 701 | WRAP_AND_RETURN(short); 702 | } else if (strcmp(argType, @encode(long)) == 0) { 703 | WRAP_AND_RETURN(long); 704 | } else if (strcmp(argType, @encode(long long)) == 0) { 705 | WRAP_AND_RETURN(long long); 706 | } else if (strcmp(argType, @encode(unsigned char)) == 0) { 707 | WRAP_AND_RETURN(unsigned char); 708 | } else if (strcmp(argType, @encode(unsigned int)) == 0) { 709 | WRAP_AND_RETURN(unsigned int); 710 | } else if (strcmp(argType, @encode(unsigned short)) == 0) { 711 | WRAP_AND_RETURN(unsigned short); 712 | } else if (strcmp(argType, @encode(unsigned long)) == 0) { 713 | WRAP_AND_RETURN(unsigned long); 714 | } else if (strcmp(argType, @encode(unsigned long long)) == 0) { 715 | WRAP_AND_RETURN(unsigned long long); 716 | } else if (strcmp(argType, @encode(float)) == 0) { 717 | WRAP_AND_RETURN(float); 718 | } else if (strcmp(argType, @encode(double)) == 0) { 719 | WRAP_AND_RETURN(double); 720 | } else if (strcmp(argType, @encode(BOOL)) == 0) { 721 | WRAP_AND_RETURN(BOOL); 722 | } else if (strcmp(argType, @encode(bool)) == 0) { 723 | WRAP_AND_RETURN(BOOL); 724 | } else if (strcmp(argType, @encode(char *)) == 0) { 725 | WRAP_AND_RETURN(const char *); 726 | } else if (strcmp(argType, @encode(void (^)(void))) == 0) { 727 | __unsafe_unretained id block = nil; 728 | [self getArgument:&block atIndex:(NSInteger)index]; 729 | return [block copy]; 730 | } else { 731 | NSUInteger valueSize = 0; 732 | NSGetSizeAndAlignment(argType, &valueSize, NULL); 733 | 734 | unsigned char valueBytes[valueSize]; 735 | [self getArgument:valueBytes atIndex:(NSInteger)index]; 736 | 737 | return [NSValue valueWithBytes:valueBytes objCType:argType]; 738 | } 739 | return nil; 740 | #undef WRAP_AND_RETURN 741 | } 742 | 743 | - (NSArray *)aspects_arguments { 744 | NSMutableArray *argumentsArray = [NSMutableArray array]; 745 | for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) { 746 | [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null]; 747 | } 748 | return [argumentsArray copy]; 749 | } 750 | 751 | @end 752 | 753 | /////////////////////////////////////////////////////////////////////////////////////////// 754 | #pragma mark - AspectIdentifier 755 | 756 | @implementation AspectIdentifier 757 | 758 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error { 759 | NSCParameterAssert(block); 760 | NSCParameterAssert(selector); 761 | NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc. 762 | if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) { 763 | return nil; 764 | } 765 | 766 | AspectIdentifier *identifier = nil; 767 | if (blockSignature) { 768 | identifier = [AspectIdentifier new]; 769 | identifier.selector = selector; 770 | identifier.block = block; 771 | identifier.blockSignature = blockSignature; 772 | identifier.options = options; 773 | identifier.object = object; // weak 774 | } 775 | return identifier; 776 | } 777 | 778 | - (BOOL)invokeWithInfo:(id)info { 779 | NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature]; 780 | NSInvocation *originalInvocation = info.originalInvocation; 781 | NSUInteger numberOfArguments = self.blockSignature.numberOfArguments; 782 | 783 | // Be extra paranoid. We already check that on hook registration. 784 | if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { 785 | AspectLogError(@"Block has too many arguments. Not calling %@", info); 786 | return NO; 787 | } 788 | 789 | // The `self` of the block will be the AspectInfo. Optional. 790 | if (numberOfArguments > 1) { 791 | [blockInvocation setArgument:&info atIndex:1]; 792 | } 793 | 794 | void *argBuf = NULL; 795 | for (NSUInteger idx = 2; idx < numberOfArguments; idx++) { 796 | const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; 797 | NSUInteger argSize; 798 | NSGetSizeAndAlignment(type, &argSize, NULL); 799 | 800 | if (!(argBuf = reallocf(argBuf, argSize))) { 801 | AspectLogError(@"Failed to allocate memory for block invocation."); 802 | return NO; 803 | } 804 | 805 | [originalInvocation getArgument:argBuf atIndex:idx]; 806 | [blockInvocation setArgument:argBuf atIndex:idx]; 807 | } 808 | 809 | [blockInvocation invokeWithTarget:self.block]; 810 | 811 | if (argBuf != NULL) { 812 | free(argBuf); 813 | } 814 | return YES; 815 | } 816 | 817 | - (NSString *)description { 818 | return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments]; 819 | } 820 | 821 | - (BOOL)remove { 822 | return aspect_remove(self, NULL); 823 | } 824 | 825 | @end 826 | 827 | /////////////////////////////////////////////////////////////////////////////////////////// 828 | #pragma mark - AspectsContainer 829 | 830 | @implementation AspectsContainer 831 | 832 | - (BOOL)hasAspects { 833 | return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0; 834 | } 835 | 836 | - (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options { 837 | NSParameterAssert(aspect); 838 | NSUInteger position = options&AspectPositionFilter; 839 | switch (position) { 840 | case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break; 841 | case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break; 842 | case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break; 843 | } 844 | } 845 | 846 | - (BOOL)removeAspect:(id)aspect { 847 | for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)), 848 | NSStringFromSelector(@selector(insteadAspects)), 849 | NSStringFromSelector(@selector(afterAspects))]) { 850 | NSArray *array = [self valueForKey:aspectArrayName]; 851 | NSUInteger index = [array indexOfObjectIdenticalTo:aspect]; 852 | if (array && index != NSNotFound) { 853 | NSMutableArray *newArray = [NSMutableArray arrayWithArray:array]; 854 | [newArray removeObjectAtIndex:index]; 855 | [self setValue:newArray forKey:aspectArrayName]; 856 | return YES; 857 | } 858 | } 859 | return NO; 860 | } 861 | 862 | - (NSString *)description { 863 | return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects]; 864 | } 865 | 866 | @end 867 | 868 | /////////////////////////////////////////////////////////////////////////////////////////// 869 | #pragma mark - AspectInfo 870 | 871 | @implementation AspectInfo 872 | 873 | @synthesize arguments = _arguments; 874 | 875 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation { 876 | NSCParameterAssert(instance); 877 | NSCParameterAssert(invocation); 878 | if (self = [super init]) { 879 | _instance = instance; 880 | _originalInvocation = invocation; 881 | } 882 | return self; 883 | } 884 | 885 | - (NSArray *)arguments { 886 | // Lazily evaluate arguments, boxing is expensive. 887 | if (!_arguments) { 888 | _arguments = self.originalInvocation.aspects_arguments; 889 | } 890 | return _arguments; 891 | } 892 | 893 | @end 894 | --------------------------------------------------------------------------------