You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

341 lines
20 KiB

/**
* Tencent is pleased to support the open source community by making QMUI_iOS available.
* Copyright (C) 2016-2020 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
//
// QMUIRuntime.h
// QMUIKit
//
// Created by QMUI Team on 2018/8/14.
//
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "NSObject+QMUI.h"
#import "NSMethodSignature+QMUI.h"
#import "QMUILog.h"
/// 以高级语言的方式描述一个 objc_property_t 的各种属性,请使用 `+descriptorWithProperty` 生成对象后直接读取对象的各种值。
@interface QMUIPropertyDescriptor : NSObject
@property(nonatomic, strong) NSString *name;
@property(nonatomic, assign) SEL getter;
@property(nonatomic, assign) SEL setter;
@property(nonatomic, assign) BOOL isAtomic;
@property(nonatomic, assign) BOOL isNonatomic;
@property(nonatomic, assign) BOOL isAssign;
@property(nonatomic, assign) BOOL isWeak;
@property(nonatomic, assign) BOOL isStrong;
@property(nonatomic, assign) BOOL isCopy;
@property(nonatomic, assign) BOOL isReadonly;
@property(nonatomic, assign) BOOL isReadwrite;
@property(nonatomic, copy) NSString *type;
+ (instancetype)descriptorWithProperty:(objc_property_t)property;
@end
#pragma mark - Method
CG_INLINE BOOL
HasOverrideSuperclassMethod(Class targetClass, SEL targetSelector) {
Method method = class_getInstanceMethod(targetClass, targetSelector);
if (!method) return NO;
Method methodOfSuperclass = class_getInstanceMethod(class_getSuperclass(targetClass), targetSelector);
if (!methodOfSuperclass) return YES;
return method != methodOfSuperclass;
}
/**
* fromClass originSelector fromClass originSelector toClass newSelector
* fromClass originSelecotr fromClass originSelector使 toClass newSelector toClass newSelector
* @warning fromClass originSelector fromClass fromClass 使使 OverrideImplementation 使 ExchangeImplementations
* @param _fromClass class
* @param _originSelector class selector fromClass
* @param _toClass class
* @param _newSelector toClass originSelector
* @return
*/
CG_INLINE BOOL
ExchangeImplementationsInTwoClasses(Class _fromClass, SEL _originSelector, Class _toClass, SEL _newSelector) {
if (!_fromClass || !_toClass) {
return NO;
}
Method oriMethod = class_getInstanceMethod(_fromClass, _originSelector);
Method newMethod = class_getInstanceMethod(_toClass, _newSelector);
if (!newMethod) {
return NO;
}
BOOL isAddedMethod = class_addMethod(_fromClass, _originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
if (isAddedMethod) {
// 如果 class_addMethod 成功了,说明之前 fromClass 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续 toClass 的这个方法被调用时可能会 crash
IMP oriMethodIMP = method_getImplementation(oriMethod) ?: imp_implementationWithBlock(^(id selfObject) {});
const char *oriMethodTypeEncoding = method_getTypeEncoding(oriMethod) ?: "v@:";
class_replaceMethod(_toClass, _newSelector, oriMethodIMP, oriMethodTypeEncoding);
} else {
method_exchangeImplementations(oriMethod, newMethod);
}
return YES;
}
/// 交换同一个 class 里的 originSelector 和 newSelector 的实现,如果原本不存在 originSelector,则相当于给 class 新增一个叫做 originSelector 的方法
CG_INLINE BOOL
ExchangeImplementations(Class _class, SEL _originSelector, SEL _newSelector) {
return ExchangeImplementationsInTwoClasses(_class, _originSelector, _class, _newSelector);
}
/**
* block class
* @param targetClass class
* @param targetSelector class targetClass
* @param implementationBlock block block block targetSelector super self class targetClass targetSelector targetClass targetSelector class targetClass UIButton.class UIView.classimplementationBlock class targetClass selector targetSelector block targetSelector IMP C super targetSelector IMP
*/
CG_INLINE BOOL
OverrideImplementation(Class targetClass, SEL targetSelector, id (^implementationBlock)(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void))) {
Method originMethod = class_getInstanceMethod(targetClass, targetSelector);
IMP imp = method_getImplementation(originMethod);
BOOL hasOverride = HasOverrideSuperclassMethod(targetClass, targetSelector);
// 以 block 的方式达到实时获取初始方法的 IMP 的目的,从而避免先 swizzle 了 subclass 的方法,再 swizzle superclass 的方法,会发现前者调用时不会触发后者 swizzle 后的版本的 bug。
IMP (^originalIMPProvider)(void) = ^IMP(void) {
IMP result = NULL;
if (hasOverride) {
result = imp;
} else {
// 如果 superclass 里依然没有实现,则会返回一个 objc_msgForward 从而触发消息转发的流程
// https://github.com/Tencent/QMUI_iOS/issues/776
Class superclass = class_getSuperclass(targetClass);
result = class_getMethodImplementation(superclass, targetSelector);
}
// 这只是一个保底,这里要返回一个空 block 保证非 nil,才能避免用小括号语法调用 block 时 crash
// 空 block 虽然没有参数列表,但在业务那边被转换成 IMP 后就算传多个参数进来也不会 crash
if (!result) {
result = imp_implementationWithBlock(^(id selfObject){
QMUILogWarn(([NSString stringWithFormat:@"%@", targetClass]), @"%@ 没有初始实现,%@\n%@", NSStringFromSelector(targetSelector), selfObject, [NSThread callStackSymbols]);
});
}
return result;
};
if (hasOverride) {
method_setImplementation(originMethod, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originalIMPProvider)));
} else {
const char *typeEncoding = method_getTypeEncoding(originMethod) ?: [targetClass instanceMethodSignatureForSelector:targetSelector].qmui_typeEncoding;
class_addMethod(targetClass, targetSelector, imp_implementationWithBlock(implementationBlock(targetClass, targetSelector, originalIMPProvider)), typeEncoding);
}
return YES;
}
/**
* block class void block
* @param targetClass class
* @param targetSelector class targetClass void
* @param implementationBlock targetSelector super selfObject self
*/
CG_INLINE BOOL
ExtendImplementationOfVoidMethodWithoutArguments(Class targetClass, SEL targetSelector, void (^implementationBlock)(__kindof NSObject *selfObject)) {
return OverrideImplementation(targetClass, targetSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
void (^block)(__unsafe_unretained __kindof NSObject *selfObject) = ^(__unsafe_unretained __kindof NSObject *selfObject) {
void (*originSelectorIMP)(id, SEL);
originSelectorIMP = (void (*)(id, SEL))originalIMPProvider();
originSelectorIMP(selfObject, originCMD);
implementationBlock(selfObject);
};
#if __has_feature(objc_arc)
return block;
#else
return [block copy];
#endif
});
}
/**
* block class block
* @param _targetClass class
* @param _targetSelector class targetClass
* @param _returnType
* @param _implementationBlock ^_returnType(NSObject *selfObject, _returnType originReturnValue) {} targetSelector super selfObject self originReturnValue super
*/
#define ExtendImplementationOfNonVoidMethodWithoutArguments(_targetClass, _targetSelector, _returnType, _implementationBlock) OverrideImplementation(_targetClass, _targetSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {\
return ^_returnType (__unsafe_unretained __kindof NSObject *selfObject) {\
\
_returnType (*originSelectorIMP)(id, SEL);\
originSelectorIMP = (_returnType (*)(id, SEL))originalIMPProvider();\
_returnType result = originSelectorIMP(selfObject, originCMD);\
\
return _implementationBlock(selfObject, result);\
};\
});
/**
* block class void block
* @param _targetClass class
* @param _targetSelector class targetClass void
* @param _argumentType targetSelector
* @param _implementationBlock ^(NSObject *selfObject, _argumentType firstArgv) {} targetSelector super selfObject self firstArgv targetSelector
*/
#define ExtendImplementationOfVoidMethodWithSingleArgument(_targetClass, _targetSelector, _argumentType, _implementationBlock) OverrideImplementation(_targetClass, _targetSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {\
return ^(__unsafe_unretained __kindof NSObject *selfObject, _argumentType firstArgv) {\
\
void (*originSelectorIMP)(id, SEL, _argumentType);\
originSelectorIMP = (void (*)(id, SEL, _argumentType))originalIMPProvider();\
originSelectorIMP(selfObject, originCMD, firstArgv);\
\
_implementationBlock(selfObject, firstArgv);\
};\
});
#define ExtendImplementationOfVoidMethodWithTwoArguments(_targetClass, _targetSelector, _argumentType1, _argumentType2, _implementationBlock) OverrideImplementation(_targetClass, _targetSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {\
return ^(__unsafe_unretained __kindof NSObject *selfObject, _argumentType1 firstArgv, _argumentType2 secondArgv) {\
\
void (*originSelectorIMP)(id, SEL, _argumentType1, _argumentType2);\
originSelectorIMP = (void (*)(id, SEL, _argumentType1, _argumentType2))originalIMPProvider();\
originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv);\
\
_implementationBlock(selfObject, firstArgv, secondArgv);\
};\
});
/**
* block class block
* @param targetClass class
* @param targetSelector class targetClass
* @param implementationBlock ^_returnType (NSObject *selfObject, _argumentType firstArgv, _returnType originReturnValue){} targetSelector super selfObject self firstArgv targetSelector originReturnValue super
*/
#define ExtendImplementationOfNonVoidMethodWithSingleArgument(_targetClass, _targetSelector, _argumentType, _returnType, _implementationBlock) OverrideImplementation(_targetClass, _targetSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {\
return ^_returnType (__unsafe_unretained __kindof NSObject *selfObject, _argumentType firstArgv) {\
\
_returnType (*originSelectorIMP)(id, SEL, _argumentType);\
originSelectorIMP = (_returnType (*)(id, SEL, _argumentType))originalIMPProvider();\
_returnType result = originSelectorIMP(selfObject, originCMD, firstArgv);\
\
return _implementationBlock(selfObject, firstArgv, result);\
};\
});
#define ExtendImplementationOfNonVoidMethodWithTwoArguments(_targetClass, _targetSelector, _argumentType1, _argumentType2, _returnType, _implementationBlock) OverrideImplementation(_targetClass, _targetSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {\
return ^_returnType (__unsafe_unretained __kindof NSObject *selfObject, _argumentType1 firstArgv, _argumentType2 secondArgv) {\
\
_returnType (*originSelectorIMP)(id, SEL, _argumentType1, _argumentType2);\
originSelectorIMP = (_returnType (*)(id, SEL, _argumentType1, _argumentType2))originalIMPProvider();\
_returnType result = originSelectorIMP(selfObject, originCMD, firstArgv, secondArgv);\
\
return _implementationBlock(selfObject, firstArgv, secondArgv, result);\
};\
});
#pragma mark - Ivar
/**
type encodingconst char * Ivar
1. isXxxTypeEncoding(const char *) BOOL isBOOLTypeEncoding()
2. isXxxIvar(Ivar) BOOL Ivar isBOOLIvar()
@see https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
*/
#define _QMUITypeEncodingDetectorGenerator(_TypeInFunctionName, _typeForEncode) \
CG_INLINE BOOL is##_TypeInFunctionName##TypeEncoding(const char *typeEncoding) {\
return strncmp(@encode(_typeForEncode), typeEncoding, strlen(@encode(_typeForEncode))) == 0;\
}\
CG_INLINE BOOL is##_TypeInFunctionName##Ivar(Ivar ivar) {\
return is##_TypeInFunctionName##TypeEncoding(ivar_getTypeEncoding(ivar));\
}
_QMUITypeEncodingDetectorGenerator(Char, char)
_QMUITypeEncodingDetectorGenerator(Int, int)
_QMUITypeEncodingDetectorGenerator(Short, short)
_QMUITypeEncodingDetectorGenerator(Long, long)
_QMUITypeEncodingDetectorGenerator(LongLong, long long)
_QMUITypeEncodingDetectorGenerator(NSInteger, NSInteger)
_QMUITypeEncodingDetectorGenerator(UnsignedChar, unsigned char)
_QMUITypeEncodingDetectorGenerator(UnsignedInt, unsigned int)
_QMUITypeEncodingDetectorGenerator(UnsignedShort, unsigned short)
_QMUITypeEncodingDetectorGenerator(UnsignedLong, unsigned long)
_QMUITypeEncodingDetectorGenerator(UnsignedLongLong, unsigned long long)
_QMUITypeEncodingDetectorGenerator(NSUInteger, NSUInteger)
_QMUITypeEncodingDetectorGenerator(Float, float)
_QMUITypeEncodingDetectorGenerator(Double, double)
_QMUITypeEncodingDetectorGenerator(CGFloat, CGFloat)
_QMUITypeEncodingDetectorGenerator(BOOL, BOOL)
_QMUITypeEncodingDetectorGenerator(Void, void)
_QMUITypeEncodingDetectorGenerator(Character, char *)
_QMUITypeEncodingDetectorGenerator(Object, id)
_QMUITypeEncodingDetectorGenerator(Class, Class)
_QMUITypeEncodingDetectorGenerator(Selector, SEL)
//CG_INLINE char getCharIvarValue(id object, Ivar ivar) {
// ptrdiff_t ivarOffset = ivar_getOffset(ivar);
// unsigned char * bytes = (unsigned char *)(__bridge void *)object;
// char value = *((char *)(bytes + ivarOffset));
// return value;
//}
#define _QMUIGetIvarValueGenerator(_TypeInFunctionName, _typeForEncode) \
CG_INLINE _typeForEncode get##_TypeInFunctionName##IvarValue(id object, Ivar ivar) {\
ptrdiff_t ivarOffset = ivar_getOffset(ivar);\
unsigned char * bytes = (unsigned char *)(__bridge void *)object;\
_typeForEncode value = *((_typeForEncode *)(bytes + ivarOffset));\
return value;\
}
_QMUIGetIvarValueGenerator(Char, char)
_QMUIGetIvarValueGenerator(Int, int)
_QMUIGetIvarValueGenerator(Short, short)
_QMUIGetIvarValueGenerator(Long, long)
_QMUIGetIvarValueGenerator(LongLong, long long)
_QMUIGetIvarValueGenerator(UnsignedChar, unsigned char)
_QMUIGetIvarValueGenerator(UnsignedInt, unsigned int)
_QMUIGetIvarValueGenerator(UnsignedShort, unsigned short)
_QMUIGetIvarValueGenerator(UnsignedLong, unsigned long)
_QMUIGetIvarValueGenerator(UnsignedLongLong, unsigned long long)
_QMUIGetIvarValueGenerator(Float, float)
_QMUIGetIvarValueGenerator(Double, double)
_QMUIGetIvarValueGenerator(BOOL, BOOL)
_QMUIGetIvarValueGenerator(Character, char *)
_QMUIGetIvarValueGenerator(Selector, SEL)
CG_INLINE id getObjectIvarValue(id object, Ivar ivar) {
return object_getIvar(object, ivar);
}
#pragma mark - Mach-O
typedef struct classref *classref_t;
/**
class
@param classes classref_t 访 NULL
@return class
@code
classref_t *classes = nil;
int count = qmui_getProjectClassList(&classes);
Class class = (__bridge Class)classes[0];
@endcode
*/
FOUNDATION_EXPORT int qmui_getProjectClassList(classref_t **classes);