本文将列举一些常用的 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