├── .gitignore ├── LICENSE ├── README.md ├── XcodeMediaLibraryTweak.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── XcodeMediaLibraryTweak ├── Aspects │ ├── Aspects.h │ └── Aspects.m ├── Category │ └── XcodeHeaders.h ├── Info.plist ├── MediaLibrayTweakSetting.h ├── MediaLibrayTweakSetting.m ├── MediaLibrayTweakSetting.xib ├── WYTweak.h └── WYTweak.m └── helpImage.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wang Yang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XcodeMediaLibraryTweak 2 | Let the Xcode Media Library more useful 3 | 4 | **Show image size** for image type media. Size is shown as @1x scale. USE IT IN CODE WITHOUT THINKING!!! 5 | 6 | ![image](helpImage.png) 7 | 8 | # Installation 9 | 1. Clone this repository. 10 | 2. Open and run the project. 11 | 3. Restart Xcode. 12 | 13 | #Usage 14 | open MediaLibrayTweak in Windows menu, click, then custom the image size string; 15 | 16 | # Xcode Version 17 | This plug-in is ONLY test in Xcode 6.3.1, but should work will on Xcode 6.x and later. If the plugin does not work, perhaps I miss the UUID of your Xcode version. 18 | 19 | Get you xcode UUID 20 | ``` 21 | defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID 22 | ``` 23 | 24 | Copy the output and add it to XcodeMediaLibraryTweak's info.plist DVTPlugInCompatibilityUUIDs Key. Then Try run and restart Xcode. 25 | 26 | Any question, issue me.^_^ 27 | #THANKS 28 | 29 | * [Xcode 4 插件制作入门](http://www.onevcat.com/2013/02/xcode-plugin/) 30 | * [Creating an Xcode Plugin: A Quick-Start Guide](http://www.overacker.me/blog/2015/01/25/creating-an-xcode-plugin)。这里有提到下面的DTXcodeUtils 31 | * [DTXcodeUtils](https://github.com/thurn/DTXcodeUtils), 一个快速上手的xcodePlugin模版,省去配置项目的烦恼 32 | * [DBSmartPanels](https://github.com/chaingarden/DBSmartPanels)。我是学习了这个代码写的XcodeMediaLibraryTweak 33 | 34 | ## TODO 35 | 1. 可以直接显示xcassets里图片的大小 -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D31C598E1B1EF00F00E04695 /* MediaLibrayTweakSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = D31C598C1B1EF00E00E04695 /* MediaLibrayTweakSetting.m */; }; 11 | D31C598F1B1EF00F00E04695 /* MediaLibrayTweakSetting.xib in Resources */ = {isa = PBXBuildFile; fileRef = D31C598D1B1EF00F00E04695 /* MediaLibrayTweakSetting.xib */; }; 12 | D321FEAD1AFEF68A00AC18A0 /* WYTweak.m in Sources */ = {isa = PBXBuildFile; fileRef = D321FEAC1AFEF68A00AC18A0 /* WYTweak.m */; }; 13 | D321FEB01AFEF7C400AC18A0 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D321FEAF1AFEF7C400AC18A0 /* AppKit.framework */; }; 14 | D321FEB41AFEF88800AC18A0 /* Aspects.m in Sources */ = {isa = PBXBuildFile; fileRef = D321FEB31AFEF88800AC18A0 /* Aspects.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | D31C598B1B1EF00E00E04695 /* MediaLibrayTweakSetting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaLibrayTweakSetting.h; sourceTree = ""; }; 19 | D31C598C1B1EF00E00E04695 /* MediaLibrayTweakSetting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MediaLibrayTweakSetting.m; sourceTree = ""; }; 20 | D31C598D1B1EF00F00E04695 /* MediaLibrayTweakSetting.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MediaLibrayTweakSetting.xib; sourceTree = ""; }; 21 | D321FEA01AFEF38000AC18A0 /* XcodeMediaLibraryTweak.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XcodeMediaLibraryTweak.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | D321FEA41AFEF38000AC18A0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | D321FEAB1AFEF68A00AC18A0 /* WYTweak.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WYTweak.h; sourceTree = ""; }; 24 | D321FEAC1AFEF68A00AC18A0 /* WYTweak.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WYTweak.m; sourceTree = ""; }; 25 | D321FEAF1AFEF7C400AC18A0 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 26 | D321FEB21AFEF88800AC18A0 /* Aspects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Aspects.h; sourceTree = ""; }; 27 | D321FEB31AFEF88800AC18A0 /* Aspects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Aspects.m; sourceTree = ""; }; 28 | D321FEB61AFEF8BC00AC18A0 /* XcodeHeaders.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XcodeHeaders.h; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | D321FE9D1AFEF38000AC18A0 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | D321FEB01AFEF7C400AC18A0 /* AppKit.framework in Frameworks */, 37 | ); 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXFrameworksBuildPhase section */ 41 | 42 | /* Begin PBXGroup section */ 43 | D321FE971AFEF38000AC18A0 = { 44 | isa = PBXGroup; 45 | children = ( 46 | D321FEA21AFEF38000AC18A0 /* XcodeMediaLibraryTweak */, 47 | D321FEAE1AFEF7A800AC18A0 /* Frameworks */, 48 | D321FEA11AFEF38000AC18A0 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | D321FEA11AFEF38000AC18A0 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | D321FEA01AFEF38000AC18A0 /* XcodeMediaLibraryTweak.xcplugin */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | D321FEA21AFEF38000AC18A0 /* XcodeMediaLibraryTweak */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | D321FEB51AFEF88F00AC18A0 /* Category */, 64 | D321FEB11AFEF88800AC18A0 /* Aspects */, 65 | D321FEAB1AFEF68A00AC18A0 /* WYTweak.h */, 66 | D321FEAC1AFEF68A00AC18A0 /* WYTweak.m */, 67 | D31C598B1B1EF00E00E04695 /* MediaLibrayTweakSetting.h */, 68 | D31C598C1B1EF00E00E04695 /* MediaLibrayTweakSetting.m */, 69 | D31C598D1B1EF00F00E04695 /* MediaLibrayTweakSetting.xib */, 70 | D321FEA31AFEF38000AC18A0 /* Supporting Files */, 71 | ); 72 | path = XcodeMediaLibraryTweak; 73 | sourceTree = ""; 74 | }; 75 | D321FEA31AFEF38000AC18A0 /* Supporting Files */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | D321FEA41AFEF38000AC18A0 /* Info.plist */, 79 | ); 80 | name = "Supporting Files"; 81 | sourceTree = ""; 82 | }; 83 | D321FEAE1AFEF7A800AC18A0 /* Frameworks */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | D321FEAF1AFEF7C400AC18A0 /* AppKit.framework */, 87 | ); 88 | name = Frameworks; 89 | sourceTree = ""; 90 | }; 91 | D321FEB11AFEF88800AC18A0 /* Aspects */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | D321FEB21AFEF88800AC18A0 /* Aspects.h */, 95 | D321FEB31AFEF88800AC18A0 /* Aspects.m */, 96 | ); 97 | path = Aspects; 98 | sourceTree = ""; 99 | }; 100 | D321FEB51AFEF88F00AC18A0 /* Category */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | D321FEB61AFEF8BC00AC18A0 /* XcodeHeaders.h */, 104 | ); 105 | path = Category; 106 | sourceTree = ""; 107 | }; 108 | /* End PBXGroup section */ 109 | 110 | /* Begin PBXNativeTarget section */ 111 | D321FE9F1AFEF38000AC18A0 /* XcodeMediaLibraryTweak */ = { 112 | isa = PBXNativeTarget; 113 | buildConfigurationList = D321FEA71AFEF38000AC18A0 /* Build configuration list for PBXNativeTarget "XcodeMediaLibraryTweak" */; 114 | buildPhases = ( 115 | D321FE9C1AFEF38000AC18A0 /* Sources */, 116 | D321FE9D1AFEF38000AC18A0 /* Frameworks */, 117 | D321FE9E1AFEF38000AC18A0 /* Resources */, 118 | ); 119 | buildRules = ( 120 | ); 121 | dependencies = ( 122 | ); 123 | name = XcodeMediaLibraryTweak; 124 | productName = XcodeMediaLibraryTweak; 125 | productReference = D321FEA01AFEF38000AC18A0 /* XcodeMediaLibraryTweak.xcplugin */; 126 | productType = "com.apple.product-type.bundle"; 127 | }; 128 | /* End PBXNativeTarget section */ 129 | 130 | /* Begin PBXProject section */ 131 | D321FE981AFEF38000AC18A0 /* Project object */ = { 132 | isa = PBXProject; 133 | attributes = { 134 | LastUpgradeCheck = 0630; 135 | ORGANIZATIONNAME = IGIU; 136 | TargetAttributes = { 137 | D321FE9F1AFEF38000AC18A0 = { 138 | CreatedOnToolsVersion = 6.3.1; 139 | }; 140 | }; 141 | }; 142 | buildConfigurationList = D321FE9B1AFEF38000AC18A0 /* Build configuration list for PBXProject "XcodeMediaLibraryTweak" */; 143 | compatibilityVersion = "Xcode 3.2"; 144 | developmentRegion = English; 145 | hasScannedForEncodings = 0; 146 | knownRegions = ( 147 | en, 148 | ); 149 | mainGroup = D321FE971AFEF38000AC18A0; 150 | productRefGroup = D321FEA11AFEF38000AC18A0 /* Products */; 151 | projectDirPath = ""; 152 | projectRoot = ""; 153 | targets = ( 154 | D321FE9F1AFEF38000AC18A0 /* XcodeMediaLibraryTweak */, 155 | ); 156 | }; 157 | /* End PBXProject section */ 158 | 159 | /* Begin PBXResourcesBuildPhase section */ 160 | D321FE9E1AFEF38000AC18A0 /* Resources */ = { 161 | isa = PBXResourcesBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | D31C598F1B1EF00F00E04695 /* MediaLibrayTweakSetting.xib in Resources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXSourcesBuildPhase section */ 171 | D321FE9C1AFEF38000AC18A0 /* Sources */ = { 172 | isa = PBXSourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | D31C598E1B1EF00F00E04695 /* MediaLibrayTweakSetting.m in Sources */, 176 | D321FEAD1AFEF68A00AC18A0 /* WYTweak.m in Sources */, 177 | D321FEB41AFEF88800AC18A0 /* Aspects.m in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin XCBuildConfiguration section */ 184 | D321FEA51AFEF38000AC18A0 /* Debug */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 189 | CLANG_CXX_LIBRARY = "libc++"; 190 | CLANG_ENABLE_MODULES = YES; 191 | CLANG_ENABLE_OBJC_ARC = YES; 192 | CLANG_WARN_BOOL_CONVERSION = YES; 193 | CLANG_WARN_CONSTANT_CONVERSION = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_EMPTY_BODY = YES; 196 | CLANG_WARN_ENUM_CONVERSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = NO; 202 | DEBUG_INFORMATION_FORMAT = dwarf; 203 | ENABLE_STRICT_OBJC_MSGSEND = YES; 204 | GCC_C_LANGUAGE_STANDARD = gnu99; 205 | GCC_DYNAMIC_NO_PIC = NO; 206 | GCC_NO_COMMON_BLOCKS = YES; 207 | GCC_OPTIMIZATION_LEVEL = 0; 208 | GCC_PREPROCESSOR_DEFINITIONS = ( 209 | "DEBUG=1", 210 | "$(inherited)", 211 | ); 212 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 213 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 215 | GCC_WARN_UNDECLARED_SELECTOR = YES; 216 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 217 | GCC_WARN_UNUSED_FUNCTION = YES; 218 | GCC_WARN_UNUSED_VARIABLE = YES; 219 | MACOSX_DEPLOYMENT_TARGET = 10.10; 220 | MTL_ENABLE_DEBUG_INFO = YES; 221 | ONLY_ACTIVE_ARCH = YES; 222 | SDKROOT = macosx; 223 | }; 224 | name = Debug; 225 | }; 226 | D321FEA61AFEF38000AC18A0 /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 231 | CLANG_CXX_LIBRARY = "libc++"; 232 | CLANG_ENABLE_MODULES = YES; 233 | CLANG_ENABLE_OBJC_ARC = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INT_CONVERSION = YES; 240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 241 | CLANG_WARN_UNREACHABLE_CODE = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | COPY_PHASE_STRIP = NO; 244 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 245 | ENABLE_NS_ASSERTIONS = NO; 246 | ENABLE_STRICT_OBJC_MSGSEND = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu99; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | MACOSX_DEPLOYMENT_TARGET = 10.10; 256 | MTL_ENABLE_DEBUG_INFO = NO; 257 | SDKROOT = macosx; 258 | }; 259 | name = Release; 260 | }; 261 | D321FEA81AFEF38000AC18A0 /* Debug */ = { 262 | isa = XCBuildConfiguration; 263 | buildSettings = { 264 | COMBINE_HIDPI_IMAGES = YES; 265 | DEPLOYMENT_LOCATION = YES; 266 | DSTROOT = "$(HOME)"; 267 | INFOPLIST_FILE = XcodeMediaLibraryTweak/Info.plist; 268 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 269 | PRODUCT_NAME = "$(TARGET_NAME)"; 270 | SKIP_INSTALL = NO; 271 | WRAPPER_EXTENSION = xcplugin; 272 | }; 273 | name = Debug; 274 | }; 275 | D321FEA91AFEF38000AC18A0 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | COMBINE_HIDPI_IMAGES = YES; 279 | DEPLOYMENT_LOCATION = YES; 280 | DSTROOT = "$(HOME)"; 281 | INFOPLIST_FILE = XcodeMediaLibraryTweak/Info.plist; 282 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SKIP_INSTALL = NO; 285 | WRAPPER_EXTENSION = xcplugin; 286 | }; 287 | name = Release; 288 | }; 289 | /* End XCBuildConfiguration section */ 290 | 291 | /* Begin XCConfigurationList section */ 292 | D321FE9B1AFEF38000AC18A0 /* Build configuration list for PBXProject "XcodeMediaLibraryTweak" */ = { 293 | isa = XCConfigurationList; 294 | buildConfigurations = ( 295 | D321FEA51AFEF38000AC18A0 /* Debug */, 296 | D321FEA61AFEF38000AC18A0 /* Release */, 297 | ); 298 | defaultConfigurationIsVisible = 0; 299 | defaultConfigurationName = Release; 300 | }; 301 | D321FEA71AFEF38000AC18A0 /* Build configuration list for PBXNativeTarget "XcodeMediaLibraryTweak" */ = { 302 | isa = XCConfigurationList; 303 | buildConfigurations = ( 304 | D321FEA81AFEF38000AC18A0 /* Debug */, 305 | D321FEA91AFEF38000AC18A0 /* Release */, 306 | ); 307 | defaultConfigurationIsVisible = 0; 308 | defaultConfigurationName = Release; 309 | }; 310 | /* End XCConfigurationList section */ 311 | }; 312 | rootObject = D321FE981AFEF38000AC18A0 /* Project object */; 313 | } 314 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/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 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/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 | signaturesMatch = NO; 203 | } 204 | } 205 | // Argument 0 is self/block, argument 1 is SEL or id. We start comparing at argument 2. 206 | // The block can have less arguments than the method, that's ok. 207 | if (signaturesMatch) { 208 | for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) { 209 | const char *methodType = [methodSignature getArgumentTypeAtIndex:idx]; 210 | const char *blockType = [blockSignature getArgumentTypeAtIndex:idx]; 211 | // Only compare parameter, not the optional type data. 212 | if (!methodType || !blockType || methodType[0] != blockType[0]) { 213 | signaturesMatch = NO; break; 214 | } 215 | } 216 | } 217 | } 218 | 219 | if (!signaturesMatch) { 220 | NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature]; 221 | AspectError(AspectErrorIncompatibleBlockSignature, description); 222 | return NO; 223 | } 224 | return YES; 225 | } 226 | 227 | /////////////////////////////////////////////////////////////////////////////////////////// 228 | #pragma mark - Class + Selector Preparation 229 | 230 | static BOOL aspect_isMsgForwardIMP(IMP impl) { 231 | return impl == _objc_msgForward 232 | #if !defined(__arm64__) 233 | || impl == (IMP)_objc_msgForward_stret 234 | #endif 235 | ; 236 | } 237 | 238 | static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) { 239 | IMP msgForwardIMP = _objc_msgForward; 240 | #if !defined(__arm64__) 241 | // 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. 242 | // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html 243 | // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783 244 | // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4) 245 | Method method = class_getInstanceMethod(self.class, selector); 246 | const char *encoding = method_getTypeEncoding(method); 247 | BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B; 248 | if (methodReturnsStructValue) { 249 | @try { 250 | NSUInteger valueSize = 0; 251 | NSGetSizeAndAlignment(encoding, &valueSize, NULL); 252 | 253 | if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) { 254 | methodReturnsStructValue = NO; 255 | } 256 | } @catch (__unused NSException *e) {} 257 | } 258 | if (methodReturnsStructValue) { 259 | msgForwardIMP = (IMP)_objc_msgForward_stret; 260 | } 261 | #endif 262 | return msgForwardIMP; 263 | } 264 | 265 | static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) { 266 | NSCParameterAssert(selector); 267 | Class klass = aspect_hookClass(self, error); 268 | Method targetMethod = class_getInstanceMethod(klass, selector); 269 | IMP targetMethodIMP = method_getImplementation(targetMethod); 270 | if (!aspect_isMsgForwardIMP(targetMethodIMP)) { 271 | // Make a method alias for the existing method implementation, it not already copied. 272 | const char *typeEncoding = method_getTypeEncoding(targetMethod); 273 | SEL aliasSelector = aspect_aliasForSelector(selector); 274 | if (![klass instancesRespondToSelector:aliasSelector]) { 275 | __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding); 276 | NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); 277 | } 278 | 279 | // We use forwardInvocation to hook in. 280 | class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding); 281 | AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); 282 | } 283 | } 284 | 285 | // Will undo the runtime changes made. 286 | static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) { 287 | NSCParameterAssert(self); 288 | NSCParameterAssert(selector); 289 | 290 | Class klass = object_getClass(self); 291 | BOOL isMetaClass = class_isMetaClass(klass); 292 | if (isMetaClass) { 293 | klass = (Class)self; 294 | } 295 | 296 | // Check if the method is marked as forwarded and undo that. 297 | Method targetMethod = class_getInstanceMethod(klass, selector); 298 | IMP targetMethodIMP = method_getImplementation(targetMethod); 299 | if (aspect_isMsgForwardIMP(targetMethodIMP)) { 300 | // Restore the original method implementation. 301 | const char *typeEncoding = method_getTypeEncoding(targetMethod); 302 | SEL aliasSelector = aspect_aliasForSelector(selector); 303 | Method originalMethod = class_getInstanceMethod(klass, aliasSelector); 304 | IMP originalIMP = method_getImplementation(originalMethod); 305 | NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass); 306 | 307 | class_replaceMethod(klass, selector, originalIMP, typeEncoding); 308 | AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector)); 309 | } 310 | 311 | // Deregister global tracked selector 312 | aspect_deregisterTrackedSelector(self, selector); 313 | 314 | // Get the aspect container and check if there are any hooks remaining. Clean up if there are not. 315 | AspectsContainer *container = aspect_getContainerForObject(self, selector); 316 | if (!container.hasAspects) { 317 | // Destroy the container 318 | aspect_destroyContainerForObject(self, selector); 319 | 320 | // Figure out how the class was modified to undo the changes. 321 | NSString *className = NSStringFromClass(klass); 322 | if ([className hasSuffix:AspectsSubclassSuffix]) { 323 | Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]); 324 | NSCAssert(originalClass != nil, @"Original class must exist"); 325 | object_setClass(self, originalClass); 326 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass)); 327 | 328 | // We can only dispose the class pair if we can ensure that no instances exist using our subclass. 329 | // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around. 330 | //objc_disposeClassPair(object.class); 331 | }else { 332 | // Class is most likely swizzled in place. Undo that. 333 | if (isMetaClass) { 334 | aspect_undoSwizzleClassInPlace((Class)self); 335 | }else if (self.class != klass) { 336 | aspect_undoSwizzleClassInPlace(klass); 337 | } 338 | } 339 | } 340 | } 341 | 342 | /////////////////////////////////////////////////////////////////////////////////////////// 343 | #pragma mark - Hook Class 344 | 345 | static Class aspect_hookClass(NSObject *self, NSError **error) { 346 | NSCParameterAssert(self); 347 | Class statedClass = self.class; 348 | Class baseClass = object_getClass(self); 349 | NSString *className = NSStringFromClass(baseClass); 350 | 351 | // Already subclassed 352 | if ([className hasSuffix:AspectsSubclassSuffix]) { 353 | return baseClass; 354 | 355 | // We swizzle a class object, not a single object. 356 | }else if (class_isMetaClass(baseClass)) { 357 | return aspect_swizzleClassInPlace((Class)self); 358 | // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place. 359 | }else if (statedClass != baseClass) { 360 | return aspect_swizzleClassInPlace(baseClass); 361 | } 362 | 363 | // Default case. Create dynamic subclass. 364 | const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String; 365 | Class subclass = objc_getClass(subclassName); 366 | 367 | if (subclass == nil) { 368 | subclass = objc_allocateClassPair(baseClass, subclassName, 0); 369 | if (subclass == nil) { 370 | NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName]; 371 | AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc); 372 | return nil; 373 | } 374 | 375 | aspect_swizzleForwardInvocation(subclass); 376 | aspect_hookedGetClass(subclass, statedClass); 377 | aspect_hookedGetClass(object_getClass(subclass), statedClass); 378 | objc_registerClassPair(subclass); 379 | } 380 | 381 | object_setClass(self, subclass); 382 | return subclass; 383 | } 384 | 385 | static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:"; 386 | static void aspect_swizzleForwardInvocation(Class klass) { 387 | NSCParameterAssert(klass); 388 | // If there is no method, replace will act like class_addMethod. 389 | IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@"); 390 | if (originalImplementation) { 391 | class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@"); 392 | } 393 | AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass)); 394 | } 395 | 396 | static void aspect_undoSwizzleForwardInvocation(Class klass) { 397 | NSCParameterAssert(klass); 398 | Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName)); 399 | Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:)); 400 | // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy. 401 | IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod); 402 | class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@"); 403 | 404 | AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass)); 405 | } 406 | 407 | static void aspect_hookedGetClass(Class class, Class statedClass) { 408 | NSCParameterAssert(class); 409 | NSCParameterAssert(statedClass); 410 | Method method = class_getInstanceMethod(class, @selector(class)); 411 | IMP newIMP = imp_implementationWithBlock(^(id self) { 412 | return statedClass; 413 | }); 414 | class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method)); 415 | } 416 | 417 | /////////////////////////////////////////////////////////////////////////////////////////// 418 | #pragma mark - Swizzle Class In Place 419 | 420 | static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) { 421 | static NSMutableSet *swizzledClasses; 422 | static dispatch_once_t pred; 423 | dispatch_once(&pred, ^{ 424 | swizzledClasses = [NSMutableSet new]; 425 | }); 426 | @synchronized(swizzledClasses) { 427 | block(swizzledClasses); 428 | } 429 | } 430 | 431 | static Class aspect_swizzleClassInPlace(Class klass) { 432 | NSCParameterAssert(klass); 433 | NSString *className = NSStringFromClass(klass); 434 | 435 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { 436 | if (![swizzledClasses containsObject:className]) { 437 | aspect_swizzleForwardInvocation(klass); 438 | [swizzledClasses addObject:className]; 439 | } 440 | }); 441 | return klass; 442 | } 443 | 444 | static void aspect_undoSwizzleClassInPlace(Class klass) { 445 | NSCParameterAssert(klass); 446 | NSString *className = NSStringFromClass(klass); 447 | 448 | _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) { 449 | if ([swizzledClasses containsObject:className]) { 450 | aspect_undoSwizzleForwardInvocation(klass); 451 | [swizzledClasses removeObject:className]; 452 | } 453 | }); 454 | } 455 | 456 | /////////////////////////////////////////////////////////////////////////////////////////// 457 | #pragma mark - Aspect Invoke Point 458 | 459 | // This is a macro so we get a cleaner stack trace. 460 | #define aspect_invoke(aspects, info) \ 461 | for (AspectIdentifier *aspect in aspects) {\ 462 | [aspect invokeWithInfo:info];\ 463 | if (aspect.options & AspectOptionAutomaticRemoval) { \ 464 | aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \ 465 | } \ 466 | } 467 | 468 | // This is the swizzled forwardInvocation: method. 469 | static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) { 470 | NSCParameterAssert(self); 471 | NSCParameterAssert(invocation); 472 | SEL originalSelector = invocation.selector; 473 | SEL aliasSelector = aspect_aliasForSelector(invocation.selector); 474 | invocation.selector = aliasSelector; 475 | AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector); 476 | AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector); 477 | AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation]; 478 | NSArray *aspectsToRemove = nil; 479 | 480 | // Before hooks. 481 | aspect_invoke(classContainer.beforeAspects, info); 482 | aspect_invoke(objectContainer.beforeAspects, info); 483 | 484 | // Instead hooks. 485 | BOOL respondsToAlias = YES; 486 | if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) { 487 | aspect_invoke(classContainer.insteadAspects, info); 488 | aspect_invoke(objectContainer.insteadAspects, info); 489 | }else { 490 | Class klass = object_getClass(invocation.target); 491 | do { 492 | if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) { 493 | [invocation invoke]; 494 | break; 495 | } 496 | }while (!respondsToAlias && (klass = class_getSuperclass(klass))); 497 | } 498 | 499 | // After hooks. 500 | aspect_invoke(classContainer.afterAspects, info); 501 | aspect_invoke(objectContainer.afterAspects, info); 502 | 503 | // If no hooks are installed, call original implementation (usually to throw an exception) 504 | if (!respondsToAlias) { 505 | invocation.selector = originalSelector; 506 | SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName); 507 | if ([self respondsToSelector:originalForwardInvocationSEL]) { 508 | ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation); 509 | }else { 510 | [self doesNotRecognizeSelector:invocation.selector]; 511 | } 512 | } 513 | 514 | // Remove any hooks that are queued for deregistration. 515 | [aspectsToRemove makeObjectsPerformSelector:@selector(remove)]; 516 | } 517 | #undef aspect_invoke 518 | 519 | /////////////////////////////////////////////////////////////////////////////////////////// 520 | #pragma mark - Aspect Container Management 521 | 522 | // Loads or creates the aspect container. 523 | static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) { 524 | NSCParameterAssert(self); 525 | SEL aliasSelector = aspect_aliasForSelector(selector); 526 | AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector); 527 | if (!aspectContainer) { 528 | aspectContainer = [AspectsContainer new]; 529 | objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN); 530 | } 531 | return aspectContainer; 532 | } 533 | 534 | static AspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) { 535 | NSCParameterAssert(klass); 536 | AspectsContainer *classContainer = nil; 537 | do { 538 | classContainer = objc_getAssociatedObject(klass, selector); 539 | if (classContainer.hasAspects) break; 540 | }while ((klass = class_getSuperclass(klass))); 541 | 542 | return classContainer; 543 | } 544 | 545 | static void aspect_destroyContainerForObject(id self, SEL selector) { 546 | NSCParameterAssert(self); 547 | SEL aliasSelector = aspect_aliasForSelector(selector); 548 | objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN); 549 | } 550 | 551 | /////////////////////////////////////////////////////////////////////////////////////////// 552 | #pragma mark - Selector Blacklist Checking 553 | 554 | static NSMutableDictionary *aspect_getSwizzledClassesDict() { 555 | static NSMutableDictionary *swizzledClassesDict; 556 | static dispatch_once_t pred; 557 | dispatch_once(&pred, ^{ 558 | swizzledClassesDict = [NSMutableDictionary new]; 559 | }); 560 | return swizzledClassesDict; 561 | } 562 | 563 | static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) { 564 | static NSSet *disallowedSelectorList; 565 | static dispatch_once_t pred; 566 | dispatch_once(&pred, ^{ 567 | disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil]; 568 | }); 569 | 570 | // Check against the blacklist. 571 | NSString *selectorName = NSStringFromSelector(selector); 572 | if ([disallowedSelectorList containsObject:selectorName]) { 573 | NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName]; 574 | AspectError(AspectErrorSelectorBlacklisted, errorDescription); 575 | return NO; 576 | } 577 | 578 | // Additional checks. 579 | AspectOptions position = options&AspectPositionFilter; 580 | if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) { 581 | NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc."; 582 | AspectError(AspectErrorSelectorDeallocPosition, errorDesc); 583 | return NO; 584 | } 585 | 586 | if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) { 587 | NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName]; 588 | AspectError(AspectErrorDoesNotRespondToSelector, errorDesc); 589 | return NO; 590 | } 591 | 592 | // Search for the current class and the class hierarchy IF we are modifying a class object 593 | if (class_isMetaClass(object_getClass(self))) { 594 | Class klass = [self class]; 595 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); 596 | Class currentClass = [self class]; 597 | do { 598 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 599 | if ([tracker.selectorNames containsObject:selectorName]) { 600 | 601 | // Find the topmost class for the log. 602 | if (tracker.parentEntry) { 603 | AspectTracker *topmostEntry = tracker.parentEntry; 604 | while (topmostEntry.parentEntry) { 605 | topmostEntry = topmostEntry.parentEntry; 606 | } 607 | NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)]; 608 | AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription); 609 | return NO; 610 | }else if (klass == currentClass) { 611 | // Already modified and topmost! 612 | return YES; 613 | } 614 | } 615 | }while ((currentClass = class_getSuperclass(currentClass))); 616 | 617 | // Add the selector as being modified. 618 | currentClass = klass; 619 | AspectTracker *parentTracker = nil; 620 | do { 621 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 622 | if (!tracker) { 623 | tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker]; 624 | swizzledClassesDict[(id)currentClass] = tracker; 625 | } 626 | [tracker.selectorNames addObject:selectorName]; 627 | // All superclasses get marked as having a subclass that is modified. 628 | parentTracker = tracker; 629 | }while ((currentClass = class_getSuperclass(currentClass))); 630 | } 631 | 632 | return YES; 633 | } 634 | 635 | static void aspect_deregisterTrackedSelector(id self, SEL selector) { 636 | if (!class_isMetaClass(object_getClass(self))) return; 637 | 638 | NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict(); 639 | NSString *selectorName = NSStringFromSelector(selector); 640 | Class currentClass = [self class]; 641 | do { 642 | AspectTracker *tracker = swizzledClassesDict[currentClass]; 643 | if (tracker) { 644 | [tracker.selectorNames removeObject:selectorName]; 645 | if (tracker.selectorNames.count == 0) { 646 | [swizzledClassesDict removeObjectForKey:tracker]; 647 | } 648 | } 649 | }while ((currentClass = class_getSuperclass(currentClass))); 650 | } 651 | 652 | @end 653 | 654 | @implementation AspectTracker 655 | 656 | - (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent { 657 | if (self = [super init]) { 658 | _trackedClass = trackedClass; 659 | _parentEntry = parent; 660 | _selectorNames = [NSMutableSet new]; 661 | } 662 | return self; 663 | } 664 | - (NSString *)description { 665 | return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, parent:%p>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.parentEntry]; 666 | } 667 | 668 | @end 669 | 670 | /////////////////////////////////////////////////////////////////////////////////////////// 671 | #pragma mark - NSInvocation (Aspects) 672 | 673 | @implementation NSInvocation (Aspects) 674 | 675 | // Thanks to the ReactiveCocoa team for providing a generic solution for this. 676 | - (id)aspect_argumentAtIndex:(NSUInteger)index { 677 | const char *argType = [self.methodSignature getArgumentTypeAtIndex:index]; 678 | // Skip const type qualifier. 679 | if (argType[0] == _C_CONST) argType++; 680 | 681 | #define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0) 682 | if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { 683 | __autoreleasing id returnObj; 684 | [self getArgument:&returnObj atIndex:(NSInteger)index]; 685 | return returnObj; 686 | } else if (strcmp(argType, @encode(SEL)) == 0) { 687 | SEL selector = 0; 688 | [self getArgument:&selector atIndex:(NSInteger)index]; 689 | return NSStringFromSelector(selector); 690 | } else if (strcmp(argType, @encode(Class)) == 0) { 691 | __autoreleasing Class theClass = Nil; 692 | [self getArgument:&theClass atIndex:(NSInteger)index]; 693 | return theClass; 694 | // Using this list will box the number with the appropriate constructor, instead of the generic NSValue. 695 | } else if (strcmp(argType, @encode(char)) == 0) { 696 | WRAP_AND_RETURN(char); 697 | } else if (strcmp(argType, @encode(int)) == 0) { 698 | WRAP_AND_RETURN(int); 699 | } else if (strcmp(argType, @encode(short)) == 0) { 700 | WRAP_AND_RETURN(short); 701 | } else if (strcmp(argType, @encode(long)) == 0) { 702 | WRAP_AND_RETURN(long); 703 | } else if (strcmp(argType, @encode(long long)) == 0) { 704 | WRAP_AND_RETURN(long long); 705 | } else if (strcmp(argType, @encode(unsigned char)) == 0) { 706 | WRAP_AND_RETURN(unsigned char); 707 | } else if (strcmp(argType, @encode(unsigned int)) == 0) { 708 | WRAP_AND_RETURN(unsigned int); 709 | } else if (strcmp(argType, @encode(unsigned short)) == 0) { 710 | WRAP_AND_RETURN(unsigned short); 711 | } else if (strcmp(argType, @encode(unsigned long)) == 0) { 712 | WRAP_AND_RETURN(unsigned long); 713 | } else if (strcmp(argType, @encode(unsigned long long)) == 0) { 714 | WRAP_AND_RETURN(unsigned long long); 715 | } else if (strcmp(argType, @encode(float)) == 0) { 716 | WRAP_AND_RETURN(float); 717 | } else if (strcmp(argType, @encode(double)) == 0) { 718 | WRAP_AND_RETURN(double); 719 | } else if (strcmp(argType, @encode(BOOL)) == 0) { 720 | WRAP_AND_RETURN(BOOL); 721 | } else if (strcmp(argType, @encode(bool)) == 0) { 722 | WRAP_AND_RETURN(BOOL); 723 | } else if (strcmp(argType, @encode(char *)) == 0) { 724 | WRAP_AND_RETURN(const char *); 725 | } else if (strcmp(argType, @encode(void (^)(void))) == 0) { 726 | __unsafe_unretained id block = nil; 727 | [self getArgument:&block atIndex:(NSInteger)index]; 728 | return [block copy]; 729 | } else { 730 | NSUInteger valueSize = 0; 731 | NSGetSizeAndAlignment(argType, &valueSize, NULL); 732 | 733 | unsigned char valueBytes[valueSize]; 734 | [self getArgument:valueBytes atIndex:(NSInteger)index]; 735 | 736 | return [NSValue valueWithBytes:valueBytes objCType:argType]; 737 | } 738 | return nil; 739 | #undef WRAP_AND_RETURN 740 | } 741 | 742 | - (NSArray *)aspects_arguments { 743 | NSMutableArray *argumentsArray = [NSMutableArray array]; 744 | for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) { 745 | [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null]; 746 | } 747 | return [argumentsArray copy]; 748 | } 749 | 750 | @end 751 | 752 | /////////////////////////////////////////////////////////////////////////////////////////// 753 | #pragma mark - AspectIdentifier 754 | 755 | @implementation AspectIdentifier 756 | 757 | + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error { 758 | NSCParameterAssert(block); 759 | NSCParameterAssert(selector); 760 | NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc. 761 | if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) { 762 | return nil; 763 | } 764 | 765 | AspectIdentifier *identifier = nil; 766 | if (blockSignature) { 767 | identifier = [AspectIdentifier new]; 768 | identifier.selector = selector; 769 | identifier.block = block; 770 | identifier.blockSignature = blockSignature; 771 | identifier.options = options; 772 | identifier.object = object; // weak 773 | } 774 | return identifier; 775 | } 776 | 777 | - (BOOL)invokeWithInfo:(id)info { 778 | NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature]; 779 | NSInvocation *originalInvocation = info.originalInvocation; 780 | NSUInteger numberOfArguments = self.blockSignature.numberOfArguments; 781 | 782 | // Be extra paranoid. We already check that on hook registration. 783 | if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) { 784 | AspectLogError(@"Block has too many arguments. Not calling %@", info); 785 | return NO; 786 | } 787 | 788 | // The `self` of the block will be the AspectInfo. Optional. 789 | if (numberOfArguments > 1) { 790 | [blockInvocation setArgument:&info atIndex:1]; 791 | } 792 | 793 | void *argBuf = NULL; 794 | for (NSUInteger idx = 2; idx < numberOfArguments; idx++) { 795 | const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx]; 796 | NSUInteger argSize; 797 | NSGetSizeAndAlignment(type, &argSize, NULL); 798 | 799 | if (!(argBuf = reallocf(argBuf, argSize))) { 800 | AspectLogError(@"Failed to allocate memory for block invocation."); 801 | return NO; 802 | } 803 | 804 | [originalInvocation getArgument:argBuf atIndex:idx]; 805 | [blockInvocation setArgument:argBuf atIndex:idx]; 806 | } 807 | 808 | [blockInvocation invokeWithTarget:self.block]; 809 | 810 | if (argBuf != NULL) { 811 | free(argBuf); 812 | } 813 | return YES; 814 | } 815 | 816 | - (NSString *)description { 817 | 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]; 818 | } 819 | 820 | - (BOOL)remove { 821 | return aspect_remove(self, NULL); 822 | } 823 | 824 | @end 825 | 826 | /////////////////////////////////////////////////////////////////////////////////////////// 827 | #pragma mark - AspectsContainer 828 | 829 | @implementation AspectsContainer 830 | 831 | - (BOOL)hasAspects { 832 | return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0; 833 | } 834 | 835 | - (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options { 836 | NSParameterAssert(aspect); 837 | NSUInteger position = options&AspectPositionFilter; 838 | switch (position) { 839 | case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break; 840 | case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break; 841 | case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break; 842 | } 843 | } 844 | 845 | - (BOOL)removeAspect:(id)aspect { 846 | for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)), 847 | NSStringFromSelector(@selector(insteadAspects)), 848 | NSStringFromSelector(@selector(afterAspects))]) { 849 | NSArray *array = [self valueForKey:aspectArrayName]; 850 | NSUInteger index = [array indexOfObjectIdenticalTo:aspect]; 851 | if (array && index != NSNotFound) { 852 | NSMutableArray *newArray = [NSMutableArray arrayWithArray:array]; 853 | [newArray removeObjectAtIndex:index]; 854 | [self setValue:newArray forKey:aspectArrayName]; 855 | return YES; 856 | } 857 | } 858 | return NO; 859 | } 860 | 861 | - (NSString *)description { 862 | return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects]; 863 | } 864 | 865 | @end 866 | 867 | /////////////////////////////////////////////////////////////////////////////////////////// 868 | #pragma mark - AspectInfo 869 | 870 | @implementation AspectInfo 871 | 872 | @synthesize arguments = _arguments; 873 | 874 | - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation { 875 | NSCParameterAssert(instance); 876 | NSCParameterAssert(invocation); 877 | if (self = [super init]) { 878 | _instance = instance; 879 | _originalInvocation = invocation; 880 | } 881 | return self; 882 | } 883 | 884 | - (NSArray *)arguments { 885 | // Lazily evaluate arguments, boxing is expensive. 886 | if (!_arguments) { 887 | _arguments = self.originalInvocation.aspects_arguments; 888 | } 889 | return _arguments; 890 | } 891 | 892 | @end 893 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/Category/XcodeHeaders.h: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeHeaders.h 3 | // XcodeMediaLibraryTweak 4 | // 5 | // Created by wangyang on 5/10/15. 6 | // Copyright (c) 2015 IGIU. All rights reserved. 7 | // 8 | 9 | #ifndef XcodeMediaLibraryTweak_XcodeHeaders_h 10 | #define XcodeMediaLibraryTweak_XcodeHeaders_h 11 | 12 | 13 | @interface DVTLibraryAssetView : NSView 14 | @property(retain, nonatomic) NSImage *image; // @synthesize image=_image; 15 | @property(copy, nonatomic) NSString *title; // @synthesize title=_title; 16 | @property(copy, nonatomic) NSString *summary; // @synthesize summary=_summary; 17 | 18 | @end 19 | 20 | @interface IDEMediaResourceVariantSet : NSObject 21 | @property(readonly, nonatomic) NSSet *resources; // IDEMediaResource的集合 22 | @property(copy, nonatomic) NSString *name; 23 | @end 24 | 25 | @interface DVTLibraryAsset : NSObject 26 | @property(retain) NSImage *image; // 缩略图 27 | @property(retain) IDEMediaResourceVariantSet *representedObject; 28 | @end 29 | 30 | @interface DVTPrimitiveFileDataType : NSObject 31 | - (id)displayName; 32 | - (id)identifier; 33 | 34 | @end 35 | 36 | @interface DVTFilePath : NSObject{ 37 | NSString *_pathString; 38 | NSURL *_fileURL; 39 | 40 | } 41 | @property(readonly) NSDictionary *fileSystemAttributes; 42 | @property(readonly) NSDictionary *fileAttributes; 43 | @property(readonly) NSString *fileTypeAttribute; 44 | @property(readonly) NSString *fileName; 45 | @property(readonly) NSURL *fileURL; 46 | @property(readonly) NSArray *pathComponents; 47 | @property(readonly) NSString *pathString; 48 | 49 | @end 50 | 51 | @interface IDEMediaResource : NSObject 52 | @property(readonly, nonatomic) DVTFilePath *sourceFilePath; 53 | @property(readonly, nonatomic) DVTPrimitiveFileDataType *contentType; // @synthesize contentType=_contentType; 54 | 55 | @end 56 | 57 | 58 | @interface DVTAssetAndGroupSet : NSObject 59 | { 60 | BOOL _isObservingAsset; 61 | DVTLibraryAssetView *_view; 62 | DVTLibraryAsset *_asset; 63 | NSSet *_groups; 64 | } 65 | 66 | + (id)observedAssetKeyPaths; 67 | @property(readonly) DVTLibraryAsset *asset; // @synthesize asset=_asset; 68 | @property(readonly) DVTLibraryAssetView *view; // @synthesize view=_view; 69 | 70 | - (id)initWithAsset:(id)arg1 andGroups:(id)arg2; 71 | 72 | @end 73 | #endif 74 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.wy.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | DVTPlugInCompatibilityUUIDs 24 | 25 | C4A681B0-4A26-480E-93EC-1218098B9AA0 26 | AD68E85B-441B-4301-B564-A45E4919A6AD 27 | A16FF353-8441-459E-A50C-B071F53F51B7 28 | 9F75337B-21B4-4ADC-B558-F9CADF7073A7 29 | E969541F-E6F9-4D25-8158-72DC3545A6C6 30 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 31 | AABB7188-E14E-4433-AD3B-5CD791EAD9A3 32 | 992275C1-432A-4CF7-B659-D84ED6D42D3F 33 | 7FDF5C7A-131F-4ABB-9EDC-8C5F8F0B8A90 34 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 35 | CC0D0F4F-05B3-431A-8F33-F84AFCB2C651 36 | 37 | NSHumanReadableCopyright 38 | Copyright © 2015 IGIU. All rights reserved. 39 | NSPrincipalClass 40 | WYTweak 41 | XC4Compatible 42 | 43 | XCPluginHasUI 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/MediaLibrayTweakSetting.h: -------------------------------------------------------------------------------- 1 | // 2 | // MediaLibrayTweakSetting.h 3 | // XcodeMediaLibraryTweak 4 | // 5 | // Created by wangyang on 6/3/15. 6 | // Copyright (c) 2015 IGIU. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MediaLibrayTweakSetting : NSWindowController 12 | @property (nonatomic, strong) NSColor *color; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/MediaLibrayTweakSetting.m: -------------------------------------------------------------------------------- 1 | // 2 | // MediaLibrayTweakSetting.m 3 | // XcodeMediaLibraryTweak 4 | // 5 | // Created by wangyang on 6/3/15. 6 | // Copyright (c) 2015 IGIU. All rights reserved. 7 | // 8 | 9 | #import "MediaLibrayTweakSetting.h" 10 | 11 | @interface MediaLibrayTweakSetting () 12 | { 13 | __weak IBOutlet NSColorWell *_colorWell; 14 | } 15 | @end 16 | 17 | @implementation MediaLibrayTweakSetting 18 | 19 | - (void)windowDidLoad { 20 | [super windowDidLoad]; 21 | _colorWell.color = self.color; 22 | } 23 | 24 | - (IBAction)colorChange:(NSColorWell *)sender { 25 | self.color = sender.color; 26 | 27 | // 保存 28 | NSData *colorData = [NSArchiver archivedDataWithRootObject:sender.color]; 29 | [[NSUserDefaults standardUserDefaults] setObject:colorData forKey:@"MediaLibrayTweakColor"]; 30 | } 31 | 32 | - (NSColor *)color{ 33 | if (!_color) { 34 | NSData *colorData = [[NSUserDefaults standardUserDefaults] dataForKey:@"MediaLibrayTweakColor"]; 35 | 36 | if (!colorData) { 37 | _color = [NSColor blackColor]; 38 | }else{ 39 | _color = [NSUnarchiver unarchiveObjectWithData:colorData]; 40 | } 41 | } 42 | 43 | return _color; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/MediaLibrayTweakSetting.xib: -------------------------------------------------------------------------------- 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 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/WYTweak.h: -------------------------------------------------------------------------------- 1 | // 2 | // WYTweak.h 3 | // XcodeMediaLibraryTweak 4 | // 5 | // Created by wangyang on 5/10/15. 6 | // Copyright (c) 2015 IGIU. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WYTweak : NSObject 12 | 13 | + (instancetype)sharedPlugin; 14 | @end 15 | -------------------------------------------------------------------------------- /XcodeMediaLibraryTweak/WYTweak.m: -------------------------------------------------------------------------------- 1 | // 2 | // WYTweak.m 3 | // XcodeMediaLibraryTweak 4 | // 5 | // Created by wangyang on 5/10/15. 6 | // Copyright (c) 2015 IGIU. All rights reserved. 7 | // 8 | 9 | #import "WYTweak.h" 10 | #import "XcodeHeaders.h" 11 | #import "Aspects.h" 12 | #import 13 | #import "MediaLibrayTweakSetting.h" 14 | 15 | static WYTweak *sharedPlugin; 16 | 17 | @interface WYTweak() 18 | { 19 | MediaLibrayTweakSetting *_setting; 20 | } 21 | @property (nonatomic, strong, readwrite) NSBundle *bundle; 22 | @end 23 | 24 | 25 | @implementation WYTweak 26 | + (void)pluginDidLoad:(NSBundle *)plugin 27 | { 28 | static dispatch_once_t onceToken; 29 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 30 | if ([currentApplicationName isEqual:@"Xcode"]) { 31 | dispatch_once(&onceToken, ^{ 32 | sharedPlugin = [[self alloc] init]; 33 | }); 34 | } 35 | } 36 | 37 | + (instancetype)sharedPlugin 38 | { 39 | return sharedPlugin; 40 | } 41 | 42 | - (void)showSettingPanel{ 43 | [_setting showWindow:nil]; 44 | } 45 | 46 | - (instancetype)init { 47 | if (self = [super init]) { 48 | [[NSNotificationCenter defaultCenter] addObserver:self 49 | selector:@selector(applicationDidFinishLaunching:) 50 | name:NSApplicationDidFinishLaunchingNotification 51 | object:nil]; 52 | } 53 | return self; 54 | } 55 | 56 | - (void)applicationDidFinishLaunching:(NSNotification*)noti { 57 | 58 | NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Window"]; 59 | if (editMenuItem) { 60 | [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]]; 61 | 62 | NSMenuItem *newMenuItem = [[NSMenuItem alloc] initWithTitle:@"MediaLibrayTweak" action:@selector(showSettingPanel) keyEquivalent:@""]; 63 | 64 | [newMenuItem setTarget:self]; 65 | [[editMenuItem submenu] addItem:newMenuItem]; 66 | 67 | } 68 | 69 | _setting = [[MediaLibrayTweakSetting alloc] initWithWindowNibName:@"MediaLibrayTweakSetting"]; 70 | 71 | 72 | [objc_getClass("DVTLibraryAssetView") aspect_hookSelector:@selector(drawRect:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo) { 73 | 74 | DVTLibraryAssetView *view = [aspectInfo instance]; 75 | 76 | NSValue *value = objc_getAssociatedObject(view, NSSelectorFromString(@"imageSize")); 77 | NSSize imageSize = [value sizeValue]; 78 | if (NSEqualSizes(NSZeroSize, imageSize)) { 79 | return ; 80 | } 81 | 82 | NSString *imageSizeString = [NSString stringWithFormat:@"size %.0fx%.0f", imageSize.width, imageSize.height]; 83 | [imageSizeString drawAtPoint:NSMakePoint(60, 40) withAttributes:@{NSFontAttributeName:[NSFont systemFontOfSize:12], NSForegroundColorAttributeName:_setting.color}]; 84 | 85 | } error:NULL]; 86 | 87 | // DVTAssetAndGroupSet把DVTLibraryAsset和DVTLibraryAssetView联系到了一起 88 | [objc_getClass("DVTAssetAndGroupSet") aspect_hookSelector:@selector(initWithAsset:andGroups:) withOptions:AspectPositionAfter usingBlock:^(id aspectInfo) { 89 | 90 | DVTAssetAndGroupSet *assetAndGroupSet = [aspectInfo instance]; 91 | 92 | DVTLibraryAsset *asset = assetAndGroupSet.asset; 93 | DVTLibraryAssetView *view = assetAndGroupSet.view; 94 | 95 | // 从asset中得到image大小 96 | // 资源存在1位、2倍、3倍图时,resourceSet就会有多个元素了 97 | id representedObject = asset.representedObject; 98 | if (![NSStringFromClass([representedObject class]) isEqualToString:@"IDEMediaResourceVariantSet"]) { 99 | return ; 100 | } 101 | 102 | NSSet *resourceSet = asset.representedObject.resources; 103 | 104 | // 随便取一个资源出来,在image size时,sdk会自动帮助我们识别图片大小 105 | IDEMediaResource *resouce = [resourceSet anyObject]; 106 | 107 | // 只有为图像资源时,才进行大小识别 108 | DVTPrimitiveFileDataType *fileType = resouce.contentType; 109 | NSString *typeString = [fileType displayName]; 110 | 111 | if ([typeString containsString:@"Image"]) { 112 | DVTFilePath *file = resouce.sourceFilePath; 113 | NSImage *image = [[NSImage alloc] initWithContentsOfURL:file.fileURL]; 114 | NSValue *sizeValue = [NSValue valueWithSize:[image size]]; 115 | 116 | objc_setAssociatedObject(view, NSSelectorFromString(@"imageSize"), sizeValue, OBJC_ASSOCIATION_RETAIN); 117 | } 118 | 119 | 120 | 121 | } error:NULL]; 122 | } 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /helpImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igiu1988/XcodeMediaLibraryTweak/6d9bb9430777abcb47fb45ab3f9a7c46b59f6694/helpImage.png --------------------------------------------------------------------------------