简介

oc以c语言为基础,加入了对面向对象编程(OOP)的支持。

工具:xcode6

书籍:《Objective-C编程》—Aaron Hillegass[美]

变量与类型

变量使用前必须声明,理由有2:

  • 有了类型信息,编译器可以检测潜在的错误,比如将对字符串进行数学运算
  • 根据类型分配存储空间

常用类型:

c语言5种常用类型:

  • 整型: int, short, long
  • 浮点: float, double
  • 字符: char
  • 指针: int *
  • 结构体: struct

id

id:声明指针时并不知道所指对象的准确类型。为此,可以使用id类型。id的含义是:指针,并可以指向任意类型的Objective-C对象.

1
id delegate; //不需要使用星号,本身就有星号的作用

局部变量,全局变量,静态变量

局部变量:函数运行时才会存在,调用函数时自动分配,函数结束后自动释放,也称自动变量
全局变量:函数外声明,到处可以访问,其他文件如需访问,用extern
静态变量:只有在该文件内可以访问


oc与框架(framework)

框架:有很多类组成的库
类:负责描述某个特定类型的对象,包括实例方法(instance method)和实例变量(instance variable)
实例:通过某个类创建的对象

消息机制

消息发送

消息发送必须写在一堆方括号中,并且必须包含以下两部分:
1.指针,指向接受消息的对象—接收方
2.方法名,要触发的方法的方法名—选择器
3.可以传入参数—参数

1
2
3
4
[now dateByAddingTimeInterval:10000];
now:接收方
dateByAddingTimeInterval:选择器
10000:参数

要调用方法,就需要向对象发送对应的消息,此为与c语言不同的地方。

消息嵌套发送

1
[[NSDate alloc] init]

系统会先向NSDate类发送alloc消息,得到返回值(指向新建的NSDate实例的指针),再向这个指针发送init消息,再返回该对象的指针,alloc负责分配内存,init负责初始化里面的数据

消息可以不带参数,也可以带多个参数

1
2
3
4
[NSDate alloc]
NSCalendar *cal = [NSCalendar currnetCalendar];
NSUinteger day = [cal ordinalityOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:now];
//方法为ordinalityOfUnit:inUnit:forDate:, 传入3个参数,用来计算某个某个日期是响应月份中的第几日

方法也可以分为类方法以及对象方法

1
2
[NSDate alloc] //对类NSDate发送alloc消息
[NSDate alloc] init] //alloc返回的结果是某个实例的指针,对该指针发送init消息

向nil发送消息

大多数语言中,不允许向nil发送消息,oc则不同,允许向nil发送消息,什么事情都不会发生。
note1:如果程序向某个对象发送了消息,但是没有得到预期的结果,需检查消息接收方是否为nil
note2:向nil发送消息,得到的返回值没有意义

@的几种含义

1.用在NSLog中
2.%@可以表示指向任何对象,会向对象发送description方法 eg:NSLog(@”%@…”)

NSArray

两种枚举方法

1
2
3
4
NSArray *dateList = [NSArray arrayWithObjects:date1, date2, date3];
NSUInteger count = [dateList count];
for(int i = 0; i < count; i++){[dateList objectAtIndex:i];} //普通枚举
for(NSDate *d in dateList){} //快速枚举,不应在枚举过程中增加或删除数组中的指针

两种类型的数组

1.NSArray,创建后不能增加或删除元素
2.NSMutableArray:可以增加或删除元素,是NSArray的子类
其他类也会有类似的两种不同类型,NSSet, NSMutableSet; NSDictionary,NSMutableDictionary


自定义类

存取方法

3种存取方法
1.函数实现存取方法
2.点号存取
3.属性 property, synthesize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
float heightInMeters;
int weightInKilos;
}
// 通过以下方法,可以存取相应的实例变量
- (float)heightInMeters;
- (void)setHeightInMeters:(float)height;
- (int)weightInKilos;
- (void)setWeightInKilos:(int)weight;

Person *person = [][Person alloc] init];
[person setWeightInKilos:96]; //函数实现存取方法
[person setHeightInMeters:1.8];
int w = [person weightInKilos];
int h = [person heightInMeters];
person.weightInKilos = 96; //点号存取方法
person.heightInMeters = 1.8;
int w1 = person.weightInKilos;
int h1 = person.heightInMeters;
//头文件中用@property属性
@property float heightInMeters
@property int weightInKilos;
//实现文件中用@synthesize属性,会自动合成存取方法
@synthesize heightInMeters, weightInKilos;

self

类似于c++的this指针,指向运行当前方法的对象的指针,当需要向自己发送消息时,就需要使用self。


继承

ios sdk中得类都继承自NSObject(直接或间接地),他虽然拥有很多方法,但是只有1个实例变量:isa指针,任何对象的isa指针都会指向创建该对象的类,可以理解为“is-a”

任何直接或间接继承了NSObject的类,它的实例对象(instacne objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。—Objective-C内存布局—描述isa指针)

消息的响应过程—继承链往上依次查找:

假设程序向某个对象发送了一个fido消息。收到消息的对象为了能响应这个消息,会通过isa指针找到该对象的类并查询“是否有名为fido的实例方法?”如果这个类拥有名为fido的方法,就会执行之。如果没有,就会向该类的父类查询象会沿着继承链向上查询,直到找到名为fido的方法,或者到达继承链的顶端为止。如果一直没有找到fido方法,并且位于继承链顶端的NSObject也回答“没有实现fido方法”时,程序就会报错

super

运行指定的方法,但是从对象的父类开始查找与之匹配的实现。与self相对应。


对象实例变量

指向另一个对象的指针,可用于描述相应两个对象之间的关系

三种常见用途:
对象属性—指针,指向某个单一的、代表某个值的对象,比如NSString,NSNumber对象
一对一关系—指针,指向单个复杂的对象。比如Employee中有一个指向Person实例的指针。
一对多关系—指针,指向某个collection类的实例,比如Person中又一个指向NSMutableArray的指针,该array中包含多个child

1
2
3
4
5
6
7
8
@interface Employee:Person
{
int employeeID;
NSString *lastName;//对象属性
Person *spouse;//一对一的关系
NSMutableArray *children;//一对多的关系
}
@end

注意,对象只会保存其他对象的指针,而不是保存其他对象,可能会导致两个问题:
1.某个对象可能有多个角色,比如,其spouse还可能是多个child的紧急联系人
2.会导致产生大量独立的对象,耗尽可用内存,因此需要保留正在使用的对象,并释放那些不用的对象(内存回收)

对象所有权与ARC(Automatic Reference Count,自动引用计数)

MRC(手动引用计数,Manual Reference Count),与ARC相对应
当某个对象的拥有方个数为0,可以判定程序不在需要该对象,从而释放该对象。(“拥有方”可以理解为“被其他人拥有”,有其他对象内的指针指向该对象,如果拥有方个数为0,就表示不被任何人拥有,而自己拥有别人就无所谓)
image
上图中描述了简单的单向拥有关系,有以下几个:
1.最左边的NSMutableArray拥有多个Employee
2.每个Employee拥有多个Asset
3.每个Asset拥有一个NSString

对于这种情形,如果释放了NSMutableArray,则后续的所有对象都会被释放
image
上图中描述了较复杂的双向拥有关系,除了上面提到的3点,还有1点:
1.每个Asset对象拥有各自的Employee,指向其对应的Employee对象

对于这种情形,如果释放了NSMutableArray,凡是拥有Asset对象的Employee对象都没有释放,因为虽然Employee对Asset的拥有关系消失了,但是Asset还对对应的Employee对象存在拥有关系,只有拥有方不为0,就不会自动释放。这就叫retain循环,是造成内存泄露的常见原因。

为了保证两个对象有一一对应的双向拥有关系,需要注意建立这种关系的时机(假设一个为父对象,一个为子对象)(—OC编程(2nd),chap20,避免内存泄露)

1.显式地设置这两个双向的关系,分别单独调用其设置关系的方法

1
2
[father method:child]
[child method:father]

2.在为子对象设置指向父对象的指针时,同时设置父对象对子对象的拥有关系

1
2
3
4
5
6
//child method
-(void) method:(Fathertype father)
{
holder = father;//为子对象设置指向父对象的指针
[father method:self];//设置父对象对子对象的拥有关系,调用父对象的对应方法
}

3.在为父对象设置对子对象的拥有关系时,同时设置子对象对父对象的拥有关系

1
2
3
4
5
6
//father method
-(void)method:(ChildType c)
{
child = c;//为父对象设置对子对象的拥有关系
[c method:self];//设置子对象对父对象的拥有关系,调用子对象的对应方法
}

如何分析内存泄露问题

性能分析(profile) & 性能分析工具(Instruments)—todo

如何解决retain循环问题

weak修饰符 与 strong修饰符

strong : 对于这个指针指向的对象,需要主张所有权,即必须要有人指向他,拥有它。如下面的例子,就表示Asset对象对NSString对象有拥有权。**当没有人拥有它时,这个label指向的NSString对象就可以释放。释放时,会收到dealloc信息(从NSObject继承),可以覆盖此dealloc方法。

__weak:通过弱引用,可以解决retain循环问题。弱引用不主张所有权,如下面的例子,就表示Asset对象对holder没有拥有权,也就是说我们的Asset对象不会对其Employee对象存在拥有权,因此删除Employee对象的时候,所有对象都可以安全释放。

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
@class Employee;
@interface Asset : NSObject
{
NSString *label;
__weak Employee *holder; //__weak修饰符,不主张所有权,也就是说Asset不会对它所对应的Employee对象存在拥有权
unsigned int resaleValue;
}
@property (strong) NSString *label; //strong修饰符,必须主张所有权,也就是说这个Asset对象对NSString对象存在拥有权
@property (weak) Employee *holder; @property unsigned int resaleValue; @end

如果对象间是父子关系,那么为了避免retain循环,通常需要遵守规则:父对象拥有子对象,但是子对象不能拥有父对象

弱引用的自动清零特性

为什么可以解决“retain循环”问题?
当某个由弱引用指向的对象被释放时,相应的指针变量会被归零,即赋为nil。

MRC(手动引用计数)

参考C++中得智能指针,同时参考网页资料)来理解reference count
可以理解为一个对象本身有一个引用计数的成员变量。当创建一个对象时,用一个指针指向这个对象。
oc中,如果新建一个对象并赋值,则这个对象初始化为1,只有retain,release操作可以改变引用计数的值。
如果调用retai,则会+1,
如果调用release,则会-1,
如果最后为0,则会自动释放该内存

下面解释书中例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//手动管理引用计数
//假设这里newEmp指向了一个Employee对象,这里需要设置holder也指向他
- (void)setHolder:(Employee *)newEmp
{
[holder release]; //原来holder可能有指向一个对象,这里需要先解除这个指向关系,也就是原来被指向的对象的拥有者先减少,也就是原来的对象的引用计数减少
[newEmp retain]; //因为holder需要指向新的对象,也就是newEmp指向的对象,因此这个对象多了一个拥有者,因此引用不过计数需要增加
holder = newEmp; //将holder指向新的一个Employee对象
}

- (void)dealloc
{
[label release]; //需要解除label的引用计数,以便自动释放内存
[holder release]; //需要解除holder对应关系,因此指向对象的引用计数需要-1
[super dealloc];
}

image

autorelease—todo

会在将来的某个时候收到release消息
什么时候收到release消息?当autorelease池(对象)被排干(drain)的时候

1
2
3
4
5
6
7
8
9
10
11
- (NSString *)description {
NSString *result = [[NSString alloc] initWithFormat:@"<%@: $%d >", [self label], [self resaleValue]];
[result autorelease];
return result;
}

// 创建autorelease池(对象)
NSAutoreleasePool *arp = [[NSAutoreleasePool alloc] init];
Asset *asset = [[Asset alloc] init];
NSString *d = [asset description]; // d指针指向的NSString对象已经在autorelease池中
[arp drain]; // description方法所返回的NSString对象会收到release消息

虽然ARC会自动使用autorelease池,但是必须由程序创建并排空相应的autorelease池,如果开启了ARC极致,也加入了创建autorelease池的语法

1
2
3
4
5
// 创建autorelease池(对象)
@autoreleasepool {
Asset *asset = [[Asset alloc] init];
NSString *d = [asset description]; // d指针指向的NSString对象已经在autorelease池中
} // autorelease池已经被排空

retain计数规则

规则如下:(假设,“你”—“当前正在使用的某个类实例”)

  • 如果创建对象的方法名是alloc或new开头,或包含copy,则你已经得到了该对象的所有权(retain计数=1,而且该对象不在NSAutoReleasePool对象中)。你负责在不需要的时候释放。常见的会传输所有权的方法:alloc(跟一个init), copy, MutableCopy
  • 其他途径创建的对象(便捷方法—todo???),你是没有所有权的(retain计数是1,而且在NSAutoReleasePool中,如果没有保留该对象,则当NSAutoReleasePool被drain时,对象自动释放)
  • 如果不拥有某个对象,但是期望他继续存在,则可以发送retain消息来获取所有权
  • 如果拥有某个对象并且不在需要使用时,可以发送release/autorelease消息。autorelease消息会导致:当NSAutoReleasePool被drain时,再向相应对象发送release消息
  • 只要至少有一个拥有方,就会继续存在,当ratain计数=0,则会受到dealloc消息,自动释放

从局部的角度来看问题


Collection类

NSArray/NSMutableArray

拥有权问题

将某个对象加入数组对象,则数组对象会成为该对象的拥有方;将某个对象移出数组对象,数组对象就会放弃该对象的所有权。init也叫初始化方法,类似于c++中得构造函数,构造函数可以因为参数不同有很多个,初始化方法也类似。

可修改版本 vs. 不可修改版本

NSArray初始化:

  • 必须以nil结尾,一边知道数组个数
  • 优点:
    • 保证数组内容“安全”,不可修改
    • 性能考虑:不可修改对象永远无须拷贝。NSArray的copy方法仅返回指向自身的指针,而NSMutableArray的copy方法则会制作一份自己的拷贝,并返回指向新数组对象的指针
1
NSArray *colors = [NSArray arrayWithObjects:@"Orange", @"Yellow", @"Green", nil];

排序

排序描述对象(sort descriptors),每个对象有一个key属性ascending属性

1
2
NSMutableArray方法
- (void)sortUsingDescriptors:(NSArray *)sortDescriptors;

为何参数是一个数组?
比如对人名排序,先按姓氏排序,如果姓氏相同,按名称升序排列,如果都相同,可按zipcode排序
image

过滤

NSPredicate类,其对象可以包含一条语句,其运算结果可以为“真”或“假”,详细可以阅读扩展文档Predicate Programming Guide

1
2
3
4
5
6
7
NSMutableArray方法
- (void)filterUsingPredicate:(NSPredicate *)predicate;
NSArray方法
- (NSArray *)filteredArrayUsingPredicxate:(NSPredicate *)predicate;

例子:创建predicate对象,并应用于allAssets数组对象,结果是返回满足predicate条件的所有数组元素,其条件就是对数组元素分别进行"holder.valueOfAssets > 70"这样的判断
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"holder.valueOfAssets > 70"]; NSArray *toBeReclaimed = [allAssets filteredArrayUsingPredicate:predicate];

NSSet/NSMutableSet

概念

里面的对象无序,唯一,最大用处是检查某个对象是否存在
eg.员工可以有多个物品,但是某个物品只属于一个员工,因此可以用NSMutableSet带起NSMutableArray来管理assets
image

访问

因为无序,所以不能以索引来访问,使用特殊的方法

1
- (BOOL)containsObject:(id)x;

当收到这个消息时,会查找set对象中和x相等的对象。
“相等”—就是”isEqual:”方法
可以重写这个方法,定义自己的“相等”比较

1
2
3
4
5
6
7
8
NSString *x = ...;
NSString *y = ...;
如果x和y的地址一样,则称“相同”
如果x和y指向的对象完全一样,则称“相等”
“相同”一定“相等”,而“相等”不一定“相同”

- (NSUInteger)indexOfObject:(id)anObject;//发送“isEqual:”消息,检查是否“相等”
- (NSUInteger)indexOfObjectIdenticalTo:(id)anObject;//用“==”运算符,检查是否“相同”

NSDictionary/NSMutableDictionary

概念

以NSString对象为索引,是键值对的集合(key-value),也是无序的
分别以“CEO”,“CTO”为key建立Dictionary对象
image

collection类相关

C语言基本类型

collection对象只能保存对象,如果需要保存float,int,指针等变量,需要先把这些c语言基本类型封装为对象,再存入collection对象。
NSNumber—保存数字类型
NSValue—保存指针和部分特定的结构类型

nil

collection对象不能保存nil,如果确实需要加入,可以使用NSNull类,该类只有一个实例,代表空。NSArray初始化时再末尾加入nil,知识用来标记数组的结尾。


常量

定义常量两种方法:#define,全局变量

预处理指令:#include, #import, #define

#import:会确保预处理器值导入特定文件一次
#include:允许多次导入同一个文件
为了简化文件导入并加快便以速度,引入预编译文件xxx.pch

全局变量

1
2
3
4
5
6
7
8
9
10
NSLocale *here = [NSLocale currentLocale];
NSString *currency = [here objectForKey:@"currency"];
NSLog(@"Money is %@", currency);
//上面个方法输入@"currency"不是很好,可用全局变量来替换
NSLocale.h中声明
extern NSString const *NSLocaleCurrencyCode;
NSLocale.m中定义
NSString const *NSLocaleCurrencyCode = @"currency";
可以方便的使用,而且不会有输错得情况发生,xcode会自动补全
NSString *currency = [here objectForKey:NSLocaleCurrencyCode];

enum

1
2
3
4
5
6
7
typedef enum  {
BlenderSpeedStir,
BlenderSpeedChop,
BlenderSpeedLiquify,
BlenderSpeedPulse,
BlenderSpeedIceCrush }

BlenderSpeed;

定义常量时,最好使用全局变量和enum,而不是#define

NSString和NSData,文件

NSString对象与文件

需要注意字符串编码,UTF-8

NSString—>文件

1
2
3
4
- (BOOL)writeToFile:(NSString *)path
atomically:(BOOL)useAuxiliaryFile
encoding:(NSStringEncoding)enc //指定字符串编码,NSUTF8StringEncoding
error:(NSError **)error //指针的指针,而且error对象一开是并没有被实例化,只有真正发生错误了才会创建error对象

文件—>NSString

1
2
3
- (instancetype)initWithContentsOfFile:(NSString *)path
encoding:(NSStringEncoding)enc
error:(NSError **)error

NSData—>文件

NSData对象“代表”内存中某块缓冲区

1
2
3
- (BOOL)writeToFile:(NSString *)path
options:(NSDataWritingOptions)mask //可以指定操作原子性,NSDataWritingAtomic
error:(NSError **)errorPtr

文件—>NSData

1
+ (instancetype)dataWithContentsOfFile:(NSString *)path

回调

NSRunLoop类,对应的实例会持续等待,当特定事件发生时,就会触发回调,向相应的对象发送消息。
三种途径实现回调:

1.目标-动作(target-action)
:在制定的时刻触发事件,很多简单的用户界面控件(按钮,滑块)都采用“目标-动作”机制。“当x发生时,向指定的对象发送某个特定的消息”,接受消息的对象就是目标(target),而消息的选择器(selector)就是动作(action)
2.辅助对象(helper objects)
“当x发生时,向遵守相应协议的辅助对象发送消息”,委托对象(delegate)和数据源(data source)是常见的辅助对象
3.通告(Notification)
通告中心,在应用开始等待前,告知通告中心“某个对象正在等待某些特定的通告,当其中的通告出现时,向指定的对象发送特定的消息”。当x发生时,会向通告中心发布通告,然后由通告中心转发给正在等待该通告的对象。

回调规则:
1.当要向一个对象发送一个回调时,使用“目标-动作对”
2.当要向一个对象发送多个回调时,apple会使用符合相应协议的辅助对象(辅助对象(委托对象或者数据源)需要实现协议的部分或全部方法)
3.向多个对象发送回调时,使用“通告”

目标-动作对

NSTimer对象使用的是目标-动作对机制。创建该对象时,要设定延迟、目标和动作。在指定的延迟时间后,该对象会向设定的目标发送指定的消息。

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
@interface Logger : NSObject
- (void)sayOuch:(NSTimer *)t;
- @end

Logger *logger = [[Logger alloc] init];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:logger //target设为logger对象
selector:@selector(sayOuch:) //action设置为logger的sayouch:方法
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run]; //循环等待

selector选择器

向某个对象发送消息时,会沿着继承层次结构向上,直到某个类回应“我有与消息名称相匹配哦的方法”
image

方法的查询必须非常快速。如果使用方法的实际名称(可能会很长)进行查询,那么查询速度会很慢。为了提速,编译器会为每个其接触过的方法附上一个唯一的数字。运行时,程序使用的是这个数字,而不是方法名,因此上面的代码中需要指定 selector:@selector(sayOuch:) //action设置为logger的sayouch:方法
image

辅助对象

比如NSURLConnection例子,其方法sendSynchronousRequest:returningResponse:er-ror:可从web服务器获取数据,但是获取数据时会阻塞该主线程,而且某些情况下可能无法实现回调,比如当web服务器要求提供用户名以及密码的时候,因此以异步的模式来使用:先要求NSURLConnection对象获取数据,然后等待回调,当发生(1)得到数据(2)web服务器要求认证信息(3)数据获取失败 这几种情况时,会触发回调。

怎样设置这样的回调???
为NSURLConnection对象设置一个辅助对象。当特定的事件发生时,该NSURLConnection对象会向辅助对象发送相应的消息。

具体是哪些消息???
Apple为NSURLConnection提供了一套协议(protocol)。协议是一系列方法声明,辅助对象可以根据协议实现相应的方法。

因此我们新建的辅助对象作为NSURLConnection的delegate(委托对象),需要实现部分或全部协议规定的方法,并在方法中定义自己的行为,比如保存从web服务器取回的数据。

1
2
3
__unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request
delegate:logger //设置delegate对象,,特定事件发生时,就会向辅助对象(delegate也就是logger对象)发送相应的消息(符合协议的一些方法),也就是让logger调用对应的消息,因此logger中需要按照协议实现部分或全部方法
startImmediately:YES];

通告中心

将多个对象通过通告中心将自己注册为观察者(observer),当发生某个事件时,会向通告中心发送通告,再由通告中心将该通告转发给相应的观察者(观察者模式),也就是说对应的观察者需要实现对应的方法

1
2
3
4
[[NSNotificationCenter defaultCenter] addObserver:logger   //将自己注册到观察中心,成为观察者
selector:@selector(zoneChange:) //收到通告之后会转而向观察者发送这个消息
name:NSSystemTimeZoneDidChangeNotification //收到的通告名称,也就是说收到这个通告之后,通告中心会向logger这个观察者发送zoneChange:的消息
object:nil];

回调与对象所有权

无论哪种类型的回调,如果代码编写有问题,都有可能使等待回调的对象得不到正确的释放

有一些规则需要遵守:(各自对象释放的时候取消这种关联,也就是只需要管好自己的就行,否则管理太乱)
1.通告中心不拥有其下的观察者,如果将某个对象注册为观察器,那么通常应该在释放该对象时,将其移出通告中心

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

2.对象不拥有其下的委托对象或数据源对象。如果某个新创建的对象是另一个对象的委托对象或数据源对象,那么该对象应该在其dealloc方法中取消相应的关联

- (void)dealloc
{
    [windowThatBossesMeAround setDelegate:nil];
    [tableViewThatBegsForData setDataSource:nil];
}

3.对象不拥有其目标。如果某个新创建的对象是另一个对象的目标,那么该对象应该在其dealloc方法中将相应的目标指针赋为0

- (void)dealloc
{
     [buttonThatKeepsSendingMeMessages setTarget:nil];
}

协议

在“辅助对象“提到过协议。
eg,iOS进程用UITableView实例来显示数据,但是该对象本身并不包含要显示的数据,必须从其他对象获取,因此,必须告诉UITableView对象,谁是你要的对象,这个对象将扮演数据源的角色。

应该怎样指定某个对象,使之成为UITableView的数据源?使用协议(protocol)

协议是一组方法声明,其中的部分方法是必需的(required),另一些则是可选的(optional)。如果某个对象要扮演特定的角色,就一定要实现相应的必须方法,并选择实现部分可选方法。

1
2
3
4
5
6
7
8
9
10
11
12
UITableView的数据源协议是UITableViewDataSource
@protocol UITableViewDataSource <NSObject>
// UITableView对象的数据源必须实现以下方法
@required //必须方法
......
@optional //可选方法
......

//使用时,只需要让对应的对象遵守这个协议就可以,这样TerrificViewController就可以作为UITableView的数据源
@interface TerrificViewController : UIViewController <UITableViewDataSource>
...
@end

但是在Logger.h中,并没有将Logger声明为遵守相应的协议。这是因为直到作者编写本书时,Apple也没有为NSURLConnection的委托对象提供正式的协议。因此该例子中并没有继承一个协议,而是直接实现了自己的方法


property list格式,以及实战iOS应用

property list

为了处理数据的需要,有时需要使用某种特定格式的文件,既可以有计算机读取,也便于人识别:属性列表(property list,xxx.plist文件,XML格式)可以有以下对象组成—NSArray,NSDictionary,NSString,NSData,NSDate,NSNumber

MVC

MVC(Model-View-Controller,模型-视图-控制器)是一种设计模式

  • model,负责保存数据,并能让其他对象访问这些数据。NSString,NSDate,NSArray等
  • view,应用中可见的元素,知道如何在屏幕上画出自己,并能响应用户输入。能够看得见的就是view对象,包括UIView,UITableView,UITextView,UIButton等
  • controller,负责执行程序的逻辑功能,以连接和驱动应用的不同组成部分,可以处理事件,并能与其他对象协同工作。model与view之间没有之间关联,需要controller从中协调,任务繁重但关键。controller会接受某些对象发出的消息,并转发相应指令给其他对象,类似于委托的工作。

image

原理解释

1
2
3
4
5
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

1.main函数作用
(1)UIApplicationMain会创建UIApplication类的实例(单实例)
(2)根据第四个参数,创建相应的类实例并设置为应用的委托对象,该对象会收到各种应用委托消息。

2.main函数流程如何转到委托对象中?
启动时UIApplication实例会向委托对象发送application:didFinishLaunchingWithOptions:消息,因此AppDelegate必须继承UIApplicationDelegate协议(代码自动生成,前面那个方法属于这个协议),且AppDelegate中必须实现这个方法。而且,凡是需要在程序能够和用户交互前就完成的初始化工作,都应在这个方法中实现。


iOS应用 vs. Mac应用

Cocoa Touch框架例子的类关系图 vs. Cocoa框架例子的类关系图
image
image

1.iOS—Cocoa Touch框架,使用UITableView, UITextField, UIButton…
Mac—Cocoa框架,使用NSTableView, NSTextField, NSButton…
2.手写代码 vs. Interface Builder —todo
3.Cocoa应用是基于文档的应用,可同时打开多个任务列表。
4.Cocoa Touch框架基于文档Document(继承NSDocument),没有应用委托对象,而Cocoa框架则又应用委托对象AppDelegate。基于文档的应用,用户可以同时打开多个文档对象,所以运行时可以有多个 Document实例,而每个实例都有自己的表格视图、按钮、任务数组和窗口等。
5.IBOutlet,IBAction,告诉Xcode,相应的指针或动作方法会通过Interface Builder而不是写代码来建立关联。设置之后就会成为插座变量,会再第8点中,在弹出菜单中显示以便选择今儿建立关联。实际上这两个关键字没有真正的含义。
6.NSTableView对象其实是嵌套对象,包含NSScrollView,NSTableView,NSTableColumn对象。如果需要选中某个特定的对象,ctrl+shift+左键单击
7.怎样自动调整窗口大小autosizing和相对位置?Xcode6中默认看不到,需要将file inspector中得auto layout关闭
8.如何创建关联关系?ctrl+鼠标拖动,其实目的就是为一些指针赋值,比如为按钮设置target,action;或者为某个NSTableView设置对应的datasource
9.既然作为NSTableView的数据源,就需要实现其对应的协议的部分方法。比如修改条目时内容变化进而保存数据。
10.保存文档,载入文档的数据处理


OC高级主题

init

1
[[NSMutableArray alloc] init];

aloc负责分配对象空间,init负责对象空间的初始化。init是实例方法,而alloc是类方法。

推荐的init写法

1
2
3
4
5
6
7
8
9
10
11
12
- (id)init {
// 调用NSObject的init方法
self = [super init];
// 父类的init方法的返回值是否为非nil?
if (self) {
//通过存取方法为voltage赋值
[self segVoltage:120];
//直接赋值,两种方法都可以
voltage = 20;
}
return self;
}

指定初始化方法

[[OwnedApplicnace Alloc] init];初始化方法调用链
image

编写初始化方法时,应该遵循以下规则:

  • 规则1:如果某个类有多个初始化方法,那么应该由其中的一个方法来完成实际的任务,该方法称为指定初始化方法。其他的初始化方法都应该(直接地或间接地)调用指定初始化方法。
  • 规则2:指定初始化方法应该先调用父类的指定初始化方法,然后再对实例变量进行初始化.
  • 规则3:如果某个类的指定初始化方法和父类的不同(这里指的是方法名不同),就必须覆盖父类的指定初始化方法,并调用新的指定初始化方法。
  • 规则4:如果某个类有多个初始化方法,就应该在相应的头文件中明确地注明,哪个方法是指定初始化方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//Appliance类
//指定初始化方法,满足规则4
- (id)initWithProductName:(NSString *)pn {
// 规则2,调用NSObject的init方法
self = [super init];
// 规则2,为productName赋值
[self setProductName:pn];
// 为voltage赋初始值
[self setVoltage:120];
return self;
}
- (id)init {
return [self initWithProductName:@"Unknown"];
}
-----
//OwnedAppliance类
//指定初始化方法,规则4
- (id)initWithProductName:(NSString *)pn
firstOwnerName:(NSString *)n {
//调用父类的初始化方法
self = [super initWithProductName:pn];
if (self) {
// 创建NSMutableSet实例,用于保存拥有者的姓名
ownerNames = [[NSMutableSet alloc] init];
// 传入的第一个拥有者姓名是否为nil?
if (n) {
[ownerNames addObject:n];
}
}
// 返回指向新对象的指针
return self;
}
- (id)initWithProductName:(NSString *)pn {
//规则3,调用新的指定初始化方法
return [self initWithProductName:pn firstOwnerName:nil];
}
-----
[[OwnedAppliance alloc] init];
//Q:上面这个方法是否可以正确的初始化实例?是否需要实现init方法?
//A: 可以正确初始化,不需要覆盖实现init方法。因为没有实现init,因此上述调用会根据调用链找父类的init方法,而父类的init方法中的有`[self initWithProductName:@"Unknown"]`局域,其中self指向的时OwnedAppliance实例(多态),所以调用OwnedAppliance的`initWithProductName:`方法,今儿调用自己的指定初始化方法`initWithProductName:firstOwnerName:`;而指定初始化方法调用了super的指定初始化方法`initWithProductName:`,而父类的这个方法最后又调用了NSObject得init方法.

禁用init方法

上面的方法带有默认实参,比如Appliance的init方法付过不指定名称会以“Unknown”作为默认参数,但如果我们必须要求一个有效值时怎么处理比较好?覆盖父类的指定初始化方法,告知程序员不能使用这个方法,并提供修改建议(抛出异常—todo)。

1
2
3
- (id)init {
@throw [NSException exceptionWithName:@"WallSafeInitialization" reason:@"Use initWithSecretCode:, not init" userInfo:nil];
}


属性

属性的特性

属性的特性可以控制如何创建存取方法
分类:
按存取类型:
rewrite(默认), readonly
按生命周期类型分(决定存方法如何处理与其相关的内存管理问题):
unsafe_unretained:默认,而且非对象类型的实例变量应该使用这个特性 —针对非对象类型
strong:保留传入的对象,并放弃原有对象(如果原有对象不再有其他拥有方就会被释放),凡是指向对象类型的实例变量通常应该用此特性。 —针对对象类型
weak:不保留传入的对象,相应的存方法会将传入的对象直接赋给实例变量。如果该对象被释放,相应的实例变量会被自动赋为nil —针对对象类型
copy:要求拷贝传入的对象,并将新对象赋给实例变量。—针对对象类型

copy—todo

[不可变对象 copy],返回相同不可变对象
[不可变对象 mutableCopy],返回新的可变对象
[可变对象 copy],返回新的不可变对象
[可变对象 mutalbeCopy],返回新的可变对象(OC中copy得使用

atomic/nonatomic

适用于多线程编程范畴。一般属性默认为atomic,因此需要手动加上nonatomic

(readwrite, copy, nonatomic)

KVC(key-value coding)

KVC(key-value coding)能够让程序通过名称直接存取属性。因为与KVC有关的方法都是在NSObject中定义的,所以凡是继承自NSObject的类都具备KVC功能。
setValue:forKey:方法,会查找名为setProductName:的存方法(set-Value:forKey:方法是在NSObject中定义的).如果没有setProductName:方法,直接为实例变量赋值。

valueForKey:方法,会查找名为productName:的取方法(valueForKey:方法是在NSObject中定义的)。如果对象没有productName:方法,就会直接返回相应的实例变量。

1
2
3
4
5
6
7
[a setProductName:@"Washing Machine"];
[a setValue:@"Washing Machine" forKey:@"productName"];
NSLog(@"the product name is %@", [a productName]);
NSlog(@"the product name is %@", [a valueForKey:@"productName"]);
//对于非对象类型,需要先转化为对象类型,比如int-->NSNumber
[a setVoltage:240];
[a setValue:[NSNumber numberWithInt:240] forKey:@"voltage"];

为什么需要KVC?
当Apple提供的框架需要向读者编写的对象写入数据时,会使用setValue:forKey:方法
当Apple提供的框架需要从读者编写的对象读取数据时,会使用valueForKey:方法
Core Data框架为例(Core Data框架能够将对象保存在SQLite数据库中,并在需要时将其还原成对象),这套框架会通过KVC来管理自定义的数据对象。


高级主题

范畴

使用范畴(category),程序员可以为任何已有的类添加方法。比如可以为NSString添加一个方法VowelCount,用来计算字符串中得元音个数。(xcode6如何创建category文件?
如果需要在其他程序中使用vowelCount方法,需要将相应的实现文件加入项目,并在构建程序时将范畴编译进去。

block对象

block对象可以理解为函数指针/匿名函数/closure/lamda。

block对象声明,定义,使用

可以没有函数名,但为了能够通过名称使用某个block对象,需要赋给block对象变量。

1
2
3
4
5
6
7
// 声明Block变量
void (^devowelizer)(id, NSUInteger, BOOL *);
// 定义block变量
devowelizer = ^(id string, NSUInteger i, BOOL *stop) { ...}
// 使用block变量
// 枚举数组对象oldStrings,针对每个数组中的对象,执行Block对象devowelizer
[oldStrings enumerateObjectsUsingBlock:devowelizer];

typedef

与函数指针类似,可以用typedef来简化书写方式,定义了一个类型,而不是变量

1
2
3
4
typedef void(^ArrayEnumerationblock)(id, NSUInteger, BOOL *);
ArrayEnumerationBlock devowelizer;
等同于
void(^devowelizer)(id, NSUInteger, BOOL *);

内存管理—todo

基于栈的block对象,当创建block的函数或者方法完成执行并范湖ihou,相应的block对象会随着栈帧的释放而被释放。
基于堆的block对象

向block对象发送copy消息,可以将其从栈拷贝至堆。

1
ArrayEnumerationBlock iVarDevowelizer = [devowelizer copy];

那些由Apple提供的并且支持Block对象的方法,例如NSArray的enumerateObjectsUsing-Block:,或者NSNotificationCenter的addOb-serverForName:object:queue:usingBlock:,都会拷贝传入的Block对象,并将其保存在堆中。通过拷贝Block对象,这些方法得以创建指向保存于堆中的Block对象的指针(并且是strong特性的引用)。

几点需要注意:—todo
1.对于block对象使用的变量,程序如何管理其生命期?
block对象中通常会使用外部创建的变量,执行block对象时,为了确保外部变量始终存在,相应的block对象会捕获这些变量。什么时候捕获呢?在将基于栈的block对象拷贝至基于堆得block对象的时候。
对于基本类型的变量,捕获意味着拷贝变量的值,并用block对象内的局部变量保存。对于指针类型的变量,block对象会用strong特性的引用,也就是说block对象用到的对象都会保留,所以在相应block对象释放前,这些对象一定不会被释放。

2.这类strong特性引用会导致retain循环问题吗?
有可能。当block对象所使用的对象保留了当前的block对象时。解决方法:现在block对象外声明一个__weak指针,然后将这个指针指向block对象的使用的对象,最后在block中使用这个新的对象。

3.可以修改block对象所拷贝的变量码?
被捕获的变量是常数,程序无法修改变量所保存的值。如果是指针,则不可修改指针,但是可以修改指针指向的对象。
如果需要在block对象内修改外部变量,可以在声明外部变量时,在前面加上__block.


其他

  • xcode6如何创建空的Empty Application? 很多书特别是翻译过来的书,里面使用的环境较老,会有empty application的类型可以选择,但是新的xcode6里面就没有,需要按照下面的方法自己新建—链接
  • #pragma mark --some comemnts,当某个类有很多方法的时候,可以使用这个指令来帮助快速分类并定位.

xcode快捷方式
自动补全,主题,颜色。。。

Ref:

todo

  • ios4th
  • 性能分析工具
  • iOS Simulator使用
  • 实例练习
  • 开源项目
  • 搞起