《EffectiveObjective-c 2.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)];

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

点击进入 第三章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 6.理解“属性”这一概念 @dynamic关键字:告诉编译器不要自动创建实现属性所用的实例变量,也不要为其创建存取...
    Code_Ninja阅读 703评论 0 3
  • Objective-C作为面向对象编程,“对象”(object)就是“基本构造单元”(building block...
    Mark_Lin阅读 322评论 0 1
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 哈儿,今天是大太阳。 想你,一直。窗外是一片小树林,一小段路梯。想牵着你去走走。 你在干嘛呢?哈老婆。是不是也在向...
    xiao钱钱阅读 475评论 0 0
  • 认识行动派琦琦源于我的前老板,后来陆陆续续又见过几次,但都只仅限于一个拥抱,最近的一次见面还是去年的琦琦分享会,她...
    瀚文ILoveU阅读 305评论 8 0