整天捣鼓嵌入式,兼职搞搞iOS,这么折腾为了啥?都是为了俺的娃!
分类: iOS平台
2015-01-22 14:48:46
一、OC简介
1.本文简称Objective-C为OC.
2.OC就是C语言,只不过其对C进行了扩展,使之有了面向对象的概念.
3.OC完全兼容C语言语法,也就是说在.m文件中,你完全可以使用C语言。当使用C语言风格的函数时,
函数不能像OC的方法那样使用消息机制来调用,同样struct定义的结构体也不具备对象的属性,不
像C++中的结构体本身就是对象。
4.C风格的函数我们称为函数,OC中类成员函数我们称之为方法,以示区别.
5.同时OC与C++也可以混合使用,这样的程序称作Objective-C++程序,程序文件名后缀为.mm.
OC不支持命名空间,类名必须是唯一的,惯用做法是类名加前缀,CoCoa框架中很多前缀,如NS,CF...
二、基本数据类型
类型 范围 格式化字符串
char 8bits %c
int 32bits %i,%o,%x,%#x
float 32bits %f,%e,%g
double 64bits %f,%e,%g
id 任何对象 %p
数据类型限定词
signed,unsigned:有符号和无符号整数,常量后加u/U,编译器会保证该数为无符号数
short: 16bits,格式化字符串为"%hi","%ho","%hx"
long int:大部分系统与int一样位宽(32位),格式化使用"%li",常量后加l/L,如131071100L.
long long int:64bits,格式化使用"%lli".
long double:格式化使用"%Lf","%Le","%Lg",常量后加l/L,如1.234E+7L.
格式化字符串说明:
1.%#x与%x是等效的.
2.%g是NSLog格式,允许NSLog确定使用科学计数法(%e)还是浮点计数法(%f)显示,
如果值x<-4或者x>5,则采用%e表示,否则采用%f表示。
3.16进制的浮点常量用p/P表示,如0x0.3P10 = 3/16 * 2^10
4.id表示未知对象,是objective-c多态和动态绑定的基础,id=nil表示id指向空对象,nil相当于C语言中
的NULL.还有一个与id类似的关键字instancetype,也可以作为方法的返回值,但不可以作为参数,而id
则可以作为参数,instancetype可以使那些非关联返回类型的方法返回所在类的类型,编译器可以检测
出instancetype类型的对象是否具有某些方法,而编译器无法检测id类型的对象所具有的方法。
5.布尔类型:BOOL,值为YES/NO.其实质是typedef char BOOL来实现的,它的定义在Foundation.h中.
6.C类型的字符串前加@表示将字符串转换为NSString类型
7.C类型的字符串必须使用"%s"进行格式化输出,NSString类型的字符串则使用"%@"。
8.引用类型或指针类型的对象表示时使用指针的形式,如定义一个NSString对象:
NSString *str = @"hello!";
而在C++中,定义一个string: string str = "hello!", string *str则表示的是一个对象指针。
对于基本类型,如NSInteger,则直接使用普通变量的形式(因为:typedef long NSInteger;):
NSInteger number = [[NSNumber initWithInteger:100] integerValue];
基本类型数据一般是存储在栈中,而引用类型数据需要使用alloc或new创建,存储在堆中。
三、方法/函数
-(void) func:(int)x andY:(int)y ...
| | | | | | | |
| | | | | | | 参数名...
| | | | | | 参数类型
| | | | |参数2标识符
| | | | 参数名
| | |参数类型
| | 方法名(我们可以将它看成是参数1标识符)
|方法返回值类型
方法类型(+类方法,-实例方法)
例:
-(void) buildBox:(int)x andY:(int)y andZ:(int)z{...}
在C++中函数重载是利用了参数类型来识别,我们会这样写上面的函数:
void buildBox(int x,int y, int z){...}
C++编译后函数名实际函数名称变为了buildBox_int_int_int这样的形式.而在OC中,是利用参数
标识符来实现方法的重载的,OC编译后函数名称变为了buildBox_andY_andZ这样的形式.我们调用
方法时应该这样写:[xxx buildBox: 3 andY: 4 andZ: 5],实际上我们可以讲方法名看作是第一
个参数的标识符。方法名称实际上是由所有参数标识符构成的,":"为参数分割符。
当然,也可以不写参数标识符:
-(void) buildBox:(int)x :(int)y :(int)z{...}
那我们调用方法的时候也只能这样调用: [xxx buildBox:3 :4 :5],这样的写法就没法清晰的
知道参数的意义了,因此不推荐省略方法的形参名称。
在OC中方法中没有C++中public,protected,private的概念,只有类似于static的概念,方法类型
为"+"表示调用该方法时不需要生成对象实例,而"-"表示只有该类的实例才可以调用该方法。
四、类
1.定义类接口(*.h文件)
@interface Demo
{
int x;
int y;
...
}
@property (readonly,noatomic) int x,y;
...
@end
2.类实现(*.m文件)
@implementation Demo
...
@end
3.类的成员变量默认为protected的,也可以使用以下指令指定成员变量的访问属性:
@public:自己、子类其他类都可以访问.
@protected:自己及子类访问,其他类不能访问.
@private:自己访问,子类和其他类都不能访问.
@package:仅供在该类所在的可执行文件镜像中被访问,其他可执行镜像中不可访问.
4.自动合成成员变量的set/get方法
@property (attr1,attr2...) int x,y; /* 在h文件接口定义中声明变量属性 */
@synthesize x,y; /* 在m文件中自动合成方法 */
OC中允许使用"."运算符来访问和设置成员变量.
attr1,attr2是变量的属性列表,属性主要分为三类:
读写属性: (readwrite/readonly)
setter语意:(assign/retain/copy)
原子性: (atomicity/nonatomic)
关联性: (strong/weak)
各属性意义如下:
readwrite: 产生setter\getter方法
readonly: 只产生简单的getter,没有setter。
assign: 默认类型,setter方法直接赋值,而不进行retain操作
retain: setter方法对参数进行release旧值,再retain新值。
copy: setter方法进行Copy操作,与retain一样
nonatomic: 禁止多线程,变量保护,提高性能
strong:
weak:
5.self关键字,相当于C++中的this,但是self不是一个实例变量,它可以被类方法调用.与之对应的是super关键字,
代表该类的父类对象.
指令用于前向申明此类型为一个类,而不用去import该类的头文件,但是当需要使用该类的
的方法是,则需import类的头文件,否则编译器无法知道类的方法定义.
五、选择器
@seletor类似于C语言的函数指针,是OC实现动态绑定和多态的一个基础特性。在OC中,程序编译时,编译器将
所有的方法名称写到一个表中,每个方法分配一个标识符,同名的方法(不同类)共用一个标识符。
SEL即代表方法的标识符。
SEL func = @selector(buildBox:andY:andZ:);
与SEL相关的还有一个IMP变量.
六、分类和协议
1.分类用于扩展类的功能,分类可以访问类的成员变量,但不能为类新增成员变量。分类所定义的方法,可以实现,
也可以不实现。分类命名必须是唯一的。
2.协议相当于Java中的接口,只定义不实现。需要由使用它的类来实现。可以使用关键字@required和@optional来
指定协议中必须实现的方法和可选实现的方法。协议中默认的方法都是@required的。
3.非正式协议,其实质就是分类。
七、重载
由于OC是根据方法名来识别函数的,重载时只要求方法名不相同,参数可以相同。比如
-(void) test:(int) intValue;
-(void) test:(float) floatValue;
上述两个函数在oc中属于重复定义,不是重载,因为它们的名字都是"test:"。
所以在oc 中的重载应该是指参数个数不同,但是返回类型后面的名字相同。
或者是 参数个数相同,但是其方法名不相同。
-(void) test:(int) intValue;
-(void) test:(float) floatValue; //错误,与第一个方法重名(test:)
-(void) test:(int) x andY:(int)y;
-(void) test:(int) x andY:(double)y;//错误,还是重名了(test:andY:)
-(void) test:(int) x :(double)y; //正确,名字不同
八、块(BLOCK)语法
代码块本质上是和其他变量类似,相当于Java中的匿名函数.不同的是,代码块存储的数据是一个函数体。
使用代码块是,你可以像调用其他标准函数一样,传入参数数,并得到返回值.
void (^printBlock)(NSString *x);
| | | | |
| | | | 参数名
| | | 参数类型
| | 块对象
| 块的语法标志
块对象返回值
printBlock = ^(NSString* str)
{
NSLog(@"print:%@", str);
};
printBlock(@"hello world!");
在使用block时几个重要点:
1.block函数外的对象,在block语句块内只有可读访问权限。
2.对外部对象进行__block申明解决了问题1.
3.各类型的变数和block之间的互动.
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42 ;
__block char localCharacter;
void (^aBlock)( void ) = ^( void )
{
++ CounterGlobal ; //可以存取。
++ CounterStatic ; //可以存取。
CounterGlobal = localCounter; //localCounter在block 建立时就不可变了。
localCharacter = 'a' ; //设定外面定义的localCharacter 变数。
};
++localCounter; //不会影响的block 中的值。
localCharacter = 'b' ;
aBlock(); //执行block 的内容。
//执行完后,localCharachter 会变成'a'
}
九、内存管理&ARC
1.OC中有两类对象,一类是结构体(包括基本类型数据),一类是NSObject对象(即我们所说的引用类型数据).
对于结构体的赋值操作,会创建一个源对象的副本(即一个新对象),而对于引用类型数据的赋值操作,则不
会创建对象副本,而是使用其引用(即指针),赋值后两个引用对象都指向同一个内存地址.
TestObject *obj1 = [[TestObject alloc] init];
TestObject *obj2 = obj1;
对象变量的实质是指针,所以obj2与obj1指向的仍是同一个对象.请牢牢记住这一点.
对于引用类型数据,当我们在一个地方引用后,需调用retain方法将其引用计数加1,当我们不再引用时,
需调用release方法将其引用计数减1,这样将可以防止引用无效的对象。如我们调用了一个函数,这个函数
使用到了obj1,我们就需要在这个函数中retain一下obj1,退出函数时release掉obj1,如果不这样做的话,
万一在函数外已经释放掉了obj1,则此函数的obj1将是一个无效的引用.(请自行脑补C语言中类似的指针使用法).
2.将对象添加到任意一种数据集合中(使用addObject方法),引用计数都会被自动加1.
3.使用常量字符串初始化的不可变字符串对象(NSString)的内存空间分配与其他对象不同,它没有引用计数机制,
程序运行期间都不会被释放,其引用计数值永远为0xffffffff.这一点与NSMutableString不同
4.强引用(strong)、弱引用(weak):如果一个对象完全属于另一个对象,则使用强引用;如果一个对象可以被多个
对象引用,则使用弱引用。strong相当于retain,weak相当于assign(但weak会在对象释放后归零,对象变为nil)。
只要对象仍存在别的对象强引用了该对象,该对象就不能被释放,如果仅有弱引用指向该对象,则该对象可以被释
放,指向该对象的引用则变成了nil。
十、OC消息机制的原理
在Objective-C中,message与方法的真正实现是在执行阶段绑定的,而非编译阶段。编译器会将消息发送转换成对
objc_msgSend方法的调用。objc_msgSend方法含两个必要参数:receiver、方法名(即:selector),如:
[receiver message]; 将被转换为:objc_msgSend(receiver, selector);
objc_msgSend方法也能hold住message的参数,如:
objc_msgSend(receiver, selector, arg1, arg2, …);
objc_msgSend方法会做按照顺序进行以下操作,以完成动态绑定:
1.查找selector所指代的程序(方法的真正实现)。因为不同类对同一方法有不同的实现,所以对方法的真正实现
的查找依赖于receiver的类
2.调用该实现,并将一系列参数传递过去
3.将该实现的返回值作为自己的返回值,返回之
消息传递的关键是,编译器构建每个类和对象时所采用的数据结构。每个类都包含以下两个必要元素:
一个指向父类的指针;
一个调度表(dispatch table)。该调度表将类的selector与方法的实际内存地址关联起来。
每个对象都有一个指向所属类的指针isa。通过该指针,对象可以找到它所属的类,也就找到了其全部父类。
当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)
中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针super找到父类,并在父类的调度表
(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的
内存地址调用该实现。通过这种方式,message与方法的真正实现在执行阶段才绑定。
为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的
缓存,缓存包含有当前类自己的selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统
首先检查receiver对象的缓存。缓存命中的情况下,消息发送(messaging)比直接调用方法(function call)只慢一点点。
十一、OC中的一些注意点
1.new与alloc init的区别:
在C++中我们用new来创建对象,在OC中,我们也可以使用new方法来创建对象:
[MyObj new]
虽然OC支持new方法,但这种写法比较少见,我们一般使用正统的创建对象的方法:
[[MyObj alloc] init]
这两种方法是有着细微的差别的:
1).new方法只能采用默认init来初始化对象,而alloc方式则可以使用自定义的init方法来初始化对象,如
initXXX系列函数.
2).alloc分配内存时采用_zoneAlloc方法,将相关联的对象分配到相邻的内存区域,以便调用时消耗更少的代价,
提升程序处理速度,而new调用的是普通的_alloc方法.
2.对象初始化的细节:
在OC中,默认初始化函数为init.
3.浅拷贝与深拷贝:
对于非容器类对象,如果对一个不可变对象进行复制,copy是指针复制,即浅拷贝:而mutableCopy则是对象复制,
即深拷贝;如果是对可变对象复制,都是深拷贝,但copy返回的对象是不可变的.