美文网首页
《EffectiveObjective-c 2.0》第二章 对象

《EffectiveObjective-c 2.0》第二章 对象

作者: 神的旨意 | 来源:发表于2017-07-26 15:23 被阅读0次

    第6条:理解"属性"这一概念

    @interface WCPerson : NSObject {
        @public
        NSString *_firstName;
        NSString *_lastName;
        @private
        NSString *_someInternalData;
    }
    @end
    
    • 以上的写法Java或C++程序比较常见,而Objective-C代码很少这么写,这种写法的问题是:对象布局在编译期(compile time)就已经固定了,只要碰到访问_firstName变量的代码,编译器就把其替换为"偏移量"(offset),这个偏移量是"硬编码"(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。这样做目前没有问题,但是如果又加了一个实例变量,那就麻烦了,比如说,假设在_firstName之前又多了一个实例变量:
    @interface WCPerson : NSObject {
        @public
        NSDate *_dateOfBirth;
        NSString *_firstName;
        NSString *_lastName;
        @private
        NSString *_someInternalData;
    }
    @end
    

    原来表示 _firstName 的偏移量现在却指向了 _dateOfBirth 了,那么 _firstName 之后的实例变量的偏移量硬编码都会读取到错误的值,对比一下,在加入 _dateOfBirth 这一实例变量之前于之后的内存布局情况,假设指针为4个字节。

    WX20170726-142309.png
    如果代码使用了编译期计算出来的偏移量,那么在修改类定义之后必须重新编译,否则会出错。
    Objective-C的做法是,把实例变量当做一种存储偏移量所用的“特殊变量”,交由类对象“(class object)保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。可以在运行期向类中新增实例变量,这就是"稳固的应用二进制接口"。
    • Objective-C中属性 @property 的作用
    @interface WCPerson : NSObject
    //声明变量为firstName的属性,特质为nonatomic和copy
    @property (nonatomic,copy)NSString *firstName;
    @end
    

    在Objective-c的编译期间会变成如下代码,

    //WCPerson.h 文件
    @interface WCPerson : NSObject
    //声明变量为firstName的属性,特质为nonatomic和copy
    @property (nonatomic,copy)NSString *firstName;
     - (NSString *)firstName;
     - (void)setFirstName:(NSString *)firstName;
    @end
    
     //WCPerson.m 文件
     #import "WCPerson.h"
     @interface WCPerson (){
         NSString *_firstName;//默认情况下,属性对应的实例变量
     }
     @end
    
     @implementation WCPerson
      //编译期间,编译器自动创建的getter方法
      - (NSString *)firstName{
         return _firstName;
     }
    //编译期间,编译器自动创建的setter方法
     - (void)setFirstName:(NSString *)firstName{
         _firstName = [firstName copy];
     }
     @end
    

    使用 @property 声明的属性,编译器在编译期做如下操作:

    1. 创建一个以 _ 为前缀的实例变量名。
    2. 创建标准的 getter,setter 方法。
      也可以使用 @synthesize 设置自定义的实例变量
    @implementation WCPerson
    @synthesize firstName = _myFirstName;
    @end
    

    也可以使用 @dynamic 告诉编译器:不创建属性所对应的实例变量,也不要创建存取方法。

    @implementation WCPerson
     @dynamic firstName;
    @end
    
    • 属性的特质
      以下都为截图,字太多了,感觉需要理解并且都是重点。
      1.png
      2.png
      3.png
      4.png
      重要提醒
      若是自己来实现属性的存取方法,应该保证其具备相关属性所声明的特质。
    //声明变量为firstName的属性,特质为nonatomic和copy
    @property (nonatomic,copy)NSString *firstName;
    
     //firstName属性的特质为copy,那么在自定义setter方法时,应该具有copy特性
      - (void)setFirstName:(NSString *)firstName{
          _firstName = [firstName copy];//这里应该满足声明的copy特性
      }
    

    第7条: 在对象内部尽量直接访问实例变量

    在.m文件中,当给属性设置值时,使用self.name=@"123",这种方式,当读取值当时候,使用_name这种方式,原因如下:

    WX20170726-165910.png
    当然也不是绝对的,也要视情况而定。

    第8条:理解“对象等同性”这一概念

    • 对象的比较 == 比较两个对象的指针是否相同,而不是比较对象本身,如果指针相同必定为同一对象。
    • 一般来说用 isEqual 来比较两个对象是否相同,如下
        NSString *foo = @"123";
        NSString *bar = [NSString stringWithFormat:@"123"];
        BOOL equalA = (foo == bar);//. NO
        BOOL equalB = [foo isEqual:bar];// YES
        BOOL equalC = [foo isEqualToString:bar];//YES
    

    字符串有自己独特的等同性判断方法 isEqualToString,且效率比 isEqual 高。

    • NSObject类及协议中有两个用于判断等同性的关键方法
    - (BOOL)isEqual:(id)object;
    + (NSUInteger)hash;
    

    NSObject类对这两个方法等默认实现是:当且仅当其“指针值”完全相等时,这两个对象才相等。要想在自定义对象中正确覆写这些方法,就必须先理解其约定。如果 isEqual 方法判定两个对象相等,那么hash方法耶必须返回同一个值,但是,如果两个对象的hash方法返回同一个值,那么 isEqual 方法未必会认为两者相等。

    hash方法与判等的关系?

    hash方法主要是用于在Hash Table查询成员用的, 那么和我们要讨论的isEqual()有什么关系呢?
    为了优化判等的效率, 基于hash的NSSet和NSDictionary在判断成员是否相等时, 会这样做
    Step 1: 集成成员的hash值是否和目标hash值相等, 如果相同进入Step 2, 如果不等, 直接判断不相等
    Step 2: hash值相同(即Step 1)的情况下, 再进行对象判等, 作为判等的结果
    简单地说就是
    hash值是对象判等的必要非充分条件

    @interface WCPerson : NSObject
    //声明变量为firstName的属性,特质为nonatomic和copy
    @property (nonatomic,copy)NSString *firstName;
    @property (nonatomic,copy)NSString *lastName;
    @property (nonatomic,assign)NSUInteger age;
    @end
    
    @implementation WCPerson
    - (BOOL)isEqual:(id)object{
        if (self == object) {//指针相同
            return YES;
        }
        if (![object isKindOfClass:[WCPerson class]]) {//同一类型
            return NO;
        }
        return [self isEqualToPerson:(WCPerson *)object];
    }
    
    - (BOOL)isEqualToPerson:(WCPerson *)person{//判断属性相同
        if (!person) {
            return NO;
        }
        BOOL haveEqualFirstNames = (!_firstName && !person.firstName) || [_firstName isEqualToString:person.firstName];
        BOOL haveEqualLastNames = (!_lastName && !person.lastName) || [_lastName isEqualToString:person.lastName];
        BOOL haveEqualAge = _age == person.age;
        return haveEqualFirstNames && haveEqualLastNames && haveEqualAge;
    }
    - (NSUInteger)hash{
        NSUInteger firstNameHash = [_firstName hash];
        NSUInteger lastNameHash = [_lastName hash];
    //    NSLog(@"firstNameHash is %ld,lastNameHash is %ld",firstNameHash,lastNameHash);
        return firstNameHash ^ lastNameHash ^_age;//按位异或运算符
    }
    @end
    

    按位异或运算符参考
    isEqualhash 参考
    总之:

    WX20170728-145939.png

    第9条:以“类族模式”隐藏实现细节

    本条部分参考该作者内容

    类族模式在UIKit(user interface framework)使用的范围已经远远超过我们的想象,比如,UIButton,NSArray,NSString等,这种模式最大的好处就是,可以隐藏抽象基类背后的复杂细节,使用者只需调用基类简单的方法就可以返回不同(或相同)的子类(或类)实例。

    先看一下可以验证这个问题的方法:

    - (BOOL)isKindOfClass:(Class)aClass;
    - (BOOL)isMemberOfClass:(Class)aClass;
    

    第一个函数的意思是:接收者是否是aClass类的实例或者从这个类继承的任何类的实例。如果是返回yes。
    第二个函数的意思是:接收者是否是aClass的实例,如果是返回yes。

    UIButton 测试

        //UIButton 的类族模式,返回的都是UIButton类
        UIButton *btnCust = [UIButton buttonWithType:UIButtonTypeCustom];
        UIButton *btnSys = [UIButton buttonWithType:UIButtonTypeSystem];
        UIButton *btnDark = [UIButton buttonWithType:UIButtonTypeInfoDark];
        NSLog(@"%@",[[btnCust class] debugDescription]);
        NSLog(@"%@",[[btnSys class] debugDescription]);
        NSLog(@"%@",[[btnDark class] debugDescription]);
        //UIButton返回的是基类实例对象,下面两个if语句都会输出
        if ([btnCust isKindOfClass:[UIButton class]]) {
            NSLog(@"isKindOfClass");
        }
        if ([btnCust isMemberOfClass:[UIButton class]]) {
            NSLog(@"isMemberOfClass");
        }
    
    //输出如下:
    2017-07-28 17:18:56.053 test[10739:166312] UIButton
    2017-07-28 17:18:56.053 test[10739:166312] UIButton
    2017-07-28 17:18:56.053 test[10739:166312] UIButton
    2017-07-28 17:34:16.167 test[10819:172000] isKindOfClass
    2017-07-28 17:34:16.167 test[10819:172000] isMemberOfClass
    

    说明,虽然UIButton根据不同的类型(UIButtonType)创建,但返回的都是同一个类的实例对象。
    其内部实现应该如下:

    - (void)drawRect:(CGRect)rect{
        if (_type == TypeA) {//_type为UIButtonType,TypeA为客户传入的类型
            //draw TypeA button
        }else if (_type == TypeB){
            //draw TypeB button
        }
    }
    

    NSArray测试

        NSArray*array = [NSArray new];//返回__NSArray0
        NSLog(@"%@",[[array class] description]);
        NSLog(@"%@", [[[NSArray arrayWithObject:@"1"] class] description]);//返回__NSSingleObjectArrayI
        NSLog(@"%@", [[[NSArray arrayWithObjects:@"1", @"2", nil] class] description]);//返回__NSArrayI,在数组元素个数大于等于2时,返回同一个对象结果
        NSLog(@"%@", [[[NSArray arrayWithObjects:@"1", @"2", @"3", nil] class] description]);
        NSLog(@"%@", [[[NSArray arrayWithObjects:@"1", @"2", @"3", @"4", nil] class] description]);
        NSLog(@"%@", [[[NSArray arrayWithObjects:@"1", @"2", @"3", @"4", @"5", nil] class] description]);
        if ([array isKindOfClass:[NSArray class]]) {
            NSLog(@"isKindOfClass");
        }
        if ([array isMemberOfClass:[NSArray class]]) {
            NSLog(@"isMemberOfClass");
        }
    
    输出结果如下:
    2017-07-28 17:45:19.344 test[10870:176145] __NSArray0
    2017-07-28 17:45:19.345 test[10870:176145] __NSSingleObjectArrayI
    2017-07-28 17:45:19.345 test[10870:176145] __NSArrayI
    2017-07-28 17:45:19.345 test[10870:176145] __NSArrayI
    2017-07-28 17:45:19.345 test[10870:176145] __NSArrayI
    2017-07-28 17:45:19.346 test[10870:176145] __NSArrayI
    2017-07-28 17:45:19.346 test[10870:176145] isKindOfClass
    

    NSArray的内部实现应该如下:

    // .h 文件
    typedef NS_ENUM(NSUInteger,LULEmployeeType) {
        LULEmployeeTypeDevlopers,
        LULEmployeeTypeProducters,
        LULEmployeeTypeTesters
    };
    
    @interface LULEmployee : NSObject
    +(LULEmployee*)employeeWithType:  (LULEmployeeType)type;
    -(void)doADayWork;
    @end
    
    //.m 文件
    
    +(LULEmployee*)employeeWithType:  (LULEmployeeType)type{
        switch (type) {//类似Java中的工厂模式,Objective-C中也大量使用了该模式。面试题会被问到iOS使用到的经典工厂模式是什么对象
            case LULEmployeeTypeDevlopers:
                return [LULEmployeeTypeDevloper new];
                break;
            case LULEmployeeTypeProducters:
                return [LULEmployeeTypeProducter new];
                break; 
            case LULEmployeeTypeTesters:
                return [LULEmployeeTypeTester new];
                break;
        }
    }
    

    由上可以知道返回的每一个对象都是 NSArray 基类的子类的实例对象,而非 NSArray 的实例对象,所以如下代码永远不会执行

    if ([array isMemberOfClass:[NSArray class]]) {
            NSLog(@"isMemberOfClass");
        }
    

    集合元素都类似,包括,NSDictionary等。
    所以对于这类(UIButton,NSArray, NSDictionary)对象判断其类型的时候,如下两种方式都不合适,

    //UIButton 可以使用此方式
    if ([array isMemberOfClass:[NSArray class]]) {
            NSLog(@"isMemberOfClass");
        }
    if ([array class] == [NSArray class]]) {//永远不会执行
            NSLog(@"isMemberOfClass");
        }
    

    这样做的意义:

    UIButton类的使用者无需关心创建出来的按钮怒提属于哪个子类,也不用考虑按钮的绘制方式等实现细节,使用者只需明白如何创建按钮即可

    第10条:在既有类中使用关联对象存放自定义数据

    • 需要在对象中存放相关信息,有两种方式,
      1. 从对象所属的类中继承一个子类,然后改用这个子类对象。
      2. 使用关联对象。
        注意:有时类的实例可能由某种机制所创建的,而开发者无法令这种机制创建出自己所写的子类实例)这种方式慎用。
        因为:如果滥用的话,它可以令代码失控,使其难于调试,造成循环引用的原因,很难查明,因为关联对象之间的关系并没有正式的定义,其内存管理语义是在关联的时候才去定义的,而不是在接口中定义好的。
    //set
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    //get
    objc_getAssociatedObject(id object, const void *key)
    //remove 
    void objc_removeAssociatedObjects(id object)
    

    第11条:理解objc_msgSend的作用

    1. Objective-C 调用方法其内部其实就是消息派发,如:
    id returnValue = [someObject messageName:parameter];
    //底层调用方法如下
    id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);
    
    1. “尾调用优化”,即某函数的最后一项操作是调用另外一个函数,那么就可以运用“尾调用优化”技术,编译器惠生产调转至另一个函数所需的指令码,而且不会向调用堆栈中推入新的“栈帧”。只有当莫函数的最后一个操作仅仅是调用其他函数而不会将其他返回值另作他用时,才执行“尾调用优化”。这优化对objc_msgSend非常关键,如果不这么做的话,那么每次调用OC方法之前,都需要为调用objc_msgSend函数准备“栈帧”。
      引用
      尾调用之所以与其他调用不同,就在于它的特殊的调用位置。
      我们知道,函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。
      参考

    第12条:理解消息转发机制

    1. 当某个属性用 @dynamic 实现时,需要用消息转发机制。先逻辑图:
      WX20170801-154931.png
    2. 上代码
      WCAutoDictionary.h 文件
    @interface WCAutoDictionary : NSObject
    @property (nonatomic, strong) NSString *string;
    @property (nonatomic, strong) NSNumber *number;
    @property (nonatomic, strong) NSDate *date;
    @property (nonatomic, strong) id opaqueObject;
    @end
    

    WCAutoDictionary.m 文件

    #import "WCAutoDictionary.h"
    #import <objc/runtime.h>
    
    @interface WCAutoDictionary ()
    @property (nonatomic, strong) NSMutableDictionary *backingStore;
    
    @end
    @implementation WCAutoDictionary
    @dynamic string, number, date, opaqueObject;
    
    - (instancetype)init{
        if (self = [super init]) {
            _backingStore = [NSMutableDictionary new];
        }
        return self;
    }
    
    //第二步 备援接收者
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        //可以"组合"来模拟出"多重继承"的某些特性,在一个对象内部,
        //可能还有一些列其他对象,
        //该对象可经由此方法将能够处理某选择子的相关内部对象返回,
        //这样的话,在外界看来,好像是该对象亲自处理了这些消息似的(此处不可修改消息内容)
       //可以交给其他类处理
        return nil;//转向第三步
    }
    //第三步
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        
    }
    
    //第一步
    + (BOOL)resolveInstanceMethod:(SEL)sel{
        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString hasPrefix:@"set"]) {
            class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
        }else{
            class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
    }
    
    id autoDictionaryGetter(id self, SEL _cmd){
        //Get the backing store from the object
        WCAutoDictionary *typedSelf = (WCAutoDictionary *)self;
        NSMutableDictionary *backingStore = typedSelf.backingStore;
        
        NSString *key = NSStringFromSelector(_cmd);
        
        return [backingStore objectForKey:key];
    }
    
    void autoDictionarySetter(id self, SEL _cmd, id value){
        WCAutoDictionary *typeSelf = (WCAutoDictionary *)self;
        NSMutableDictionary *backingStore = typeSelf.backingStore;
        //setOpakeqObject:
        NSString *selectorString = NSStringFromSelector(_cmd);
        NSMutableString *key = [selectorString mutableCopy];
        
        [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
        
        [key deleteCharactersInRange:NSMakeRange(0, 3)];
        
        NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
        [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
        
        if (value) {
            [backingStore setObject:value forKey:key];
        }else{
            [backingStore removeObjectForKey:key];
        }
    }
    
    @end
    

    调用方法

        WCAutoDictionary *dict = [WCAutoDictionary new];
        //dict.date赋值 触发+ (BOOL)resolveInstanceMethod:(SEL)sel方法
        dict.date = [NSDate dateWithTimeIntervalSince1970:123134123];
        //dict.date取值 触发+ (BOOL)resolveInstanceMethod:(SEL)sel方法
        NSLog(@"dict.date = %@",dict.date);
    

    文章总结的很好,直接拿来用


    WX20170801-160321.png

    第13条:用“方法调配技术”调试“黑盒方法”

        Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
        Method swappedmethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
        method_exchangeImplementations(originalMethod, swappedmethod);
    
        NSString *string = @"ThIS iS my iOS";
        NSLog(@"lowercaseString = %@",[string lowercaseString]);
        NSLog(@"uppercaseString = %@",[string uppercaseString]);
    
    输出: 
    2017-08-01 17:21:17.837 test[3562:131744] lowercaseString = THIS IS MY IOS
    2017-08-01 17:21:17.837 test[3562:131744] uppercaseString = this is my ios
    

    使用 method_exchangeImplementations 可以调换实现的方法。
    当然也可以有助于调试,当需要调试系统中的某个方法的时候,如下:
    .h 文件

    @interface NSString (WCMyAdditions)
    - (NSString *)eoc_myLowerCaseString;
    @end
    

    .m 文件

    @implementation NSString (WCMyAdditions)
    //该方法调和后,是@selector(lowercaseString)的实现方法
    - (NSString *)eoc_myLowerCaseString{
        NSString *lowercase = [self eoc_myLowerCaseString];//调和之后,该方法返回的是原来系统方法 lowercaseString的实现
        NSLog(@"%@ => %@",self,lowercase);
        return lowercase;
    }
    @end
    

    执行代码

        Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
        Method swappedmethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowerCaseString));
        method_exchangeImplementations(originalMethod, swappedmethod);
        
        NSString *string = @"ThIS iS my iOS";
        [string lowercaseString];
    输出:
    2017-08-01 17:24:23.549 test[3597:133219] ThIS iS my iOS => this is my ios
    

    此方式方便于调试,不可滥用。

    第14条:理解“类对象”的用意

    1. 每个Objective-C对象实例都是指向某块内存数据的指针。OC 声明对象实例的时候都是如下做法
    NSString *string = @"ThIS iS my iOS";
    id temp = @"123";
    

    而类的结构如下

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *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指针和super_class父类。

    • isa 指针负责查找对象所属的类。
    • super_class负责查找对象所属类在继承体系中的父类。
      如下实验证明:
    NSString *string = @"ThIS iS my iOS";
    //lldb 输出
    (lldb) p string
    (__NSCFConstantString *) $0 = 0x000000010f3b7430 @"ThIS iS my iOS" //string 对象真正属于__NSCFConstantString 类
    (lldb) p string->isa //证明上一步
    (Class) $1 = __NSCFConstantString
    (lldb) p [string superclass]
    (Class) $2 = __NSCFString
    (lldb) p [[string superclass] superclass]
    (Class) $3 = NSMutableString
    (lldb) p [[[string superclass] superclass] superclass]
    (Class) $4 = NSString
    ////到此处可以得知__NSCFConstantString是NSString的子类,所以我们经常使用到的NSString类其实是NSString的子类__NSCFConstantString,此处对应于前面讲的第九条,[类族模式]
    (lldb) p [[[[string superclass] superclass] superclass] superclass]
    (Class) $5 = NSObject
    (lldb) p [[[[[string superclass] superclass] superclass] superclass] superclass]
    (Class) $6 = nil  //NSObject的父类是nil
    (lldb) 
    

    下图是思维导图


    WX20170802-161732.png
    1. 在类继承体系中查询类型信息,即使用
    //isMemberOfClass判定步骤:1. 通过实例对象的isa指针得到实例所属的类
    //2. 之后再用 == 判断实例所属的类和比较的类是否为同一类,返回判断结果
    isMemberOfClass:  // 判断对象是否为某个特定类实例
    //isKindOfClass判定步骤:
    //1. 同isMemberOfClass步骤一
    //2. 同isMemberOfClass步骤二,但此处如果返回结果是false,
    //则在使用实例对象的super_class,找到实例对象的父类,在用 == 进行比较,如果还是false,
    //则在找实例对象的super_class,继续比较,直到找到位置,如果使用实例对象的super_class查找到根类为NSObject还是没有比较成功的话,
    //那么就返回false.
    isKindOfClass:    //判断对象是否为某类或其派生类的实例
    
    WX20170802-164143.png
    上图很好的解释了,isa指针和super_class在代码中的使用。
    为什么步直接使用 == 而代替 isMemberOfClass:isKindOfClass:(当然 isKindOfClass:是不能代替的) 呢?
    书中的解释,看着很难理解:
    WX20170802-170320.png
    代码解释吧 参考
    //YFStudent.h文件
    @interface YFStudent : NSObject
    //这里没有提供公开的方法和属性哦
    @end
    
    //YFStudent.m文件
    @interface YFStudent ()
    @property (nonatomic,copy)NSString *studentNum;
    @end
    
    @implementation YFStudent
    
    - (void)study{
        NSLog(@"正在学习");
    }
    @end
    
    //YFPerson.h 文件
    @interface YFPerson : NSObject
    //这里没有提供公开的方法和属性哦
    @end
    //YFPerson.m 文件
    @interface YFPerson ()
    @property (nonatomic,copy)NSString *name;
    @end
    
    @implementation YFPerson
    
    - (void)eat{
        NSLog(@"%@正在吃饭",self.name);
    }
    @end
    
    //YFProxy.h 文件   NSProxy和NSObject是同一级别的
    @interface YFProxy : NSProxy
    - (void)transformToObject:(NSObject *)object;
    @end
    
    //YFProxy.m 文件
    @interface YFProxy ()
    @property (nonatomic, strong) NSObject *object;
    @end
    
    @implementation YFProxy
    
    - (void)transformToObject:(NSObject *)object{
        self.object = object;
    }
    //第一步,方法签名, sel为要执行的方法的@selector()选择器
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
        NSMethodSignature *methodSignature;
        if (self.object) {
            methodSignature = [self.object methodSignatureForSelector:sel];
        }else{
            methodSignature = [super methodSignatureForSelector:sel];
        }
        return methodSignature;
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation{
        if (self.object) {
            [invocation setTarget:self.object];//设置调用的目标
            [invocation invoke];//开始调用方法
        }
    }
    @end
    

    调用

        YFPerson *person = [[YFPerson alloc] init];
        YFStudent *student = [[YFStudent alloc] init];
        YFProxy *proxy = [YFProxy alloc];
        [proxy transformToObject:person];
        if ([proxy isKindOfClass:[YFPerson class]]) {//此处如果用==的话,则会为false,lldb调试如下:
    /**
    (lldb) p proxy->isa
    (Class) $0 = YFProxy
    (lldb) p [proxy superclass]
    (Class) $1 = NSProxy
    */
    //如果使用isKindOfClass:这个的话,那么会走proxy的方法签名methodSignatureForSelector 
    //跟调用[person isKindOfClass:[YFPerson class]] 就类似了,
    //所以作者推崇使用isKindOfClass:做类型信息查询
            NSLog(@"true");
        }
        //尽管YFPerson没有提供公开的方法和属性但仍然可以调用该对象的name属性和eat方法
        [proxy performSelector:@selector(setName:) withObject:@"小明"];
        [proxy performSelector:@selector(eat)];
        
        [proxy transformToObject:student];
        [proxy performSelector:@selector(study)];
    

    以上代码实现一下就明白了。

    点击进入 第三章

    相关文章

      网友评论

          本文标题:《EffectiveObjective-c 2.0》第二章 对象

          本文链接:https://www.haomeiwen.com/subject/xovnkxtx.html