第十章 对象的初始化

两种创建对象的方法:

1. [类名 new]  2. [ [类名 alloc ] init ];  其中第二种方法是应该熟练使用的方法。 

初始化对象:

对于继承了NSObject的类来说,调用超累的init方法可以让NSObject执行它所需的所有操作,以便于对象能够响应消息并处理保留计数器。而对于从其他类继承的类,通过这种方法可以实现自身的全新初始化。

与分配对应的操作时初始化,初始化从操作系统取得一块内存用于存储对象,init方法(执行初始化操作的方法)一般都会返回正在初始化的对象。 例如: Car *car = [ [ Car alloc ] init ];

- (id) init{

    if (self = [super init]) {

        engine = [Engine new];

        tires[0] = [Tire new];

        tires[1] = [Tire new];

        tires[2] = [Tire new];

        tires[3] = [Tire new];

    }

    return (self);

//init

在上面的例子中,self通过固定距离寻找实例变量所在的内存位置,如果从init返回一个新的对象,则需要更新self以便于其后的实例变量的引用可以被映射到正确的内存位置。这也是上面例子需要self = [super init]进行赋值的原因。这个赋值操作只会影响该init方法中的self值,不影响范围以外的任何内容。

如果在初始化一个对象时出现问题,则init方法可能会返回nil。表示未能初始化该对象。


-(id) initWithFormat : (NSString *) format, … ;

string = [[NSString alloc] initWithFromat : @ “%d  or  %d “,34,234]; 输出  34  or  234

-(id) initWithContentsOfFile:(NSString *) path encoding:(NSStringEncoding) enc error:(NSError **) error

这个initWithContentsOfFile:encoding:error:方法用来打开指定路径上的文本文件,读取文件内容,并使用文件内容初始化一个字符串。

NSError *error = nil;

NSString *string = [ [ NSString alloc] initWithContentsOfFile:@“/tmp/test.txt” encoding:NSUTF8StringEncoding error :&error];


第十一章 属性。

在Cocoa中,分配和初始化是两个分离的操作:来自NSObject的类方法alloc为对象分配一块内存区域并将其清零,实例方法init用于获得一个对象并使其运行。一个类可以拥有多个初始化方法。

@property 意味着声明了一个新对象的属性。

@property float rainHandling;  意思是AllWeather类的对象具有float类型的属性,其名称为rainHandling。

还可以通过调用-setRainHandling:来设置属性,通过-rainHandling来访问属性。

所有的属性都是基于变量的,所以在你合成(synthesize)getter和setter方法的时候,编译器会自动创建于属性名称相同的实例变量。


有两个地方可以用来添加实例变量声明:头文件和实现文件。

这两个地方的区别是:假设有一个子类,并且想要从子类直接通过属性来访问变量,这种情况下,变量就必须放在头文件中。如果变量只属于当前类,则可以把它们放在 .m 文件里。


点表达式

如果点表达式出现在了等号的左边,改变量名称setter方法(-setRainHandling: 和 -setrainHandling:)将被调用。

如果出现在了对象变量的右边,则该变量名称的getter方法(-rainHandling和-snowHandling) 将被调用。

self.name = @“Car”; == [self setName:@“Car”];  点表示法知识调用相同方法的快捷方式。所以这两种实现的内容是相同的。


nonatomic可以提高访问速度,在iOS上用的比较多。如果不想保留某个变量对象,可以使用assign特性,这样可以避免发生保留死循环。

如果没有为属性指定任何特性,它们默认使用nonatomic和assign,你也可以为保留的指针(即OC对象)指定retain和copy特性,而其他C类型和不可保留的指针必须使用assign特性并且要手动管理内存。如果自己定义了setter或getter方法,那么就不能使用atomic特性了,必须使用nonatomic特性。

@dynamic 关键字可以告诉编译器不要生成任何代码或者创建相应的的实例变量。


~~~~~~~~ 这两天因为有事情耽误没有更新,在看这两章的时候出现了很多不理解的地方。例如:在self和super如何使用,以及怎么区分这上面出现了很大的理解错误,不过幸好度娘很伟大,让我找到了解决方法。如下:

@interface Person:NSObject {
            NSString*  name;
        }
        - (void) setName:(NSString*) yourName;
        @end
        
        @interface PersonMe:Person {
            NSUInteger age;
        }
        - (void) setAge:(NSUInteger) age;
        - (void) setName:(NSString*) yourName andAge:(NSUInteger) age;
        @end
        
        @implementation PersonMe
        - (void) setName:(NSString*) yourName andAge:(NSUInteger) age {
            [self setAge:age];
            [super setName:yourName];
        }
        @end
        
        int main(int argc, char* argv[]) {
            NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]
            PersonMe* me = [[PersonMe alloc] init];  
            [me setName:@"asdf" andAge:18];  
            [me release];  
            [pool drain];  
            return 0;  
        }

上面有简单的两个类,在子类PersonMe中调用了自己类中的setAge和父类中的setName,这些代码看起来很好理解,没什么问题。
然后我在setName:andAge的方法中加入两行:

NSLog(@"self ' class is %@", [self class]);  NSLog(@"super' class is %@", [super class]);

这样在调用时,会打出来这两个的class,先猜下吧,会打印出什么?按照以前oop语言的经验,这里应该会输出:

self ' s class is PersonMe  super ' s class is Person

但是编译运行后,可以发现结果是:

self 's class is PersonMe  
super ' s class is PersonMe

self 的 class 和预想的一样,怎么 super 的 class 也是 PersonMe?

真相

self 是类的隐藏的参数,指向当前当前调用方法的类,另一个隐藏参数是 _cmd,代表当前类方法的 selector。这里只关注这个 self。super 是个啥?super 并不是隐藏的参数,它只是一个“编译器指示符”,它和 self 指向的是相同的消息接收者,拿上面的代码为例,不论是用 [self setName] 还是 [super setName],接收“setName”这个消息的接收者都是 PersonMe* me 这个对象。不同的是,super 告诉编译器,当调用 setName 的方法时,要去调用父类的方法,而不是本类里的。

当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法

当输出 [self class] 和 [super class] 时,是个怎样的过程:

当使用 [self class] 时,这时的 self 是 PersonMe,在使用 objc_msgSend 时,第一个参数是 receiver 也就是 self,也是 PersonMe* me 这个实例。第二个参数,要先找到 class 这个方法的 selector,先从 PersonMe 这个类开始找,没有,然后到 PersonMe 的父类 Person 中去找,也没有,再去 Person 的父类 NSObject 去找,一层一层向上找之后,在 NSObject 的类中发现这个 class 方法,而 NSObject 的这个 class 方法,就是返回 receiver 的类别,所以这里输出 PersonMe。

当使用 [super class] 时,这时要转换成 objc_msgSendSuper 的方法。先构造 objc_super 的结构体吧,第一个成员变量就是 self,第二个成员变量是 Person,然后要找 class 这个 selector,先去 superClass 也就是 Person 中去找,没有,然后去 Person 的父类中去找,结果还是在 NSObject 中找到了。然后内部使用函数 objc_msgSend(objc_super->receiver, @selector(class))  去调用,此时已经和我们用 [self class] 调用时相同了,此时的 receiver 还是 PersonMe* me,所以这里返回的也是 PersonMe。


下面这是关于初始化要注意的问题:

    众所周知,Objective-C是一门面向对象的语言,一般情况下,我们在Objective-C中定义一个类时,总要提供一个初始化方法,一般大家都是这样写的:

- (MyClass *)init

        {

            self = [super init];

            if (self) {

                //执行一些资源、变量的初始化工作

            }

            return self;


        }

这样一段简单的代码,却有很多可以思考的问题:

1、为什么要通过[super init]来调用父类的初始化方法,父类的初始化方法里又执行了什么东西?

      首先,我们知道对象继承的概念,一个子类从父类继承,那么也要实现父类的所有功能,这就是is-a的关系,比如说狗是哺乳动物,那么狗必定具有哺乳动物的特征和功能。所以在子类的初始化方法中,必须首先调用父类的初始化方法,以实现父类相关资源的初始化。例如我们在初始化狗这一对象时,必须先初始化哺乳动物这一对象,并把结果赋予狗,以使狗满足属于哺乳动物这一特征。

     典型的,在iOS下,所有的类都继承于NSObject,而NSObject的init方法很简单,就是return self。当父类的初始化完成之后,即self不为nil的情况下,就可以开始做子类的初始化了。

2、是否一定要提供初始化方法,是否一定要使用init作为初始化方法?

     我们在Objective-C中创建一个对象通常使用

    MyClass *newclass = [[MyClass alloc] init];

或者

     MyClass *newclass = [Myclass new];

    

new方法是NSObject对象的一个静态方法,根据apple的文档,该方法实际上就是alloc和init方法的组合,实际上二者是一样的,但 apple还是推荐我们使用第一种方法,为什么呢?因为使用第一种方法,你可以使用自己定义的init方法来做一些初始化(用自己写的init*****方法),当然,如果子类没有提供 init方法,自然调用的就是父类的init方法了。所以说,从安全性的角度来收,作为开发者我们在对象使用之前是一定要对对象进行初始化的,因此在定义类的时候一定要提供初始化方法。但是否一定要使用init作为方法名呢?答案是不一定。使用init作为方法名只是你重写了NSObject的init方法而已,如果你自己重新定义一个初始化方法,也是完全可以的,只要你在使用的时候记得调用新定义的初始化方法就可以了。

但是,这种方法从设计角度来看我觉得是不可取的。在可复用性方面会比较差,如果确有必要定义一些接受不同参数的初始化方法,我的建议是,先定义一个init的公用方法,再到其他方法中调用它,如:

01- (id)init   init的公用方法
02{
03    self = [super init]; 
04    if (self) {
05
06    }
07    return self;
08}
09
10- (id)initWithString:(NSString *)aString
11{
12    [self init];
13    self.name = aString;  
14}
15
16- (id)initWithImage:(UIImage *)aImage
17{
18    [self init];
19    self.p_w_picpath = aImage;  
20}

补充:

在面向对象编程中,如果编写一个类而没有包含构造函数,这个类仍能编译并且完全可以正常使用。如果类没有提供显式的构造函数,编译器会提供一个默认的构造函数给你。除了创建对象本身,默认构造函数的唯一工作就是调用其超类的构造函数。在很多情况下,这个超类是语言框架的一部分,如java中的 Object类,objective-c 中的NSObject类。

不论是何种情况,在类中至少包含一个构造函数是一种很好的编程实践,如果类中有属性,好的实践往往是初始化这些属性。


以上这两段都是网上大神的经验,看后对于理解初始化和self,super会有帮助。