iOS 开发基础:迅速掌握 Objective C
Objective C 是 C 语言的超集,继承了 C 语言的语法、基本数据类型、控制流语句;同时加入了对象和动态的运行时:类和方法、动态类型和绑定、直到运行时才做责任推导。
类与对象
类的定义分为接口和实现,对象方法和类方法用 -
和 +
标识,使用 []
进行方法调用,方法调用被视为发送消息:
//声明
@interface Person : NSObject
-(void)sayHello;
-(void)Greeting;
@end
//实现
@implementation Person
-(void)sayHello {
NSLog(@"Hello!");
}
-(void)greeting{
[self sayHello];
}
@end
类本身也是对象,其类型为
Class
。
方法不允许重载(编译错),子类中方法将覆盖父类中同名方法。因为 objc 的类型是动态的,所以调用时的指针类型并不重要,总会调用到正确的(子类)方法:
@interface BadPerson: Person
-(void) sayHello;
@end
@implementation BadPerson
-(void) sayHello{
NSLog(@"Great!");
}
@end
...
Person p = [[BadPerson alloc] init];
Person q = [Person new];
[p sayHello]; // Great!
[q sayHello]; // Hello!
init
可能返回与alloc
不同的对象,所以应该总是将返回值赋给某个指针。
变量的判等使用 ==
,对象判等则使用 isEqual
方法;变量声明时最好初始化,而对象指针则会自动初始化为 nil
。
数据封装
类声明中可以包含属性声明,使外部能够访问对象的数据。属性可以添加限定符来限定访问权限,objc 会自动为属性生成 getter
和 setter
,该方法名也可用户指定,实现同名方法即可自定义访问器。objc 将为每个属性生成一个实例,与属性同名并拥有前导下划线,当然用户可以指定:
@interface XYZPerson : NSObject
@property (readwrite) NSString *firstName;
@property (readonly, getter=isFinished) NSString *lastName;
@end
@implementation XYZPerson
@synthesis firstname = ivar_firstName;
...
_lastName = @"Jobs"; // 可以这样访问属性
ivar_firstName = @"Steve"; // 使用访问器才是推荐的做法
...
@end
NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@"Johnny"];
// 访问器方法可用点来调用
NSString *firstName = somePerson.firstName;
somePerson.firstName = @"Johnny";
objective c 属性与方法并无 public
与 private
的区别。类中声明的变量(无论是对象属性还是类属性)都是对外不可见的,外部可见的只有方法。
objective c 通过引入 .h
文件来使方法可见(即 public
)。因为 .m
文件不会引入,其中定义的方法只有当前类可见(即 private
)。
所以定义在 .m
文件中的类扩展(Extension,@interface(){}
)相当于私有方法和属性。
@propperty
的本质是生成访问器方法。属性访问是原子操作,即 objc 会原子的调用访问器方法。在构造函数中总是应该访问实例变量而不是属性访问器,否则可能产生副作用(MVC 通知、子类不知情的重写)。
@interface{}
中声明的变量将被作为对象变量,@interface{}
外禁止声明变量; @implementation{}
中声明的变量也将被视为对象变量,@implementation
外的变量将被作为类变量。
@interface SomeClass : NSObject {
// 实例变量
NSString *_myNonPropertyInstanceVariable;
}
// Error! @interface 中禁止声明变量
NSString *_someVariable;
...
@end
@implementation SomeClass {
// 实例变量
NSString *_anotherCustomInstanceVariable;
}
// 类变量
NSString *_classVariable;
...
@end
属性可以声明为 strong
或 weak
,用它们来组织对象图。要避免形成 strong
引用环,否则当它们的拥有者析构后,它们将同时保持 alive
。有一些 Cocoa 和 Cocoa Touch 类不支持 weak
,此时应使用 unsafe unretained
,对象离开作用域后不再 alive
,但指针不会设为 nil
(unsafe)。
// 对象
@property (unsafe_unretained) NSObject *unsafeProperty;
// 变量
NSObject * __unsafe_unretained unsafeReference;
类的扩展
Category 用来给既有的类添加功能(方法),适用于任何已有的类,即使没有该类的实现源码。添加 Category 方法后,添加的方法与既有方法没有任何区别,适用于原始类以及原始类的子类。
// file: XYZPerson+XYZPersonNameDisplayAdditions.h
#import "XYZPerson.h"
@interface XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString;
@end
// file: XYZPerson+XYZPersonNameDisplayAdditions.m
#import "XYZPerson+XYZPersonNameDisplayAdditions.h"
@implementation XYZPerson (XYZPersonNameDisplayAdditions)
- (NSString *)lastNameFirstNameString {
return [NSString stringWithFormat:@"%@, %@", self.lastName, self.firstName];
}
@end
除了用于扩展既有类的功能,还可以用于将复杂类分为多个文件,这样使用时也可以方便地添加功能。
在 Category 的接口中添加属性是合法的,然而在实现中添加实例变量是被禁止的(所以 objc 不会合成该属性,也不会生成访问器方法)。唯一给现有类添加传统属性的方法是 Extension。
Category 中的方法名产生冲突时,调用结果是未定义的,即依赖于编译器实现。这一问题在使用 NS 类库时更为突出,所以小心的命名很重要,推荐的做法是:在方法前加前缀,就像在自定义类前加前缀一样。
Extension 与 Category 类似,区别在于 Extension 需要在原始实现代码中添加实现,于是需要有原始类的实现源码。同时,Extension 允许在接口定义的括号中添加属性与实例变量。
类接口用于定义类的公共接口,而 Extension 一般用于定义实现相关的方法和属性,常常在实现文件中加入 Extension 的接口定义,用来添加私有方法和属性。
协议的使用
Protocol 定义了在某种情形下会用到的一系列方法,本质上是一种通信合同。例如饼状图的数据源:
@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end
@optional
后的都是可选的,也可用@required
来终结。
提供这样三个方法(第三个是可选的)的类才满足该协议,其语义就是该类可以作为饼状图的数据源。使用数据源时也会指定这样的协议:
@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
@end
可以声明一个类满足某个协议(如果没有实现会有编译警告),协议间也可以继承:
// 协议继承
@protocol MyProtocol <NSObject>
@end
// 声明满足某个协议
@interface MyClass : NSObject <MyProtocol,AnotherProtocol>
@end
如果在运行时调用的协议方法没有实现则会出错,为此可以来检测方法是否被实现:
NSString *thisSegmentTitle;
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}
协议用来隐藏对象所属的类,或者用于所属的类未知的情形。
数据类型与块语法
objc 兼容 C 的基本类型和运算符,并提供了一系列基本数据类型,如 BOOL
, NSInteger
。当用于平台相关的 API 调用时,NSInteger
会有优势,它是平台无关的。同时 objc 常采用 C 的结构体来组织数据。
objc 对象可以用来储存基本数据类型。例如 NSString
可以用来存储字符串类型,NSNumber
可以用来储存 C 的基本标量类型,其他的值可以用 NSValue
来储存。
objc 提供了很多集合类型:数组用来表示有序集合,集合用来表示无序集合,字典用来收集键值对:
NSArray *someArray = @[firstObject, secondObject, thirdObject];
NSSet *simpleSet =
[NSSet setWithObjects:@"Hello, World!", @42, aValue, anObject, nil];
NSDictionary *dictionary = @{
@"anObject" : someObject,
@"helloString" : @"Hello, World!",
@"magicNumber" : @42,
@"aValue" : someValue
};
在 objc 中
nil
表示没有对象,如果要将在集合中加入空对象应该使用[NSNull null]
。
支持 NSFastEnumeration
协议的集合类型可以这样遍历:
for (<Type> <variable> in <collection>) {
...
}
块是一种只完成单一任务的对象,可以存储和传递。也可以访问局部(当前作用域)变量,这使得它有点像闭包和 lambda 演算。其声明和定义如下:
// 声明
void (^simpleBlock)(void);
// 定义
simpleBlock = ^{
NSLog(@"This is a block");
}
// 初始化(声明+定义)
void (^simpleBlock)(void) = ^{
NSLog(@"This is a block");
};
// 调用
simpleBlock();
错误处理
可以创建一个 NSError
对象来引发错误:
NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
NSString *desc = NSLocalizedString(@"Unable to…", @"");
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
NSError *error = [NSError errorWithDomain:domain
code:-101
userInfo:userInfo];
异常处理:
@try {
// do something that might throw an exception
}
@catch (NSException *exception) {
// deal with the exception
}
@finally {
// optional block of clean-up code
// executed whether or not an exception occurred
}
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2014/08/21/objc.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。