├── .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 | 
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
--------------------------------------------------------------------------------