前言
做开发做了好几年,前几年写iOS代码,去年写Web端的代码。然后发现学到的东西有限,了解的东西确实很多但是都不够深入。今年重新做iOS开发,痛定思痛,来研究或者说来熟悉下iOS中的各项技术。后面的博客可能会参照各个大佬的博客加上我自己的一些理解。
技术更新
后面更新的博客主要为iOS开发内容,当然有空的时候我会去研究下前端和后端以及数据库、大数据、AI相关的东西。
废话不多说,先看下这一篇博客要说的东西,可能内容有点多。
runtime
runtime版本
OC2.0 之后的版本为Modern版本:运行与64位系统中
OC2.0以前是 legacy版本:老的32位程序
runtime 是用c和汇编写的
原理
- 消息传递机制
[obj method]被转化为objc_msgSend(obj,method)
1.通过obj的isa指针找到它的class
2.在class的methodList里面找到method
3.如果没找到,就到它的superclass里面去找
4.一旦找到这个函数,name就去执行这个函数的IMP
- objc_cache
每次去找methodlist然后找一遍会出现效率低的问题
objc_class的objc_cache来实现对常用方法的缓存。
意思就是将常用的方法缓存下来,运行时,先去找cache中是否有方法,然后再重复上述步骤。
那么这个cache是怎么实现的呢,大致就是将一个key和函数的IMP存起来,然后在执行的时候找到key去执行IMP,然后算法更新这个方法是不是常用的。
- 结构体
1.objc_object
isa就是这个结构体的指针
objc_object
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
2.objc_class
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
结构体说明:
isa -》class的isa指针
name - 》class 的名字
version - 》class 的版本
info - 》包含的信息
instance_size-》 实例大小
ivars-》属性列表
methodlists-》方法列表
cache -》方法cache
prtocols-》协议列表
3.obj_mehod
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
结构体说明:
method_name-》方法的名字
method_types-》方法的类型
method_imp-》方法的IMP
runtime应用
- 给分类关联属性
- 给类动态添加方法
- 方法交换
- 获取类的详细列表
- 其他
1.给分类关联属性
给分类添加属性主要用到下面三个方法
- 根据key设置关联对象的value
objc_setAssociatedObject(id object, void * key, id value, <objc_AssociationPolicy policy); - 根据key取出关联对象的value
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key); - 移除所有关联对象
objc_removeAssociatedObjects(id _Nonnull object);
代码
给ViewController添加一个分类,命名为Extension
创建的分类如下
分类的.h代码:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (Extension)
@property(nonatomic, copy)NSString *contentView;
@end
NS_ASSUME_NONNULL_END
分类的.m代码
#import "UIViewController+Extension.h"
#import <objc/runtime.h>
static const void *key = &key;
@implementation UIViewController (Extension)
- (void)setContentView:(UIView *)contentView{
objc_setAssociatedObject(self, &key, contentView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *)contentView{
return objc_getAssociatedObject(self, &key);
}
@end
在UIViewController中调用
self.contentView = @"hello";
NSLog(@"%@",self.contentView);
代码以及相关说明
这个key值一定要定义全局静态变量 一个key对应一个value
声明:static void *key = "key";
用法1:&key作为参数
用法2:写成@selector(value)作为参数,其中value就是你属性的名字,这样写也获取了一个静态无返回值的地址。
2.给类动态添加方法
1.直接给当前类动态添加一个方法
传过来的方法是不是想要的方法,如果是就添加
+(BOOL)resolveInstanceMethod:(SEL)sel;
给类添加一个方法,参数说明 cls:给那个类添加 name:方法名字 IMP:指针(可以根据IMP的定义定义一个IMP的指针)types:”v@:“(编码表示(v)无返回值 (@)对象 (:)选择器)
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types);
添加成功 return YES;
代码
在ViewController.m中
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(work)) {
class_addMethod([self class], sel,(IMP)work,"v@:");
}
return NO;
}
void work(id objct, SEL sel){
NSLog(@"i am working");
}
调用
[self performSelector:@selector(work)];
2.转给备用接收者
第一种方案:
-(id)forwardingTargetForSelector:(SEL)aSelector;
声明一个对象,return这个对象,让另外的对象去执行这个方法
代码:
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// if (sel == @selector(work)) {
// class_addMethod([self class], sel,(IMP)work,"v@:");
// }
return NO;
}
void work(id objct, SEL sel){
NSLog(@"i am working");
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(work)) {
Person *person = [Person new];
return person;
}
return nil;
}
调用
[self performSelector:@selector(work)];
新建Person类,在Person类中添加一个work方法即可
第二种方案:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
通过签名发送给anInvocation
-(void)forwardInvocation:(NSInvocation *)anInvocation;
[anInvocation invokeWithTarget:target];
在上面的方法中如果 target类中有anInvocation.selector,name就交给target类去执行这个方法,否则就执行[self doesNotRecognizeSelector:sel];
上面也称之为方法重定向
代码
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// if (sel == @selector(work)) {
// class_addMethod([self class], sel,(IMP)work,"v@:");
// }
return NO;
}
void work(id objct, SEL sel){
NSLog(@"i am working");
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
// if (aSelector == @selector(work)) {
// Person *person = [Person new];
// return person;
// }
return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(work)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [NSMethodSignature methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
Person *p = [Person new];
if ([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}else{
[self doesNotRecognizeSelector:sel];
}
}
调用
[self performSelector:@selector(work)];
3.方法交换
1.直接方法交换
获取类的方法 返回值为Method
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
交换方法
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
2.截取系统方法交换
在+(void)load{}中截取系统的方法并且交换 例如不想用系统的某个方法 需要自己实现,那么可以分类然后在分类中自定义一个方法 并且替换系统的方法。单例执行一次。
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
代码
- (void)exchageMethod{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method m1 = class_getInstanceMethod([self class], @selector(printA));
Method m2 = class_getInstanceMethod([self class], @selector(printB));
method_exchangeImplementations(m1, m2);
});
}
- (void)printA{
NSLog(@"A");
}
- (void)printB{
NSLog(@"B");
}
调用
[self exchageMethod];
[self printA];
结果会打印“B”;
4.获取类各种详细列表
- 获取属性名字
代码:
- (void)obtainPropertyName{
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
for (NSInteger i = 0; i<count; i++) {
NSString *name = @(property_getName(propertyList[i]));
NSString *attribute = @(property_getAttributes(propertyList[i]));
NSLog(@"%@ %@",name,attribute);
}
}
- 获取成员变量名字
- (void)obtainIvars{
unsigned int outCount = 0;
Ivar *ivarList = class_copyIvarList([Person class], &outCount);
for (NSInteger i = 0; i < outCount; i++) {
NSLog(@"%@",@(ivar_getName(ivarList[i])));
}
}
- 获取方法名字
- (void)obtainMthods{
unsigned int outCount = 0;
Method *methods = class_copyMethodList([Person class], &outCount);
for (NSInteger i = 0; i < outCount; i++) {
NSLog(@"%@", NSStringFromSelector(method_getName(methods[i])));
}
}
- 获取遵守的协议名字
- (void)obtainProtocols{
unsigned int outCount = 0;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &outCount);
for (NSInteger i = 0; i < outCount; i++) {
Protocol *pro = protocolList[i];
NSLog(@"%@", @(protocol_getName(pro)));
}
}
当然你如果要获取协议名字,那么你首先应该在这个类中遵守某个协议。
5.其他
- 字典转模型
代码(这个代码只是自己简单的实现,具体的字典转模型还需要考虑很多,以后有机会单独再写一篇博客)
+ (instancetype)modelWithDic:(NSDictionary *)dictionary{
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);
id p = [[self alloc]init];
for (int i = 0 ; i < outCount ; i ++ ) {
NSString *name = @(ivar_getName(ivars[i]));
if ([dictionary objectForKey:[name substringFromIndex:1]]) {
[p setValue:[dictionary objectForKey:[name substringFromIndex:1]] forKey:[name substringFromIndex:1]];
}
}
return p;
}
调用:
NSDictionary *dic = @{@"name":@"zhaoqian",@"sex":@"man",@"age":@"15"};
Person * nep = [Person modelWithDic:dic];
NSLog(@"%@\n%@\n%ld\n",nep.name,nep.sex,nep.age);
- 同一方法高频率调
代码:
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
- 编码解码(只要重写这两个方法就行,当然也必须遵守NSCoding协议)
代码:
- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
unsigned int outCout = 0;
Ivar *ivars = class_copyIvarList([self class], &outCout);
for (int i = 0 ; i < outCout; i++) {
NSString *name = @(ivar_getName(ivars[i]));
[aCoder encodeObject:[self valueForKey:name] forKey:name];
}
free(ivars);
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
self = [super init];
if (self) {
unsigned int outCout = 0;
Ivar *ivars = class_copyIvarList([self class], &outCout);
for (int i = 0 ; i < outCout; i++) {
NSString *name = @(ivar_getName(ivars[i]));
[aDecoder decodeObjectForKey:name];
}
free(ivars);
}
return self;
}
KVO
KVO 一对一
NSNotificationCenter 一对多
KVO可以监听单个属性的变化,也可以监听集合对象的变化
KVO原理
实际上是创建了一个 NSKVONotifying_XXX的子类,然后用runtime的isa-swizzling技术实现。
KVO实现
1.简单理解的kvo
kvo意思就是当属性的值变化的时候,监听这个变化的值,并且得到监听结果。那么可以在某个类中重写该属性的setter方法,然后回调回来接收,但是我写的不能动态的实现监听 ,要在每个属性的setter方法中去实现回调,实现了简单的kvo。
代码
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^callBack)(id);
@interface Person : NSObject
@property (nonatomic, copy)NSString *name;
@property (nonatomic, assign)NSInteger age;
@property (nonatomic, copy)callBack block;
- (void)addObserverForkeyPath:(NSString *)keyPath;
@end
NS_ASSUME_NONNULL_END
Person.m
#import "Person.h"
#import <objc/runtime.h>
@interface Person(){
NSString *_name;
NSInteger _age;
}
@property (nonatomic, strong)NSString *keyPath;
@end
@implementation Person
- (void)addObserverForkeyPath:(NSString *)keyPath{
if (!self.keyPath) {
return;
}
self.keyPath = keyPath;
}
- (void)setName:(NSString *)name{
if (_name!=name) {
_name = name;
if (self.block) {
self.block(name);
}
}
}
- (void)setAge:(NSInteger)age{
if (_age!=age) {
_age = age;
}
if (self.block) {
self.block(@(age));
}
}
调用:
Person *p = [Person new];
[p addObserverForkeyPath:@"name"];
p.block = ^(id info) {
//TODO
};
2.利用runtime的方法实现KVO
我将代码说明全部注释在代码中了,就直接贴代码了
创建一个NSObect的分类 分类中重新添加代码
NSObject+Extension.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^ValueChangeBlock)(id obeserverObject, NSString *key, id oldValue, id newValue);
@interface NSObject (Extension)
// 添加kvo并回调
-(void)addObserver:(NSObject *)observer forKey:(NSString *)key options:(NSKeyValueObservingOptions)options block:(ValueChangeBlock)callBack;
// 移除kvo
- (void)removeObserver:(NSObject *)observer forKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
NSObject+Extension.m
#import "NSObject+Extension.h"
#import <objc/runtime.h>
#import <objc/message.h>
static char const * kObservers= "ZHAOQIAN";
// ObserverInfo Model
@interface ObserverInfo : NSObject
// 监听的对象
@property (nonatomic, copy) NSString *observerName;
// 监听的健
@property (nonatomic, copy) NSString *key;
// 回调
@property (nonatomic, copy) ValueChangeBlock block;
@end
@implementation ObserverInfo
// 实例方法
- (instancetype)initWithObserver:(NSString *)observerName key:(NSString *)key block:(ValueChangeBlock)block
{
self = [super init];
if (self) {
_observerName = observerName;
_key = key;
_block = block;
}
return self;
}
@end
@implementation NSObject (Extension)
// 添加观察者
- (void)addObserver:(NSObject *)observer forKey:(NSString *)key options:(NSKeyValueObservingOptions)options block:(ValueChangeBlock)callBack{
// 利用key 返回其setter方法的方法名,例如变为“setNmae”这样类型的
NSString *str = private_setterForKey(key);
// 根据方法名字获取方法
Method setterMethod = class_getInstanceMethod([self class], NSSelectorFromString(str));
// 获取父类的类名
NSString *oldClassName = NSStringFromClass([self class]);
// 创建子类的类名
NSString *kvoClassName = [@"Notify_" stringByAppendingString:oldClassName];
// 创建子类
Class kvoClass;
kvoClass = objc_lookUpClass(kvoClassName.UTF8String);
if (!kvoClass) {
kvoClass = objc_allocateClassPair([self class], kvoClassName.UTF8String, 0);
objc_registerClassPair(kvoClass);
}
// 在子类中重新实现setter方法
if (setterMethod) {
class_addMethod(kvoClass, NSSelectorFromString(str), (IMP)setterIMP, "v@:@");
}else{
// 如果没有setter方法,那么方法交换下
Method method1 = class_getInstanceMethod(self.class, @selector(setValue:forKey:));
Method method2 = class_getInstanceMethod(self.class, @selector(swizz_setValue:forKey:));
method_exchangeImplementations(method1, method2);
}
// 将self设置为它的子类
object_setClass(self, kvoClass);
// 创建模型
ObserverInfo *info = [[ObserverInfo alloc]initWithObserver:observer.description key:key block:callBack];
// 动态获取观察者属性
NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
if (!observers) {
observers = [NSMutableArray array];
// 添加观察者属性
objc_setAssociatedObject(self, kObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observers addObject:info];
}
// 移除观察者
- (void)removeObserver:(NSObject *)observer forKey:(NSString *)key{
NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
ObserverInfo *info;
for (ObserverInfo *temp in observers) {
if ([temp.observerName isEqualToString:observer.description] && [temp.key isEqualToString:key]) {
info = temp;
break;
}
}
if (info) {
[observers removeObject:info];
}else{
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@ does not register observer for %@",observer.description, key] userInfo:nil];
}
}
// 回调
- (void)swizz_setValue:(id)value forKey:(NSString *)key{
id oldValue = [self valueForKey:key];
[self swizz_setValue:value forKey:key];
NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
for (ObserverInfo *temp in observers) {
if ([temp.key isEqualToString:key]) {
temp.block(self,key,oldValue,value);
}
}
}
// 指针交换的回调
void setterIMP(id self,SEL _cmd,id newValue){
NSString *setterName = NSStringFromSelector(_cmd);
NSString *temp = private_upperTolower([setterName substringFromIndex:@"set".length], 0);//去除set并将大写改成小写
NSString *key = [temp substringToIndex:temp.length-1];//去除冒号
id oldValue = [self valueForKey:key];
// 父类结构体
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 方法发给父类
((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
// 回调
NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
for (ObserverInfo *temp in observers) {
if ([temp.key isEqualToString:key]) {
temp.block(self, key, oldValue, newValue);
}
}
}
// 内联函数
// 返回方法名字,例如“setName”这样类型的
static inline NSString * private_setterForKey(NSString *key){
key = private_lowerToUpper(key, 0);
return [NSString stringWithFormat:@"set%@:",key];
}
// 将某个位置的字母小写转大写
static inline NSString * private_lowerToUpper(NSString *str,NSInteger location){
NSRange range = NSMakeRange(location, 1);
NSString *lowerLetter = [str substringWithRange:range];
return [str stringByReplacingCharactersInRange:range withString:lowerLetter.uppercaseString];
}
// 将某个位置的字母大写转小写
static inline NSString * private_upperTolower(NSString *str,NSInteger location){
NSRange range = NSMakeRange(location, 1);
NSString *lowerLetter = [str substringWithRange:range];
return [str stringByReplacingCharactersInRange:range withString:lowerLetter.lowercaseString];
}
@end
写在结尾
很多内容还是不够深入,后面如果有更深入的研究我会更新在这个博客中。
如果内容能帮到和我一样的同学,随手点个小星星也是不错的。
网友评论