A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© lijunyusmile 中级黑马   /  2016-7-12 00:15  /  902 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文


    }

    return 0;

}

预处理指令import会自动检验头文件有没有被包含过,防止重复包含,NSLOG是日志输出,OC字符串以@开头,自动换行,int类型的占位符是@i。OC所有关键字以@开头,@autoreleasepool与内存管理有关。



OC中的类分两个文件,.h用来声明类的变量和函数,

.m文件负责实现,与.h配合使用。OC中最根本的类叫NSObject,OC是单继承的。声明类以@interface开头,以@end结尾,实现类用@implementation开头,以@end结尾。继承用冒号。OC当中使用一个类时,导包就是#import一个类的头文件。

声明类时,成员变量要声明在大括号中,方法声明在大括号外,如果是对象方法要写-号,静态方法要写+号,所有在.h文件当中声明的方法都是公共方法,凡是类型,都要写括号,在方法名后,一个参数要一个括号,如:

//Student.h

#import <Foundation/Foundation.h>

@interface Student : NSObject {

    int age;

}

-(int)getAge;

-(void)setAge:(int)age;

@end

实现类时,首先要导入.h的声明.

//Student.m

#import "Student.h"

@implementation Student

- (int)getAge {

    return age;

}

- (void)setAge:(int)newAge {

    age = newAge;

}

@end

对象的创建需要调用类的静态方法alloc分配内存,调用静态方法要写[],里面是类名和方法名,返回值需要用指针来接收,也就是说OC中的对象都要写个*,比如这句话调用了Student的一个静态方法alloc分配内存,并返回了一个指针来接收,其实alloc方法返回的是id类型,可以暂时理解为任何类型:

Student *stu = [Student alloc];

分配内存后要调用一个动态方法进行初始化,相当于stu指针给Student发送了一个init消息:

stu = [stu init];

也就是说定义一个变量需要两句话,但是很麻烦,所以可以连起来写,这种方法最常用:

Student *stu = [[Student alloc] init];

OC不支持垃圾回收,需要用后释放:

[stu release];

调用方法不用括号:

[stu setAge:100];

整个调用代码:

#import <Foundation/Foundation.h>

#import "Student.h"

int main(int argc, const char * argv[])

{

    @autoreleasepool {

        Student *stu = [[Student alloc] init];

        [stu setAge:100];

        int age = [stu getAge];

        NSLog(@"%i", age);

        [stu release];

    }

    return 0;

}

实际上,OC中的get方法不推荐使用get前缀,都是直接写变量名,比如[stu age];。

如果要定义有多个参数的方法可以:

- (void)setAge:(int)newAge andNo:(int)newNo{

    age = newAge;

    no = newNo;

}

调用:

[stu setAge:100 andNo:1];

也就是说方法名可以拆成多个部分。

注意,这里的方法的方法名是setAge:andNo:,冒号也是方法名的一部分。



OC也支持点语法,比如:person.age = 10;

不过这句话并不是java那样给成员变量赋值,而是调用了对应的set方法,在编译的时候会自动转化为方括号的语法。如果点语法在等号左边,则调用set方法,在右边则调用get方法。为了和成员变量加以区分,OC推荐成员变量都以_开头。在set方法当中,绝对不能调用self.age=newAge,根据上面的解释,这种写法会死循环。同样在get方法里也不能return self.age。在OC当中用点语法来表示get/set方法。

在点语法当中,



OC中的init是一个对象方法,返回id类型,可以自己写构造方法,自己写构造函数的时候需要先调用父类构造函数,由于在内存分配时可能失败,所以要判nil,严格的实现写法如下:

-(id)initWithAge:(int)age andNo:(int)no {

    if (self = [super init]){

        self.age = age;

        self.no = no;

    }

    return self;

}

调用如下:

Student *stu = [[Student alloc] initWithAge:10 andNo:2];

注意OC当中构造函数并没有要求函数名和类名一样,其中点语法调用的是set方法,不存在死循环问题。



如果没有构造方法,可以用下面的语法来创建对象,这是个简写的语法,不推荐这么用。

Student *stu = [Student new];



可以通过NSLog(@"%@", stu);语句打印一个对象的内存地址,如果想自己重写输出格式,需要复写如下方法:

-(NSString *)description{

    NSString *str = [NSString stringWithFormat:@"age is %i and no is %i", self.age, self.no];

    return str;

}



可以让一个变量自动释放内存,需要多调用一个autorelease方法,比如:

Student *stu = [[[Student alloc] initWithAge:10 andNo:2] autorelease];

通常情况下,使用系统自带的一些静态方法创建的对象都是可以自动释放的。



OC的成员变量默认是protected的,子类可以访问。对于成员变量,提倡使用get和set方法。OC访问权限只有@public,@protected和@private三种,可以如下声明:

@interface Student : NSObject {

    @public

    int _age;

    int _no;

}

这样声明的话,两个变量全是public的,这样就需要在外部使用一些C++语法访问了,是不提倡这样做的。

关于方法的权限,因为别人要使用你的类,所以包含在.h文件里的就是公有方法,如果直接把方法写在.m文件中,就是私有方法了。

如果想直接访问成员变量,那么它首先是public的,然后需要使用->符号访问。



在OC方法当中,动态方法当中使用self,谁调用这个方法self就是谁,在静态方法当中,self代表的是当前类。



实际上,OC自身提供了属性机制:

#import <Foundation/Foundation.h>

@interface Student : NSObject {

    int _age;

}

@property int age;

@end

当编译器遇到原型关键字时,会自动把这行代码展开成get和set方法的“声明”。但是.h里没有实现,所以需要在.m里用一句话实现:

#import <Foundation/Foundation.h>

#import "Student.h"

@implementation Student

@synthesize age;

@end

更灵活的一点是,如果通过synthesize实现了get和set方法,那么如果在.h文件里找不到同名的变量,会自动生成一个“同名”的“私有”的成员变量,所以在.h文件里这个成员变量也可以省略了。

也就是说@synthesize age生成的成员变量是age,但是为了区分成员变量和get方法,通常习惯把成员变量定义成_age,所以为了让生成的age使用_age,所以要改成:

@synthesize age = _age;

在这种方式下,是不会生成一个叫age的同名成员变量的,这时会生成一个叫_age的成员变量(注意是私有的而不是保护的)。也就是说一个完整的封装是:

@interface Student : NSObject {

    int _age;

}

@property int age;

@end

#import <Foundation/Foundation.h>

#import "Student.h"

@implementation Student

@synthesize age = _age;

@end

在XCode4.5以上的版本当中,连synthesize那一句都可以不写了,直接在.h里声明一个变量就完事了,而且在这种方式下,访问的是私有的_age成员变量!

如果我们手动实现了get或set方法,那么synthesize就不会生成对应的方法了。



所有继承了NSObject的对象都需要内存管理,OC在对象的内部维护一个整数作为引用计数器,当它为0时会回收这个对象。当对象创建完时(alloc,new,copy),这个数字为1。给对象发送retain或release消息,可以让计数器+1或-1.当一个对象计数器为0时,系统会自动调用dealloc消息,可以重写这个方法做内存释放控制,重写时在最后面调用父类的这个方法,但是不要人工调这个方法。可以发送retainCount消息获取引用计数器值。在对象被回收之后,不要多次release,否则会导致野指针错误。

OC的ARC机制全称自动引用计数,意思是把所有release之类的工作全交给系统来管理。



对于对象的聚合/组合,比如Student拥有Book,这种方式是不推荐的:

Student *stu = [[Student  alloc] initWithAge:10];

Book *book = [[Book alloc] initWithPrice:3.5];

stu.book = book;

[book release];

[stu release];

是因为点语法调用的setter方法并没有改变book对象的引用计数,所以之后两个对象才会被正常释放并没有内存泄露。下面看一个例子:

//book.h

#import <Foundation/Foundation.h>

@interface Book : NSObject

@property float price;

-(id)initWithPrice:(float)price;

@end

//book.m

#import "Book.h"

@implementation Book

-(id)initWithPrice:(float)price{

    if (self == [super init]){

        _price = price;

    }

    return self;

}

-(void)dealloc{

    NSLog(@"book:%f 被销毁了", _price);

    [super dealloc];

}

@end

//student.h

#import <Foundation/Foundation.h>

#import "Book.h"

@interface Student : NSObject {

    //这里需要定义一个成员变量,因为自己写了get和set方法

    Book *_book;

}

@property int age;

-(id)initWithAge:(int)age;

-(Book *)book;

-(void)setBook:(Book *)book;

-(void)readBook;

@end

//student.m

#import "Student.h"

@implementation Student

-(id)initWithAge:(int)age{

    if (self == [super init]){

        _age = age;

    }

    return self;

}

-(Book *)getBook {

    return _book;

}

-(void)setBook:(Book *)book {

    //需要先判断,防止重复赋值,同时防止retain一个已释放的野指针

    if(_book != book){

        //先释放旧的成员变量,OC中即便释放nil也不会出空指针错误

        //但是释放一个野指针就会报错

        [_book release];

        //必须要手动给子对象的引用计数+1

        _book = [book retain];

    }

}

-(void)readBook{

    NSLog(@"当前读的书是:%f", _book.price);

}

-(void)dealloc{

    //在类的set方法里retain,类自己就要负责销毁

    [_book release];

    NSLog(@"student:%i 被销毁了", _age);

    //最后调用父类的释放方法

    [super dealloc];

}

@end

//main.m

#import <Foundation/Foundation.h>

#import "Student.h"

#import "Book.h"

void test(Student *stu){

    Book *book = [[Book alloc] initWithPrice:3.5];

    stu.book = book;

    //在哪个方法里分配,就在哪个方法里释放

    [book release];

    stu.book = book;

   

    Book *book2 = [[Book alloc] initWithPrice:4.5];

    //经常会重新调用set方法,所以它的内部负责释放先前的book

    stu.book = book2;

    [book2 release];

}

int main(int argc, const char * argv[])

{

    @autoreleasepool {

        Student *stu = [[Student  alloc] initWithAge:10];

        test(stu);

        [stu readBook];

        [stu release];

    }

    return 0;

}



如果一个类持有另一个类,而且在.h文件里使用#import "Book.h"导入的话,会把.h全拷过去,性能会受影响,而且getset等方法也会暴露出去,一般不这么写,习惯在.h文件里使用关键字:@class Book;,只要知道Book是个类就可以了,在.m文件里真正要用到类里的方法时,再使用#import "Book.h"来获取其中的方法。一个典型的错误就是A类improt了B类,B也improt了A类,这样就会死循环而报错。而且,如果有多个文件同时improt了一个头文件,那么一旦这个头文件发生了改变,其他引用它的文件都要重新编译,而@class不存在这个问题。

需要注意的是,如果是继承某个类,就一定要improt头文件,因为这样才能知道它里面定义了什么方法。如果只是定义成员变量或属性,就@class。



假设这时候Student类里有很多对象做属性,那么代码是这样:

#import "Student.h"

#import "Book.h"

#import "Card.h"

@implementation Student

-(void)setBook:(Book *)book{

    if (_book != book){

        [_book release];

        _book = [book retain];

    }

}

-(void)setCard:(Card *)card{

    if (_card != card){

        [_card release];

        _card = [card retain];

    }

}

-(void)dealloc{

    [_book release];

    [_card release];

    [super dealloc];

}

@end

这些内存管理的set方法太啰嗦了,所以可以不手写这些set方法,而在属性定义的时候这样:

@class Book;

@class Card;

@interface Student : NSObject

@property (retain) Book *book;

@property (retain) Card *card;

@end

这里的retain代表先release旧值,然后retain新值,就不用写内存安全的成员对象的set方法了。所以所有OC对象最好都这么写,但是如果不写,就会生成不管理内存的标准getset方法。

对于常量类型的成员变量,有这样的写法:

@property (assign) int age;

这个assign可以不写,默认就是这样,而且不能写retain,非OC对象不需要管理内存。

Property后面可以带多个参数,用逗号间隔,比如:

@property (nonatomic,retain) Card *card;

这里可以写的属性有三种:

getter的处理:readwrite(默认)/readonly(只生成get方法),

setter的处理:assign(默认,直接赋值)/retain(先释放后ratain)/copy(先释放后copy),

原子性:atomic(默认,给getset方法加锁,线程安全)/nonatomic(禁止多线程保护,性能更好)。通常iphone开发不考虑线程安全。

对于BOOL类型,需要保证get方法变成IsXxxx,需要加上参数:

@property (nonatomic, getter = isRich) BOOL rich;



OC有自动释放池机制,这与java垃圾回收不同,在池子释放时,会对池子里所有的对象调用一次release方法(并不是销毁)。OC对象只要发送一条autorelease消息,OC就会把这个对象添加到释放池当中。所以说autorelease实际是把对象的释放延迟到池子释放了,但是它并不会改变计数器。

最常用的写法是:

    @autoreleasepool {

        Student *stu = [[[Student alloc]init]autorelease];

    }



在OC中有一个潜规则,用来快速创建一个对象的静态方法和类名相同,而且静态方法都自动释放,比如有一个创建Student的静态方法:

@interface Student : NSObject

+(id)student;

@end

实现的时候一定要注意自动释放:

+(id)student {

    return [[[Student alloc]init]autorelease];

}

静态方法一般都不需要我们手动来管理内存。

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马