Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
Objective-C的动态性是由Runtime API来支撑的
Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
isa详解
- 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(
union
)结构,还使用位域来存储更多的信息
typedef unsigned long uintptr_t; 8个字节,64位
union isa_t
{
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
isa详解 – 位域
nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息
has_assoc
是否有设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
shiftcls
存储着Class、Meta-Class对象的内存地址信息
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
extra_rc
里面存储的值是引用计数器减1
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
共用体(union)例子:
#import
@interface Person : NSObject
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end
#import "Person.h"
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
@interface Person()
{
union {
***************
char占用1个字节,8位
***************
char bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= MJTallMask;
} else {
_tallRichHandsome.bits &= ~MJTallMask;
}
}
- (BOOL)isTall
{
return !!(_tallRichHandsome.bits & MJTallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome.bits |= MJRichMask;
} else {
_tallRichHandsome.bits &= ~MJRichMask;
}
}
- (BOOL)isRich
{
return !!(_tallRichHandsome.bits & MJRichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome.bits |= MJHandsomeMask;
} else {
_tallRichHandsome.bits &= ~MJHandsomeMask;
}
}
- (BOOL)isHandsome
{
return !!(_tallRichHandsome.bits & MJHandsomeMask);
}
@end
Class的结构
苹果开源源码https://opensource.apple.com/tarballs/objc4/
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; 方法缓存
class_data_bits_t bits; 用于获取具体的类信息
}
struct class_rw_t {
......
private:
using ro_or_rw_ext_t = objc::PointerUnion;
}
struct class_rw_ext_t {
**************************
成员变量是只读的,在类objc_registerClassPair之后就不能添加了
**************************
const class_ro_t *ro;
**************************
方法是可读写的,objc_registerClassPair之后也可以添加
**************************
method_array_t methods; //方法列表
property_array_t properties; //属性列表
protocol_array_t protocols; //协议列表
char *demangledName;
uint32_t version;
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance对象z占用的内存空间
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
class_rw_ext_t
里面的methods、properties、protocols是二维数组,是可读可写的,- 包含了类的初始内容、分类的内容class_ro_t
里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容method_t
是对方法\函数的封装
struct method_t {
SEL name; 函数名
const char *types; 编码(返回值类型、参数类型)
MethodListIMP imp; 指向函数的指针(函数地址)
...
};
IMP代表函数的具体实现
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
可以通过@selector()
和sel_registerName()
获得
可以通过sel_getName()
和NSStringFromSelector()
转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的
typedef struct objc_selector *SEL;
types包含了函数返回值、参数编码的字符串
iOS中提供了一个叫做
@encode
的指令,可以将具体的类型表示成字符串编码
方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
struct cache_t {
explicit_atomic _buckets; 散列表
explicit_atomic _mask; 散列表的长度
mask_t _occupied; 已经缓存的方法数量
}
struct bucket_t {
explicit_atomic _imp; 函数内存地址
explicit_atomic _sel; sel作为key
}
缓存查找
objc-cache.mm
ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
......
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set(sel, imp, cls);
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
......
}
__arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
objc_msgSend执行流程
OC中的方法调用,其实都是转换为objc_msgSend函数的调用
例如:
NSObject *obj = [[NSObject alloc] init];
[obj init];
[NSObject initialize];
***********************
消息接收者(receiver):obj
消息名称:init
***********************
objc_msgSend(obj, @selector(init));
***********************
消息接收者(receiver):[NSObject class]
消息名称:initialize
***********************
objc_msgSend([NSObject class], @selector(initialize));
objc_msgSend的执行流程可以分为3大阶段
1. 消息发送
源码https://opensource.apple.com/tarballs/objc4/
objc-msg-arm64.s
ENTRY _objc_msgSend
.....
END_ENTRY _objc_msgSend
objc-runtime-new.mm
lookUpImpOrForward
源码流程如上,东西太多,就不解析了
执行流程如下图
2. 动态方法解析
源码如下:
// No implementation found. Try method resolver once.
*************************
动态方法解析
*************************
if (slowpath(behavior & LOOKUP_RESOLVER)) {
*************************
标志位,执行过后就不在进入该方法
*************************
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
.....
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
*************************
进入消息发送
*************************
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
动态解析:
#import "Person.h"
#import
@implementation Person
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
****************************
类C方法动态解析
****************************
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
- (void)other
{
NSLog(@"%s", __func__);
}
****************************
实例C方法动态解析
****************************
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 动态添加test方法的实现
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
****************************
实例OC方法动态解析
****************************
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
*****************************
Method可以理解为等价于
struct method_t {
SEL name; 函数名
const char *types; 编码(返回值类型、参数类型)
MethodListIMP imp; 指向函数的指针(函数地址)
...
}
*****************************
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
3. 消息转发
源码没有开源,下面是伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
实例方法示例:
@interface Student : NSObject
- (void)test;
@end
@implementation Student
- (void)test
{
NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
- (void)test;
@end
#import "Person.h"
#import
#import "Student.h"
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
***********************
objc_msgSend([[Student alloc] init], aSelector)
如果 return nil 就进入 methodSignatureForSelector:方法;
***********************
return [[Student alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
*****************************
方法签名:返回值类型、参数类型
*****************************
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
***********************
- (void)test转为C++
void test(id * self, SEL _cmd)
void->v id->@ SEL->: 16->两个参数都是指针占用16个字节,其中id类型参数从0开始
SEL类型参数从第8个字节开始
type也可以省略数字v@:
***********************
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
***********************
NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
anInvocation.target 方法调用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]
***********************
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
Student *s = [[Student alloc] init];
SEL sel = anInvocation.selector;
if ([s respondsToSelector:sel]) {
[anInvocation invokeWithTarget:s];
}else{
[self doesNotRecognizeSelector:sel];
}
}
@end
调用
Person *person = [[Person alloc] init];
[person test];
类方法示例:
@interface Student : NSObject
+ (void)test;
@end
@implementation Student
+ (void)test{
NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
+ (void)test;
@end
#import "Person.h"
#import
#import "Student.h"
@implementation Person
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) return [Student class];
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = anInvocation.selector;
if ([[Student class] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[Student class]];
}else{
[self doesNotRecognizeSelector:sel];
}
}
@end
调用
[Person test];
具体流程如下:
forwardInvocation:方法中自定义任何逻辑
以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
面试题:
1.讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
....
2.下面代码输出是什么,为什么
@interface Person : NSObject
- (void)run;
@end
@implementation Person
- (void)run
{
NSLog(@"%s", __func__);
}
@end
@interface Student : Person
@end
@implementation Student
- (void)run
{
[super run];
}
- (instancetype)init
{
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]); // Student
NSLog(@"[self superclass] = %@", [self superclass]); // Person
NSLog(@"--------------------------------");
NSLog(@"[super class] = %@", [super class]); // Student
NSLog(@"[super superclass] = %@", [super superclass]); // Person
}
return self;
}
@end
结果如下
[self class] = Student
[self superclass] = Person
--------------------------------
[super class] = Student
[super superclass] = Person
首先看[self class]和[self superclass]的底层实现
**************************
实例方法返回self的类对象
**************************
- (Class)class
{
return object_getClass(self); 返回self的isa指针指向的Class
}
**************************
实例方法返回self的superclass对象
**************************
- (Class)superclass
{
return class_getSuperclass(object_getClass(self)); 返回self的isa指针指向的Class的Superclass
}
接下来看run方法super调用
- (void)run
{
[super run];
}
转为C++
static void _I_Student_run(Student * self, SEL _cmd) {
struct objc_super arg = {(id)self, [Person class]};
objc_msgSendSuper(arg, @selector(run));
}
struct objc_super {
__unsafe_unretained _Nonnull id receiver; // 消息接收者
__unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};
********************************
the instance of the class that is to receive the
message and the superclass at which to start searching for the method implementation.
实例的类对象接收消息,也就是方法的调用者Student,方法从父类开始查找
********************************
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
[super message]的底层实现
1.消息接收者仍然是子类对象
2.从父类开始查找方法的实现
注意:
上面super调用转为C++代码是
static void _I_Student_run(Student * self, SEL _cmd) {
struct objc_super arg = {(id)self, [Person class]};
objc_msgSendSuper(arg, @selector(run));
}
实际上在运行过程中是转成了
static void _I_Student_run(Student * self, SEL _cmd) {
struct objc_super2 arg = {(id)self, [Student class]};
objc_msgSendSuper2(arg, @selector(run));
}
struct objc_super2 {
id receiver;
Class current_class;
};
_objc_msgSendSuper2的汇编源码
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL, _objc_msgSendSuper2
END_ENTRY _objc_msgSendSuper2
_objc_msgSendSuper2虽然接收的两个参数中的class是receiver的Class对象,但是方法寻找还是从父类寻找
证明:
- (void)run
{
**********************
断点
**********************
[super run];
}
在Debug Workflow->Always Show Disassembly中查看
Interview-super`-[Student run]:
......
0x100000d9e <+46>: call 0x100000f16 ; symbol stub for: objc_msgSendSuper2
.....
如果不想程序跑起来也可以通过Product->Perform Action ->Assemble "xxx.m"查看当前文件的汇编代码
3.以下代码能不能执行成功?如果可以,打印结果是什么?
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end
@implementation Person
- (void)print
{
NSLog(@"my name is %@", self->_name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Person class];
void *obj = &cls;
[(__bridge id)obj print];
}
@end
解答:
person的实例对象指向的内存地址的前8个字节(isa指针)存储的就是Person类对象的内存地址
obj对象指向的内存地址cls存储的是Person类对象
所以cls其实就是isa指针,所以obj就是Person的实例对象,可以调用print
self->_name调用是先找到person对象指向的内存地址,然后忽略isa,在找到_name
- (void)viewDidLoad {
[super viewDidLoad];
}
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
struct objc_super2 arg = {(id)self, [ViewController class]};
objc_msgSendSuper2(arg, @selector(ViewController));
}
viewDidLoad其实是创建了一个临时的结构体变量(先创建的,位于栈空间的高地址),其中有两个参数self 和 [ViewController class]
类似_name的查找,obj指向的内存地址先忽略cls指针,去找结构体的临时变量,然后忽略self
最终找到[ViewController class],所以最终打印的是
my name is
4.isKindOfClass和isMemberOfClass的区别
源码NSObject.mm中
先看实例方法源码实现
********************************
判断方法的调用者的类对象是否和传入的cls类型相同
********************************
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
********************************
判断方法的调用者的类对象是否和cls类型相同,或者是cls的子类
********************************
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
@interface Person : NSObject
@end
@implementation Person
@end
id person = [[Person alloc] init];
NSLog(@"%d", [person isMemberOfClass:[Person class]]); 1
NSLog(@"%d", [person isMemberOfClass:[NSObject class]]); 0
NSLog(@"%d", [person isKindOfClass:[Person class]]); 1
NSLog(@"%d", [person isKindOfClass:[NSObject class]]); 1
在看类方法实现
********************************
判断方法的调用者的元类对象是否和传入的cls类型相同
********************************
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
********************************
判断方法的调用者的元类对象是否和cls类型相同,或者是cls的子类
********************************
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
NSLog(@"%d", [Person isMemberOfClass:object_getClass([Person class])]); 1
NSLog(@"%d", [Person isMemberOfClass:object_getClass([NSObject class])]); 0
********************************
传入的不是元类对象
********************************
NSLog(@"%d", [Person isMemberOfClass:[NSObject class]]); 0
NSLog(@"%d", [Person isKindOfClass:object_getClass([NSObject class])]); 1
********************************
基元类的superclass->基类
********************************
NSLog(@"%d", [Person isKindOfClass:[NSObject class]]); 1
LLVM的中间代码(IR)
Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)
可以使用以下命令行指令生成中间代码
clang -emit-llvm -S main.m
语法简介
@ - 全局变量
% - 局部变量
alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
i32 - 32位4字节的整数
align - 对齐
load - 读出,store 写入
icmp - 两个整数值比较,返回布尔值
br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
label - 代码标签
call - 调用函数
具体可以参考官方文档:https://llvm.org/docs/LangRef.html
1.Runtime API – 类
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
销毁一个类
void objc_disposeClassPair(Class cls)
获取isa指向的Class
Class object_getClass(id obj)
设置isa指向的Class
Class object_setClass(id obj, Class cls)
判断一个OC对象是否为Class
BOOL object_isClass(id obj)
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
获取父类
Class class_getSuperclass(Class cls)
2.Runtime API – 成员变量
获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
示例(动态的创建类并添加成员变量和方法):
void run(id self, SEL _cmd)
{
NSLog(@"_____%@ - %@", self, NSStringFromSelector(_cmd));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
***********************
创建类
***********************
Class newClass = objc_allocateClassPair([NSObject class], "Person", 0);
***********************
成员变量是只读的,添加要在objc_registerClassPair之前
***********************
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
***********************
注册类
***********************
objc_registerClassPair(newClass);
id person = [[newClass alloc] init];
//KVC设值
[person setValue:@10 forKey:@"_age"];
[person setValue:@20 forKey:@"_weight"];
[person run];
NSLog(@"%@ %@", [person valueForKey:@"_age"], [person valueForKey:@"_weight"]);
// 在不需要这个类时释放
objc_disposeClassPair(newClass);
}
return 0;
}
_____ - run
10 20
示例(获取和设置成员变量,遍历一个类的所有成员变量):
@interface Person : NSObject
@property (assign, nonatomic) int age;
@property (copy, nonatomic) NSString *name;
@end
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
***********************
获取成员变量信息
***********************
Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
***********************
设置和获取成员变量的值
***********************
Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
Person *person = [[Person alloc] init];
object_setIvar(person, nameIvar, @"lilei");
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
NSLog(@"%@ %d", person.name, person.age);
***********************
遍历一个类的所有成员变量
***********************
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
}
return 0;
}
结果如下
_age i
lilei 10
_age i
_name @"NSString"
运用(字典转模型):
@interface NSObject (Json)
+ (instancetype)njf_objectWithJson:(NSDictionary *)json;
@end
#import "NSObject+Json.h"
#import
@implementation NSObject (Json)
+ (instancetype)njf_objectWithJson:(NSDictionary *)json
{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
[obj setValue:json[name] forKey:name];
}
free(ivars);
return obj;
}
@end
3.Runtime API – 属性
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
4.Runtime API – 方法
获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
运用(交换方法):
@interface Person : NSObject
- (void)run;
- (void)test;
@end
@implementation Person
- (void)run
{
NSLog(@"%s", __func__);
}
- (void)test
{
NSLog(@"%s", __func__);
}
@end
调用
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
}
return 0;
}
结果如下
-[Person test]
runtime关联对象
思考:如何实现给分类“添加成员变量”?
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
关联对象提供了以下API
- 添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
- 获得关联对象
id objc_getAssociatedObject(id object, const void * key)
- 移除所有的关联对象
void objc_removeAssociatedObjects(id object)
关联对象的原理
例子:
@interface Person (Test)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end
#import "Person+Test.h"
#import
@implementation Person (Test)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// 隐式参数
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(int)weight
{
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight
{
// _cmd == @selector(weight)
return [objc_getAssociatedObject(self, _cmd) intValue];
}
面试题:
什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
......