5月份马上就要结束了,这个月的 iOS 学习计划就要告一段落了。这是最后一篇总结,主要是将 Objective-C 语法进行了一个比较全面的总结。以后还是会继续专注于 Android ,当然偶尔会学习下 iOS ,防止掌握的知识遗忘,好不容易学习这么久。
Party *partyInstance = [Party alloc]; [partyInstance init]; 对象创建一般需要调用 alloc 和 init 两个方法。 alloc 为对象分配内存,然后此时对象并没有初始化,所以不能正常工作。 init 方法调用后,对象被初始化,此后才可以正常工作。
发送消息,即我们所说的调用方法。发送一个消息,包含如下3个要素:
其关系如下图所示:
partyInstance = nil; 通常,要销毁某个对象,只需要将该对象设置为 nil 。值得注意的是, iOS 中可以给空对象发送消息,因此不需要做判空操作。
如下为Array的继承关系图:
其中 NSObject 为所有类的父类。
NSString 不可变字符串 NSMutableString 可变字符串,继承自 NSString 字符串表示:
NSString *myString = @"Hello, World!"; 字符串内容,以 @ 符号开头。
格式化输出:
int a = 1; float b = 2.5; char c = 'A'; NSString *d = "Hello"; NSLog(@"Integer: %d, Float: %f, Char: %c, String: %@", a, b, c, d); 字符串类型以 %@ 表示。
数组创建:
NSMutableArray *items = [[NSMutableArray alloc] init]; [items addObject:@"One"]; [items addObject:@"Two"]; [items addObject:@"Three"]; [items insertObject:@"Zero" atIndex:0]; items = nil; 数组遍历:
for (int i = 0; i < [items count]; i++) { NSString *item = [items objectAtIndex:i]; NSLog(@"%@", item); } for(NSString *item in items) { NSLog(@"%@", item); } 关于添加空值:
NSArray 或 NSMutableArray 不允许添加 nil ,但可以使用 NSNull 。
[items addObject:[NSNull null]]; Objective-C 没有命名空间的概念,而是通过使用类名前缀进行区分。前缀通常根据应用或公司名称来定义,一般2-3个大写英文字符。
实例变量创建
//BNRItem.h #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)v; - (int)valueInDollars; @end //BNRItem.m #import "BNRItem.h"; @implementation BNRItem - (void)setItemName:(NSString *)str { _itemName = str; } - (NSString *)itemName { return _itemName; } - (void)setSerialNumber:(NSString *)str { _serialNumber = str; } - (NSString *)serialNumber { return _serialNumber; } - (void)setValueInDollars:(int)v { _valueInDollars = v; } - (int)valueInDollars { return _valueInDollars; } 实例变量访问
//发送消息访问 BNRItem *item = [[BNRItem alloc] init]; [item setItemName:@"Red Sofa"]; [item setSerialNumber:@"A1B2C"]; [item setValueInDollars:100]; [item itemName]; [item serialNumber]; [item valueInDollars]; [item dateCreated]; //点号访问 item.itemName = @"Red Sofa"; item.serialNumber = @"A1B2C"; item.valueInDollars = 100; NSLog(@"%@ %@ %@ %@", item.itemName, item.serialNumber, item.valueInDollars); 如下为不声明属性变量和声明属性变量的等价对比图。
示例代码如下:
//BNRItem.h @interface BNRItem : NSObject @property BNRItem *containedItem; @property BNRItem *container; @property NSString *itemName; @property NSString *serialNumber; @property int valueInDollars; @property NSDate *dateCreated; @end 当我们使用 @property 声明属性变量时,实际上意味着:实例变量和getter/setter方法的声明以及getter/setter方法的实现。
@property (nonatomic, readwrite, strong) NSString *itemName;` 括号括起来的部分就是属性修饰符。
总共有3类属性修饰符: 多线程类型 、 读写类型 、 内存管理类型 。
nonatomic 非原子的 atomic 原子的。适用于多线程,默认类型 readwrite 可读写。默认类型 readonly 只读 readwrite 意味着系统实现了 setter 和 getter 方法。 readonly 意味着只实现 getter 方法。
strong 强引用 weak 弱引用。避免循环强引用出现内存泄露问题 copy 拷贝 unsafe_unretained copy : 当属性指向类的实例,而该类具有可变子类(如 NSString / NSMutableString , NSArray / NSMutableArray )时,此时建议用 copy 修饰符。
为什么对于 NSString 和 NSArray 使用 copy 更安全呢?因为若不使用 copy ,当属性指向的可变对象同时被其他外部对象引用时,外部对象可能修改该可变对象的值,从而会影响到当前属性。而使用 copy 并不直接引用到可变对象,只是对可变对象的值做了一个拷贝,指向的是一个新对象。
@property (nonatomic, copy) NSString *itemName; @property (nonatomic, copy) NSString *serailNumber; //如上copy修饰意味着setter里的实现是: - (void)setItemName:(NSString *)itemName { _itemName = [itemName copy]; } unsafe_unretained : 使用 unsafe_unretained 修饰符修饰对象时,若对象被销毁,则该对象不会自动设置为`nil`,这是和 weak 修饰符不同的地方。
unsafe_unretained 是非对象属性(如基本数据类型)的默认修饰符。
对象属性以上4种都可以使用,默认是 strong ,一般都会显示声明,因为默认类型可能会变化。
由于定义 @property 属性时,系统是会生成默认 getter/setter 的。如果不想使用默认的,则可以进行复写如果只复写setter/getter中的某一个方法,则系统不会生成被复写了的方法,未被复写的方法还是会生成的。另外值得注意的是,如果setter/getter均被复写,则系统不会生成相应方法且不会创建相应的实例变量,如果你想使用对应的实例变量,需要手动声明。
@synthesize : 默认情况下, @property 声明的属性也是 @synthesize 的。
正是由于 @synthesize 的存在,使得属性变量能够自动产生对应的实例变量和setter/getter方法以及实现相应的setter/getter方法。
+ 开头 - 开头 初始化方法:
//BNRItem.h @interface BNRItem - (instancetype)initWithItemName:(NSString *)name valueInDollars:(int)value serialNumber:(NSString *)sNumber; - (instancetype)initWithItemName:(NSString *)name; @end (1) instancetype
方法返回类型,通常用于 init 初始化相关方法的返回类型。
返回类型不写成BNRItem *, 是因为如果BNRItem有子类时,其子类方法的返回类型应该是子类类型,又由于子类继承了BNRItem相关方法,然而 Objective-C 不允许一个类中存在两个消息的方法名相同,而参数或返回类型不同的情况。
(2) id
当不确定对象类型时,可以使用类型id。可用于修饰变量、方法参数和返回值。
(3) self
对象自身
(4) super
父类。通常发送消息给某个对象时,会先从该对象查找对应方法,如果没找到会继续从父类中查找,一直到NSObject,直到查到为止。
+ (instancetype)randomItem { NSArray *randomAdjectiveList = @[@"Fluffy", @"Rusty", @"Shiny"]; NSArray *randomNounList = @[@"Bear", @"Spork", @"Mac"]; NSInteger adjectiveIndex = arc4random() % [randomAdjectiveList count]; NSInteger nounIndex = arc4random() % [randomNounList count]; NSString *randomName = [NSString stringWithFormat:@"%@ %@", [randomAdjectiveList objectAtIndex:adjectiveIndex], [randomNounList objectAtIndex:nounIndex]]; int randomValue = arc4random() % 100; NSString *randomSerialNumber = [NSString stringWithFormat:@"%c%c%c%c%c", '0' + arc4random() % 10, 'A' + arc4random() % 26, '0' + arc4random() % 10, 'A' + arc4random() % 26, '0' + arc4random() % 10]; BNRItem *newItem = [[self alloc] initWithItemName:randomName valueInDollars:randomValue serialNumber:randomSerialNumber]; return newItem; } 类方法的实现中,应该使用 self ,而不能使用类名本身,以便子类可以调用该类方法。
很多语言使用 try catch 捕获异常,虽然 Objective-C 也具备这个能力,然而一般不这么使用。 Apple 认为,异常属于代码错误,应该在编码阶段就解决,而不是在运行阶段处理异常。
内存管理,从大的方面来说,主要指栈和堆上的内存管理。
当一个方法执行的时候,内存分配在 Stack 上。假设有如下调用关系: `main() ->randomItem -> alloc`,当我们开始执行 main() 方法时,会从栈底到栈顶依次给 main()及其本地变量、randomItem方法及其本地变量、alloc方法及其本地变量 分配内存。当方法执行完成时,会从栈顶到栈底依次出栈。
所有 Objective-C 中的对象,内存分配都在 Heap 上。通常使用指针存储对象在堆上的内存地址。
当一个对象不被其他对象持有的时候,该对象将被销毁。通常分如下几种情况:
弱引用存在,是为了解决强引用的循环依赖,导致内存没法释放的问题。通常情况下,两个实例之间的引用存在一个包含与被包含关系。一般将具备包含关系的实例设置为弱引用。
先看一段代码:
//BNRItem.h @interface BNRItem : NSObject { BNRItem *_containedItem; BNRItem *_container; } - (void)setContainedItem:(BNRItem *)item; - (BNRItem *)containedItem; - (void)setContainer:(BNRItem *)item; - (BNRItem *)container; @end //BNRItem.m @implementation BNRItem - (void)setContainedItem:(BNRItem *)item { _containedItem = item; item.container = self; } //main.m BNRItem *backpack = [[BNRItem alloc] initWithItemName:@"Backpack"]; BNRItem *calculator = [[BNRItem alloc] initWithItemName:@"Calculator"]; back.containedItem = calculator; backpack = nil; calculator = nil; 以上代码,当 main() 方法执行完时,由于 backpack 和 calculator 指向的对象互相强引用,因此无法释放内存,会导致内存泄露。解决办法是将 _container 设置为弱引用。
__weak BNRItem *_container; autoreleasepool 和 ARC ARC 即 Automatic Reference Counting ,自动引用计数。是相对于 MRC (Manual Reference Counting,手动引用计数)而言的。
若使用 autoreleasepool 关键字括起来,当括号里方法执行完时,实例对象会自动释放。
@autoreleasepool { BNRItem *item = [BNRItem someItem]; } iOS 使用 ARC 和 autoreleasepool 机制实现内存管理。
iOS Programming:The Big Nerd Ranch Guide