OC学习笔记
简介
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 | [NSDate alloc] |
方法也可以分为类方法以及对象方法
1 | [NSDate alloc] //对类NSDate发送alloc消息 |
向nil发送消息
大多数语言中,不允许向nil发送消息,oc则不同,允许向nil发送消息,什么事情都不会发生。
note1:如果程序向某个对象发送了消息,但是没有得到预期的结果,需检查消息接收方是否为nil
note2:向nil发送消息,得到的返回值没有意义
@的几种含义
1.用在NSLog中
2.%@
可以表示指向任何对象,会向对象发送description
方法 eg:NSLog(@”%@…”)
NSArray
两种枚举方法
1 | NSArray *dateList = [NSArray arrayWithObjects:date1, date2, date3]; |
两种类型的数组
1.NSArray,创建后不能增加或删除元素
2.NSMutableArray:可以增加或删除元素,是NSArray的子类
其他类也会有类似的两种不同类型,NSSet, NSMutableSet; NSDictionary,NSMutableDictionary
自定义类
存取方法
3种存取方法
1.函数实现存取方法
2.点号存取
3.属性 property, synthesize
1 | #import <Foundation/Foundation.h> |
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 | @interface Employee:Person |
注意,对象只会保存其他对象的指针,而不是保存其他对象,可能会导致两个问题:
1.某个对象可能有多个角色,比如,其spouse还可能是多个child的紧急联系人
2.会导致产生大量独立的对象,耗尽可用内存,因此需要保留正在使用的对象,并释放那些不用的对象(内存回收)
对象所有权与ARC(Automatic Reference Count,自动引用计数)
MRC(手动引用计数,Manual Reference Count),与ARC相对应
当某个对象的拥有方个数为0,可以判定程序不在需要该对象,从而释放该对象。(“拥有方”可以理解为“被其他人拥有”,有其他对象内的指针指向该对象,如果拥有方个数为0,就表示不被任何人拥有,而自己拥有别人就无所谓)
上图中描述了简单的单向拥有关系,有以下几个:
1.最左边的NSMutableArray拥有多个Employee
2.每个Employee拥有多个Asset
3.每个Asset拥有一个NSString
对于这种情形,如果释放了NSMutableArray,则后续的所有对象都会被释放
上图中描述了较复杂的双向拥有关系,除了上面提到的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 | #import <Foundation/Foundation.h> |
如果对象间是父子关系,那么为了避免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];
}
autorelease—todo
会在将来的某个时候收到release消息
什么时候收到release消息?当autorelease池(对象)被排干(drain)的时候
1 | - (NSString *)description { |
虽然ARC会自动使用autorelease池,但是必须由程序创建并排空相应的autorelease池,如果开启了ARC极致,也加入了创建autorelease池的语法
1 | // 创建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 | NSMutableArray方法 |
为何参数是一个数组?
比如对人名排序,先按姓氏排序,如果姓氏相同,按名称升序排列,如果都相同,可按zipcode排序
过滤
NSPredicate类,其对象可以包含一条语句,其运算结果可以为“真”或“假”,详细可以阅读扩展文档Predicate Programming Guide
1 | NSMutableArray方法 |
NSSet/NSMutableSet
概念
里面的对象无序,唯一
,最大用处是检查某个对象是否存在
eg.员工可以有多个物品,但是某个物品只属于一个员工,因此可以用NSMutableSet带起NSMutableArray来管理assets
访问
因为无序,所以不能以索引来访问,使用特殊的方法
1 | - (BOOL)containsObject:(id)x; |
当收到这个消息时,会查找set对象中和x相等的对象。
“相等”—就是”isEqual:”方法
可以重写这个方法,定义自己的“相等”比较1
2
3
4
5
6
7
8NSString *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对象
collection类相关
C语言基本类型
collection对象只能保存对象,如果需要保存float,int,指针等变量,需要先把这些c语言基本类型封装为对象,再存入collection对象。
NSNumber—保存数字类型
NSValue—保存指针和部分特定的结构类型
nil
collection对象不能保存nil,如果确实需要加入,可以使用NSNull类,该类只有一个实例,代表空。NSArray初始化时再末尾加入nil,知识用来标记数组的结尾。
常量
定义常量两种方法:#define,全局变量
预处理指令:#include, #import, #define
#import
:会确保预处理器值导入特定文件一次#include
:允许多次导入同一个文件
为了简化文件导入并加快便以速度,引入预编译文件xxx.pch
全局变量
1 | NSLocale *here = [NSLocale currentLocale]; |
enum
1 | typedef enum { |
定义常量时,最好使用全局变量和enum,而不是#define
NSString和NSData,文件
NSString对象与文件
需要注意字符串编码,UTF-8
NSString—>文件
1 | - (BOOL)writeToFile:(NSString *)path |
文件—>NSString
1 | - (instancetype)initWithContentsOfFile:(NSString *)path |
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 | #import <Foundation/Foundation.h> |
selector选择器
向某个对象发送消息时,会沿着继承层次结构向上,直到某个类回应“我有与消息名称相匹配哦的方法”
方法的查询必须非常快速。如果使用方法的实际名称(可能会很长)进行查询,那么查询速度会很慢。为了提速,编译器会为每个其接触过的方法附上一个唯一的数字。运行时,程序使用的是这个数字,而不是方法名,因此上面的代码中需要指定 selector:@selector(sayOuch:) //action设置为logger的sayouch:方法
辅助对象
比如NSURLConnection例子,其方法sendSynchronousRequest:returningResponse:er-ror:
可从web服务器获取数据,但是获取数据时会阻塞
该主线程,而且某些情况下可能无法实现回调,比如当web服务器要求提供用户名以及密码的时候,因此以异步
的模式来使用:先要求NSURLConnection对象获取数据,然后等待回调,当发生(1)得到数据(2)web服务器要求认证信息(3)数据获取失败 这几种情况时,会触发回调。
怎样设置这样的回调???
为NSURLConnection对象设置一个辅助对象。当特定的事件发生时,该NSURLConnection对象会向辅助对象发送相应的消息。
具体是哪些消息???
Apple为NSURLConnection提供了一套协议(protocol)。协议
是一系列方法声明,辅助对象可以根据协议实现相应的方法。
因此我们新建的辅助对象作为NSURLConnection的delegate(委托对象),需要实现部分或全部协议规定的方法,并在方法中定义自己的行为,比如保存从web服务器取回的数据。
1 | __unused NSURLConnection *fetchConn = [[NSURLConnection alloc] initWithRequest:request |
通告中心
将多个对象通过通告中心将自己注册为观察者(observer),当发生某个事件时,会向通告中心发送通告,再由通告中心将该通告转发给相应的观察者(观察者模式),也就是说对应的观察者需要实现对应的方法
1 | [[NSNotificationCenter defaultCenter] addObserver:logger //将自己注册到观察中心,成为观察者 |
回调与对象所有权
无论哪种类型的回调,如果代码编写有问题,都有可能使等待回调的对象得不到正确的释放
有一些规则需要遵守:(各自对象释放的时候取消这种关联,也就是只需要管好自己的就行,否则管理太乱)
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 | UITableView的数据源协议是UITableViewDataSource |
但是在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会接受某些对象发出的消息,并转发相应指令给其他对象,类似于委托的工作。
原理解释
1 | int main(int argc, char * argv[]) { |
1.main函数作用
(1)UIApplicationMain会创建UIApplication类的实例(单实例)
(2)根据第四个参数,创建相应的类实例并设置为应用的委托对象,该对象会收到各种应用委托消息。
2.main函数流程如何转到委托对象中?
启动时UIApplication实例会向委托对象发送application:didFinishLaunchingWithOptions:
消息,因此AppDelegate必须继承UIApplicationDelegate
协议(代码自动生成,前面那个方法属于这个协议),且AppDelegate中必须实现这个方法。而且,凡是需要在程序能够和用户交互前就完成的初始化工作,都应在这个方法中实现。
iOS应用 vs. Mac应用
Cocoa Touch框架例子的类关系图 vs. Cocoa框架例子的类关系图
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 | - (id)init { |
指定初始化方法
[[OwnedApplicnace Alloc] init];
初始化方法调用链
编写初始化方法时,应该遵循以下规则:
- 规则1:如果某个类有多个初始化方法,那么应该由其中的一个方法来完成实际的任务,该方法称为
指定初始化方法
。其他的初始化方法都应该(直接地或间接地)调用指定初始化方法。 - 规则2:指定初始化方法应该先调用父类的指定初始化方法,然后再对实例变量进行初始化.
- 规则3:如果某个类的指定初始化方法和父类的不同(这里指的是方法名不同),就必须覆盖父类的指定初始化方法,并调用新的指定初始化方法。
- 规则4:如果某个类有多个初始化方法,就应该在相应的头文件中明确地注明,哪个方法是指定初始化方法。
1 | //Appliance类 |
禁用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 | [a setProductName:@"Washing Machine"]; |
为什么需要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
4typedef 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:
- ios开发路线简述
- ios学习资料整理—github
- Programming with Objective-C(官网)
- 快速入门
- taobao网上得课程代码
- Objective-C内存布局—描述isa指针是什么
todo
- ios4th
- 性能分析工具
- iOS Simulator使用
- 实例练习
- 开源项目
- 搞起