利用swizzle打造切换主题的变色技术

307 阅读2分钟

一、swizzle

简单来说,swizzle有如下几个常用的方法

1. 给系统类增加成员变量

#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@interface UILabel (Associate)
- (nonatomic, strong) UIColor *FlashColor;
@end

#import "UILabel+Associate.h"
@implementation UILabel (Associate)
	
static char flashColorKey;
- (void) setFlashColor:(UIColor *) flashColor{
    objc_setAssociatedObject(self, &flashColorKey, flashColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIColor *) getFlashColor{
   return objc_getAssociatedObject(self, &flashColorKey);
}
@end

2. 调换IMP

+ (void)swizzleInstanceMethod:(Class)class originSelector:	(SEL)originSelector otherSelector:(SEL)otherSelector
{
 	Method otherMehtod = class_getInstanceMethod(class, otherSelector);
Method originMehtod = class_getInstanceMethod(class, originSelector);
// 交换2个方法的实现
method_exchangeImplementations(otherMehtod, originMehtod);
}

3. 动态增加方法

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
 
@interface EmptyClass:NSObject
@end
 
@implementation EmptyClass
@end
 
void sayHello(id self, SEL _cmd) {
    NSLog(@"Hello");
}
 
- (void)addMethod {
    class_addMethod([EmptyClass class], @selector(sayHello2), (IMP)sayHello, "v@:");
 
    // Test Method
    EmptyClass *instance = [[EmptyClass alloc] init];
    [instance sayHello2];
}

其中types参数为"i@:@“,按顺序分别表示:

i:返回值类型int,若是v则表示void  
@:参数id(self)  
::SEL(_cmd)  
@:id(str)

这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档

4. 获取某个类的成员变量或属性

unsigned int numIvars; //成员变量个数
Ivar *vars = class_copyIvarList(NSClassFromString(@"UIView"), &numIvars);
//Ivar *vars = class_copyIvarList([UIView class], &numIvars);
    
NSString *key=nil;
for(int i = 0; i < numIvars; i++) {

   Ivar thisIvar = vars[i];
   key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];  //获取成员变量的名字
   NSLog(@"variable name :%@", key);
   key = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)]; //获取成员变量的数据类型
   NSLog(@"variable type :%@", key);
}
free(vars);

5. 获取成员函数

Method *meth = class_copyMethodList(NSClassFromString(@"UIView"), &numIvars);
//Method *meth = class_copyMethodList([UIView class], &numIvars);
    
for(int i = 0; i < numIvars; i++) {
   Method thisIvar = meth[i];
   
   SEL sel = method_getName(thisIvar);
   const char *name = sel_getName(sel);
   
   NSLog(@"zp method :%s", name);
}
free(meth);

二、基础换主题思路

主题我们可以将其划分为颜色图片两大块.

1. 颜色的替换

我们可以设置不同的key,将key对应的颜色值作为value,放在苹果的plist表中。当切换新主题的时候,我们去加载不同的plist即可

2. 图片替换

图片的话只要设置好对应目录即可。给出相同的名字,然后从网上下载好资源,本地建立一样名称的文件夹,并设置好即可。切换主题的时候,从不同目录去加载。

三、优化技巧

按照上诉方法大家在进行编码的时候是不是会发现代码很凌乱,通知或者委托到处在。这里提供一个简单的技巧。
我们利用swizzle在对系统控件加入一个reloadAppearance 方法,用来设将我们给系统设置好的属性给其赋值。

第一步. 在UIView中

#import <objc/runtime.h>
@implementation UIView (Custom)
#pragma mark - ISkinProtocol

- (void)reloadAppearance
{
    // 遍历所有view reloadAppearance
    for (UIView* subView in self.subviews) {
        [subView reloadAppearance];
    }
    
    [self setNeedsDisplay];
}

第二步. 由于其他子View如UILabel都继承自UIView,这里只举一个例子,UILabel的写法:

#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@interface UILabel (Custom)
- (nonatomic, assgin) int skinNormalTextColor;
@end

#import "UILabel+Custom.h"
@implementation UILabel (Custom)
	
- (void)reloadAppearance
{
[super reloadAppearance];//遍历子view
self.skinTextColorNormal = self.skinTextColorNormal;//核心点,会设置上textColor的颜色
}
static NSString *skinNormalTextColorKey = @"skinNormalTextColorKey";
- (int)skinTextColorNormal
{
    NSNumber* skinTextColorNormal = objc_getAssociatedObject(self, skinNormalTextColorKey);
    return [skinTextColorNormal intValue];
}

- (void)setSkinTextColorNormal:(int)skinTextColorNormal
{
    objc_setAssociatedObject(self, skinNormalTextColorKey, [NSNumber numberWithInt:skinTextColorNormal], OBJC_ASSOCIATION_RETAIN);
    
    if (skinTextColorNormal != kColorInvalid) {
        [self setTextColor:__QQGLOBAL_COLOR_USEDEFAULT(skinTextColorNormal, self.skinIsSetDefault)];
    }
}
@end

第三步. 使用方法:

Appdelegate中提供一个reloadpublic方法即可:

[self.navigationcontroller.view reloadAppearance];

这样就会从View递归自动遍历到UILabel,UIImageView那些控件了。

小结:

之前在网上看到的思路在代码编写的时候,用了发送通知或者delegate的方法去通知界面刷新,但是只处理了图片,在颜色上没有很好的处理。而且代码显得十分臃肿。有效的利用swizzle我们使我们的代码的结构和可读性大大增强,减少代码量。

demo下载链接:

变色demo

参考链接:

1.IOS使用 swizzle 解决一些错误
2.Objective-C Runtime 运行时之四:Method Swizzling
3.Objective-C的hook方案(一): Method Swizzling
4.Objective-C Runtime Reference
5.iOS运行时获取对象的成员变量和成员方法