【草稿】斯坦福大学ios开发课程解读(三)

本系列不按照课程的安排进行。最后会整理出来,目前只是草稿。每节课的回顾会放到最后再添加。

       今天我们要聊一下objective c,今天全部都是objective c。

为什么要用properties?

      首先,我们要深入讲解一下property。其他语言只有实体变量,property到底是干嘛的?首要的原因是实体变量的安全性和继承能力。如果我们允许被继承,子类要动这些实体变量,我们需要能够参与进来。如果子类设置了某个值,我们需要检查范围,保证不会破坏父类。第二个原因是它还提供了一个阀门给延迟实例化、UI更新、一致性检测。property有很多宝贵的作用,所以我们不要直接读取实体变量。

实体变量

     人们会问property必须要有实体变量吗?答案当然是非必须的。那么怎样才能让property没有实体变量呢?办法就是不要使用@synthesize,自己创建setter和getter。这样就不会有辅助的实体变量,你需要自己制造或者计算property,周四会有演示。

另外一个问题,可以有实体变量但是没有property吗?当然也是也可以的,但是现阶段我们任何时候都用property来产生。

为什么使用.号?

    最重要的原因是看起来舒服些,同时也使得property的读取更明显一点,读代码时候更容易发现这是在调用getter。.号还能配合C语言里的结构体。使用点符号法self.propery 访问@property 比普通的[self property] 方法更直观。【其他说法:在C 结构体中,也是使用点符号方法访问属性。这两个方法看上去很相似,但它们有两点大不同:

其一,我们不能发送消息给C 结构体,因为C 结构体根本没有可执行的方法。

其二,C 结构体绝不分配在heap 中,因此不能通过指针来访问到它。(注,它分配在stack 中。内存管理中分为heap 和stack )】

例子1 

typedef struct {
       float x;
       float y;
} CGPoint;

@interface Bomb
@property CGPoint position;
@end

@interface Ship:vehicle
@property float width;
@property float height;
@property CGPoint center,

- (BOOL)getsHitByBomb:(Bomb *)bomb;
@end
     任何类型,比如类,结构体,自定义类型的名字都需要大写,其他名字都不大写,property、方法、本地变量。请严格遵守这个规则,好让其他人更容易读懂你的代码。CGPoint是个结构体,所以大写。

     我还建了些类,一个是Bomb,它有一个C结构体的property,注意这不是结构体指针,就是个结构体。还有一个类Ship,有三个property,还有一个方法getHitByBomb,表示如果被炸弹击中就被摧毁了。然后来看实现代码。

@implementation Ship
@synthesize width, height, center;

- (BOOL)getsHitByBomb:(Bomb *)bomb
{
       float leftEdge = self.center.x -self.width/2;
       float rightEdge=……;
       return ((bomb.position.x >=leftEdge)&&
               (bomb.position.x <=rightEdge)&&
               (bomb.position.y >=topEdge)&&
               (bomb.position.y <=bottomEdge));
}

@end
以bomb.position.y为例,前面一个.号是存取property, 后面一个.号是存取C结构体,这两个的组合太完美了,不是吗?

strong VS weak

    ios5比ios4已经简单很多了,但我还是要保证你们都懂这个过程。strong和weak是指针的属性。当遇到比如UIBotton *这样的东西的时候,这个property是个指针,需要指定strong或者weak。要搞清楚strong和weak的区别:strong表示(这仅针对对象而言,不包括其他类型,ios几乎只有对象指针)保留它所指向的堆上的内存区域直到它不再指向这块区域了。假如这个指针指向了nil,那么就不再指向这块区域了。或者说没有其他strong型指针指向这块区域,那么该区域就会被回收。weak表示只要还有strong型指针指向该区域,那么该区域就会被保留。也就是说即使我不再指向它了,只要别的strong指针还指向它,它就会被保留。事实上,weak还表示没有strong指针指向它了,它就被清除出内存,指向它的weak指针就会被指向nil,因为指针不能读取不存在的东西。weak只在ios5上起作用。【此处内容翻译的斯坦福课程讲的不是很清楚,可以查阅http://blog.csdn.net/favormm/article/details/7023322】这不是垃圾回收。我们用reference count来表示堆上还有多少strong指针。当它变成0的时候就立即释放。不像垃圾回收,谁也不知道什么时候会被释放。strong和weak都是针对property的,本地变量都是strong的。当函数结束之后,这个指针不再指向它了,就会马上释放。

   有人会问“当我的对象要被清理出内存的时候会收到通知吗?”,答案是肯定的,就是这个dealloc方法,但是在这门课上,你很少会用到它,因为到这一步的时候已经几乎被清理出内存了,你也做不了什么,你不能再操作property的实体变量,因为马上就要被清理出内存了。在ios4你还需要手动管理的时候很重要,但是现在已经无所谓了,不用担心dealloc。问题:我在文档里看到了一个叫release的东西?那是ios4里的东西,ios5里已经用不到了。如果你要调用release,编译器可能甚至都不会让你调用它,所以不要用release、retain或其他内存管理的东西,ios5已经都为你做好了。【其他说法:但是这种清理heap 的机制不是垃圾回收机制,在iOS 上没有这种内存自动回收机制。它采用的一种更好的机制,称之为引用计算(reference counting )。这种机制可以展开来讲很多,但在iOS 中又有了新机制,称为自动应用计算(ARC) 。了解它可以有助于理解内存管理。】


nil

nil表示0值,指针0表示不指向任何东西,所有synthesize生成的实体变量初始值都是0,如果你希望你的指针指向什么东西,你可以调用setter或者在getter里使用延迟初始化。可以使用类似if  (ogj){}的结构来隐含地测试是否为nil。向nil发送消息不会导致崩溃,结果是什么也没发生。如果有返回值,那就是0。除了一个情况,如果返回的是一个C结构体呢?假如你给一个指向nil地对象发送消息,要求返回一个C结构体,那么你得到的是一个未定义的结构体,这个东西是随机的、不是你能预想到的。前面的例子里,CGPoint p=[obj getLocation];假如obj为nil,那么p将得到一个未定义的C结构体。


BOOL

大家都懂的,NO表示0,YES表示非0。这里不用TRUE和FALSE。能否小写bool?答案是不可以的。

布尔类型

   许多语言都有布尔(Boolean)类型,当然这是个专用术语,指的是存储真值和假值的变量。Objective-C也不例外。

   C语言拥有布尔数据类型bool,它具有true值和false值。Objective-C提供了相似的类型BOOL,它具有YES值和NO值。顺便提一下,Objective-C的BOOL类型比C语言的bool类型早诞生十多年。这两种不同的布尔类型可以在同一个程序中共存,但在便携Cocoa代码时要使用BOOL。

Objective-C中的BOOL实际上是一种对带符号的字符类型(signed char)的定义(typedef),它使用8位存储空间。YES定义为1,NO定义为0(使用#define)。

Objective-C并不讲BOOL作为仅能保存YES或NO值的真正的布尔类型来处理。编译器将BOOL认做8位二进制数,YES或NO只是一种约定,这引发一个小问题:如果不小心讲一个长于1字节的整型值(例如short或int值)赋给一个BOOL变量,那么只有低位字节会用作BOOL值,假设该低位字节刚好为0(例如8960,携程十六进制为0x2300),BOOL值将会是0,即NO值。(http://blog.csdn.net/yangbo_hbzjk/article/details/7280167)


实例方法VS类方法

①实例方法以”-“号开始

 

   -(BOOL)dropBomb:(Bomb *)bomb  
                at:(CGPoint )position  
              from:(double)altitude;

类方法以”+“号开始

+ (id) alloc;// 这是类方法

+ (Ship *)motherShip;
+ (NSString *)stringWithFormat:...

②实例方法和其他语言里一样普通,对象是实例对象。

   类方法的对象是类不是实例,通常用来创建对象或者工具方法

③调用语法的区别

实例方法只能实例去调用,语法为[<pointer to instance> method]

Ship *ship = .......;//instance of a ship

destroyed = [ship dropBomb:firecracker
                        at:dropPoint
                      from:300.0];


同样,类方法只能用类去调用,语法为[Class method] 。

Ship *ship = [Ship motherShip];//Ship 是一个类,调用类方法motherShip来创建对象ship

NSString *resultString =[NSString stringWithFormat:@“%g”, result];//NSString 也是一个类型,同上理

[[ship class] doSomething];//ship 是一个实例化的对象,必须使用[ship class] 转换成类后才能调用类方法,而直接使用ship 实例无法调用到类方法。


(注,首字符为大写的都是类,小写的则是实例化的对象,这是命名规则)


④self/super 的区别

在实例中,self的意思是该类的对象,super是指该类超类的对象。根据继承原理,无论发消息给self还是super,其实都是发给了self,区别在于调用的是谁的方法。

在类中,self的意思是该类,super是指该类的超类。这边老外讲的不大清楚,一直没明白那句”in a class method,self means other class methods“。

【其他说法:

关于self大家需要记住下面的规则:

1,实例方法里面的self,是对象的首地址。

2,类方法里面的self,是Class.

尽管在同一个类里面的使用self,但是self却有着不同的解读。在类方法里面的self,可以翻译成class self;在实例方法里面的self,应该被翻译成为object self。在类方法里面的self和实例方法里面的self有着本质上的不同,尽管他们的名字都叫self。

和其他的语言类似,下面是类方法的一些规则,请大家务必记住。

1,类方法可以调用类方法。

2,类方法不可以调用实例方法,但是类方法可以通过创建对象来访问实例方法。

3,类方法不可以使用实例变量。类方法可以使用self,因为self不是实例变量。

4,类方法作为消息,可以被发送到类或者对象里面去(实际上,就是可以通过类或者对象调用类方法的意思)。】

什么时候用实例方法?几乎一直在用。什么时候用类方法?当要创建一个实例时或者获取一个共享的实例,或者获取关于类的一些公共信息。


实例化(instantiation)

①使用其他对象创建自己的对象

NSString’s - (NSString *)stringByAppendingString:(NSString *)otherString;
NSString’s & NSArray’s - (id)mutableCopy;//返回一个可变的对象
NSArray’s - (NSString *)componentsJoinedByString:(NSString *)separator;//合并NSArray中的各个元素并创建一个新的字符串


②不是所有的使用其他对象创建而生成的对象都是新建的,有的对象会只是一个指针,而不是在heap 中再划分内存创建对象。但是,如果使用copy 关键字,则会创建新对象。

NSArray’s - (id)lastObject;
NSArray’s - (id)objectAtIndex:(int)index;


比如在数组中获取最后一个元素,它不会新建一个元素,只会返回一个指向最后元素的指针。正好说一下,所有数组中的元素都是strong指针。除非数组释放或者移除该元素,否则它一直是被strong指针所指。所有类似数组和字典都是一样的。

③获得对象的主要方法还是使用类方法。

NSString’s + (id)stringWithFormat:(NSString *)format, ...
UIButton’s + (id)buttonWithType:(UIButtonType)buttonType;
NSMutableArray’s + (id)arrayWithCapacity:(int)count;
NSArray’s + (id)arrayWithObject:(id)anObject;


④使用Allocating  和  initializing 创建新对象

NSMutableArray *stack = [[NSMutableArray alloc] init];

CalculatorBrain *brain = [[CalculatorBrain alloc] init];
Allocating 是通过NSObject 的类方法alloc 在heap 中分配一个空间给新对象。如@synthesize 就是执行这个操作。因此所有的@sythesize 的对象都已经在heap 中分配了一个足够大的内存空间。分配的对象的初始值可能是0或者nil。但这只是第一步,如果要使用该对象,还必须初始化,否则会直接crash 。不管对象是否需要初始化,我们永远要记住在alloc外边加init或者initwith。永远要这么写,决不能只是alloc,甚至也不能下一行再init,一定要直接加在后面。alloc由NSObject完成,而且对于多数类而言,都有很多不带参数的init方法,但NSObject 类只有一个初始化方法为init 。你必须设计能被alloc和init的类,但你被允许有其他任意多个init方法。假如你的init方法有参数,那么这个方法需要以init开头。举个例子,UIView也就是屏幕上的矩形显示区域,UIButton和UILabel都来自它,非常重要。它有一个init方法,叫做initWithFrame,允许初始化为一个矩形。还可以只用init,会得到初始化大小(0,0)坐标(0,0),可以稍后再设置坐标大小。所以它有这么两种初始化的方法。NSString有超过十几种的初始方法,StringWithFormat等于[self alloc]initWithFormat;initWithData可以把各种编码的数据转化为字符串。
- (id)initWithFrame:(CGRect)aRect; // initializer for UIView
UIView *myView = [[UIView alloc] initWithFrame:thePerfectFrame];
 
- (id)initWithCharacters:(const unichar *)characters length:(int)length;
- (id)initWithFormat:(NSString *)format, ...;
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;
 
      init这边有个小技巧,所有的类必须为子类提供一个初始化方法,方便子类初始化的时候,先调用父类初始化。因为你调用子类的时候会调用子类的初始化,当你这么做的时候需要调用这个init方法先初始化父类,知道这个指定的init方法很重要,比如UIView的初始化方法是initWithFrame,所以如果我继承了UIView,那么我需要在初始化里写[super initWithFrame];否则父类就没办法初始化。我不要调用其他的init方法,只要指定的那个。可以查看文档找到父类指定的init方法,然后在子类的初始化里调用这个方法。
     下面要讲你自己写的初始化方法。注意这些init方法返回类型是不固定的,为了继承的原因必须写成id。但如果你有一个本地变量在通过初始化赋值时,那么就应该是固定类型。例如,MyObject *obj=[[MyObject alloc]int;这边我并没写id obj=......;即使init返回的是id,而是写MyObject,这样就可以确保编译器不会发送错误内容。
     如何创建自己的初始化方法?初始化是非常常见的做法,但是objective c里面有property,所以不常用。
@implementation MyObject
- (id)init
{
self = [super init]; // call our super’s designated initializer
if (self) {
// initialize our subclass here
}
return self;
}
@end
MyObject指定的初始化方法是init,这里有行很特别的代码,self=[super init];在obj-c里self是本地指针,这是唯一可以对它赋值的情况。那么我们为什么要对它赋值呢?这是一种协议机制,确保super的初始化在前面。如果super没有初始化,就返回nil。所以如果因为某些原因,super没有完成初始化,譬如依赖的数据库挂了,那么就返回nil。所以要在子类里检查父类是否返回了nil。这里我的init调用了父类的指定初始化方法init,检查它是否返回nil。如果不是nil,那就init自己,然后返回self。id是obj-c内置类型,id的表象很像void *,但是编译器知道id的确切类型。所以当对它发送消息时,能先检查一遍。比如你可以任意发消息给void *,但是发个id会先检查它是否是个对象,可能无法检查id是否能回应消息,但至少知道一部分合法消息。
      以上是指定初始化,现在是一个快捷初始化。它不是指定的,但是很方便。
@implementation CalculatorBrain
- (id)initWithValidOperations:(NSArray *)anArray
{
     self=[self init];
     self.validOperations = anArray;//不会起作用,假如self==nil
     return self;
}

@end
这里我让我的CalculatorBrain只能被合法的运算符初始化,注意这个ini里面我没有调用super的指定初始化方法,我在自己的快捷初始化里调用了自己的指定初始化方法。这就是我说的所有其他的初始化都会去到指定初始化,然后再向上到super。

动态连接(Dynamic Binding)
      这是obj-c特有的,不是所有其他语言都有的。你们已经了解了固定类型和id,他们在runtime时候没有区别。唯一区别是编译器能否辅助发现bug。还了解了绝对不要用id *,指针的指针是没有意义的。消息的执行函数的决定发生在runtime。你可以想像,[obj * method]在编译器里是个C函数,我们叫它obj-c message sent,这个函数有的参数,一个是对象指针,一个是方法,其他的就和方法里的参数一致。想象一下这个过程,在运行过程中每次发消息都会调用这个函数。这个函数做的第一件事就是查看这是什么类,然后找到类的方法。如果这个类和它的父类都没有这个方法实现,程序就崩溃。所以这个过程中,无所谓类型式id或者NSString什么的,这个函数会自己去代码中查找。所以给出具体类型只是为了方便查找bug。
@interface Vehicle
- (void)move;
@end

@interface Ship:Vehicle
- (void)shoot;
@end

Ship *s = [[Ship alloc]init];
[s shoot];
[s move];

Vehicle *v = s;//可以这么做吗?当然可以,Ship也是Vehicle
[v shoot];

id obj = .....;
[obj shoot];
[obj someMethodNameThatNameThatNoObjectAnywhereRespondsTo];

NSString *hello = @"hello";
[hello shoot];
Ship *helloShip = (Ship *)hello;
[helloShip shoot];
[(id)hello shoot];
      我有2个类,一个是Vehicle,有个方法move,还有个是Ship,除了move,还能shoot。我在这里为Ship分配了空间并执行了shoot,一切都很顺利。然后我要它move,因为继承了,所以也没问题。那么我可以做[v shoot]吗?不行。v是Vehicle,它没有shoot方法。事实上v这个指针内存上是有shoot方法的,但是编译器不会知道。所以类型检查是基于指针的固定类型的。但这个不会崩溃,还是会执行。因为这个v其实是Ship,Ship可以shoot,但编译器会抱怨。那么如果是一个没有类型的对象呢?我让它shoot,编译器会投诉吗?不会,只要编译器能找到这个方法就没有问题。因为它认为你知道id是什么,编译器不会对id发出警告。没有办法知道这个指针真实的对象,因为都在代码里。所以不会报错也不会崩溃,只要还能找到shoot方法。如果是随便一个方法呢?那么编译器就要投诉了。编译器投诉是件好事,这样你再输入的时候它就会提示。再来一个常量字符串hello,我让hello去shoot,这肯定会失败,所以编译器在你输入的时候会提示你。那么如果我把hello映射在Ship上,编译器会通过,编译器认为拟在映射,你明白自己在做什么。虽然不会被警告,但是运行会崩溃。如果我映射到id上,同样没有警告直接崩溃。
【其他说法:

其一,所有的对象都被分配在heap 中,所以你一直可以使用指针

例如:

NSString *s = ...; //  静态类型定义

id obj = s; //  非静态类型定义,但完全合法,不要使用”id *” 因为它表示一个指针的指针。

 

其二,在runtime 时才会知道运行的代码是什么

静态类型如第一点所说的NSString * 和id 的比较,静态类型只不过在编译时更有利于我们发现bug 。

如果runtime 时,没有找到所运行的代码,就crash 。但我们可以使用“introspection ”去判断代码是否可以执行。】



自查(Introspection)

      id很危险,可以让程序崩溃,怎么办呢?我们使用自我测量。使用id不是一直都是坏的,比如不同类型的数组就很好用。如果数组里有很多类型的时候就要用id了。还有如果你想要对象的成员是private的,你又想把它交出去,但你不想被知道它是什么类,就设为id,就像cookie一样。但是有时候我们需要知道某些id的类,这三个方法可以帮你知道。isKindOfClass告诉你是否属于该类或者它的子类,isMemberOfClass则不包括继承类。respondsToSelector是另一个概念,这个对象是否能回应这个方法,不管它的类是什么。这三个是使用id的常用工具。这几个方法的参数比较巧妙。
if ([obj isKindOfClass:[NSString class]]) {
NSString *s = [(NSString *)obj stringByAppendingString:@”xyzzy”];
}
方法1 的参数值为[NSString class] ,即发送一个名为class 的类方法给NSString类而得到它的类名。这里的例子是判断对象是否是NSString。

if ([obj respondsToSelector:@selector(shoot)]) {
[obj shoot];
} else if ([obj respondsToSelector:@selector(shootAt:)]) {
[obj shootAt:target];
}

方法3 的参数值为一个选择器,如@selector(shoot) 。这里的例子是判断对象中有没有方法shoot ,有就响应为true 。

selector比较特别,在obj-c里是用来描述方法的。用了@selector就可以不用再写上参数的类型和名字,只要写上了调用的关键字和冒号就可以了。看一些第二个例子,shootAt:有一个参数,你可以像这样发消息而不会崩溃。注意这里参数的类型没有写出来。所以如果你有几个同名的selector有不同类型参数,就会乱了,别这样做。obj-c里有个类型用来保留selector的返回值。这个返回值的类型是SEL。
SEL shootSelector = @selector(shoot);
SEL shootAtSelector = @selector(shootAt:);
SEL moveToSelector = @selector(moveTo:withPenColor:);

在NSObject 类中,可以通过方法performSelector: (执行无参数的selector) 或者  performSelector:withObject: (执行带有一个对象参数的selector)来执行选择器。例如:

[obj performSelector:shootSelector];

[obj performSelector:shootAtSelector withObject:coordinate];

 

在NSArray 类中,可以通过makeObjectsPerformSelector: 让数组里所有的对象来执行选择器操作。例如:

[array makeObjectsPerformSelector:shootSelector]; // cool, huh?

[array makeObjectsPerformSelector:shootAtSelector withObject:target]; // target is an id

 

在UIButton 类中,可以通过  - (void)addTarget:(id)anObject action:(SEL)action ...; 方法来执行选择器。例如UIButton的方法addTarget:action:@selector:

[button addTarget:self action:@selector(digitPressed:) ...];

如果已经有button实例,就可以按照上面这么写,这就是在storyboard上添加target的代码方法。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值