本文将列举一些常用的 API
Working with Classes class_getName
1 const char * class_getName(Class cls)
作用:获取类名的字符串
返回值:如果类是 Nil
返回空串
class_getSuperclass
1 Class class_getSuperclass(Class cls)
作用:获取一个类的父类
返回值:如果传入的是根类或者 Nil
返回 Nil
Discussion:你可以使用 NSObject
的 superclass
方法代替它
1 BOOL class_isMetaClass(Class cls)
class_getInstanceSize
1 size_t class_getInstanceSize(Class cls)
作用:返回一个类的大小(单位:字节)
返回值:如果传入 Nil
返回 0
class_addIvar
1 BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
作用:给一个类动态添加一个成员变量
返回值:如果添加成功,返回 YES
,否则返回 NO
Discussion:这个方法将会在 objc_allocateClassPair
之后 objc_registerClassPair
之前调用,给一个已经存在的类添加成员变量并不支持。而且我们不能给元类添加成员变量。
class_copyIvarList
1 Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
作用:描述一个类的成员变量
返回值:返回指向第一个成员变量的 Ivar 指针
Discussion:传入的 outCount
将告诉我们这个类有多少个成员变量。
###
class_getIvarLayout
###
class_getWeakIvarLayout
###
class_setIvarLayout
###
class_setWeakIvarLayout
ivarLayout
和 weakIvarLayout
分别记录了哪些 ivar 是 strong
或是 weak
,都未记录的就是基本类型和 __unsafe_unretained
的对象类型。这两个值可以通过 runtime 提供的几个 API 来访问:
1 2 3 4 const uint8_t *class_getIvarLayout(Class cls) const uint8_t *class_getWeakIvarLayout(Class cls) void class_setIvarLayout(Class cls, const uint8_t *layout) void class_setWeakIvarLayout(Class cls, const uint8_t *layout)
但我们几乎没可能用到这几个 API,IvarLayout 的值由 runtime 确定,没必要关心它的存在,但为了解决上述问题,我们试着破解了 IvarLayout 的编码方式
class_getProperty
1 objc_property_t class_getProperty(Class cls, const char *name)
作用:返回一个属性
返回值:如果类没有声明或者属性不存在返回 Nil
class_copyPropertyList
1 objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
作用:描述一个类的属性列表
返回值:返回一个 objc_property_t
类型的指针数组描述声明的类的属性。传入的 outCount
表示属性个数
class_addMethod
1 BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
作用:给一个类动态的添加方法
返回值:添加成功返回 YES
否则 NO
注意:types 参数是方法的参数类型,详见:Type Encoding
Discussion:该方法可以覆盖父类的实现,但是不能添加类中已有的实现如果要改变类中以后的实现请使用 method_setImplementation
示例:
1 2 3 4 5 6 7 void myMethodIMP(id self , SEL _cmd){ } you can dynamically add it to a class as a method (called resolveThisMethodDynamically) like this : class_addMethod([self class ], @selector (resolveThisMethodDynamically), (IMP) myMethodIMP, "v@:" );
class_getInstanceMethod
1 Method class_getInstanceMethod(Class aClass, SEL aSelector)
作用:从一个类中获取一个实例方法
返回值:当该类中或者父类中都没有该方法 返回 NULL
注意:注意该方法如果在父类中寻找实现,时因为自己调用了 class_copyMethodList
方法而没有寻找到实现。
class_getClassMethod
1 Method class_getClassMethod(Class aClass, SEL aSelector)
class_copyMethodList
1 Method * class_copyMethodList(Class cls, unsigned int *outCount)
作用:描述一个类中的实例方法列表
返回值:返回一个指向 Method
类型的指针数组。
注意:父类的实例方法不会被列入其中
举例:
1 2 3 4 5 6 7 8 9 10 11 12 @interface Person : NSObject @property (strong , nonatomic ) NSString *name;@property (assign , nonatomic ) int age;@property (assign , nonatomic ) double weight;- (void )text; + (void )run; @end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @implementation Person - (void )text { NSLog (@"text ---- %@" , self ->_name); } - (void )dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self ]; } + (void )run { NSLog (@"run" ); } @end
1 2 3 4 5 6 7 8 unsigned int outCount = 0 ; Method *methodList = class_copyMethodList([Person class ], &outCount); for (NSInteger i = 0 ; i < outCount; i++) { NSString *name = NSStringFromSelector (method_getName(methodList[i])); NSLog (@"%@" , name); }
打印结果如下:
1 2 3 4 5 6 7 8 9 2014-11-12 10:57:14.536 test[785:55071] age 2014-11-12 10:57:14.536 test[785:55071] setAge: 2014-11-12 10:57:14.536 test[785:55071] setWeight: 2014-11-12 10:57:14.536 test[785:55071] .cxx_destruct 2014-11-12 10:57:14.536 test[785:55071] dealloc 2014-11-12 10:57:14.536 test[785:55071] name 2014-11-12 10:57:14.537 test[785:55071] weight 2014-11-12 10:57:14.537 test[785:55071] setName: 2014-11-12 10:57:14.537 test[785:55071] text
类方法并没有被打印。
class_replaceMethod
1 IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
class_getMethodImplementation
1 IMP class_getMethodImplementation(Class cls, SEL name)
class_respondsToSelector
1 BOOL class_respondsToSelector(Class cls, SEL sel)
class_addProtocol
1 BOOL class_addProtocol(Class cls, Protocol *protocol)
class_addProperty
1 BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
class_replaceProperty
1 void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
1 BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
class_copyProtocolList
1 Protocol ** class_copyProtocolList(Class cls, unsigned int *outCount)
class_copyProtocolList
1 Protocol ** class_copyProtocolList(Class cls, unsigned int *outCount)
class_copyProtocolList
1 Protocol ** class_copyProtocolList(Class cls, unsigned int *outCount)
Working with Instance Variables ivar_getName
1 const char * ivar_getName( Ivar ivar)
ivar_getTypeEncoding
1 const char * ivar_getTypeEncoding( Ivar ivar)
Working with Methods method_invoke_stret
1 void method_invoke_stret(id receiver, Method m, ...)
作用:如果返回值时一个结构体,调用这个方法
注意:调用这个方法来实现比 method_getImplementation
要快
method_getName
1 SEL method_getName( Method method)
method_getImplementation
1 IMP method_getImplementation( Method method)
作用:获取方法实现
返回值:是一个 IMP
类型的函数指针
method_getNumberOfArguments
1 unsigned method_getNumberOfArguments( Method method)
method_exchangeImplementations
1 void method_exchangeImplementations( Method m1, Method m2)
作用:交换两个方法的方法实现
注意:这时原子形式的,是线程安全的。
1 2 3 4 IMP imp1 = method_getImplementation(m1); IMP imp2 = method_getImplementation(m2); method_setImplementation(m1, imp2); method_setImplementation(m2, imp1);
Working with Selectors sel_getName
1 const char * sel_getName(SEL aSelector)
sel_isEqual
1 BOOL sel_isEqual(SEL lhs, SEL rhs)
runtime 实际开发中用法列举 关于 runtime 的知识写了这么多,具体在工作中应该怎样使用呢?
由于苹果是不开源的,因此我们要使用一些系统的框架或者控件时,往往因为其给我们提供的接口太少而不能有效的实现我们的需求。因此,runtime 就派上用场了。
虽然我们无法直接窥探苹果系统内部的结构,但通过 runtime 系统的运行和方法,我们是可以在运行时获取到系统内部类的成员变量、方法、属性以及所遵守的协议的。此时,我们大概就能清楚其内部时如何运作的,这也要归功于 iOS 开发比较讲究的命名规范,我们一看到成员变量的意思,大致就能猜到其作用了。
runtime 基本用法 下面就演示一下如何获取系统内部类的成员变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 UIButton *button = [UIButton buttonWithType:(UIButtonTypeSystem )];unsigned int outCount = 0 ; Ivar *ivarList = class_copyIvarList([UIButton class ], &outCount); for (NSInteger i = 0 ; i < outCount; i++) { NSString *ivarName = @(ivar_getName(ivarList[i])); NSLog (@"%@" , ivarName); }
打印结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2014-11-12 11:57:36.215 test[912:89888] _externalFlatEdge 2014-11-12 11:57:36.215 test[912:89888] _contentLookup 2014-11-12 11:57:36.215 test[912:89888] _contentEdgeInsets 2014-11-12 11:57:36.216 test[912:89888] _titleEdgeInsets 2014-11-12 11:57:36.216 test[912:89888] _imageEdgeInsets 2014-11-12 11:57:36.216 test[912:89888] _backgroundView 2014-11-12 11:57:36.216 test[912:89888] _floatingContentView 2014-11-12 11:57:36.216 test[912:89888] _contentBackdropView 2014-11-12 11:57:36.216 test[912:89888] _imageView 2014-11-12 11:57:36.216 test[912:89888] _titleView 2014-11-12 11:57:36.217 test[912:89888] _initialized 2014-11-12 11:57:36.217 test[912:89888] _lastDrawingControlState 2014-11-12 11:57:36.217 test[912:89888] _selectGestureRecognizer 2014-11-12 11:57:36.217 test[912:89888] _buttonFlags 2014-11-12 11:57:36.217 test[912:89888] _effectiveContentView 2014-11-12 11:57:36.218 test[912:89888] _maskAnimationView 2014-11-12 11:57:36.218 test[912:89888] _selectionView 2014-11-12 11:57:36.218 test[912:89888] _lazyTitleViewFont 2014-11-12 11:57:36.218 test[912:89888] _contentConstraints 2014-11-12 11:57:36.218 test[912:89888] _internalTitlePaddingInsets
可以看到,其内部是有很多成员变量的,而且通过他们的名字,不难猜到他们的作用。
在工作中我们可以使用 KVC 修改其中某个成员变量的值以达到我们的需求。具体见 KVC 。
MJExtension 实现字典转模型的原理 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 Person *p = [[Person alloc] init]; NSDictionary *dict; unsigned int count = 0 ; Ivar *vars = class_copyIvarList([Person class ], &count); for (int i = 0 ; i < count; i++) { Ivar var = vars[i]; const char *ivarName = ivar_getName(var); const char *ivarEncoding = ivar_getTypeEncoding(var); NSString *ivarNameOC = [NSString stringWithUTF8String:ivarName]; [p setValue:dict[ivarNameOC] forKey:ivarNameOC]; }
runtime 在归档中的应用 在 iOS 开发中,归档是一件比较繁琐的事情,因为有可能你的成员变量有很多几十个甚至上百个。这时我们归档如果再一个一个 encodeObject:
是件很麻烦的事。这时我们通过运行时遍历所有类的成员变量,然后通过 ivar_getName
方法获取变量名,再转成 OC 字符串就可以带入到上述 encodeObject:
方法了。几行代码就可以完成。
黑魔法:Method swizzle 在工作中就遇到过这样的需求。当时苹果刚发布 iOS7,iOS7改动是非常大的,它将立体化改为平面化。促使性能极大提高,但同时,为以前的项目所涉及的图片大多就不适合继续使用了。这时,我们就需要对用户的设备进行检测,如果是 iOS6 则使用旧图片,而 iOS7 则换成新图片。但是项目文件非常多,我们不可能将所有的的系统自带的 -imageNamed:
方法替换成自己的方法。其实我们可以这样去实现:
假设项目中我们的代码时这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #import "ViewController.h" @interface ViewController ()@property (weak , nonatomic ) IBOutlet UIImageView *faceImageView;@property (weak , nonatomic ) IBOutlet UIImageView *vipImageView;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; self .faceImageView.image = [UIImage imageNamed:@"face" ]; self .vipImageView.image = [UIImage imageNamed:@"vip" ]; }
以下是项目正在使用的图片
如果需求要在不改变源代码的情况下,将图片换成:
目前项目运行效果是这样的:
首先,我们需要给 UIImage 写一个分类,添加以下方法,以实现我们的需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @implementation UIImage (AMExtension )+ (UIImage *)AM_imageNamed:(NSString *)name { BOOL isIOS7 = [[UIDevice currentDevice].systemVersion floatValue] >= 7.0 ; UIImage *image = nil ; if (isIOS7) { NSString *newName = [name stringByAppendingString:@"_os7" ]; image = [UIImage imageNamed:newName]; } else { image = [UIImage imageNamed:name]; } return image; } @end
然后,也是关键的一步,就是在分类一开始加载到内存中的时候,我们必须让 runtime 系统了解一件事,就是当程序运行时,系统的 imageNamed:
方法的实现被改成 AM_imageNamed:
方法。这样就大功告成,代码如下:
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 @implementation UIImage (AMExtension )+ (void )load { Method originalMethod = class_getClassMethod(self , @selector (imageNamed:)); Method ourMethod = class_getClassMethod(self , @selector (AM_imageNamed:)); method_exchangeImplementations(originalMethod, ourMethod); } + (UIImage *)AM_imageNamed:(NSString *)name { BOOL isIOS7 = [[UIDevice currentDevice].systemVersion floatValue] >= 7.0 ; UIImage *image = nil ; if (isIOS7) { NSString *newName = [name stringByAppendingString:@"_os7" ]; image = [UIImage AM_imageNamed:newName]; } else { image = [UIImage AM_imageNamed:name]; } return image; }
我们并没有改变原来的代码,但是运行效果如下:
同理,你还可以使用 Method Swizzle 去替换系统其他的方法,一些你认为你能写的更好的,在项目中。
Demo 见:Swizzle