a) Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
1) C、C++都是 编写代码-->编译连接-->运行
2) 而OC则可以在运行的时候动态的去修改,例如动态的去调用自身类或者其他类的方法,或者增加、交换方法的实现
b) Objective-C的动态性是由Runtime API来支撑的
c) Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
a) &(按位与)如果大家都是1结果才是1,其他都是0,用来取出或设置特定的位
b) |(按位或)只要有一个1结果就是1,其他都是,用来取出或设置特定的位
c) ~(位运算取反)
#import
@interface MJPerson : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end
#import "MJPerson.h"
// &可以用来取出特定的位
// 0000 0111
//&0000 0100
//------
// 0000 0100
// 掩码,一般用来按位与(&)运算的
// 宏的替换就是字符串替换所以要注意运算先后次序
//#define MJTallMask 1
//#define MJRichMask 2
//#define MJHandsomeMask 4
//#define MJTallMask 0b00000001
//#define MJRichMask 0b00000010
//#define MJHandsomeMask 0b00000100
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
@interface MJPerson()
{
char _tallRichHansome;
}
@end
@implementation MJPerson
// 0010 1010
//&1111 1101
//----------
// 0010 1000
- (instancetype)init
{
if (self = [super init]) {
_tallRichHansome = 0b00000100;
}
return self;
}
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHansome |= MJTallMask;
} else {
_tallRichHansome &= ~MJTallMask;
}
}
- (BOOL)isTall
{
// 只占1位 返回的也是1位 然而boll类型是一个字节所以会强制变成8位(全部用你当前的结果覆盖其余的位)那么解决办法 !!(2个感叹号) 或者 定义结构体的时候占2位
// 结果用BOLL强制转 或者 !!(2个感叹号)
return !!(_tallRichHansome & MJTallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHansome |= MJRichMask;
} else {
_tallRichHansome &= ~MJRichMask;
}
}
- (BOOL)isRich
{
return !!(_tallRichHansome & MJRichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHansome |= MJHandsomeMask;
} else {
_tallRichHansome &= ~MJHandsomeMask;
}
}
- (BOOL)isHandsome
{
return !!(_tallRichHansome & MJHandsomeMask);
}
@end
d) 用结构体的方式实现
e) 结构体是支持位域 字节排布 从低到高 从右到左
f) 位运算的效率比直接取值效率高
#import "MJPerson.h"
//#define MJTallMask (1<<0)
//#define MJRichMask (1<<1)
//#define MJHandsomeMask (1<<2)
@interface MJPerson()
{
// 位域
// 结构体是支持位域 字节排布 从低到高 从右到左
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
}
@end
@implementation MJPerson
- (void)setTall:(BOOL)tall
{
_tallRichHandsome.tall = tall;
}
- (BOOL)isTall
{
return !!_tallRichHandsome.tall;
}
- (void)setRich:(BOOL)rich
{
_tallRichHandsome.rich = rich;
}
- (BOOL)isRich
{
return !!_tallRichHandsome.rich;
}
- (void)setHandsome:(BOOL)handsome
{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)isHandsome
{
return !!_tallRichHandsome.handsome;
}
@end
g) 用共用体的方式实现
1) 共用体:大家共用一块内存
2) 结构体:结构体内的成员都是单独存在的,占不同的内存
#import "MJPerson.h"
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)
@interface MJPerson()
{
union {
int bits;
struct {
char tall : 4;
char rich : 4;
char handsome : 4;
char thin : 4;
};
} _tallRichHandsome;
}
@end
@implementation MJPerson
- (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);
}
- (void)setThin:(BOOL)thin
{
if (thin) {
_tallRichHandsome.bits |= MJThinMask;
} else {
_tallRichHandsome.bits &= ~MJThinMask;
}
}
- (BOOL)isThin
{
return !!(_tallRichHandsome.bits & MJThinMask);
}
@end
h) 例如kvo或者autoresizingMask是怎么知道传入的不同按位或的值呢?
1) 最终传入的值与自身进行按位与如果成立则包含了自身;
a) 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
b) 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
c) 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
d) 64位之前isa就是Class类型,是isa_t类型
e) isa详解-位域
a) 类对象/元类对象的地址值最后三位都是0,因为isa底层原理
b) 一开始是只有class_ro_t,然后创建class_rw_t并且拷贝class_ro_t的内容,并且将bits指向class_rw_t
c) class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
d) class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
e) method_t是对方法/函数的封装
f) Type Encoding
g) cache(方法缓存)
1) Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
2) 方法缓存cache调用过的方法直接扔到缓存里面,下次再调用直接从cache调用
3) 缓存源码实现查找
3.1) objc-cache.mm
3.2) bucket_t * cache_t::find(cache_key_t k, id receiver)
h) 散列表
1) 存的时候通过一个位运算去存,前面没有的就会设置为NULL(牺牲内存空间来换取执行效率/空间换时间)
2) 散列表扩容的时候是在原来容量基础上X2,并且清空原来缓存的数据,重新开始缓存
3) 存或者取的时候有可能是一样的 那么做法是-1或者+1取,重新生成index
a) OC中的方法调用,其实都是转换为objc_msgSend函数的调用
b) objc_msgSend的执行流程可以分为3大阶段
1) 消息发送
2) 动态方法解析
3) 消息转发
c) 源码跟读
d) 消息发送
1) 查找方式
1.1) 二分查找(排好序的 从中间开始分然后查找)
1.2) 线性查找(普通的for循环遍历查找)
e) 动态方法解析
1) 开发者可以实现以下方法,来动态添加方法实现
1.1) +resolveInstanceMethod:// 对象方法
1.2)
+resolveClassMethod:// 类方法
2) 动态解析过后,会重新走“消息发送”的流程
2.1) “从receiverClass的cache中查找方法”这一步开始执行
f) 消息转发
1) 这个步骤你就开始找不到源码了
2) 你自己的类没有能力处理这个方法
3) 元类对象:是一种特殊的类对象,类对象的类对象
4) 类方法也是有消息转发机制的
5) 开发者可以在forwardInvocation:方法中自定义任何逻辑
6) 生成NSMethodSignature方式
a) @dynamic (告诉编译器不要自动生成setter&getter方法的实现、不要自动生成成员变量)
b) @synthesize (自动生成setter&getter方法的实现和自动生成成员变量)
c) super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
1) struct objc_super2
1.1) receiver是消息接收者
1.2) current_class是receiver的Class对象-->superclass找父类对象
2) SEL
a) Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)
1) OC --> 中间代码(.ll文件) --> 汇编、机器代码
b) 可以使用以下命令行指令生成中间代码
1) clang -emit-llvm -S main.m
c) 语法简介
1) @ - 全局变量
2) % - 局部变量
3) alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
4) i32 - 32位4字节的整数
5) align - 对齐
6) load - 读出
7) store 写入
8) icmp - 两个整数值比较,返回布尔值
9) br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
10) label - 代码标签
11) call - 调用函数
d) 官方文档:
1) https://llvm.org/docs/LangRef.html
a) 查看私有成员变量
1) 设置UITextField占位文字的颜色
b) 字典转模型
1) 利用Runtime遍历所有的属性或者成员变量
2) 利用KVC设值
c) 替换方法实现
1) class_replaceMethod
2) method_exchangeImplementations
a) 类
1) 动态创建一个类(参数:父类,类名,额外的内存空间)
1.1) Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
2) 注册一个类(要在类注册之前添加成员变量)
2.1) void objc_registerClassPair(Class cls)
3) 销毁一个类
3.1) void objc_disposeClassPair(Class cls)
4) 获取isa指向的Class
4.1) Class object_getClass(id obj)
5) 设置isa指向的Class
5.1) Class object_setClass(id obj, Class cls)
6) 判断一个OC对象是否为Class
6.1) BOOL object_isClass(id obj)
7) 判断一个Class是否为元类
7.1) BOOL class_isMetaClass(Class cls)
8) 获取父类
8.1) Class class_getSuperclass(Class cls)
b) 成员变量
1) 获取一个实例变量信息
1.1) Ivar class_getInstanceVariable(Class cls, const char *name)
2) 拷贝实例变量列表(最后需要调用free释放)
2.1) Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
3) 设置和获取成员变量的值
3.1) void object_setIvar(id obj, Ivar ivar, id value)
3.2) id object_getIvar(id obj, Ivar ivar)
4) 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
4.1) BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
5) 获取成员变量的相关信息
5.1) const char *ivar_getName(Ivar v)
5.2) const char *ivar_getTypeEncoding(Ivar v)
c) 属性
1) 获取一个属性
1.1) objc_property_t class_getProperty(Class cls, const char *name)
2) 拷贝属性列表(最后需要调用free释放)
2.1) objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
3) 动态添加属性
3.1) BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
4) 动态替换属性
4.1) void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
5) 获取属性的一些信息
5.1) const char *property_getName(objc_property_t property)
5.2) const char *property_getAttributes(objc_property_t property)
d) 方法
1) 获得一个实例方法、类方法
1.1) Method class_getInstanceMethod(Class cls, SEL name)
1.2) Method class_getClassMethod(Class cls, SEL name)
2) 方法实现相关操作
2.1) IMP class_getMethodImplementation(Class cls, SEL name)
2.2) IMP method_setImplementation(Method m, IMP imp)
2.3) void method_exchangeImplementations(Method m1, Method m2)
3) 拷贝方法列表(最后需要调用free释放)
3.1) Method *class_copyMethodList(Class cls, unsigned int *outCount)
4) 动态添加方法
4.1) BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
5) 动态替换方法
5.1) IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
6) 获取方法的相关信息(带有copy的需要调用free去释放)
6.1) SEL method_getName(Method m)
6.2) IMP method_getImplementation(Method m)
6.3) const char *method_getTypeEncoding(Method m)
6.4) unsigned int method_getNumberOfArguments(Method m)
6.5) char *method_copyReturnType(Method m)
6.6) char *method_copyArgumentType(Method m, unsigned int index)
7) 选择器相关
7.1) const char *sel_getName(SEL sel)
7.2) SEL sel_registerName(const char *str)
8) 用block作为方法实现
8.1) IMP imp_implementationWithBlock(id block)
8.2) id imp_getBlock(IMP anImp)
8.3) BOOL imp_removeBlock(IMP anImp)
a) objc_msgSendSuper 和 objc_msgSendSuper2区别
b) 数组可以当成指针来用
c) 分类的方法尽量+属于自己的前缀区分,万一覆盖了呢
d) 方法替换:替换系统的方法 例如拦截整个项目中所有按钮的点击事件
e) hook:钩子函数 拦截系统的方法 塞入自己的实现
f) 类簇(cu):我们看到的类型不一定是他的真实类型 NSString、NSArray、NSDictionary,真实类型是其他类型
g) 转成底层代码的方式
1) 转成cpp文件(作为参考)
2) 程序运行起来查看汇编
3) 转成汇编
a) isKindOfClass // 传入的对象的类对象是否==判断的类对象或者他的子类
b) isMemberOfClass // 传入的对象的类对象是否==判断的类对象
c) super class的返回类型是谁取决于方法调用者是谁即:消息接收者 消息接收者仍然是子类对象,只不过从父类的方法开始寻找方法
c) 底层实现原理
a) 函数调用堆栈的问题
1) 局部变量分配在栈空间
2) 栈空间分配,从高地址到低地址
b) 寄存器(存储在cpu里面,相当于一个元件,参数都是存储在这里面)比内存的效率更高