iOS知识梳理之Runtime

1.isa and super_class

在OC中id可以指向任何一个对象实例

1.1 那么什么是id呢?

// A pointer to an instance of a class.
typedef struct objc_object *id;

可以看到在typedef内已经有*,所以id任何一个对象实例都不需要加*

1.2 那么objc_object又是什么呢?

/// Represents an instance of a class.
struct objc_object {
    Class isa;
};

1.3 那么什么是Class呢?

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

1.4 那么什么是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;

下载objc源代码,在 objc-runtime-new.h 中,我们发现 objc_class有如下定义:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;   
    ...
    ...
}

可以看出无论是类的对象实例还是都是objc_object
也就是说也是对象
所以在OC中万物皆对象

写两个类:Father+Son,Son继承自Father
回到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;

Son的super_class指向Father
Father的super_class指向NSObject
那么
Son的isa又指向什么呢?
Father的isa又指向什么呢?
这里要引入一个新的概念叫元类.



Son实例对象的isa指向 Son
Son的isa指向 Son的元类
Father的isa指向 Father的元类
Son的元类的父类指向 Father的元类的父类

最奇怪的是循环链的顶层,
NSObject的superclass指向nil
NSObject的isa指向NSObject的元类
NSObject的元类的superclass指向NSObject
NSObject的元类的isa指向NSObject的元类自己

2.objc_class内部的各种list

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;

在objc_class结构体中:

  • ivars

ivars是指向objc_ivar_list成员变量列表的指针。

  • methodLists

methodLists是指向objc_method_list指针的指针,objc_method_list就是用来存储当前类的方法链表,objc_method存储了类的某个方法的信息。

  • cache

cache是指向objc_cache结构体的指针;objc_cache 用来缓存用过的方法,提高性能。

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
实际指向objc_cache结构体,如下:

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的总数。在方法查找中,Runtime使用这个字段确定数组的索引位置
occupied: 实际占用cache buckets的总数
buckets: 指定Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。

cache和methodLists都存储着一些Method,Method的结构我们会在说消息发送的时候具体说

3. 属性与成员变量

3.1 属性说白了还是成员变量

@interface Son : Father
{
    NSInteger _age;
}

@property(nonatomic,copy)NSString * name;

-(void)logMethods;
-(void)logIvars;
-(void)logPropertys;

@end
-(void)logMethods{
    unsigned int outCount = 0;
    Method * methodList = class_copyMethodList(self.class, &outCount);
    for(int i = 0; i < outCount; i++) {
        NSLog(@"Method-%@",NSStringFromSelector(method_getName(methodList[i])));
    }
}

-(void)logIvars
{
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList(self.class, &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        const char *propertyName = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:propertyName];
        NSLog(@"Ivar-%@",key);
    }
}
-(void)logPropertys
{
    unsigned int outCount = 0;
    objc_property_t *properties = class_copyPropertyList(self.class, &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        const char *propertyName = property_getName(property);
        NSString *key = [NSString stringWithUTF8String:propertyName];
        NSLog(@"Property-%@",key);
    }
}
Son * son = [Son new];
[son logPropertys];
[son logIvars];
[son logMethods];

打印:

2018-04-11 11:31:06.218 RuntimeTrain[91944:1371911] Property-name
2018-04-11 11:31:06.219 RuntimeTrain[91944:1371911] Ivar-_age
2018-04-11 11:31:06.219 RuntimeTrain[91944:1371911] Ivar-_name
2018-04-11 11:31:06.219 RuntimeTrain[91944:1371911] Method-logPropertys
2018-04-11 11:31:06.220 RuntimeTrain[91944:1371911] Method-logIvars
2018-04-11 11:31:06.220 RuntimeTrain[91944:1371911] Method-logMethods
2018-04-11 11:31:06.220 RuntimeTrain[91944:1371911] Method-.cxx_destruct
2018-04-11 11:31:06.220 RuntimeTrain[91944:1371911] Method-name
2018-04-11 11:31:06.220 RuntimeTrain[91944:1371911] Method-setName:
2018-04-11 11:31:06.221 RuntimeTrain[91944:1371911] Method-init

可以看出属性也在包含在成员变量内,只不过系统自动的加了一个set和一个get方法而已

3.2 Non Fragile ivars

那么什么是Ivar呢?

typedef struct objc_ivar *Ivar;
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}   

这里我们注意第三个成员 ivar_offset。它表示基地址偏移字节。

在编译我们的类时,编译器生成了一个 ivar布局,显示了在类中从哪可以访问我们的 ivars 。看下图:




上图中,左侧的数据就是地址偏移字节,我们对 ivar 的访问就可以通过 对象地址 + ivar偏移字节的方法。但是这又引发一个问题,看下图:




我们增加了父类的ivar,这个时候布局就出错了,我们就不得不重新编译子类来恢复兼容性。
而Objective-C Runtime中使用了Non Fragile ivars,看下图:


使用Non Fragile ivars时,Runtime会进行检测来调整类中新增的ivar的偏移量。 这样我们就可以通过 对象地址 + 基类大小 + ivar偏移字节的方法来计算出ivar相应的地址,并访问到相应的ivar。

@interface Student : NSObject
{
    @private
    int age;
}
@end

@implementation Student

- (NSString *)description
{
    NSLog(@"current pointer = %p", self);
    NSLog(@"age pointer = %p", &age);
    return [NSString stringWithFormat:@"age = %d", age];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *student = [[Student alloc] init];
        Ivar age_ivar = class_getInstanceVariable(object_getClass(student), "age");
        int *age_pointer = (int *)((__bridge void *)(student) + ivar_getOffset(age_ivar));
        NSLog(@"age ivar offset = %td", ivar_getOffset(age_ivar));
        *age_pointer = 10;
        NSLog(@"%@", student);
    }
    return 0;
}

打印:

2018-04-11 12:24:38.892 Test[4143:466864] age ivar offset = 8
2018-04-11 12:24:38.893 Test[4143:466864] current pointer = 0x1001002d0
2018-04-11 12:24:38.893 Test[4143:466864] age pointer = 0x1001002d8
2018-04-11 12:24:38.894 Test[4143:466864] age = 10

4. 消息发送+转发流程

4.1 消息发送

  • SEL

selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:

typedef struct objc_selector *SEL;

其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器
不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器

  • IMP
typedef id (*IMP)(id, SEL, ...);

它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法

  • Mothed
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
} 

objc_class内的objc_method_list结构体存储着objc_method数组列表
objc_class内的cache结构体存储着使用过objc_method的数组列表

以上两个objc_method的数组列表就像字典:一个Method代表一个键值对,键就是选择器(SEL),值是一个实现(IMP),消息在发送过程中的方法查找也就是用了这样的键值对进行查找的

1.检查selector是否需要忽略。(ps: Mac开发中开启GC就会忽略retain,release方法。)
2.检查target是否为nil。如果为nil,直接cleanup,然后return。(这就是我们可以向nil发送消息的原因。)
3.然后在target的Class中根据Selector去找IMP

寻找IMP的过程:

4.先从当前class的cache方法列表(cache methodLists)里去找
5.找到了,跳到对应函数实现
6.没找到,就从class的方法列表(methodLists)里找
7.还找不到,就到super class的方法列表里找,直到找到基类(NSObject)为止
8.最后再找不到,就会进入动态方法解析和消息转发的机制

4.2 消息转发

当你给一个对象发送了一条它无法识别的消息的时候,就有了消息转发

// 第一步:实现此方法,在调用对象的某方法找不到时,会先调用此方法,允许
// 我们动态添加方法实现
+(BOOL)resolveInstanceMethod:(SEL)sel//1
{
   // 我们这里没有声明有eat方法,因此,我们可以动态添加eat方法
    if ([NSStringFromSelector(sel) isEqualToString:@"dynamicMehod"]) {
        class_addMethod(self, sel, (IMP)dynamicMehod, "v@:");//dynamicMehod是C函数
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
// 第二步:返回一个其他的对象来处理这个SEL
- (id)forwardingTargetForSelector:(SEL)aSelector//2
{
    return someObject or nil;
}
// 第三步:返回方法签名。如果返回nil,则表示无法处理消息 调用-doesNotRecognizeSelector
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector//3
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"dynamicMehod"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
// 第四步,返回了方法签名,都会进入这一步,这一步用户调用方法
// 改变调用对象等
// 当我们实现了此方法后,-doesNotRecognizeSelector:不会再被调用
// 如果要测试找不到方法,可以注释掉这一个方法
-(void)forwardInvocation:(NSInvocation *)invocation//4
{
 // 我们还可以改变方法选择器
    [anInvocation setSelector:@selector(anyMethod)];//另一个OC方法
    // 改变方法选择器后,还需要指定是哪个对象的方法
    [anInvocation invokeWithTarget:self];
}
-(void)doesNotRecognizeSelector:(SEL)aSelector
{
}

消息转发一旦奏效,表面是看是一个对象也能对一条它无法识别的消息做出反应,但万不能以为就是用respondsToSelector:去检测receivermessage的关系也会返回YES,那就大错特错了.转发是假象,respondsToSelector:是走最传统的消息发送机制来检测的.

5.category

一个类的设计不可能完美,也不会完全按照你的想象来,我们想给一个类加属性加方法,除了写一个类继承这个类,category也是很好的选择
具体请看美团的这篇文章

6.方法交换

根据不同的系统版本给予不同的图

@implementation UIImage (exchangeAct)

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method one = class_getClassMethod([self class], @selector(imageNamed:));
        Method two = class_getClassMethod([self class], @selector(pg_imageNamed:));
        method_exchangeImplementations(one, two);
    });
}

+(UIImage *)pg_imageNamed:(NSString *)imageName
{
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        imageName = [imageName stringByAppendingString:@"_aft7"];
    }
    return [UIImage pg_imageNamed:imageName];
}

@end

下面是使用method swizzling应该注意的点:

6.1 +load vs. +initialize

Swizzling应该只在load方法中使用

oc会在运行时自动调用每个类的两个方法,+load 会在类初始化加载的时候调用;+initialize方法会在程序调用类的第一个实例或者类方法的时候调用。这两个方法都是可选的,只会在实现的时候才去调用。由于method swizzling会影响到全局的状态,因此最小化竞争条件的出现变得很重要,+load方法能够确保在类的初始化时候调用,这能够保证改变应用行为的一致性,而+initialize在执行时并不提供这种保证,实际上,如果没有直接给这个类发送消息,该方法可能都不会调用到。

6.2 dispatch_once

Swizzling应该只在dispatch_once中完成

如上,由于swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是其中的一种预防措施,因为它能保证不管有多少个线程,代码只会执行一次。GCD的dispatch_once 能够满足这种需求,因此在method swizzling应该将其作为最佳的实践方式。

6.3 调用 _cmd

看起来下面的代码可能导致无限循环:

+(UIImage *)pg_imageNamed:(NSString *)imageName
{
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        imageName = [imageName stringByAppendingString:@"_aft7"];
    }
    return [UIImage pg_imageNamed:imageName];
}

可奇怪的是,它并不会。在swizzling的过程中,pg_imageNamed:已经被重新指向UIImage的原始实现-imageNamed:,但是如果我们在这个方法中调用imageNamed:则会导致无限循环。「别怕,调用_cmd,其实是调用原函数」

7.关联属性

@implementation UITextField (limitLength)

static const void * PGLimitLengthKey = @"PGLimitLengthKey";

-(void)setLimitLength:(NSInteger)limitLength
{
    objc_setAssociatedObject(self, &PGLimitLengthKey, @(limitLength), OBJC_ASSOCIATION_ASSIGN);
}

-(NSInteger)limitLength
{
    return [objc_getAssociatedObject(self, &PGLimitLengthKey) integerValue];
}

@end

这里涉及到了3个函数:

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
Behavior@property EquivalentDescription
OBJC_ASSOCIATION_ASSIGN@property (assign) / @property (unsafe_unretained)弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC@property (nonatomic, strong)强引用关联对象,且为非原子操
OBJC_ASSOCIATION_COPY_NONATOMIC@property (nonatomic, copy)复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN@property (atomic, strong)强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY@property (atomic, copy)复制关联对象,且为原子操作

8.字典转模型

@implementation NSObject (createByDic)

static NSSet * _foundationClasses;

+(NSDictionary *)pg_customKeyDic
{
    return nil;
}
+(NSDictionary *)pg_modelInArray;
{
    return nil;
}

+ (void)load
{
    _foundationClasses = [NSSet setWithObjects:
                          [NSObject class],
                          [NSURL class],
                          [NSDate class],
                          [NSNumber class],
                          [NSDecimalNumber class],
                          [NSData class],
                          [NSMutableData class],
                          [NSArray class],
                          [NSMutableArray class],
                          [NSDictionary class],
                          [NSMutableDictionary class],
                          [NSString class],
                          [NSMutableString class], nil];
}

+ (BOOL)isClassFromFoundation:(Class)c
{
    return [_foundationClasses containsObject:c];
}

+(id)pg_objectWithDic:(NSDictionary *)dic
{
    id thing = [self new];
    Class class = self;
    while (class && [NSObject isClassFromFoundation:class] == NO) {
        unsigned int outCount = 0;
        Ivar * ivars = class_copyIvarList(class, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString * propertyName = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            NSDictionary * customKeyDic = [class pg_customKeyDic];
            NSString * key = @"";
            if (customKeyDic[propertyName]) {
                key = customKeyDic[propertyName];
            }else{
                key = propertyName;
            }
            id value = dic[key];
            if (value == nil) continue;
            
            NSString * type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            NSRange range = [type rangeOfString:@"@"];
            if (range.location != NSNotFound) {
                
                NSLog(@"-%@-%@-",key,type);
                
                type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
                if (![type hasPrefix:@"NS"]) {
                    Class class = NSClassFromString(type);
                    value = [class pg_objectWithDic:value];
                }else if ([type isEqualToString:@"NSArray"]) {
                    NSArray * array = (NSArray *)value;
                    NSDictionary * modelInArray = [class pg_modelInArray];
                    NSString * className = modelInArray[propertyName];
                    if (className.length <= 0) {
                        value = @[];
                    }else{
                        Class class = NSClassFromString(modelInArray[propertyName]);
                        NSMutableArray * muArr = [NSMutableArray array];
                        for (int i = 0; i < array.count; i++) {
                            [muArr addObject:[class pg_objectWithDic:value[i]]];
                        }
                        value = [muArr copy];
                    }
                }
            }
            [thing setValue:value forKeyPath:propertyName];
        }
        free(ivars);
        class = [class superclass];
    }
    return thing;
}

@end
NSString * path = [[NSBundle mainBundle] pathForResource:@"model" ofType:@"json"];
NSData * jsonData = [NSData dataWithContentsOfFile:path];  
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
User * ly = [User pg_objectWithDic:json];

在真实应用的时候,可能出现如下状况:
1.服务端给我的键名,我们并不喜欢
2.还有就是字典内有数组,数组内的内容要转成另外一个模型的数组

这要求我们向外暴露:自定义属性名的方法+指点数组内容转成什么模型的数组的方法
被转的模型也要实现这两个方法

@implementation User

+(NSDictionary *)pg_customKeyDic
{
    return @{@"userID":@"id"};
}

+(NSDictionary *)pg_modelInArray
{
    return @{@"friends":@"Friend"};
}

@end

9.快速归解档

@implementation NSObject (quickINandOUT)

static NSSet * _foundationClasses;

+ (void)load
{
    _foundationClasses = [NSSet setWithObjects:
                          [NSObject class],
                          [NSURL class],
                          [NSDate class],
                          [NSNumber class],
                          [NSDecimalNumber class],
                          [NSData class],
                          [NSMutableData class],
                          [NSArray class],
                          [NSMutableArray class],
                          [NSDictionary class],
                          [NSMutableDictionary class],
                          [NSString class],
                          [NSMutableString class], nil];
}

+ (BOOL)isClassFromFoundation:(Class)c
{
    return [_foundationClasses containsObject:c];
}

-(void)quickINWithCoder:(NSCoder *)coder
{
    Class class = [self class];
    while (class && [NSObject isClassFromFoundation:class] == NO) {
        unsigned int outCount = 0;
        Ivar * ivars = class_copyIvarList(class, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString * key = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            NSArray * ignorePropertyNameArray = [class ignorePropertyNameArray];
            if ([ignorePropertyNameArray containsObject:key]) continue;
            id value = [self valueForKeyPath:key];
            [coder encodeObject:value forKey:key];
        }
        free(ivars);
        class = [class superclass];
    }
}

-(void)quickOUTWithCoder:(NSCoder *)coder
{
    Class class = [self class];
    while (class && [NSObject isClassFromFoundation:class] == NO) {
        unsigned int outCount = 0;
        Ivar * ivars = class_copyIvarList(class, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString * key = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
            NSArray * ignorePropertyNameArray = [class ignorePropertyNameArray];
            if ([ignorePropertyNameArray containsObject:key]) continue;
            id value = [coder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
        class = [class superclass];
    }
}

在真实应用的时候,可能出现如下状况:
有的属性我们并不想参与归档解档

这要求我们的分类向外暴露:忽略的个别属性名称的方法
被操作的模型也要实现这个方法

+(NSArray *)ignorePropertyNameArray
{
    return @[@"gemo"];
}

10.KVO 元类切换

_people = [[People alloc]init];
NSLog(@"%@,%@",[_people class],object_getClass(_people));
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"%@,%@",[_people class],object_getClass(_people));

打印

2018-04-11 13:04:05.698 ZCRuntimeTrain[96168:1645690] People,People
2018-04-11 13:04:05.698 ZCRuntimeTrain[96168:1645690] People,NSKVONotifying_People

这已经看出,object_getClass(_people)[_people class]是不一样的

除了isa被切换之外,
在这个新类里面重写被观察的对象四个方法。class,setter,dealloc,_isKVOA

_user = [[People alloc]init];
NSLog(@"%@",object_getClass(_people));
[self logMethods];
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];NSLog(@"%@",object_getClass(_people));
[self logMethods];
-(void)logMethods{
    unsigned int outCount = 0;
    Method * methodList = class_copyMethodList(object_getClass(_user), &outCount);
    for(int i = 0; i < outCount; i++) {
        NSLog(@"Method-%@",NSStringFromSelector(method_getName(methodList[i])));
    }
}
People
Method-.cxx_destruct
Method-name
Method-setName:

NSKVONotifying_People
Method-setName:
Method-class
Method-dealloc
 Method-_isKVOA

10.1 重写class方法

重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容

10.2. 重写setter方法

在新的类中会重写对应的set方法,是为了在set方法中增加另外两个方法的调用:

- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key

当然就是不用set方法,而直接用KVC,willChangeValueForKey+didChangeValueForKey也是会被调用的

10.3 重写dealloc方法

销毁新生成的NSKVONotifying_类

10.4 重写_isKVOA方法

这个私有方法估计可能是用来标示该类是一个KVO机制声称的类

这当然也给我以警醒,不要用class的方法来判断实例是什么类,而要用->isa来判断

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值