一、在.h文件中,头文件引入的时机尽量延后,只有确有需要时才引入,这样可以减少累的使用者所需引入的头文件数量。
eg:#import “YFViewController.h”
现在可以这样引入:@class YFViewController;(向前申明)
然后在.m文件中有用到这个YFViewController,再导入这个头文件。
二、多用字面量语法,少用与之等价的方法
eg: 字面量语法:简洁,方便
NSString *sth1String = @"Hello world1";
//两种赋值的方法:
NSArray *listArray1 = @[@"one",@"two",@"three"];//字面量语法
NSArray *listArray2 = [NSArray arrayWithObjects:@"one",@"two",@"three" ,nil];
//两种取值的方法:
NSString *index1 = listArray1[0];//字面量语法
NSString *index2 = [listArray2 objectAtIndex:1];
id objc1 = @"1";
id objc2 = nil;
id objc3 = @"2";
NSArray *list1 = [NSArray arrayWithObjects:objc1,objc2,objc3, nil];
NSLog(@"list1-->%@",list1);//打印出“1”
NSArray *list2 = @[objc1,objc2,objc3];
NSLog(@"list2-->%@",list2);//运行后直接报异常
//字典的两种写法
NSDictionary *dic1 = [NSDictionary dictionaryWithObjectsAndKeys:@"Yuna",@"FirstName",@"You",@"LastName", [NSNumber numberWithInt:26],@"age",nil];
NSDictionary *dic2 = @{@"firstName":@"Yuna",@"lastName":@"You",@"age":@26};//字面量字典
//取值
NSString *name = [dic1 objectForKey:@"FirstName"];
NSString *age = dic2[@"age"];
三、多用类型常量,少用#define预处理指令
`#define ANIMATION_DURATION 0.3
此方式只是字符串替换
假设此指令声明在某个头文件中,那么引入这个头文件的代码,其中ANIMATION_DURATION都会被替换
static const NSTimeInterval kAnimationDuration = 0.3
此方式定义的常量包含类型信息,描述了常量的类型
`
四、用枚举表示状态、选项、状态码
eg:socket connection的状态
//默认从0开始,后面依次递增1
enum YCFConnectionState {
YCFConectionStateDisconnected,
YCFConnectionStateConnecting,
YCFConnectionStateConnected,
};
//当然也可以手动赋值
enum YUConnectionState {
YUConnectionStateDisconnected = 1,
YUConnectionStateConnectiong,
YUConnectionStateConnected,
};
//定义选项,而这些选项可以彼此组合,只要枚举定义的对,各选项之间就可以通过“按位或操作符”来组合
enum YCFPlayState {
YCFPlayStateNone = 0,
YCFPlayStateEat = 1 << 0,
YCFPlayStateDrink = 1 << 1,
YCFPlayStateSleep = 1 << 2,
YCFPlayStateSing = 1 << 3,
};
//这个组合使用的时候:YCFPlayStateEat | YCFPlayStateDrink
//枚举在switch语句中的使用
switch (currentState) {
case YCFConectionStateDisconnected:
//...to do sth..
break;
case YCFConnectionStateConnecting:
//..to do sth...
break;
case YCFConnectionStateConnected:
//...to do sth..
break;
// default:
// break;
}
/*
我们总是在switch语句最后加上default分支,然而,若是用枚举来定义状体,
则最好不要有default分支。这样的话,如果稍后又加了一种状态,那么编译器酒会发出警告信息,
提示新加入的状态未在switch分支中处理。
*/
五、理解“属性”这一概念
“属性”是Object-C的一项特性,用于封装对象中的数据。Object-C对象通常会把其他所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”来访问。
六、在即有类中使用关联对象存放自定义数据
//需要引入#import <objc/runtime.h>
objc_setAssociatedObject(id object, <#const void *key#>, <#id value#>, objc_AssociationPolicy policy)
//此方法以给定的key和policy为object设置关联对象值value。
objc_getAssociatedObject(id object, <#const void *key#>)
//此方法根据给定的key从某object中获取对应的关联对象值
objc_removeAssociatedObjects(id object)
//移除指定object的全部关联对象
//注意:关联对象的key是个不透明的指针<#const void *key#>,通常使用静态全局变量
有个Person类,它有个name属性,我们在运行时给它添加一个关联对象:
//person
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
//给Person添加一个category
//.h文件
#import "Person.h"
@interface Person (PersonPrivateInfo)
-(void)setPersonPrivateInfo:(NSString *)infoString;
-(NSString *)getPersonPrivateInfo;
@end
//.m文件
#import "Person+PersonPrivateInfo.h"
#import <objc/runtime.h>
static const char PersonalInfoKey;
@implementation Person (PersonPrivateInfo)
-(void)setPersonPrivateInfo:(NSString *)infoString{
objc_setAssociatedObject(self, &PersonalInfoKey, infoString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)getPersonPrivateInfo{
return objc_getAssociatedObject(self, &PersonalInfoKey);
}
@end
//在VC中调用
Person *pp = [[Person alloc] init];
[pp setPersonPrivateInfo:@"Hello World"];
NSLog(@"%@",[pp getPersonPrivateInfo]);//Hello world
七、理解objc_msgSend的作用
在对象上调用方法是OC中经常使用的功能,叫做“传递消息”,消息有name或selector,可有参数,也可有返回值。
在C中,是静态绑定,也就是说在编译期间就能决定运行时所应调用的函数
[someObject sendInfo:parameter];
void objc_msgSend(void /* id self, SEL op, ... */ )
/*
id self:接受者 ->someObject
SEL:选择器/方法 ->sendInfo
剩余的为参数 ->parameter
[someObject sendInfo:parameter];
//这个可转为 objc_msgSend(someObject,@selector(sendInfo:),parameter);
objc_msgSend函数
*/
注:1、objc_msgSend 函数会依据接受者与选择器的类型来调用适当的方法,为了完成此操作,该方法需要在接受者所属的类中
搜寻其“方法列表”,如果能找到雨选择器名称相符的方法,就跳至其实现代码,若是找不到,就会沿着继承体系继续向上查找,等找到合适的方法之后再跳转。若果最终还是找不到相符的方法,那就执行“消息转发”操作
2、消息转发分为两个阶段。第一阶段先查询接受者所属的类,看其是否能动态天假方法,以处理当前这个“未知的选择器(unknown selector)”,这叫做“动态方法解析”。第二阶段涉及“完整的消息转发机制”,如果运行时系统已经把第一阶段执行完了,那么接受者自己就无法再以动态新增方法的手段来响应包含该选择器的消息了。此时,运行时系统会把请求接受者以其他的手段来处理与消息相关的方法调用(首先,请接受者看看有没有其他对象处理这条消息,若有,则运行时系统会把消息转给那个对象,于是消息转发过程结束,一起如常。若没有其他的接受者,则启动完整的消息转发机制,运行时系统会把与消息有关的全部细节都封装到NSInvocation对象中,再给接受者最后一次机会,令其设法解决当前还未处理的这条消息)
动态方法解析:
有个Calculator类,有个初始化方法和add方法
//.h文件
@interface Calculator : NSObject{
float value;
}
-(void)add:(NSNumber *)val;
-(instancetype)initWithVal:(float)val;
@end
//.m文件
#import "Calculator.h"
@implementation Calculator
-(instancetype)initWithVal:(float)val{
if(self == [super init]){
value = val;
}
return self;
}
-(void)add:(NSNumber *)val{
NSLog(@"add:%f",value + [val floatValue]);
}
@end
//在VC中调用
Calculator *cl = [[Calculator alloc] initWithVal:10];
// performSelector调用方法 后面跟的参数是id类型,所以-(void)add:(NSNumber *)val,val的是参数类型必须是封装成基本数据类型
[cl performSelector:@selector(add:) withObject:@1];
//隐式调用方法不存在的方法:minus
[cl performSelector:@selector(minus:) withObject:@3.1];//方式1
NSString *str = [cl performSelector:@selector(uppercaseString)];//方式2
NSLog(@"str-->%@",str);
[cl performSelector:@selector(oo)];//方式3
//给Calculator添加一个category
#import "Calculator+Minus.h"
#import <objc/runtime.h>
@implementation Calculator (Minus)
/*
消息转发分为3种
objc_msgSend,这个方法在当前类中没有找到,会去父类中查找,会这样一级级的往上找,就会形成一个分发表
如果在父类中也没有找到,那么就会启用消息转发,给开发者提供很多自定义的操作,如果当前找到了,会把当前的方法放在类的缓存表里[为了加快访问和调用的速度]耗时较长。
在一个函数找不到时,Objective-C提供了三种方式去补救:
1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数,这是实例方法,还有个类方法resolveClassMethod
2、调用forwardingTargetForSelector让别的对象去执行这个函数
3、调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
如果都不中,调用doesNotRecognizeSelector抛出异常。
在对象类的 dispatch table 中尝试找到该消息。如果找到了,跳到相应的函数IMP去执行实现代码;
如果没有找到,Runtime 会发送 +resolveInstanceMethod: 或者 +resolveClassMethod: 尝试去 resolve 这个消息;
如果 resolve 方法返回 NO,Runtime 就发送 -forwardingTargetForSelector: 允许你把这个消息转发给另一个对象;
如果没有新的目标对象返回, Runtime 就会发送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你可以发送 -invokeWithTarget: 消息来手动转发消息或者发送 -doesNotRecognizeSelector: 抛出异常。
利用 Objective-C 的 runtime 特性,我们可以自己来对语言进行扩展,解决项目开发中的一些设计和技术问题。
*/
//C函数
//minus(当前类,方法修饰符,参数)
void minus(id self, SEL _cmd, NSNumber *val){
NSLog(@"%0.2f",[[self valueForKey:@"value"] floatValue] - [val floatValue]);
}
/*
1、静态方法
这个函数在运行时(runtime),没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。
根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。
class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "i@:@");
其中types参数为"i@:@“,按顺序分别表示:
i:返回值类型int,若是v则表示void
@:参数id(self)
::SEL(_cmd)
@:id(str) 标示有参数
*/
+(BOOL)resolveInstanceMethod:(SEL)sel{
// if (sel == @selector(minus:)) {
//给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"minus:"]) {
class_addMethod([self class], sel, (IMP)minus, "V@:f");
return YES;
}
return [super resolveInstanceMethod:sel];
}
/*
类似于多继承,将消息转发出去
开发者没有实现minus:,这将调用forwardingTargetForSelector
流程到了这里,系统给了个将这个SEL转给其他对象的机会。
返回参数是一个对象,如果这个对象非nil、非self的话,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。
*/
-(id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(uppercaseString)) {
return @"hello Yuna";
}
return nil;
}
//这个函数和后面的forwardInvocation:是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString *sel = NSStringFromSelector(aSelector);
if ([sel rangeOfString:@"set"].location == 0) {
//动态造一个 setter函数
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
} else {
//动态造一个 getter函数
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
}
//
真正执行从methodSignatureForSelector:返回的NSMethodSignature。在这个函数里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(forwardingTargetForSelector只能以Selector的形式转向一个对象)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSMutableDictionary *data = data = [[NSMutableDictionary alloc] init];
//拿到函数名
NSString *key = NSStringFromSelector([anInvocation selector]);
if ([key rangeOfString:@"set"].location == 0) {
//setter函数形如 setXXX: 拆掉 set和冒号
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
//从参数列表中找到值
[anInvocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
//getter函数就相对简单了,直接把函数名做 key就好了。
NSString *obj = [data objectForKey:key];
[anInvocation setReturnValue:&obj];
}
}
/*
可以看到,方式1:在会执行+(BOOL)resolveInstanceMethod:(SEL)sel,返回YES,说明我们实现了minus方法
方式2:由于我们在本类中没有实现uppercaseString方法,我们将消息转发出去,调用-(id)forwardingTargetForSelector:(SEL)aSelector,可以看到有其他对象实现了这个方法
方式3:消息转发了这个oo方法,但是也没有其他对象实现,只能让forwardInvocation来执行,通过运行可以发现,即使当前类或者其他类没有实现oo方法,但是程序并没有crash掉,因为我们在- (void)forwardInvocation:(NSInvocation *)anInvocation中进行了相关处理。
*/