UI_APPEARANCE_SELECTOR协议的属性交换技术

3

我正在尝试交换UIView的backgroundColor属性。

在进行交换之前,我执行以下操作:

@implementation UIView (Cat1)
+(void)load {
NSArray *selectors = @[
                     //Highliter swizzling
                     NSStringFromSelector(@selector(setBackgroundColor:)),
                     NSStringFromSelector(@selector(backgroundColor))
                     ];

[[self appearance] setBackgroundColor:[UIColor redColor]];

for (NSString *name in selectors) {
    SEL originalSelector = NSSelectorFromString(name);
    SEL swizzledSelector = NSSelectorFromString([NSString stringWithFormat:@"swizzled_%@",name]);
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
    class_addMethod(self,
                    originalSelector,
                    class_getMethodImplementation(self, originalSelector),
                    method_getTypeEncoding(originalMethod));
    class_addMethod(self,
                    swizzledSelector,
                    class_getMethodImplementation(self, swizzledSelector),
                    method_getTypeEncoding(swizzledMethod));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

然后,在调用交换方法时,我除了调用原始方法外什么也不做:
-(void)swizzled_setBackgroundColor:(UIColor *)color {

[self iio_swizzled_setBackgroundColor:color];
}

然后,发生了以下崩溃:2017-03-08 19:04:37.863 Develop-InsertViewer[69285:7916872] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Please file a radar on UIKit if you see this assertion.' *** First throw call stack: ( 0 CoreFoundation 0x0000000108506e65 __exceptionPreprocess + 165 1 libobjc.A.dylib 0x0000000107c7ddeb objc_exception_throw + 48 2 CoreFoundation 0x0000000108506cca +[NSException raise:format:arguments:] + 106 3 Foundation 0x00000001078ca5a2 -[NSAssertionHandler handleFailureInFunction:file:lineNumber:description:] + 169 4 UIKit 0x0000000106227b35 PushNextClassForSettingIMP + 469 5 UIKit 0x000000010622810a TaggingAppearanceObjectSetterIMP + 37 6 InsertFramework 0x00000001071332be -[UIView(Cat1) swizzled_setBackgroundColor:] + 62 7 UIKit 0x0000000105d78fca -[UILabel _commonInit] + 238 8 UIKit 0x0000000105d7913d -[UILabel initWithFrame:] + 94 9 UIKit 0x0000000105b9cc4e -[UIView init] + 62 10 0x0000000105533300 _TTOFCSo7UILabelcfT_S_ + 16 11 0x0000000105532e44 _TFCSo7UILabelCfT_S_ + 68 12 0x0000000105569be3 _TFC12InsertViewer21StarterViewControllercfT5coderCSo7NSCoder_GSqS0__ + 51 13 0x0000000105569d2d _TToFC12InsertViewer21StarterViewControllercfT5coderCSo7NSCoder_GSqS0__ + 45 14 UIKit 0x0000000105ecb7db -[UIClassSwapper initWithCoder:] + 241 15 UIKit 0x000000010609f822 UINibDecoderDecodeObjectForValue + 705 16 UIKit 0x000000010609f558 -[UINibDecoder decodeObjectForKey:] + 278 17 UIKit 0x0000000105ecb4b1 -[UIRuntimeConnection initWithCoder:] + 180 18 UIKit 0x000000010609f822 UINibDecoderDecodeObjectForValue + 705 19 UIKit 0x000000010609f9e3 UINibDecoderDecodeObjectForValue + 1154 20 UIKit 0x000000010609f558 -[UINibDecoder decodeObjectForKey:] + 278 21 UIKit 0x0000000105eca6c3 -[UINib instantiateWithOwner:options:] + 1255 22 UIKit 0x0000000106232c40 -[UIStoryboard instantiateViewControllerWithIdentifier:] + 181 23 UIKit 0x0000000106232d93 -[UIStoryboard instantiateInitialViewController] + 69 24 UIKit 0x0000000105b0dfa6 -[UIApplication _loadMainStoryboardFileNamed:bundle:] + 94 25 UIKit 0x0000000105b0e2d6 -[UIApplication _loadMainInterfaceFile] + 260 26 UIKit 0x0000000105b0cb54 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1390 27 UIKit 0x0000000105b09e7b -[UIApplication workspaceDidEndTransaction:] + 188 28 FrontBoardServices 0x000000010b41e754 -[FBSSerialQueue _performNext] + 192 29 FrontBoardServices 0x000000010b41eac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
如果我在其他类上执行[self appearance],例如UIToolBar,也会发生这种情况。有什么想法吗?
1个回答

2
外观系统将所有属性瓶颈到单个实现TaggingAppearanceObjectSetterIMP中。该实现使用_UIAppearanceTagObjectForSelector检查选择器的名称,并在关联对象缓存(存储在__UIAppearanceCustomizedSelectorsAssociationKey中)中查找其名称。如果还没有,它将调用_TagForSelectorWithAxes来确定映射到该选择器的内容,并更新缓存。

您的运行时技巧使一些非常复杂的运行时技巧更加复杂。不难理解为什么会出问题... :D

基本问题是方法的名称必须是setBackgroundColor,而不是swizzled_setBackgroundColor。您可以通过交换实际实现函数而不是方法来解决这个问题。您可以查看NSNotificationCenter+RNSwizzle.m以了解手动执行此操作的示例。

即使解决了这个问题,我也不会感到惊讶您遇到其他微妙的问题。我的建议是(好吧,我的建议是“永远不要篡改生产代码,因为它太脆弱了”,但如果您忽略了这个建议...)在Hopper中查看外观代码,并跟踪实现,以确保一切配对。这并不是复杂,Hopper的伪代码模式几乎可以展示所有情况(我花了大约2分钟来研究我发布的内容)。话虽如此,这是非常棘手的实现细节,它可能会在没有警告的情况下轻易更改(导致新的崩溃)。如果您可以在不篡改的情况下完成此操作,则强烈建议这样做。


1
引用《第五元素》中的米拉·乔沃维奇的话: 大爆炸...谢谢你,罗布! - yanivH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接