Post on 05-Dec-2014
description
Objective-C
范圣刚,princetoad@gmail.com,www.tfan.org
•Objc 基础
•通过⼀一个RandomPossessions 的⼩小程序看⼀一下有关:类,实例,对象,消息,初始化⽅方法,类⽅方法,数组,字符串和格式化字符串,访问器,异常
• iOS 应⽤用
•Objective-C: C 语⾔言的扩展
• Cocoa Touch: Objective-C 类的集合
•⾯面向对象编程(object-oriented programming)
Objective-C
对象
Party的表⽰示•属性•名称•⽇日期•受邀⼈人列表•操作•发送email提醒
•打印名称标签•取消party
C
•结构体•数据成员•类型和名称•malloc
• C 函数
Class
•类•实例•结构体成员和实例变量•⽅方法:名称,返回值类型,参数列表,实例变量访问•运⾏行⽅方法:message
使⽤用实例
使⽤用实例•使⽤用类的实例•指向对象的变量•指针变量 -> 对象在内存中的地址
•指向对象的变量声明:Party *partyInstance;
•只是⼀一个可以指向 Party 对象的变量,不是对象本⾝身。
创建对象•对象的⽣生命周期:被创建,发送消息,不⽤用的时候销毁
• alloc 消息:Party *partyInstance = [Party alloc];
•当你拥有⼀一个指向实例的指针时,就可以向它发送消息
•第⼀一个消息 -> initialization 消息:[partyInstance init];
•合并:Party *partyInstance = [[Party alloc] init];
• nested message send
发送消息
消息剖析•中括号中•三个部分
• receiver: ⼀一个指向被要求执⾏行⽅方法的对象的指针
• selector: 要执⾏行的⽅方法的名称
• arguments: 作为参数提供给⽅方法的值
• [partyInstance addAttendee:somePerson]
• 发送 addAttendee: 消息给 partyInstance(receiver) 触发 addAttendee: ⽅方法(通过 selector 命名)并且传⼊入 somePerson(⼀一个argument)
• message 可以有⼀一个参数,多个参数,或者没有参数
[ partyInstance addAttendee: somePersonwithDish: deviledEggs ]
receiver 是被发送消息的对象指针
selector 是被触发的⽅方法的名称 arguments 被⽅方法使⽤用
发送消息
其他语⾔言partyInstance.addAttendeeWithDish(somePerson, deviledEggs)
Objective-C
[partyInstance addAttendee:somePerson withDish:deviledEggs];
销毁对象• partyInstance = nil;
nil
• if (venue == nil) ...
• if (!venue) ...
•向 nil 变量发送消息• if (venue) { [venue sendCon!rmation]; }
• nil-check 是没有必要的
开始 RandomPossessions
把模板代码换成创建和销毁数组实例
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]){
@autoreleasepool { // // insert code here...// NSLog(@"Hello, World!"); // 创建⼀一个 mutable 数组对象,把它的地址保存到 items 变量 NSMutableArray *items = [[NSMutableArray alloc] init]; // 销毁 items 指向的对象 items = nil; } return 0; }
•⼀一但实例化完数组以后,我们就可以给它发送消息了,例如 addObject: 和 insertObject:atIndex
int main(int argc, const char * argv[]){
@autoreleasepool { // // insert code here...// NSLog(@"Hello, World!"); // 创建⼀一个 mutable 数组对象,把它的地址保存到 items 变量 NSMutableArray *items = [[NSMutableArray alloc] init]; // 向 items 发送 addObject: 消息,每次传递⼀一个字符串 [items addObject:@"One"]; [items addObject:@"Two"]; [items addObject:@"Three"]; // 发送另外⼀一个消息 insertObject:atIndex 给同样的数组对象 [items insertObject:@"Zero" atIndex:0];
•为了确认这些 string 被添加到了数组,我们来加⼀一段代码把它们打印出来
•构建并运⾏行,看⼀一下控制台的输出 // 遍历数组 for (int i=0; i < [items count]; i++) { // 隐式发送 description 消息 NSLog(@"%@", [items objectAtIndex:i]); }
创建字符串• [items addObject:@”One”];
•@符号 + 字符串 = Objective-C 中的硬编码字符串
•NSString 的实例
• alloc?
•@:仅针对 NSString 的⽅方便的⽣生成字符串的缩写 NSString *myString = @"Hello, World!"; int len = [myString length]; len = [@"Hello, World" length]; myString = [[NSString alloc] initWithString:@"Hello, World!"]; len = [myString length];
格式化字符串•NSLog 打印到控制台,接收可变数量的参数•第⼀一个参数是必须的,⽽而且必须是 NSString 实例,称作 format string,包含⽂文本和⼀一些 token。
• token 以百分号%作为前缀
•每⼀一个传给函数的附加参数替换掉 format string 中的⼀一个token
• token 同时也指定了他们对应的参数的类型 int a = 1; float b = 2.5; char c = 'A'; NSLog(@"整数: %d 浮点数: %f 字符: %c", a, b, c);
%@
•任何对象• description 消息,返回⼀一个 NSString
•所有对象都实现了 description 消息
NSArray 和 NSMutableArray
•NSArray
•不可变数组•实例化之后就不能再增加或者删除对象了•NSMutableArray
• NSArray ⼦子类
•可变数组•可以被修改(动态增加删除对象)
指针,引⽤用和内存地址
数组能放什么?•数组只能持有 Objective-C 对象的引⽤用
•基本数据类型和 C 结构体不能增加到数组
•⼀一个单独的数组可以包含不同类型的对象•和强类型语⾔言不同
数组操作•⼤大⼩小:int numberOfObjects = [array count];
• addObject: 在数组 后增加对象
• insertObject:atIndex: 在特定位置插⼊入对象
•注意:不能往数组中增加 nil ,可以使⽤用 NSNull
• [array addObject:[NSNull null]]
• objectAtIndex: 获取数据
⼦子类化⼀一个 Objective-C 类•层次结构 (hierarchy)
•只有⼀一个超类(superclass)•NSObject
• root class
•⾓角⾊色是实现所有 Cocoa Touch 中的对象的基本⾏行为
• alloc, init, description
类的层次结构•扩展•重写
创建⼀一个NSObject 的⼦子类• target 勾选• BNRItem, BNRItem.h, BNRItem.m
•保留所有的 C 语⾔言关键字
•Objective-C 额外的关键字使⽤用@前缀区分
•关键字 @interface 在 Objective-C 中声明⼀一个类
•冒号后跟⽗父类•只允许单⼀一继承• @interface ClassName : SuperclassName
• @end 指⽰示类已经被完全声明了
实例变量类的实例变量紧跟类的声明之后,在⼤大括号中进⾏行声明
#import <Foundation/Foundation.h>
@interface BNRItem : NSObject{ NSString *itemName; NSString *serialNumber; int valueInDollars; NSDate *dateCreated;}
@end
BNRItem 实例
访问器⽅方法•有了实例变量之后,要能够 get,或 set 他们的值
• get/set 实例变量的⽅方法称作 accessors, 或者分别叫做getters 和 setters.
•Objective-C 的 setter ⽅方法名:set + 实例变量名
• getter ⽅方法名:和实例变量名⼀一样
BNRItem 访问器⽅方法#import <Foundation/Foundation.h>
@interface BNRItem : NSObject{ NSString *itemName; NSString *serialNumber; int valueInDollars; NSDate *dateCreated;}
- (void)setItemName:(NSString *)str;- (NSString *)itemName;
- (void)setSerialNumber:(NSString *)str;- (NSString *)serialNumber;
- (void)setValueInDollars:(int)i;- (int)valueInDollars;
- (NSDate *)dateCreated;
@end
实例⽅方法• description
•重写:在实现⽂文件中定义,不需要再做声明// 重写 description- (NSString *)description{ NSString *descriptionString = [[NSString alloc] initWithFormat:@"%@ (%@): 价值 $%d, 登记⽇日期 %@", itemName, serialNumber, valueInDollars, dateCreated]; return descriptionString;}
初始化器• alloc -> ⽣生成⼀一个实例,返回⼀一个指向它的指针
• init -> 给所有的实例变量初始化值
•⾃自定义初始化过程简化代码•命名习惯:以init开头
BNRItem 的 Initializer
•⽅方法名(或者叫selector)是:initWithItemName:valueInDollars:serialNumber:
•这个selector 具有三个 label:initWithItemName:, valueInDollars:, 和 serialNumber:。表明⽅方法接收三个参数
•这些参数每个都有⼀一个类型和⼀一个参数名•类型在圆括号中,参数名紧跟类型
}- (id)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber;- (void)setItemName:(NSString *)str;
id
•返回值类型是id
•指向任意对象的指针• BNRItem * ?
•⼦子类化的问题
isa
•从 initializer 返回的对象的类型我们是知道的(向类发送 alloc )
•对象⾃自⾝身也知道它⾃自⼰己的类型 isa 指针
•每个对象都有⼀一个名为 isa 的实例变量
•当⼀一个实例通过向类发送 alloc 创建的时候,类把返回的对象的 isa 实例变量设成指回到⽣生成它的类
• isa 指针的意思就是说这个对象是⼀一个这个类的实例
isa 指针
实现designated initializer
•第⼀一件事,使⽤用 super 调⽤用超类的初始化器
• 后,使⽤用 self 返回成功初始化过的对象// 实现初始化⽅方法- (id)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber{ // 调⽤用⽗父类的指定的初始化器 self = [super init]; // 给实例变量初始化值 [self setItemName:name]; [self setSerialNumber:sNumber]; [self setValueInDollars:value]; dateCreated = [[NSDate alloc] init]; // 返回初始化对象的地址 return self;}
理解 self
•在⽅方法内部,self 是隐式的本地变量
•不需要声明,⾃自动设成指向被发送消息的对象•可以⽤用于给⾃自⾝身发消息的情况下• return self;
理解 super
•重写⽅方法时保持超类⽅方法中的操作,在其上增加新的内容
• super:编译器指⽰示符
•指定名称的⽅方法的查找顺序:对象类⾃自⾝身 -> 超类 -> 超类的超类
•给 super 发消息,直接跳过 self 本⾝身, 从其超类开始
• initializer 失败的话返回 nil
• self = [super init];
其他初始化器以及初始化链•⼀一个或多个初始化⽅方法•初始化⽅方法链式调⽤用的好处•减少出错的机会•代码更容易维护
// 使⽤用初始化⽅方法链,没有的参数可以传⼊入默认值- (id)initWithItemName:(NSString *)name{ return [self initWithItemName:name valueInDollars:0 serialNumber:@""];}
重写 init
- (id)init{ return [self initWithItemName:@"Item" valueInDollars:0 serialNumber:@""];}
Initializer 链
initializer 简单规则•类从其超类继承了所有的 initializer,同时可以根据其⺫⽬目的任意增加
•每个类选择⼀一个 initializer 作为其 designated initializer
• designated initializer 调⽤用超类的 designated initializer
•类的任意其他 initializer 调⽤用类的 designated initializer
•如果类声明了⼀一个和其超类不同的 designated initializer,超类的 designated initializer 必须被重写来调⽤用新的 designated initializer
使⽤用 initializer
•检查重写的 init 是否⽣生效
•使⽤用 designated initializer 替换掉设置实例变量的代码
类⽅方法•⽅方法•实例⽅方法•类⽅方法•实例⽅方法被发送给类的实例(像init)
•类⽅方法被发送给类本⾝身(像alloc)
•类⽅方法⼀一般不是被⽤用来创建⼀一个类的新的实例,就是⽤用来提取类的⼀一些全局属性
•类⽅方法不能在实例上操作,或者访问实例变量
声明类⽅方法•实例⽅方法的声明: 在返回值前⾯面 使⽤用减号“-” 表⽰示
•类⽅方法的声明:使⽤用加号“+” 字符
• convenience method:stringWithFormat, randomItem, 返回⾃自⾝身类型的对象
•类⽅方法中的 self
•指向类本⾝身,⽽而不是实例•⼦子类调⽤用的问题
测试⼦子类
for (int i=0; i<10; i++) { BNRItem *p = [BNRItem randomItem]; [items addObject:p]; } for (int i = 0; i < 10; i++) { NSLog(@"%@", [items objectAtIndex:i]); }
异常和⽆无法识别的 Selectors
异常•⼀一个对象只会响应它的类实现了相关⽅方法的消息•动态类型的objc,编译时⽆无法确定
•异常exception,run-time errors(运⾏行时错误,和编译时错误相对compile-time errors)
快速枚举
•Objective-C 2.0 引⼊入了fast enumeration
for (int i = 0; i < 10; i++) { NSLog(@"%@", [items objectAtIndex:i]); }
for (BNRItem *item in items) { NSLog(@"%@", item); }