成员变量
// 获取成员变量信息
Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
// 设置和获取成员变量的值
Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");
MJPerson *person = [[MJPerson alloc] init];
object_setIvar(person, nameIvar, @"123");
object_setIvar(person, ageIvar, (__bridge id)(void *)10);
NSLog(@"%@ %d", person.name, person.age);
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([MJPerson 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);
object_setIvar(person, ageIvar, (__bridge id)(void *)10);这个方法传的值,必须是OC类型。当成员变量是基本类型时,比如这里是int类型,不能是NSNumber类型@(10),必须用void *并桥接成OC类型。不能用int *桥接class_copyIvarList创建出来的必须释放free(ivars);runtime里如果调用了copy或create创建出来的必须释放。
类
MJPerson *person = [[MJPerson alloc] init];
[person run];
object_setClass(person, [MJCar class]);
[person run];
NSLog(@"%d %d %d",
object_isClass(person),
object_isClass([MJPerson class]),
object_isClass(object_getClass([MJPerson class]))
);
// NSLog(@"%p %p", object_getClass([MJPerson class]), [MJPerson class]);
// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
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);
MJPerson *person = [[MJPerson alloc] init];
object_setClass(person, newClass);
[person run];
id dog = [[newClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@20 forKey:@"_weight"];
[dog run];
NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
// 在不需要这个类时释放
objc_disposeClassPair(newClass);
- 添加成员变量和方法,必须在注册类之前。一旦注册就无法再添加成员变量。所以无法动态创建已经存在的类。
- 方法的添加可以放在注册后,因为方法放在class_rw_t里。成员变量放在class_ro_t里。
- 不需要这个类的时要释放。
objc_allocateClassPair从名字可以看出(Pair),创建的是两个对象,类对象和元类对象。
字典转模型
#import "NSObject+Json.h"
#import <objc/runtime.h>
@implementation NSObject (Json)
+ (instancetype)mj_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)];
// 设值
id value = json[name];
if ([name isEqualToString:@"ID"]) {
value = json[@"id"];
}
[obj setValue:value forKey:name];
}
free(ivars);
return obj;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
//字典转模型
NSDictionary *json = @{
@"id" : @20,
@"age" : @20,
@"weight" : @60,
@"name" : @"Jack"
// @"no" : @30
};
MJPerson *person = [MJPerson mj_objectWithJson:json];
[MJCar mj_objectWithJson:json];
MJStudent *student = [MJStudent mj_objectWithJson:json];
NSLog(@"123");
}
return 0;
}
属性
方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
}
return 0;
}
void myrun()
{
NSLog(@"---myrun");
}
void test()
{
MJPerson *person = [[MJPerson alloc] init];
// class_replaceMethod([MJPerson class], @selector(run), (IMP)myrun, "v");
class_replaceMethod([MJPerson class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"123123");
}), "v");
[person run];
}
method_exchangeImplementtations交换方法本质是,交换imp.这个操作会清空缓存。
具体应用
设置UITextField的placeholder的字体颜色
正常:
NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
attrs[NSForegroundColorAttributeName] = [UIColor redColor];
self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
但是也可以,找到这个属性,查看它的类型,然后通过runtime设置。
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField 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);
Ivar ivar = class_getInstanceVariable([UITextField class],"_placeholderLabel");
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
NSLog(@"%@",class_getSuperclass(NSClassFromString(@"UITextFieldLabel")));
self.textField.placeholder = @"请输入用户名";
// NSLog(@"%@",class_getSuperclass([UITextFieldLabel class]));//报错,这个类是前向声明。我们必须找到它的父类
//
// //找到UITextFieldLabel的父类
// NSLog(@"%@",class_getSuperclass(NSClassFromString(@"UITextFieldLabel")));
//
//
// [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];
placeholderLabel.textColor = [UIColor redColor];
13.0后不允许用KVC方式,设置其成员变量了。运行崩溃。 用下面的方法:
Ivar ivar = class_getInstanceVariable([UITextField class],"_placeholderLabel");
UILabel *label = object_getIvar(self.textField,ivar);
label.textColor = [UIColor redColor];
改变按钮点击事件
@implementation UIControl (Extension)
+ (void)load
{
// hook:钩子函数
Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
method_exchangeImplementations(method1, method2);
}
- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
// 调用系统原来的实现
[self mj_sendAction:action to:target forEvent:event];
// [target performSelector:action];
// if ([self isKindOfClass:[UIButton class]]) {
// // 拦截了所有按钮的事件
//
// }
}
-
[btn addTarget:<#(nullable id)#> action:<#(nonnull SEL)#> forControlEvents:<#(UIControlEvents)#>]本质上是调用了UIController的sendAction:to:forEvent: -
调用系统原来的实现是
[self mj_sendAction:action to:target forEvent:event];,因为两个方法已经交换了。而不是sendAction:to:forEvent:。
解决数组元素为nil
NSString *obj = nil;
NSMutableArray *array = [NSMutableArray array];
[array addObject:@"jack"];
[array insertObject:obj atIndex:0];
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
[self mj_insertObject:anObject atIndex:index];
}
报错:reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
NSArray,NSMutableArray,NSDictionary,NSMutableDictionary等都是运行在类簇模式下。真实类型是其他类型。
字典key为nil
以下代码并不会crash:
NSString *obj = nil;
NSDictionary *dict = @{@"name" : [[NSObject alloc] init],
@"age" : @"jack"};
NSString *value = dict[obj];
NSLog(@"%@", [dict class]);
以下代码会崩溃:
NSString *obj = nil;
NSMutableDictionary *mulDict = [NSMutableDictionary dictionary];
mulDict[@"name"] = @"jack";
mulDict[obj] = @"rose"; // 会崩溃
mulDict[@"age"] = obj; //不会崩溃
// [mulDict setObject:nil forKey:@"nickname"]; //会崩溃
NSLog(@"%@", [mulDict class]);
解决方案:
@implementation NSMutableDictionary (Extension)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"__NSDictionaryM");
Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
method_exchangeImplementations(method1, method2);
Class cls2 = NSClassFromString(@"__NSDictionaryI");
Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
method_exchangeImplementations(method3, method4);
});
}
- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
if (!key) return;
[self mj_setObject:obj forKeyedSubscript:key];
}
- (id)mj_objectForKeyedSubscript:(id)key
{
if (!key) return nil;
return [self mj_objectForKeyedSubscript:key];
}
@end
dispatch_once保证这部分代码只运行一次,这里也可以不要。因为load方法一般只走一次。- 这个分类用的时候不需要导入,因为
+load方法不导入也会走的。