黑马程序员--08 OC 核心语法

来源:互联网 发布:linux查看硬盘个数 编辑:程序博客网 时间:2024/06/08 05:37

                                   ------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1.点语法


本质:是方法调用,并不是访问成员变量,所以前边的声明是不能少的

p.age  =  10;  等价  [p  setAge:10];

int a = p.age;  等价 [p age];

编译器特性:当使用点语法时,编译器会自动展开称相应的方法。


2.成员变量作用域


基本概念  :局部变量,全局变量,都有自己的作用域,成员变量也不例外

四大类型的成员变量的访问方式:

a.@private  :  只能在当前类的实现@implementation中直接访问,也就是对象中,但是可以用set方法和get方法来访问

b.@protect :  可以在当前类及子类的的实现@implementation中直接访问,也就是对象中

c.@public   : 任何地方都能通过成员变量名直接访问成员变量,也就是对象中

d.@package:只要处在同一个框架中,就能直接访问(介于@private,@protect之间)

在声明中什么都不写就代表了是protected

在实现中什么都不写代表私有private,但是m文件不被main函数包含,所以是不认的

注意:    a.@implementation中也可以定义成员变量但不能与@interface中的变量名相同,所以没有@interface,只有@implementation也可以开发一个类。

b.@interface中在成员变量默认为@protect,@implementation中成员变量默认为@private(因为.m文件不会被包含即使在@implementation中使用@public来说明成员变量的类型也没用,还是@private类型;只有.h文件才可以被包含,所以在@interface中使用@public是有作用的)。


3.@property和 @synthesize 


@property:可以自动生成,某个成员变量的setter和getter方法声明,如果自己同时写便不会生成。

@property  int   age; 

@synthesize :可以自动生成成员变量的setter和getter方法实现,并且会访问这个成员变量。

 @synthesize  age  =  _age;


 @synthesize细节:    

@synthesize age = _age ;

setter和getter方法实现中默认会访问成员变量 _age。

如果成员变量_age不存在,就会自动生成一个@private(因为@synthesize写在@implementation中)的成员变量_age。

@synthesize age  ;

setter和getter方法实现中默认会访问成员变量 age。

如果成员变量age不存在,就会自动生成一个@private。


 Xcode4.4之后的特性

@property独揽@synthesize功能,即@property可以同时生成setter和getter的声明和实现,默认情况下,setter和getter方法中的实现会去访问下划线_开头的成员变量。


手动实现

若手动实现setter方法编译器就会生成getter和成员变量(成员变量已存在便不会生成)

若手动实现getter方法编译器就会生成setter和成员变量(成员变量已存在便不会生成)

若同时手动实现setter和getter方法,编译器就不会生成不存在的成员变量(即没有成员变量)也就是没有_age这个变量了,去掉其中一个实现,那么还是有_age的

@interface Car : NSObject@property int age,height; // 这句话能够做三件事,1:声明private的成员变量_开头的,2:声明set和get方法,3:实现set和get方法@end@implementation Car@endint main(){    Car *p = [Car new];       // [p setAge:10];        p.age = 10; // 用点语法来代替set方法        //[p age];        int s = p.age; // 用点语法来代替get方法        NSLog(@"%d",s);        return 0;}


4.  数据类型  id  


万能指针,能指向任何OC对象,相当于NSObject *

typedef  struct objc object{

   Class isa;

}* id;                   //注意: id后面不要加*

int main(){    id p = [Car new]; // id相当于NSObject *       [p setAge:10];        //p.age = 10; // 用点语法来代替set方法        //[p age];        int s = [p age]; // 用点语法来代替get方法        NSLog(@"%d",s);        return 0;}



5.构造方法


new方法和默认的init方法 

+new是一个类方法,用来创建一个对象, 但是new方法把所有属性的值初始化为0, 在开发中不常用.  

 

完整的创建出一个可用的对象,需要两步:

1分配存储空间给对象, 返回一个有了存储空间的对象(对象地址) (返回类型是id).

2给对象初始化, 返回一个可用的对象

 

这两步分别对应一个方法:

1 类方法 +alloc分配空间

2 对象方法 -init  初始化对象 (默认的-init方法初始化所有参数为0)

 

new方法里面只做这两件事:

1 调用+alloc分类存储空间

Person *p1 = [Person alloc];

2 调用-init进行初始化

Person *p = [p1 init];




#重写init方法#

init就是构造方法,用来初始化对象. 构造方法是对象方法,因为它需要设置成员变量的值.

默认构造方法中所有的成员变量的值都设为0.我们想让新建对象时成员变量的默认值不是0, 可以自己重写构造方法.

-init的方法是父类NSObject的方法.可以在子类中重写.

在类的实现中重写 (重写方法可以不用声明,直接实现就可以)

@implementation Person- (id) init {        // 必须要先调用父类的init方法进行初始化,因为父类中声明的成员变量子类也有, 需要在init中初始化(比如把isa指针指向当前的类).        self = [super init];      // 初始化父类的成员变量,返回初始化好的对象        // 判断是否初始化成功        if (self != nil) {                // 给当前类的成员变量赋值                _age = 20;        }        // 返回已经初始化完毕的对象        return self;}@end/*       简写方式- (id) init {        if (self = [super init]) {     // 调用父类方法初始化, 并判断self是否有值                _age = 20;              // 给成员变量赋值        }        return self;                      //返回初始化完毕的对象}*/  


# 小结 #

构造方法 -初始化对象的方法.

重写构造方法,可以让对象创建时, 成员变量默认为非0的值.

重写构造方法的步骤:

1 调用父类的init方法,并判断是否为空值

2 初始化子类的成员变量.

3 返回初始化完毕的对象



6.自定义构造方法

重写init方法时,对象的成员变量初始值只能是固定的值. 自定义构造方法可以在初始化时给成员变量赋不同的初值.

自定义的构造方法需要声明和实现 (重写父类的方法不需要声明)

 

自定义构造方法的规范 :

a.一定是对象方法,一定以-开头

b. 返回值是id类型

c. 方法名以一般init开头 (方便程序员之间的交流,一看就知道是构造方法)

d. 方法内部的格式和重写init的格式相同 :调用super的方法并判断空值,给自己的成员变量赋值, 返回对象


@interface Person : NSObject@property NSString *name;@property int age;// 添加自定义构造方法- (id) initWithName:(NSString *)name;- (id) initWithName:(NSString *)name andAge:(int)age;@end @implementation Person// 重写init方法,默认名字SCL- (id) init {        if (self = [super init]) {                _name = @"SCL";        }        return self;}// 实现自定义的构造方法- (id) initWithName:(NSString *)name {        if (self = [super init]) {                  // 和重写init方法是同样的步骤                _name = name;        }        return self;}- (id) initWithName:(NSString *)name andAge:(int)age {        if (self = [super init]) {                _name = name;                _age = age;        }        return self;}@end int main() {        Person *p = [[Person alloc] initWithName:@"Rose" andAge:18];     // 调用自己写的初始化方法新建对象        return 0;} 



注意,

如果有继承,通常只给自己的成员变量直接赋值, 父类的成员变量通过调用父类的初始化方法对其赋值


假如Student类是Person的子类,比Person类多了一个属性_no,我们想给Student添加初始化方法

@interface Student : Person@property int no;- (id) initWithNo:(int)no;- (id) initWithName:(NSString *)name andAge:(int)age andNo:(int)no;@end @implementation Student- (id) initWithNo:(int)no {        if (self = [super init]) {                _no = no;        }        return self;}- (id) initWithName:(NSString *)name andAge:(int)age andNo:(int)no {        if (self = [super init]) {                _no = no;                // _name = name; 错误.因为_name在父类中是@private,不可以通过_name访问                // [self setName:name];     可以使用set方法设置, 但是不建议                // self.age = age;               也可以使用点语法, 但是也不建议这样做         }        return self;}@end int main() {        Student *s = [[Student alloc] initWithNo:2];        return 0;}// 合理的初始化父类提供的属性,最好使用父类的初始化方法   void  test(){- (id) initWithName:(NSString *)name andAge:(int)age andNo:(int)no {        if (self = [super initWithName:name andAge:age]) {     //调用父类的初始化方法                _no = no;        }        return self;}


    结论:父类属性交给父类处理子类的属性子类处理,即这个成员变量属于谁就让谁来初始化


# 分类 Category #

使用场合 :

        比如我们已经有一个Person类,有它定义的方法. 我们想给Person类新加一些方法,但是要求不能修改Person类 (Person.h和Person.m文件).

一种方法是使用继承新建一个子类, 就可以包含Person类的所有方法并添加新的方法.

另一种方法是使用分类, 不需要新建类,也可以扩充类的方法.

 

分类: 

扩充类方法,不修改类文件. 可以添加对象方法和类方法.利于团队开发.

  

分类的使用

一个类可以有很多个分类.每个分类都可以给它添加不同的方法.

/分类的声明#import "Person.h"               // 需要引用主类, 可以调用主类的方法 (比如属性的set/get方法)@interface Person(MJ)        // 分类用 (小括号) 表示- (void) study;@end //分类的实现#import "Person+MJ.h"         // 分类文件的文件名默认为 主类名+分类名@implementation Person(MJ)- (void) study {        NSLog(@"Person is studying. ");}@end //分类的使用#import <Foundation/Foundation.h>#import "Person.h"                  // 包含主类文件#import "Person+MJ.h"           // 包含分类文件int main() {        Person *p = [[Person alloc] init];        [p study];                       // 可以对这个类的对象调用分类中的方法        return 0;}


注意:

a. 只能增加方法,不能增加成员变量. 如果需要增加成员变量,可以考虑创建子类继承.

b. 分类的实现中可以访问主类的成员变量.

c.分类可以重写主类的方法,但是不建议使用. 如果分类中有和原类中同名的方法,会覆盖原类的方法, 这样会导致主类的方法无法再被调用,不建议这么做.

d. 如果不同的分类中有同名的方法,和编译的顺序有关, 会调用最后编译的分类中的这个方法.在 Build Phases - Compile Sources中可以查看.可以看到只编译.m源文件 (.c .cpp), .h文件不会被编译,安装从上到下的顺序编译. 可以调整文件编译的顺序.注意就算把主类放到分类的后面编译, 依然会调用最后编译的分类中的方法.  (Link Binary with Libraries是链接的框架, 就是命令行的 -framework XXX,可以添加其它框架,告诉Xcode哪些框架需要链接)

也就是说如果分类中有重名的方法,会相互覆盖, 也会覆盖掉主类中同名的方法.分类中不建议写同名的方法

方法调用的优先级:

 调用方法时,会先在分类中寻找,如果没有,再到原类中寻找,如果没有,再到父类中寻找,如果没有,再到头文件中寻找.


7.类对象

 

什么是类对象

在OC中, 类其实也是一个对象, 是Class类型的对象, 也叫类对象.

 

Person *p = [[Person alloc] init];

Person类其实也是个对象. Person对象是Person类型的对象. Person类是Class类型的对象.

Person对象叫做实例对象, Person类这个对象叫做类对象.

类名就代表一个类对象. (也就是说, 每个类都只有一个类对象)

创建Person类其实就是使用Class类创建Person类这个对象. (创建Person对象就是使用Person类创建对象)

 

Class类型的定义

typedef def struct objc_class *Class;

可以看到Class类型的定义中包含了星号.

 

 

#类对象的获取#

可以通过对象或者类的class方法返回类对象. class方法返回Class类型的对象(星号被省略), 即获取内存中的类对象.

Class c = [p class];                  // c就是Person类对象.

Class c2 = [p2 class];              // c2和c是同一个对象, 可以打印它们的地址, 是一样的.

Class c3 = [Person class];      // c3和c和c2都是同一个类对象

 

注意:

Class这个名字里已经包含星号了, 所以使用时不用再写星号.

 

 

#类对象的使用#


前面讲到, 先加载类对象, 再通过类对象来创建实例对象. 可以通过class方法来获取内存中的类对象(指针).

类对象c和Person等价. Person类名其实就是这个类对象.

 

可以通过类对象来调取类方法.

比如Person类中有一个 +(void)test 方法

[Person test];等价于[c test];

也就是说 [[p class] test]; 和 [Person test]; 是一样的. [p class] 和 Person 是同一个类对象.

 

通过类对象指针来创建实例对象 (new是类方法)

Person *p = [c new]; Person *p2 = [Person new];作用是一样的.


 

 

#类的加载和初始化#


类的 +load方法 和 +initialize 方法


a.在程序启动时, 系统会先把程序中所有的类和分类都加载进内存(即使没使用这个类也会加载). 加载时会调用类的 +load方法, 只调用一次。且先加载原始类在加载分类。

b.第一次使用某个类的时候会调用+initialize方法对类进行初始化, 只调用一次。如果有分类, 也会被加载和初始化.


注意:

a.当需要调用类的初始化方法时 ,如果分类中重写了initialize方法,  不会调用主类的+initialize方法. 

b.+load方法是在加载时调用的, 所以主类和分类的+load方法都会被调用.

 

初始化方法的应用

重写+initialize方法来监听一个类在什么时候被第一次使用. 或者在类第一次被使用时需要做一些操作就要写在+initialize里.

 

# 小结 #

加载

a.当程序启动时, 会加载程序中的所有类(父类, 子类, 和分类). 并在加载后调用所有类(父类, 子类, 分类)的+load方法. 只调用一次.

b.先加载父类, 再加载子类, 再加载分类.(即先加载原始类在加载分类)

c.每个类的+load方法只调用一次

初始化 

a.当第一次使用某个类时, 会调用当前类的+initialize方法.

b.如果这个类有父类, 父类也要初始化.  即先初始化父类, 再初始化当前类.

c.如果这个类有分类, 分类的的初始化方法会覆盖主类的初始化方法, 所以需要调用主类的初始化方法时, 其实调用的是分类的初始化方法.

d.每个类的+initialize方法只调用一次

 


8.description方法

 


调用:

description方法是NSObject自带的方法. 有类方法和对象方法。


作用:

输出类或对象的属性。

当使用NSLog(@"%@")打印对象时, 就调用了对象的 -description 方法, 把-description方法返回的字符串输出到屏幕上. 默认输出的是对象的类名和地址,<类名:内存地址> 可以通过重写description方法打印出对象的内容.

 

#重写-description方法#


@implementation Person- (NSString *)description {        // 需要返回一个字符串        return [NSString stringWithFormat:@"Person: age=%d, name=%@", _age, _name];}@end


#关于+description方法#


调用: 

Class c = [Person class];    // 获得类对象

NSLog(@"%@", c);            // 即 NSLog(@"%@", [Person class]);  不可以直接使用类名Person, 必须使用一个Class对象

作用:

输出类或对象的属性。

 当使用类对象来调用NSLog(@"%@"), 会调用类的+description方法, 返回NSString*并打印. 默认输出的是类名. 可以重写+description来输出其它内容.

 

#小结 #


使用NSLog和%@打印时: 

如果打印的是实例对象, 调用-description方法, 默认打印类名和对象地址.

如果打印的是类对象, 调用+description方法, 默认打印的是类名.

 


9.SEL数据类型 

  

含义:

SEL类型的数据代表方法. 对象在调用方法时, 会把方法转换成SEL类型的数据.或者说, 发消息其实发送的就是SEL数据. (消息机制说的就是SEL的使用)SEL的全称是selector.

 

方法在内存中的存储

假设Person类含有两个方法, 一个类方法+test 和一个对象方法 -test2(它们可以同名).程序运行时, 首先给Person类分配存储空间, 里面包含Person类的方法列表. 每个方法都对应着一个SEL数据. 根据SEL的值就可以找到方法的地址, 进而调用方法(可以理解为储存了方法的地址).

当对象调用对象方法时, 通过isa指针访问类的方法列表, 是先把test2转换成SEL类型的数据, 再根据SEL的值到类的方法中寻找对应的方法, 再通过方法地址来调用方法.


 

SEL类型的定义

typedef struct objc_selector *SEL;  可以看到SEL数据也自带星号. 但是它不是对象, 而是一个结构体指针.

 

SEl数据的创建:

a.使用方法名  SEL sel1 = @selector(test2);

b.使用字符串SEL sel2 = NSSelectorFromString(@"test3:");

注意:

a.如果方法有参数, 方法名的冒号是一定要写的, 否则会报找不到对应的方法(没有参数的方法)

b.这里的方法名可以随意, 可以写一个不存在的方法, 并转换成SEL数据, 并不会报错. 但是如果使用这个SEL给对象发消息的话, 就会报错找不到方法了.

 

使用SEL变量发送消息

发消息其实发送的就是SEL数据, 所以也可以使用SEL变量给对象发送消息


a.[p test2];

b.[p performSelector:@selector(test2)];      //调用了p的test2方法

(可以写 [p performSeletor:sel1];   // 之前定义了 sel1 就是 test2 对应的SEL数据.)

c.[p performSelector:@selector(test3:) withObject:@"abc"];  // 方法有参数 和[p test3:@"abc"]; 是一样的.

注意:

方法名test3:是有冒号的. 要不就会找不到方法 (unrecognized selector sent to instance).

 

SEL变量与字符串转换


可以把SEL变量转换成字符串打印, 不能直接打印, 打印出的是它对应方法的方法名


a.NSString *s = NSStringFromSelector(sel2);          // 把sel2转换成字符串

  NSLog(@"%@", s);            // 打印结果是 test3:

b.SEl s =NSSelectorFromString(@"test3");


 _cmd

其实每个方法内部都有一个SEL数据 (SEL)_cmd 代表当前方法, 可以用它获得当前方法的名称.


- (void) test2 {        NSString *s = NSStringFromSelector(_cmd);     // 把当前方法转换成字符串        NSLog(@"%@", s);                  // 可以打印出来} //调用时打印的是这个方法的方法名 test2


如果使用__func__打印, 会打印出方法的全名:

NSLog(@"%s", __func__);          // 打印结果是 -[Person test2].



0 0