iOS学习

(iOS)Effective Objective-C 2.0 读

2016-07-14  本文已影响95人  酱油不爱醋

第一章 熟悉Objective-C

1.OC的起源

oc使用了消息结构而非函数调用。使用消息结构的语言,其运行时所执行的代码由运行环境决定,而使用函数调用的语言,则由编译器决定。

OC对象所占内存总是分配在“堆空间”,而不会分配在“栈”上。分配在堆中的内存必须直接管理,而分配栈上的用于保存变量的内存 则会在其栈帧弹出时自动清理。

当我们看到一个变量类型是已知的,就分配在栈里面,比如,int、double等。其他未知的类型,比如自定义的类型,因为系统不知道需要多大,所以程序自己申请,这样就分配在堆里面百度文库

466833EB-AF2E-4735-9606-7956183155CE.png

2.在类的头文件中尽量少引入其他头文件

3.多用字面量语法,少用与之等价的方法

4.多用类型常量,少用#define预处理指令

应该使用

static constant NSTimeInterval kAnimationDuration = 0.3;

而不要使用
#define Animation_duration 0.3
如果想要在该常量的编译单元之外使用,那么使用这种形式

//In the header file
 extern NSString *constant EOCStringConstant;//这里用UIKIT_EXTERN会比较好
//In the implementation file
NSString *const EOCStringConstant = @"Value";`

常量定义应从右至左解读,EOCStringConstant就是一个常量,而这个常量是指针,指向NSString对象。(大概意思应该是,const修饰的是常量,那么EOCStringConstant是一个常量,而*是一个指针,最后,它的类型是NSString。)

5.用枚举表示状态、选项、状态码

第二章 对象、消息、运行期

6.理解“属性“这一概念

7.在对象内部尽量直接范文实例变量

8.理解“对象等同性”这一概念

9. 以“类簇模式”隐藏实现细节

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

11. 理解objc_msgSend的作用

C语言的函数调用方式:C语言使用“静态绑定”,也就是说,在编译期就能决定运行时所应调用的函数。编译器在编译代码的时候就已经知道程序中有哪些函数了。于是会直接生成调用这些函数的命令。
  动态绑定:在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有的方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则取决于运行期决定,甚至可以在程序运行时改变,这些特性使得OC成为一门真正的动态语言,类似如下

void printHello(){
    printf("Hello, world!");
}
void printGoodbye(){
    print("goodbye,world!\n");
}
void doTheThing(int type){
void (*fnc)();
if(type == 0){
    fnc = printHello;
}else{
    fnc = printGoodby;
}
}

一般的方法调用:

 id returnValue = [someObject messageName:parameter] 

本例中,somObject叫做“接收者”(receiver),messageName叫做“选取器”(selector)。选取器与参数合起来称为“消息”(message)。编译器看到此消息后,将其转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,叫做objc_msgSend,“原型”如下
void objc_msgSend(id self, SEL cmd,...)
  这是个“参数个数可变的函数,能接受两个或两个以上的参数。第一个参数代表接收者,第二个代码选取器,后续参数就是消息中的那些参数,其顺序不变。选取器指的就是方法名。“选取器”与“方法”这两个词经常交替使用。编译器会把刚才那个例子中的消息转换为如下函数:

  id returnValue = objc_msgSend(someObject,
                               @selector(messageName:),
                                parameter);

objc_msgSend函数会一句接收者与选取器的类型来调用适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其“方法列表”,如果能找到与选取器名称相符的方法,就调至其实现代码。没有就沿着继承体系向上查找。如果最终找不到,就执行“消息转发”操作。
还有另外一些边界情况,将交由oc运行环境中的另一些函数来处理:

12. 理解消息转发机制

消息转发分为两大阶段,第一阶段先征询接收者所属的类,看其是否能动态添加方法,以处理当前这个“未知的选取器”,这叫做“动态方法解析"。第二阶段涉及“完整的消息转发机制”,如果runtime已经把第一阶段执行完了,那么接收者自己无法再以动态添加的方法来相应包含该选取器的消息了。此时,运行期系统会请求接收者以其他手段来处理与消息相关的方法调用。这又细分为两部,首先,看有没其他对象处理。有就给它。即“备援的接收者”。没有,则启动“完整的消息转发机制”。runtime会把与消息有关的全部细节都封装到NSInvocation对象,再给接收者最后一次机会。

动态方法解析:

对象在收到无法解读的消息后,首先将调用其所属类的下列表方法
+ (BOOL)resolveInstanceMethod:(SEL)selecotor
  该方法的参数就是那个未知的选取器,其返回值表示这个类能否新新增一个实例方法用以处理这个选取器。在继续往下执行转发机制之前,本类有机会新增一个处理此选取器的方法。加入尚未实现的方法不是实例方法,而是类方法,那么会调用另一个叫resolveClassMethod:的方法
  使用这种方法的前提是,相关方法的实现代码已经写好,只等着运行时,动态插在类里就可以了。此方法常用来实现@dynamic属性。

备援接收者

当前接收者还有第二次机会能处理未知的选取器,在这一步中,runtime系统会问它,能不能把这条消息转给其他接收者来处理。其对应处理方法:
- (id)forwardingTargetForSelector:(SEL)selector
  方法参数代表未知的选取器,若当前接收者能找到备援对象,则将其返回,找不到,就返回nil。通过此方案,我们可以用“组合”来模拟出“多重集成”的某些特性。在一个对象内部,可以还有其他对象,改对象经由此方法将能够处理某选取器的相关内部对象返回,这样的话,看起来就是该对象处理的。
注意,我们无法操作经由这一步所转发的消息,如果想在发送给备援接收者之前修改消息内容,那就得通过完整的消息转发机制来做。

完整的消息转发

如果转发算法已经来到这一步的话,那么唯一能做的就是启用完整的消息转发机制。首先创建NSInvocation对象,把尚未处理的那条消息有关的全部细节全部封装与其中。此对象包含选取器,目标(target)及参数。在触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。
此步骤会调用下列方法来转发消息:

 - (void)forwardInvocation:(NSInvocation *)invocation 

这个方法可以实现的很简单:只需改变调用目标,使消息在新目标上得以调用即可。然而这样实现出来的方法与“备援接收者”方案所实现的方法等效。比较有用的实现方法:在触发消息前,先以某种方式改变消息内容,比如追加另一个参数,或者改换选取器等。
实现此方法时,若发现某调用操作不应由本类处理,则需调用超类的同名方法。这样的话,继承体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSObject类方法,那么该方法还会继而调用doesNotRecognizeSelector:以抛出异常,此异常表明选取器最终未能得到处理。

消息转发流程

13. 用"方法转换“调试”黑盒方法“

使用** 方法转换 **可以让我们既不需要源代码,也不需要通过继承子类来覆写方法就能改变这个类本身的功能。这样一来,新功能将在本类的所有实例中生效,而不是仅限于腹写了相关方法的那些子类实例。
  类的方法列表会把选取器的名称映射到相关的方法实现之上,使得“动态消息派发系统”能够据此找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫IMP,原型如下

id (*IMP)(id,SEL, ...)

NSString类可以响应uppercaseString等选取器。

NSString选取器映射表

 #emsp;想交换方法实现,可用下列函数:

void method_exchangeImplementations(Method m1, Method m2)

此函数的两个参数表示待交换的两个方法实现,而方法实现则可通过下列函数获得:

Method class_getInstanceMethod(Class aClass, SEL aSelector)

此函数根据给定的选择从类中去除与之相关的方法。执行下列代码,即可交换比如lowercaseString和uppercaseString方法实现:

Method originalMethod = class_getInstanceMethod([NSStringclass], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSStringclass], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);

如果想要在调用lowercaseString时记录某些信息,这时可以通过交换方法来实现
  新方法可以添加到NSString的一个category钟:

@interface NSString (EOCMyAdditions)
- (NSString *)eoc_myLowercaseString;
@end

上述新方法将与原有的lowercaseString方法呼唤
  新方法的实现可以这样写:

@implementation NSString (EOCMyAdditions)
- (NSString *)eoc_myLowercaseString{
     NSString *lowercase = [self eoc_myLowercaseString];
     NSLog(@"%@ = > %@", self, lowercase);
     return lowercase;
}
@end

这段代码看上去好像会是死循环,不过要记住,此方法是准备和lowercaseString方法呼唤的。所以,在运行期,eoc_myLowercaseString选取器实际上对应于原有的lowercaseString方法实现。最后通过以下来交换实现:

Method originalMethod = class_getInstanceMethod([NSStringclass], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSStringclass], @selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);

14. 理解类对象的用意

元类继承
上一篇 下一篇

猜你喜欢

热点阅读