11,22,23,24,25,28暂时不需要看
1 第一个简单的iOS应用
- 单击按钮可以改变文字。
1.1 创建Xcode项目
- 创建项目的时候Xcode会提供一些包含通用代码的模版,可以根据需要选择模版。
- 创建项目是需要填写Organization Name和Company Identifier,也可以填入自己的公司名称和公司的反向域名。
- Class Prefix在Xcode 7.2创建项目里面已经没有了,或许是因为默认创建的项目类太少,但是自己在创建类的时候还是加上前缀为好,因为OC没有命名空间。
- 工作控件左侧是导航面板区域,中间是编辑区域。
1.2 模型-视图-控制器
- 简单介绍了MVC,下图清晰阐述了MVC中各模块扮演的角色。
1.3 设计Quiz
- 使用MVC思想,设计一个名叫Quiz的程序,实现单击按钮改变文字。
- 下面的应用对象图,勾勒出了对象之间的相互关系。
1.4 创建视图控制器
- 新版本Xcode会自动创建ViewController类了,且用storyboard文件来管理视图。
-
storyboard是多个xib文件集合的描述文件,也采用xml格式。那么storyboard与xib比较,区别在于:
- 一个工程中可以有多个xib文件,一个xib文件对应着一个视图控制器和多个视图。而使用storyboard时,一个工程只需要一个主storyboard文件就可以了。
- 在包含多个视图控制器的情况下,采用storyboard管理比较方便,而且storyboard还可以描述界面之间的导航关系。
-
Interface Builder一些介绍:
1.5 创建界面
- 单击main.storyboard编辑视图,画布中间的视图比较宽,可以在右侧属性面板将Use Auto Layout勾选掉。
- 通过Interface Builder在main.storyboard中添加Button和Label。
- 构建项目时main.storyboard会被编译为NIB文件,然后Xcode会将NIB文件拷贝到应用程序包(bundle)中,其实就是应用程序以.app结尾的后缀。
- 应用程序运行时会从程序包中按需载入NIB文件并激活文件中的对象。
1.6 创建关联
- 通过connection,一个对象可以知道另一个对象在内存中的位置,从而使这两个对象可以协同工作。
- Interface Builder中的两种关联:
- outlets:指向对象的指针变量。
- actions:动作方法,在视图对象和用户发生交互时会被调用。
- 新版本Xcode创建项目使用storyboard所以没有File's Owner,可以通过打开Assistant editor方便的关联对象:
- 创建关联在main.storyboard中的体现如下:
1.7 创建模型
-
ViewController对象创建完毕后会收到消息:
initWithNibName:bundle:
,所以可以在这里进行一些数据创建和初始化。- 不一定非得在
initWithNibName
中初始化数据,新版本Xcode创建的默认项目,使用initWithNibName
不会有效果,可以使用initWithCoder
。 - initWithNibName和initWithCoder的区别
- 不一定非得在
-
关于代码自动补全功能:
- 要实现模糊匹配可以安装FuzzyAutocomplete插件。
- 自动补全后会有一些占位符,如果要使用占位符,可以按回车即可。
1.8 其它
- AppDelegate应用程序委托是每个iOS应用都必须具备的启动入口。
- 新版本Xcode不需要在委托中添加代码,如果按照书中添加代码,会出现白屏。
- 模拟器中选择Simulator|Reset Content and Settings...可以将模拟器还原到默认设置并删除所有应用。
2 Objective-C
- 开发iOS应用使用Objective-C和Cocoa Touch框架.
2.1 对象
- Objective-C语言中,实例变量的变量名之前通常会加一个下划线,如_name,_date,_budget。
- 调用某个对象的方法,可以向对象发送相应的消息(message)。
2.2 使用对象
- 创建变量并进行初始化:
Party *partyInstance = [[Party alloc] init];
,将多个消息合并在一行的写法叫做嵌套消息发送(nested message send)。 - 消息发送代码各组成部分:
- 在Objective-C中,方法的唯一性取决于方法名,即使参数类型和返回类型不同,一个类也不能有两个名称相同的方法。
2.3 编写命令行工具PandomItems
- 不能在快速枚举
for in
中添加或删除对象,否则抛出异常。 - Objective-C格式字符串基本和C语言相同,
%@
对应的实参类型是指向任何一种对象的指针,通过覆盖description
方法定义格式化输出。
2.4 创建Objective-C类的子类
- Objective-C只允许单继承,所有的类都只能有一个父类。
- Objective-C保留了C语言的关键字,并增加了特有的关键字,新的关键字都用前缀@加以区分。
- Objective-C类中的存取方法名的规范为:
- 存方法命名规则为英文单词set加上要修改的实例变量名,以
_itemName
为例,存方法名为setItemName:
。 - 取方法名就是实例变量的变量名
itemName
,其它语言大多为:getItemName
。
- 存方法命名规则为英文单词set加上要修改的实例变量名,以
-
#import
和#include
的差别在于,#import
可以确保不会重复导入同一个文件。 - 使用点语法存取变量例如
item.itemName
编译后和发消息一样,也是调用之前写好的存取方法,所以存取方法名要按照规范书写。Apple官方代码坚持使用点语法存取实例变量。 - 实例方法和类方法:调用实例方法时,需要向类的对象发送消息;调用类方法时,则向类自身发送消息。
使用存取方法访问实例变量是良好的编程习惯,即使是访问对象自身的实例变量,也应该使用存取方法,但是在初始化方法中例外。
-
初始化方法
- 每个初始化方法以英文单词
init
开头,Objective-C中命名约定很重要,应该严格遵守。 - 初始化方法的返回类型是
instancetype
,该关键字表示方法的返回类型和调用方法的对象类型相同。 - 在
instancetype
引入之前,初始化方法返回类型是id
,表示指向任意对象的指针,类似C语言的void*
。
- 每个初始化方法以英文单词
-
instancetype
和id
-
instancetype
只能用来表示方法返回类型,id
可以用来表示变量和方法参数类型。 -
id
的定义是指向任意对象的指针,所以不需要再加*
,比如:id item
。
-
- 书中提到的指定初始化方法(designated initializer)不明白,根据书中后面的"其它初始化方法与初始化方法链的理解",指定初始化方法不同在于可以供其它初始化方法调用,但不调用其它初始化方法,通常参数最多。
-
self
和super
-
self
为类或对象自身,存在于方法中,是一个隐式(implicit)局部变量,相当于c++的this
。 -
super
为父类,向super
发消息,其实就是向self发消息,但是要求系统再查找方法时跳过当前对象的类,从父类开始查询。
-
- 在初始化方法中应该直接访问实例变量,而不是使用存取方法。因为初始化方法执行时,无法确定新创建对象是否已经处于正确设置,所以应该直接访问类中实例变量。(其实这只是习惯问题)
-
初始化方法总结的若干规则:
- 类会继承父类所有的初始化方法,也可以加入其它初始化方法。
- 初始化方法要直接或间接调用父类的指定初始化方法。
- 其它初始化方法要直接或间接调用自身的指定初始化方法。
类方法的声明和实例方法的声明差别在于第一个字符,实例方法为
-
,类方法为+
。- 在头文件中实例变量声明写在最前面,然后是类方法,接下来是初始化方法,最后是其它方法。这也是一种约定规范。
- 在Objective-C中,如果某个类方法的返回类型是这个类的对象(例如:
stringWithFormat:
),就可以将该类方法称为便捷方法(convenience method)。
2.5 深入学习NSArray与NSMutableArray
- Objective-C数组相关:
- 可以存储不同类型继承自
NSObject
的对象指针,不能保存基本C类型变量。 - 不能将
nil
加入数组,可以用NSNull
代替,例如:[items addObject:[NSNull null]];
。 - 访问数组可以使用下标语句
NSString *foo = items[0];
或发消息NSString *foo = [items objectAtIndex:0];
,这两种方法效果是相同的。
- 可以存储不同类型继承自
- 现在方括号的作用有3种:发送消息、存取方法、访问数组,如果使用点语法存取方法,下标语法访问数组,可以清晰突出发送消息代码。
- 在
NSMutableArray
中,可以使用下标语法向数组中添加和修改对象,等价于insertObject:atIndex:
和replaceObjectAtIndex:withObject:
发消息。
2.6 异常与未知选择器
对书中的疑惑:
- 书中意思是说所有类对象都是运行时绑定,但是感觉并不是所有OC对象都是运行时(runtime)绑定,除了书中展示的使用
id
来接收创建的对象时,这时候书中所说的isa
可能就不起作用了?
- 关于错误和异常处理:
2.12 如何为类命名
- OC没有提供命名空间机制,为了区分类,需要为类添加前缀。例如,开发一个MovieView应用,就可以在所有和项目相关的类名前加上MOV。
- Apple提供的类名都为2个字符前缀,为了减少与Apple未来发布类产生冲突的可能,我们要使用至少3个字符前缀。
2.13 #import和@import
-
@import Fundation
告诉编译器需要使用Foundation框架,之后编译器会优化预编译头文件和缓存编译结果过程,文件中不再明确引用框架,编译器会根据@import自动导入相应框架。 - 目前只有Apple提供的框架可以使用@import,如果需要导入自己写的类和框架,只能使用
#import
。
新版本的Xcode已经不会自动创建预编译头文件,因为预编译头文件会带来代码复用困难等问题。
3 通过ARC管理内存
- 提出了"自动引用计数(automatic reference counting,ARC)"的概念。(涉及到自动管理资源的都会有引用计数的概念,类似C++智能指针)
- ARC和java的GC的区别是:ARC为编译时计算引用计数,GC在运行时计算。
3.1 栈
- 执行方法或函数是从内存栈(stack)中分配空间,每块空间称为帧(frame),用来保存方法内声明的变量值。
- 调用main函数时栈的变化过程如下图:
3.2 堆
- 堆(heap)是内存中另一块区域,和栈是分开的。
栈和堆区别是:栈按后进先出规则保存一组帧,堆则包含了大量无序的活动对象,需要通过指针来访问。
发送alloc消息是从堆上分配内存。
- iOS应用通过ARC来自动完成堆内存管理工作。
3.3 指针变量与对象所有权
- 如果堆内存中对象,没有指针指向它的话就会被释放掉,系统会自动记录有多少对象指针引用它,1个引用ARC就为1,2个为2,0个内存会被释放。
- NSObject实现了一个名为dealloc的方法,当某个对象即将被释放时,程序会调用该方法。
3.4 强引用与弱引用
- 只要指针变量指向了某个对象,那么相应的对象就会多一个引用计数,并且不会被释放。这种指针特性称为强引用(strong reference)。
- 也可以选择让指针变量不影响某指向对象的引用计数,这种不会改变对象拥有者个数的指针特性称为弱引用。
- 弱引用用来解决强引用循环问题(strong reference cycle,也称保留循环),当两个以上对象相互之间有强引用时就回产生强引用循环。
强引用循环问题
- 存在强引用循环问题如下图:
图中两个BNRItem对象中包含两个指向BNRItem对象的指针_container和_containerItem互相指向彼此。
当items设置为nil后,没有两个BNRItem对象引用计数减1,但是互相依然强引用,所以内存无法释放,如下图:
解决强引用问题
- 要解决上面的强引用循环问题,需要将新创建的两个BNRItem对象之间的某个指针改为弱引用。
- 大部分强引用循环可以确定一个父子关系,父对象使用具有强引用特性的指针,指向子对象。子对象使用弱引用特性指针,指回父对象。这样就可以避免强引用循环问题。
- 将上面的_container设为弱引用,如:
__weak BNRItem *_container;
,这样就解决了强引用循环问题,如下图:
3.5 属性
- 属性相当于:声明实例对象+相应存取方法。它们的等价表如下:
- 表中都具有一个实例变量
_name
和一对存取方法,右边只需要一个属性就完成了。 如果声明一个名为
itemName的
属性,编译器会自动生成实例变量_itemName
、取方法itemName
和存方法setItemName:
。-
自定义属性存取方法:
- 如果覆盖了存取方法(或为只读属性覆盖了取方法),编译器就不会自动创建相应变量了,如果需要实例变量就需要明确声明。
属性的特性
- 任何属性都有一组特性,如:
@property (nonatomic, readwrite, strong) NSString *itemName;
- 多线程特性:
- 两种可选类型
nonatomic
和atomic
,通常设为nonatomic
。
- 两种可选类型
- 读/写特性:
- 两种可选类型
readwrite
和readonly
,如果时readonly
只会生成取方法。
- 两种可选类型
- 内存管理特性:
- 有4种可选类型:
strong
,weak
,copy
,unsafe_unretained
. - 对于需指向非对象属性,如:
int
,默认会选用unsafe_unretained
,不做内存管理。 - 当属性是可变子类对象(例如,NSString/NSMutableString或NSArray/NSMutableArray)指针时,应设置
copy
特性。因为可变子类有可能被其它引用者修改,所以会复制一个新的对象给属性。
- 有4种可选类型:
4 视图与视图层次结构
4.1 视图基础
- 视图是UIView对象,或是UIView子类对象。
- 视图可以处理事件,例如触摸。
- 视图会按层次结构排列,位于视图层次结构顶端的是应用窗口。
4.2 视图层次结构
- 任何一个应用都只有一个UIWindow对象,负责包含应用中所有视图,视图还可以有自己的子视图。
- 视图层次结构构成应用的界面:
- 视图会将自己绘制到layer上,然后组合起来绘制到屏幕上,每个UIView对象又一个layer属性,指向一个CALayer类对象。
4.3 创建UIView子类
- 新版本Xcode创建的项目使用ViewController管理视图,如果要实现书中画红色矩形需在
viewDidLoad
中添加如下代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGRect firstFrame = CGRectMake(160, 240, 100, 150);
BNRHypnosisView *firstView = [[BNRHypnosisView alloc] initWithFrame:firstFrame];
firstView.backgroundColor = [UIColor redColor];
[self.view addSubview:firstView];
CGRect secondFrame = CGRectMake(20, 30, 50, 50);
BNRHypnosisView *secondView = [[BNRHypnosisView alloc] initWithFrame:secondFrame];
secondView.backgroundColor = [UIColor blueColor];
[firstView addSubview:secondView];
}
- 视图相关文章:
4.4 在drawRect:方法中自定义绘图
Xcode使用lldb调试:
- 与调试器共舞 - LLDB 的华尔兹
- XCODE LLDB TUTORIAL
- lldb不支持点语法,输出视图的
bounds
应输入:p (CGRect)[firstView bounds]
- 视图相对于父视图的位置,是通过视图的
frame
和父视图的bounds
结合计算出来的。 -
drawRect相关:
-
UIBezierPath相关:
- 书中最后画同心圆代码:
- (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
UIBezierPath *path = [[UIBezierPath alloc] init];
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
[path moveToPoint:CGPointMake(center.x + currentRadius, center.y)];
[path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:2.0 * M_PI clockwise:YES];
}
path.lineWidth = 10;
[[UIColor lightGrayColor] setStroke];
[path stroke];
}
其它
- Apple Developer Library
- 关于Core Graphics:
- 关于Core Graphics类型的Ref后缀:
- 带有Ref后缀的类型是Core Graphics中用来模拟面向对象机制的C结构。
- 使用这种类型用来区分指针变量,方便判断指针变量是指向C结构,还是可以接收消息的Objective-C对象。
- ARC无法管理带有Ref后缀的对象内存,所以需要手动释放。
- 关于开源项目core-plot:
5 重绘与UIScrollView
- 只会在类内部使用的属性和方法也可以写在类扩展文件中.
- 可以保持头文件的精简。
- 子类无法访问在类扩展中声明的属性。
#import "BNRHypnosisView.h"
@interface BNRHypnosisView()
@property (strong, nonatomic) UIColor *circleColor;
@end
@implementation BNRHypnosisView
@end
- 视图发送重绘消息:
[self setNeedsDisplay];
- UIScrollView对象适用于尺寸大于屏幕的视图,当某个视图是UIScrollView对象子视图时,UIScrollView会画出该视图的某块区域,如下图:
6 视图控制器
-
在xcode 6以上版本创建
Empty Application
项目: -
Auto Layout相关
创建空项目后,跑起来是iphone4s的屏幕大小。。。???
- 原因:Xcode 6 默认新建的启动页面为 LaunchScreen.xib,系统通过检测是否有这个文件,来判断 app 是否支持 iphone 6 & 6 plus. 如果要支持 iOS 7,还必须添加 Launch Image assets.
- 解决方法:添加Default.png、Default-568h@2x.png、Default@2x.png三张图片进来,可以加到Supporting Files里。
- 参考:Xcode 6 新建工程运行在 iOS 7 上下有黑边的问题, iOS Xcode运行时上下黑边的解决办法
6.1 创建UIViewController子类
- 视图控制器设置为UIWindow对象的rootViewController作用:
- IWindow对象会将该视图控制器的view作为子视图加入窗口。
- 可以自动调整view大小,将其设置为与窗口大小相同。
6.2 另一个视图控制器
- File'Owner对象是一个占位符对象,当视图控制器将XIB文件加载为NIB文件时,首先会创建XIB中的所有视图对象,然后将自己替换掉相应的File's Owner。
- 为什么
outlet
要声明为弱引用:- 这是一种编程约定,当系统的可用内存偏少时,视图控制器会自动释放视图并在之后需要显示时再创建。
- 按照书中代码输出UIDatePicker时间,会出现选择时间和输出看起来不一致问题:
- 将输出的
date
改为[date descriptionWithLocale:[NSLocale systemLocale]]
。http://*.com/questions/24971367/set-uidatepicker-timezone-to-simulaters-current-timezone
6.4 视图控制器初始化
- 按照约定,将XIB文件命名为和视图控制器同名,下面两段代码都能正常初始化:
// This will get a pointer to an object that represents the app bundle
NSBundle *appBundle = [NSBundle mainBundle];
// Look in the appBundle for the file BNRReminderViewController.xib
BNRReminderViewController *rvc = [[BNRReminderViewController alloc] initWithNibName:@"BNRReminderViewController" bundle:appBundle];
BNRReminderViewController *rvc = [[BNRReminderViewController alloc] init];
6.5 添加本地通知
- 本地通知是在ios4.0之后添加的,但是在ios8之后,在设置通知之前,需要先对通知进行注册,注册需要的通知类型,否则收不到响应类型的通知消息。
6.6 加载和显示视图
- 关于
viewDidLoad
:- 在视图控制器加载完NIB文件之后被调用,此时视图控制器中所有视图属性都已经指向正确的视图对象。
- 为了实现延迟加载,凡是和View有关的初始化代码都应该在
viewDidLoad
中实现。
- 关于
viewWillAppear
:- 该方法会在视图控制器的view添加到应用窗口之前被调用。
- 何时选择
viewDidLoad
和viewWillAppear
:- 如果只需要在应用启动后设置一次视图对象,就选择
viewDidLoad
;如果用户每次看到视图控制器的view时都需要对其进行设置,则选择viewWillAppear
。
- 如果只需要在应用启动后设置一次视图对象,就选择
6.7 与视图控制器及其视图进行交互
7 委托与文本输入
7.1 文本框(UITextField)
- 第一响应者:
-
UIWindow
有一个firstResponder
当用户点击文本框时,UIWindow
将firstResponder
指向该对象,文本框就会成为第一响应者。 -
UITextField
成为第一响应者屏幕就回弹出键盘,一旦第一响应者不是UITextField
键盘就回消失。 - 除用户点击外,还可以向
UITextField
发送becomeFirstResponder
使其成为第一响应者;向对象发送resignFirstResponder
使其失去第一响应者状态。
-
7.2 委托
- 为
UITextField
对象设置委托,UITextField
对象会在发生事件时向委托发送相应消息,由委托处理事件。 在委托中通畅应该将对象自身作为第一个参数,可以根据该参数判断发送该消息的对象。多个对象可能具有相同的委托,一个视图控制器可能有多个
UITextField
。Many of the message it sends to its delegates are informative: “OK, I am done editing!”. Here are some of those:
- (void)textFieldDidEndEditing:(UITextField *)textField;
- (void)textFieldDidBeginEditing:(UITextField *)textField;
- Some of the message it sends to its delegate are queries: “I am about to end editing and hide the keyboard. OK?” Here are some of those:
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField;
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField;
- (BOOL)textFieldShouldClear:(UITextField *)textField;
- (BOOL)textFieldShouldReturn:(UITextField *)textField;
7.3 协议
- Objective-C中的协议就像是Java或C#语言中的接口。(类似C++中的回调接口)
- 委托对象根据这个协议为其“感兴趣”的事件实现相应的方法,如果一个类实现了某个协议中规定的方法,就称这个类遵守该协议。
- 将类声明为遵守指定协议:
@interface BNRHypnosisViewController() <UITextFieldDelegate, OtherDelegate>
@end
- iOS SDK几乎所有的类中的
delegate
属性都是弱引用,这是为了避免对象及其委托之间产生强引用循环,这样就会造成内存泄漏。
其它
- 每个iOS应用都有且只有一个UIApplication对象,由
main.m
中的UIApplicationMain
函数创建。 - Another thing the function UIApplicationMain does is create an instance of the class that will serve as the UIApplication’s delegate.
- The final argument to the UIApplicationMain function is an NSString that is the name of the delegate’s class. So, this function will create an instance of BNRAppDelegate and set it as the delegate of the UIApplication object.
- The first event added to the run loop in every application is a special “kick-off” event that triggers the application to send a message to its delegate. This message is application:didFinishLaunchingWithOptions:.
8 UITableView与UITableViewController
8.1 编写Homepwner
8.2 UITableViewController
- A UITableView typically needs a delegate that can inform other objects of events involving the UITableView. The delegate can be any object as long as it conforms to the UITableViewDelegate protocol.
- When a UITableViewController creates its view, the dataSource and delegate instance variables of the UITableView are automatically set to point at the UITableViewController.
8.3 UITableView数据源
- 单例对象示例:
//
// BNRItemStore.h
// Homepwner
#import <Foundation/Foundation.h>
@interface BNRItemStore : NSObject
+ (instancetype)sharedStore;
@end
//
// BNRItemStore.m
// Homepwner
#import "BNRItemStore.h"
@implementation BNRItemStore
+ (instancetype)sharedStore {
static BNRItemStore *sharedStore = nil;
// Do I need to create a sharedStore?
if (!sharedStore) {
sharedStore = [[self alloc] initPrivate];
}
return sharedStore;
}
// If a programmer calls [[BNRItemStore alloc] init], let him
// know the error of his ways
- (instancetype)init {
@throw [NSException exceptionWithName:@"Singleton" reason:@"Use +[BNRItemStore sharedStore]" userInfo:nil];
return nil;
}
// Here is the real (secret) initializer
- (instancetype)initPrivate {
self = [super init];
return self;
}
@end
9 编辑UITableView
9.1 编辑模式
- 使用NSBundle类可以载入指定的XIB文件,该类是“应用程序”和“应用程序包”之间的接口。
10 UINavigationController
- 多个视图控制器传递数据方案:由根视图控制器保存所有的数据,然后将数据的子集传给下一个视图控制器。
11 相机
- 生成GUID/UUID的方法:
NSUUID *uuid = [[NSUUID alloc] init];
NSString *key = [uuid UUIDString];
- 单击return和触摸视图关闭键盘:
// UITextField的委托方法
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
// 顶层视图应该为UIControl才能响应触摸事件
- (IBAction)backgroundTapped:(id)sender
{
[self.view endEditing:YES];
}
15 自动布局入门
15.2 自动布局系统
- 对于自动布局系统来说,Retina屏幕的尺寸与非Retina屏幕的尺寸是“相同”的,因为视图的坐标单位使用的是点而不是像素。
- 自动布局系统为视图定义了一个对齐矩形(alignment rectangle),该矩形有若干布局属性。
- 布局属性定义了视图的对齐矩形:
- 约束既可以定义布局属性的具体值,也可以定义布局属性之间的关系。自动布局系统会根据约束和布局属性之间的关系计算出每一个布局属性的值。
- 最近相邻(nearest neighbor)是指同级视图在某个方向上距离最近的兄弟视图,如果视图该方向上没有兄弟视图,那么最近相邻视图就是父视图。
- 最近相邻视图:
15.4 调试约束问题
- 如果视图在XIB文件中的frame与自身约束不一致,就会发生视图位置错误。视图位置错误是指视图在运行时frame与其画布frame不同。修复此问题有两种方法:
- 选择Update Frames会与当前约束匹配。
- 选择Update Constraints会将约束和当前frame进行匹配。
18 保存、读取与应用状态
18.1 固化
- 为了能固化(Archiving)或解固(Unarchiving)某个对象,相应的对象类必须遵守
NSCoding
协议,并且实现两个必须的方法:encodeWithCoder:
andinitWithCoder:
.
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
@end
-
encodeWithCoder:
方法将所有属性编码到(NSCoder *)aCoder
参数中,要固化的属性必须也遵守NSCoding
属性(基础类型除外),无论编码哪种类型的数据,必须有相应的键。 -
initWithCoder:
方法还原之前通过encodeWithCoder:
编码的所有对象,实例代码如下:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_itemName = [aDecoder decodeObjectForKey:@"itemName"];
_serialNumber = [aDecoder decodeObjectForKey:@"serialNumber"];
_dateCreated = [aDecoder decodeObjectForKey:@"dateCreated"];
_itemKey = [aDecoder decodeObjectForKey:@"itemKey"];
_valueInDollars = [aDecoder decodeIntForKey:@"valueInDollars"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.itemName forKey:@"itemName"];
[aCoder encodeObject:self.serialNumber forKey:@"serialNumber"];
[aCoder encodeObject:self.dateCreated forKey:@"dateCreated"];
[aCoder encodeObject:self.itemKey forKey:@"itemKey"];
[aCoder encodeInt:self.valueInDollars forKey:@"valueInDollars"];
}
- XIB文件也是基于固化机制,和普通固化文件相比略有差别,但两者保存和载入的流程大致相同。
- 在Xcode中将某个视图拖到画布时,Xcode会创建相应对象。
- 保存XIB文件时,Xcode会将这些视图固化到文件(
UIView
遵守NSCoding
协议)。 - 需要载入XIB文件时,就会解固XIB文件中的视图。
18.2 应用沙盒
- 每个iOS应用都有自己专属的应用沙盒,iOS系统会将每个应用的沙盒目录和文件系统的其它部分隔离。
- 应用沙盒包含目录有:
- Application Bundle 包含应用可执行文件和所有资源文件。
- Documents/ 存放应用运行时生成的并且需要保留的数据,iTunes或iCloud会在同步设备时备份该目录。
- Library/Caches/ 存放应用运行时生成的需要保留的数据,同步时不会备份。
- Library/Preferences/ 存放所有的偏好设置,同步时会备份。
- tmp/ 存放应用运行时所需的临时数据,当某个应用没有运行时,iOS系统可能会清除该目录。
- 获取 Documents 目录的代码:
- (NSString *)itemArchivePath {
// Make sure that the first argument is NSDocumentDirectory
// and not NSDocumentationDirectory
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// Get the one document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:@"items.archive"];
}
-
NSSearchPathForDirectoriesInDomains
函数返回值是包含NSString
的NSArray
对象,这是因为对OS X可能有多个目录匹配某组指定查询条件,但iOS上只会有一个匹配目录。上面代码会获取数组第一个也是唯一一个NSString
对象,然后在该字符串后面追加固化文件的文件名。
18.3 NSKeyedArchiver与NSkeyedUnarchiver
21 Web服务与UIVebView
21.1 Web服务
- 要从Web服务器获取数据,需要使用NSURL, NSURLRequest, NSURLSessionTask, NSURLSession四个类。
- URL字符串必须是URL安全的,在URL中不允许有空格和双引号等字符出现,必须使用转译序列来替换。
NSString *search = @"Play some \"Abba\"";
NSString *escaped = [search stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// escaped is now "Play%20some%20%22Abba%22"
// If you need to un-escape a percent-escaped string, NSString has the method:
// - (NSString *)stringByRemovingPercentEncoding;
- 使用
NSJSONSerialization
可以处理JSon数据,例如:NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
关于
App Transport Security has blocked a cleartext HTTP (http://) resource load since
错误:
it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
- 在iOS 9中,苹果将原http协议改成了https协议,使用 TLS1.2 SSL加密请求数据。使用xcode 7为iOS 9开发应用 如果直接访问"http:// ... "会出现App Transport Security。
- 参见:iOS 9使用HTTP(App Transport Security问题)
- 主线程也称为用户界面线程,所以修改界面的代码必须在主线程中运行。
- 默认情况下,
NSURLSessionDataTask
在后台线程,当Web服务请求成功后,BNRCoursesViewController
需要调用reloadData
方法重新加载UITableView
对象数据。为了让reloadData
方法在主线程中运行,可以使用dispatch_async
函数,实例代码如下:
- (void)fetchFeed
{
NSString *requestString = @"http://bookapi.bignerdranch.com/courses.json";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:req
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
self.courses = jsonObject[@"courses"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
[dataTask resume];
}
21.2 UIWebView
- 使用
UIWebView
对象可以显示指定网页内容,这样就可以在应用内直接打开网页。
21.3 认证信息
- Web服务可以在返回HTTP响应时附带认证要求,这时发起方应该提供相应的用户名和密码。应用收到认证要求时,
NSURLSession
委托会收到- URLSession:task:didReceiveChallenge:completionHandler:
消息,可以在该消息中发送用户名和密码,完成认证。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
NSURLCredential *cred = [NSURLCredential credentialWithUser:@"BigNerdRanch"
password:@"AchieveNerdvana"
persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
}
21.6 深入学习:HTTP请求主体
-
NSURLSessionTask
会使用HTTP协议来和Web服务进行通信,发送和接收的数据必须符合HTTP规范,NSURLSessionTask
提供多种方法,来设置HTTP请求的各方面。 - 本章例子中的HTTP请求格式图:
- 除了从服务器获取信息,也可以通过HTTP的POST方法向服务器发送信息。
- 向服务器上传图片的代码:
NSURL *someURL = [NSURL URLWithString:@"http://www.photos.com/upload"];
UIImage *image = [self profilePicture];
NSData *data = UIImagePNGRepresentation(image);
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:someURL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:90];
// This adds the HTTP body data and automatically sets the Content-Length header
req.HTTPBody = data;
// This changes the HTTP Method in the request-line
req.HTTPMethod = @"POST";
// If you wanted to set the Content-Length programmatically...
[req setValue:[NSString stringWithFormat:@"%d", data.length] forHTTPHeaderField:@"Content-Length"];
26 NSUserDefaults
- 每个应用包都有一个plist文件,用来存储用户偏好设置,可以通过
NSUserDefaults
类访问。 - 为应用创建一个设置束(settting bundle),可以在系统设置中修改偏好设置。
26.1 NSUserDefaults
- 使用
NSUserDefaults
实例代码:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *greeting = [defaults objectForKey:@"FavoriteGreeting"];
// If the user expresses a preference, you can set the value for that key:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"Hello" forKey:@"FavoriteGreeting"];
-
NSUserDefaults
会自动将对象存储到对应的plist文件中,支持:NSArray
,NSDictionary
,NSString
,NSData
,NSNumber
。如果存储plist文件不支持文件,可以先归档为NSData类型。 Objective-C运行环境会在创建某个类第一个对象之前调用该类的
initialize
方法。声明全局变量,注册默认设置的实例代码:
// At launch time, the first thing that will happen is the registering of the factory settings.
// It is considered good style to declare your preference keys as global constants.
// Open BNRAppDelegate.h and declare two constant global variables:
#import <UIKit/UIKit.h>
extern NSString * const BNRNextItemValuePrefsKey;
extern NSString * const BNRNextItemNamePrefsKey;
@interface BNRAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
// In BNRAppDelegate.m, define those global variables and use them to register the factory defaults in +initialize:
#import "BNRAppDelegate.h"
#import "BNRItemsViewController.h"
#import "BNRItemStore.h"
NSString * const BNRNextItemValuePrefsKey = @"NextItemValue";
NSString * const BNRNextItemNamePrefsKey = @"NextItemName";
@implementation BNRAppDelegate
+ (void)initialize
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *factorySettings = @{BNRNextItemValuePrefsKey: @75,
BNRNextItemNamePrefsKey: @"Coffee Cup"};
[defaults registerDefaults:factorySettings];
}
@end
27 控制动画
- 将一段文字移到屏幕中间,再移到一个随机位置的,而且伴随着渐变效果的代码:
// Set the label's initial alpha
messageLabel.alpha = 0.0;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
messageLabel.alpha = 1.0;
}
completion:NULL];
[UIView animateKeyframesWithDuration:1.0 delay:0.0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:0.8 animations:^{
messageLabel.center = self.view.center;
}];
[UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{
int x = arc4random() % width;
int y = arc4random() % height;
messageLabel.center = CGPointMake(x, y);
}];
} completion:^(BOOL finished) {
NSLog(@"Animation finished");
}];