[OC学习笔记]熟悉Objective-C

24 篇文章 1 订阅

Objective-C使用“消息结构”而非“函数调用”。使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。采用消息结构的语言,不论是否多态,总是在运行时才会去查找所要执行的方法。
Objective-C是C的“超集”(superset)。
OC中的指针是用来指示对象的。想要声明一个变量,令其指代某个对象,可以:

NSString *someString = @"The string";

所有OC语言的对象都必须这样声明,因为对象所占内存总是分配在“堆空间”(heap space)中,而绝不会分配在“栈”(stack)上。不能在栈中分配OC对象:

NSSring stackString; 
// error: interface type cannot be statically allocated

someString变量指向分配在堆里的某块内存,其中含有一个NSString对象。也就是说,如果再创建一个变量,令其指向同一地址,那么并不拷贝该对象,只是这两个变量会同时指向该对象:

NSString *someString = @"The string";
NSString *anotherString = someString;

请添加图片描述
分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧(stack frame)弹出时自动清理。
OC将堆内存管理抽象出来了,不需要mallocfree来分配或释放对象所占内存。OC运行期环境把这部分工作抽象为一套内存管理架构,叫做“引用计数”。
定义里不含*的变量,它们可能会使用“栈空间”(stack space)。这些变量所保存的不是OC对象。比如CoreGraphics框架中的CGRect:

CGRect frame;
frame.origin.x = 0.0f;
...

CGRect是C结构体,其定义:

struct CGRect {
	CGPoint origin;
	CGSize size;
}
typedef struct CGRect CGRect;

改用OC对象,性能会受影响。

一、在类的头文件中尽量少引入其他头文件

例:

//EOCPerson.h
#import <Foundation/Foundation.h>
//#import <EOCEmployer.h>
//不在此引入头文件
@class EOCEmployer;
//使用向前声明(forward declaring)
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;

EOCPerson的实现文件则要引入EOCEmployer类的头文件,因为需要知道其所有接口细节来使用它。
将引入头文件的时机尽量延后,只在确有需要时引入,这样可以减少类的使用者所引入头文件的数量。否则会增加编译时间。
向前声明也解决了两个类相互引用的问题。假设要为EOCEmployer类加入新增及删除雇员的方法,那么其头文件会加入如下定义:

- (void)addEmployee:(EOCPerson*)person;
- (void)removeEmployee:(EOCPerson*)person;

如果在各自头文件中引入对方头文件,则会导致循环引用(chicken-and-egg situation)。这会导致两个类里有一个无法被正确编译。
有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的声明移至“class-continuation分类”中,如果不行的话,就把协议单独放在一个头文件中,然后将其引入。

二、多用字面量语法,少用与之等价的方法

字面数值:

NSNumber *someNumber = [NSNumber numberWithInt: 1];
//下面的代码更加整洁:
NSNumber *someNumber2 = @1;
//此外,其他数据类型也可以使用字面量语法:
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//也适用于:
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);

字面量数组:

NSArray *animals = [NSArray arrayWithObjects: @"cat", @"dog", @"mouse", @"badger", nil];
//字面量语法:
NSArray *animals2 = @[@"cat", @"dog", @"mouse", @"badger"];

NSString *dog = [animals objectAtindex: 1];
NSString *dog2 = animals[1];

使用字面量语法创建数组时,若数组元素对象中有nil,会抛出异常。arrayWithObjects:方法会依次处理各个参数,直到发现nil为止。这个差别说明,使用字面量语法更安全,抛出异常令程序终止执行,这比创建好数组后才发现元素个数少了要好。向数组中插入nil通常说明程序有错,而通过异常可以更快地发现这个错误。
字面量字典:

NSDictionary *personData = [NSDictionary dictionaryWithObjects:
	@"Matt", @"firstName",
	@"Galloway", @"lastName",
	[NSNumber numberWithInt: 28], @"age",
	nil];

NSDictionary *personData2 = 
	@{@"Matt", @"firstName",
	  @"Galloway", @"lastName",
	  @"age" : @28};
//与数组一样,用字面量语法创建字典时也有个问题,那就是一旦有值为nil,便会抛出异常。

NSString *lastName = [personData objectForKey:@"lastName"];

NSSrting *lastName2 = personData[@"lastName"];

可变数组与字典:

[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];

mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";

局限:
想创建自定义子类的实例,必须采用“非字面量语法”。
使用字面量创建的字符串、数组、字典对象都是不可变类型。若要可变版本,则需要复制一份:

NSMutableArray *mutable = [@[@1, @2, @3]mutableCopy];

三、多用类型常量,少用#define预处理指令

#define ANIMATION_DURATION 0.3

这样做,会使定义出来的常量没有类型信息,此外,预处理过程会把所有的ANIMATION_DURATION一律替换成0.3。这样的话,假设它声明在某个头文件中,那么所有引入了这个头文件的代码,其ANIMATION_DURATION都会被替换。
因此,使用下面的方法就更好:

static const NSTimeInterval kAnimationDuration = 0.3;

此外,还要注意常量名:若常量局限于某个“编译单元”(即“实现文件”)之内,在前面加字母k;若常量在类外可见,则通常以类名为前缀。
变量一定要同时使用staticconst来声明。const表示不可修改,static意味着该变量仅在此变量的编译单元内可见。
实际上,若一个变量既声明为static,又声明const,那么编译器不会创建该符号,而是会像#define一样替换为常量。不过,这类常量含有类型信息。
有时候需要对外公开某些常量,比如使用NSNotificationCenter通知他人。用一个对象来派发通知,令其他欲接受通知的对象向该对象注册,这样就能实现该功能了。派发通知时,需要用字符串来表示此项通知的名称,这个名称就可以声明为一个外界可见的常值变量
此类常量需放在“全局符号表”中,以便可以在定义该常量的编译单元之外使用。应该这样定义:

//header file
extern NSString *const ECOStringConstant;
//implementation file
NSString *const ECOStringConstant = @"VALULE";

常量定义从右向左读:一个常量,这个常量是指针,指向NSString对象。此类常量必须要定义,而且只能定义一次。
为了避免名称冲突,最好使用与之相关的类名做前缀。例如:

//EOCAnimatedView.h
extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
//EOCAnimatedView.m
const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;

四、用枚举表示状态、选项、状态码

应该用枚举表示状态机的状态、传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
如果把传递给某个方法的选项表示为枚举类型,而多个选项又可以同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
NS_ENUMNS_OPTIONS宏来定义枚举类型,并指明其底层数据类型。这样可以确保枚举是用开发者所选的底层数据类型实现出来的。而不会采用编译器所选的类型。
在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值