几年前初学iOS的时候,iOS编程是我的启蒙书,那时候看觉得很有难度,现在粗略重温一遍。温故而知新,发现里面还是有挺多干货的。记录一下。
第一章
开发者中心的4个重要概念:
Developer Certificate:这份证书文件会通过Keychain Access(钥匙串访问)程序,加入读者当前使用的钥匙串。Xcode会使用这份证书为代码签名。
App ID:应用程序标识(application identifier)是一串能够在App store中唯一标识应用的字符串。应用程序标识通常为这种形式:com.bignerdranch.awesomeApp,其中应用名称跟在公司名称后面。Provisioning Profile中的应用程序标识必须和应用的程序包标识(bundle identifier)匹配。针对开发的profile,App ID可以包含通配符,匹配任意的程序包标识。
Device ID(UDID,设备标识):每一个iOS设备都有一个唯一的标识。
Provisioning Profile:需要在开发设备和计算机上保存Provisioning Profile文件。该文件对应这些设置:1份开发者标识和1组设备标识(只有和这些标识匹配的设备才能安装)。Provisioning Profile文件的后缀名是.mobileprovision。
注意:Xcode在将应用安装至设备时,会通过计算机上的某个Provisioning Profile获得合适的证书(Certificate),并用这份证书为应用的二进制签名。接着,开发设备的UDID会和Provisioning Profile中的某个UDID匹配,应用程序标识(application identifier)会和程序包标识(bundle identifier)匹配。最后,Xcode会将签名后的二进制文件传入开发设备,并经由设备上的同一个Provisioning Profile确认后并最终启动。
第二章
在Objective-C中,方法的唯一性取决于方法名。因此,即使参数类型或返回类型不同,一个类也不能有两个名称相同的方法。
NSLog支持一种额外的转换说明:%@,对应的实参类型是任何一种对象。程序在处理格式化字符串时,如果遇到%@,则不会直接替换为相应位置的实参。程序会先向对应位置的实参发送description消息,得到description消息所返回的NSString对象,然后使用得到的NSString对象替换%@。因为程序回想%@所对应的实参发送消息,所以这些实参必须是对象,又因为每个对象都实现了description方法,所以任何对象都可以对应%@。
数组对象只能保存指向Objective-C对象的指针,所以不能将基本类型的变量或C结构加入到数组对象中。
#import和C语言中的#include作用相同,差别是#import不会重复导入同一个文件。
isa:任何一个对象都有一个名为isa的实例变量。在程序向某个类发送alloc消息并创建对象后,相应的类会为新创建的对象的isa实例赋值,将其指回自己,即创建该对象的类。所以任何一个对象都可以通过自己的isa指针,知道自身的类型。(isa,is a表示“是一个”的意思)。Objective-C的很多特性都源自于isa指针。在运行时,当某个对象收到消息后,会根据isa指针所指向的类,执行和相应的消息想匹配的方法。这种特性和多数编译语言不同,在这些多数的编译语言,需要执行的方法在编译时就决定了。而Objective-C的这种特性表明,需要执行的方法只有在运行时才能确定。
self:self存在于方法中,是一个隐式局部变量。编写方法时不需要声明self,并且程序会自动为self赋值,指向收到消息的对象自身(大多数面向对象的语言也有这个概念,有些将其称为this)。通常情况下,self会用来向对象自己发送消息。
super:在覆盖某个类的某个方法时,往往需要保留该方法在父类中的实现,然后在其基础上扩充子类的实现,为了能更方便地完成这项任务,Objective-C提供了名为super的编译器指令。那么super是怎样工作的呢?通常情况下,当某个对象收到消息时,系统会先从这个对象的类开始,查询和消息名相同的方法。如果没有找到,则会在这个对象的父类中继续查找。该查询过程会沿着继承路线向上,直到找到相应的方法名为止(如果直到顶层结构的顶端也没能找到合适的方法 ,程序会抛出异常)。向super发送消息,其实是向self发送消息,但是要求系统在查找方法时跳过当前对象的类。向父类开始查询。
异常和未知选择器:Objective-C是一种动态类型语言,无法在编译时(即构建应用时)判断某个对象是否能够响应特定的消息。如果Xcode判断应用会向某个对象发送其无法响应的消息,就会显示相应的警告,但是代码仍然能够编译通过。出于某些原因(原因很多),如果应用最后还是向某个对象发送了其无法响应的消息,那么程序在运行时会抛出异常(exception),异常也称为运行时错误(run-time error)。这是因为异常只会在应用运行时才会出现。和运行时错误相对应的是编译时错误,编译时错误只会在构建应用或编译代码时出现。一个最常见的运行时错误是unreconized selector(未知选择器),未知选择器的意思是某个对象收到了其没有实现的消息。
第三章
堆:堆(heap)是指内存中的一块区域,应用中的所有对象都会保存在堆中。当应用向某各类发送alloc消息时,系统会从堆中分配一块内存,其大小足够存放相应的对象的全部实例变量。
比如可以以NSDate对象的实例来解析内存分配的问题。该对象包含两个实例变量:一个是double类型的变量(用于保存从某个固定时间点算起的时间差,单位为秒),另一个是继承自NSObject的isa指针(所有的对象都会继承)。一个double变量的大小是8个字节(byte),一个isa指针的大小是4个字节。因此,NSDate类收到alloc消息后,系统会在堆上为新的NSDate对象分配一个大小为12字节的内存空间。
一个对象永远不会直接保存另一个对象,所有的对象在堆中都是独立存在的。如果需要,一个对象可以保存指向其他对象的引用,级可以将其他对象的地址赋给指针实例变量。
栈:栈是内存中的另一块区域,和堆是分开的。堆包含了大量无序的对象,需要通过指针来保存这些对象在堆中的地址。栈,则会按先进后出,后进先出的规则来保存一组帧(frame,为了方便读者辨认,下面会将frame翻译为栈帧)。当程序执行某个方法(或函数)时,会从栈中分配一块内存空间,这块空间成为栈帧。栈帧负责保存程序在方法中声明的变量的值。在方法中声明的变量为局部变量(local variable)。当某个应用启动并运行main函数时,它的栈帧会被保存在栈的底部。当main函数调用另一个方法时,这个方法的栈帧会压入栈的顶部。被调用的方法还可以调用其他方法,依此类推,最终会在栈中形成一个塔状的栈帧序列。当被调用的方法结束时,程序会将其栈帧从栈中“弹出”并释放。如果同一个方法被再次调用,则应用会创建一个全新的栈帧,并将其压入栈的顶部。
__weak:使用__weak关键字,可以将某个变量声明为具有弱引用特性。如__weak Person *person;弱引用有一项很有用的功能,当程序释放了某个对象时,会自动地将指向该对象的具有弱引用特性的指针全部设置为nil。
__unsafe__unretained:除了使用__weak,还可以使用__unsafe__unretained来声明变量。具有__unsafe__unretained特性的指针不会改变其指向的对象的拥有方个数(这点与__weak相同)。但是当程序释放某个对象后,不会自动的将指向该对象的具有__unsafe__unretained特性的指针设置为nil。这也是为什么具有__unsafe__unretained特性的变量是不安全的。(Objective-C之所以会提供__unsafe_unretained特性,主要是出于兼容性考虑:弱引用__weak是iOS5引入的新特性,针对老版本的iOS开发的应用,如果需要使用ARC,并且保留原有的内存管理逻辑,就必须使用__unsafe__unretained。
第四章
深入学习:构建阶段、编译器错误和连接器错误
Xcode会分步骤构建应用,这些步骤成为构建阶段(build phases),构建阶段需要完成的任务如下:
1、编译源代码(Compile Sources):该阶段会编译构建目标所需的源代码,凡是加入项目的源代码文件,默认都会加入该构建阶段。
2、连接二进制文件(Link Binary With Libraries):Xcode会将编译后的代码和指定的框架(库)连接起来,使代码能够使用相应的框架中的类。
3、拷贝程序包资源(Copy Bundle Resources):完成代码的编译和连接后,Xcode会生成一个可执行文件,并将其放入应用程序包(应用程序包其实是一个目录)。接着,Xcode会将指定的文件加入程序包。这些资源是运行应用时要使用的数据文件,例如Xib文件、图片和声音文件。凡是加入项目的非源文件,默认都会加入该构建阶段。
使用Xcode构建应用时,会在编译源代码的阶段发现错误。连接二进制和库的阶段有时也会出错。
1、编译源代码分为两步:预处理(preprocessing)和编译。
预处理
预处理的作用是为每个实现文件(.m)创建一个中间文件(intermediate file)(.mi文件)。中间文件和实现文件一样,都是Objective-C代码,但是中间文件的体积可能会很大。
预处理器处理完实现文件中的所有预处理指令后,会生成一个中间文件。预处理指令是带有前缀#的语句,例如#import、#define等。预处理器在处理#import语句时,会将语句替换成导入文件的内容。
我自己理解的预处理过程:预处理器先处理.m文件中带#前缀的预处理语句,会导入相应的文件内容。然后.m文件会生成类型为.mi的中间文件。
编译
完成预处理步骤后,Xcode会在编译之前生成的中间文件,将Objective-C代码转换成机器码(机器码的意思应该就是机器可是识别的代码格式)。新生成的机器码会被保存在目标文件.o中,一个中间文件就对应着一个目标文件。
开发应用时,大部分错误会发生在这个将中间文件转换成机器码的编译阶段。当编译器“看不懂”代码时,就会报错。这类在编译阶段产生的错误成为编译时错误(compile-time errors)或语法错误(syntax errors)。常见的编译时错误有放错分号;的位置、方括号[]或花括号{}不匹配、拼写错误或字母大小写错误等。
连接
目标文件.o包含了实现文件.m中实现的方法的机器码。但是某个实现文件可能会用到其他实现文件中的代码。例如WhereamiViewController.m会使用startUpdatingLocation方法,而此方法的机器码存在于CLLocationManager.m的目标文件中。
编译器不是将startUpdateingLocation方法的代码拷贝至WhereamiViewController.m的目标文件中,而是设置一个连接,指向CLLocationManager.m的目标文件。连接二进制文件和库就是处理这类连接的阶段,简称连接阶段。
框架是一组类的集合,而类由头文件.h和实现文件.m两个文件定义。框架的特别之处是,框架的实现文件是已经预编译好的(即框架中的.m文件已经经过了预处理和编译的过程),并且会将处理后的目标文件分成一个或多个库文件(所以我们看到Objective-C的框架是没有.m文件的,相应的实现文件已经转成机器码)。如凡是用Core Location框架的代码的类,编译器都会将其目标文件中放置相应的连接,指向Core Location库。
如果编译器无法处理某个连接(例如无法找到包含相应代码的目标文件,或者目标文件没有包含被引用的代码),就会产生连接器错误(linker error)。因为连接器错误会包含陌生的术语,并且错误不是源自某行代码,所以对于新手而言,这类错误更难看懂。(比如我们有时候集成一些第三方的插件,如友盟推送,如果没有在Lind Binary With Libraries导入规定的框架或库,就会产生连接器错误)
第五章
响应对象(UIResponder)负责接收和响应和处理与其相关的事件。UIView是UIResponder的子类。所以UIView也可以接收事件。在应用的响应对象中,会有一个对象成为第一响应者。同一时刻,只能有一个响应者成为第一响应者。第一响应者会处理那些与屏幕位置无关的事件。例如:轻按事件会被发送给某个被单击的视图对象,但是摇动事件和屏幕位置无关,所以会被发送给第一响应者对象。
#import “”和<>的区别:在导入文件时,如果是自己创建的文件,就要用双引号,如果是框架的头文件,就要用尖括号。编译器在处理尖括号时,只会在系统库里查找相应的文件。编译器在处理双引号的导入时,会先在项目目录中查找,如果没找到,才会在系统库里查找。
第六章
每个视图都会维护一张图片,即视图的外观。如UIButton对象的图片是带居中文字的圆角四方形。UILabel对象的图片是单纯的文字。当视图的数据(例如UIButton的title属性、UILabel的text属性)发生变化时,视图会重画这张图片,从而使视图的外观能够匹配数据的变化。
应用在重画屏幕(应用在处理完当前事件后重画屏幕)时,UIWindow对象会先将其图片画在屏幕上。接着,UIWindow对象的所有子视图会将各自的图片画在屏幕上,然后这些子视图的所有子视图会画出各自的图片,以此类推。创建用户界面的过程可以总结为:为每个视图创建相应的图片,然后将这些视图加入视图层次结构。
负责绘制视图的方法是drawRect:方法,默认情况下,这个方法不做任何事情。UIView的子类可以覆盖drawRect:方法来完成自定义的绘图任务。覆盖drawRect:方法后,需要使用特定的绘图方法或函数,才能使UIView的子类创建相应的图片。这些绘图方法或函数属于Core Graphics框架。覆盖drawRect:之后的第一件事情就是获取绘图上下文。绘图上下文作用是1、维护各种绘图状态(如当前的绘图颜色、线条粗细)2、执行绘图操作。应用在向某个视图发送drawRect:方法前,会先自动创建一个绘图上下文,然后将其设置为当前上下文。
frame包括视图大小和相对于父识图的位置。bounds与的位置和大小与父视图无关,是其在真实坐标系中的位置。通常情况下,bounds的origin会是(0,0),size和frame的size相同。
夜的第七章
UIViewController的指定初始化方法是initWithNibNane:bundle:方法。如果这个方法的两个入参都传nil,那么会找跟控制器相同名称的xib文件、并在mainBundle中查找。其实init方法就相当于initWithNibName:bundle:两个入参传nil。
当某个控制器由于内存警告而卸载其视图时,会向自己发送viewDidUnload方法。不过现在这个方法已经被弃用了。
创建一个UITabBarController,然后赋给self.window.rootViewController。设置tabbarController的viewControllers数组,数组里面装的都是已经初始化的UIViewController。这时候每个UIViewController都有一个tabBarItem属性,通过tabBarItem属性设置title和image。
UIApplicationMain函数做了什么?1、创建一个运行循环,让程序可以不死并持续做事情。2、创建一个AppDelegate对象,该对象时应用的代理对象, 在特定的时候,特定的代理方法会回调。
对于Retina屏幕:如果一个点是1像素x1像素,那么使用1@的图片,如果一个点是2像素x2像素,那么使用2@,如果一个点是3像素x3像素,那么使用3@。
第八章
如果程序将某个对象注册成为观察器,就必须在释放该对象的时候将其移出通告中心。原因:如果程序释放了某个已经注册成为观察器的对象,但是没有将其移出通告中心。当通告中心转发该对象曾经关心的事情时,会向已经不存在的该对象发送通知,导致程序崩溃。
第十一章
UISlipViewController只适合于iPad使用。
UINavigationController里面有UINavigationBar。但是栈帧上的Controller有navigationItem。navigationItem里面有barButtonItem(left和right),还有一个titleView。
UIToolbar和UINavigationBar的原理很相似,都可以加入UIBarButtonItem,但是UINavigationBar只可以在左右放入两个,而UIToolbar可以放入一组。
UIImagePickerController,需要设置sourceType和delegate,sourceType包括UIImagePickerControllerSourceTypeCamera、UIImagePickerControllerSourceTypePhotoLibriry、UIImagePickerControllerSourceTypeAlbum。但是需要先判断isSourceTypeAvailable。然后以模态的形式弹出来。还有一个属性是mediaTypes数组,可以用来限制用户选择媒体的类型。媒体类型有两种,kUTTypeImage、kUTTypeMovie。auvalableMediaTypesForSourceType:方法可以检查是否能拍摄视频。UIImagePickerController会将拍摄的视频存入临时目录(tmp),(临时目录是不安全的,随时可能被清理)。可以用NSFileManager存入caches目录或者使用UISaveVideoAtPathToSavePhotoAlbum([mediaURL path],nil,nil,nil);[NSFileManager defaultManager] removeItemAtPath:[mediaURL path] error:nil];
如果选择完图片跳回来,有可能之前的控制器的页面会由于内存警告而释放了ImageView,从而导致无法设置。所以需要一个图片保存的类。
第十二章
有很多种途径可以生成无重复字符串。本节使用一种Cocoa Touch提供的一种机制,这种机制可以生成唯一标识(UUID,也称GUID)。类型为CFUUIDRef的指针所指向的C结构代表UUID,UUID是基于当前时间、计数器和硬件标识(通常为以太网卡的MAC地址)等数据计算生成。
// 创建一个CFUUID,CFUUIDCreate函数知道如何创建无重复的字符串
CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
CFUUIDRef的类型名称有前缀CF,这是因为它源自于Core Foundation框架(后缀Ref代表它是指针。Core Foundation是一套C语言API),提供多种C结构和C函数。当要创建Core Foundation框架中的特定结构时,必须调用相应的C函数,这些函数的函数名是有规律的,一般都会以某种结构的类型名称为起始,然后为英文单词Create(例如上面的CFUUIDCreate)。此外,这些函数的第一个实参类型都是CFAllocatorRef,用于指定内存的分配方式。通常情况下都会传入kCFAllocatorDefault,要求系统自行决定内存分配方式。
CFUUIDRef指向C结构,使用成员变量来保存UUID。如果使用字符串来代表该结构,则示例如下:28153B74-4D6A-12F-9D61-155EA4C32167
通过C函数CFUUIDCreateString,可以根据指定的CFUUIDRef创建相应的字符串对象。
// 根据指定的CFUUIDRef创建相应的字符串对象
CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault,newUniqueID);
Core Foundation中的很多结构都包含Objective-C类的版本,并且两者可以自由转换(toll-free-bridging)。例如,对应CFStringRef的Objective-C类是NSString,对应CFArrayRef的Objective-C类是NSArray。这些支持自由转换的结构和类实例之所以能够自由转换,是因为两者在内存中的存储结构完全相同。
NSString *key = (__bridge NSStrign *)newUinqueIDString;
这段代码使用了__bridge关键字。要理解__bridge的作用,必须先了解Core Foundation的C结构的内存管理机制。
Core Foundation与toll-free-bridging:
当程序丢失了某个指向Objective-C对象的指针时(指针变量被释放,或者指向了其他对象),ARC就会起作用,将相应的对象标记为失去了一个拥有方。但是ARC对Core Foundation的C结构无效。因此,在程序失去了某个指向Core Foundation的C结构的指针前,必须调用一个特定的函数,将相应的C结构的拥有方减1.这个特定的函数是CFRelease。
如果在失去指针钱没有调用CFRelease,那么相应的C结构的拥有方数会保持不变,从而导致内存泄漏。
CFRelease(newUniqueIDString);CFRelease(newUniqueID);
针对Core Foundation的C结构(如下简写为结构),有如下若干内存管理规则:
1、指针变量可以拥有其指向的结构,前提是该结构必须由函数名包含Create或Copy的函数创建的。如NSString *key = (__bridge NSStrign *)newUinqueIDString; ---- key指针变量可以拥有newUniqueIDString这个结构,且这个结构是由Create函数创建的。
2、如果某个指针拥有一个结构,就必须在程序失去该指针前调用CFRelease函数,将相应结构的拥有方个数减少1。失去指针是指程序修改了某个指针变量,将其指向了其他对象或结构(包括nil),或者是程序释放了指向某个对象的指针变量。
3、一旦某个指针调用了CFRelease函数,就不要再尝试访问该指针。
下面介绍一下__bridge关键字。因为ARC对Core Foundation的C结构无效,所以当代码将某个Core Foundation的C结构转换成Objective-C对象时,ARC将无法正确处理相应的内存管理问题。转换运算符仲的(__bridge)关键字的作用是要求ARC忽略针对该指针的内存管理规则。因此,当ARC在处理下面这行代码的时候,不会将key变量设置为NSString对象的拥有方。NSStrign *key = (__bridge NSString *)newUinqueIDStrign;对于赋值后的指针变量key,ARC可以正确地发挥作用。
第十三章
区别是iPad还是iPhone:[[UIDevice currentDevice] userInterfaceIdiom]; iPad为userInterfaceIdiomPad、iPhone为userInterfaceIdiomPhone。
UIPopoverController,只能在iPad上使用。不过已经被弃用了。现在用UIPopoverPresentationController,可以在iPhone和iPad上使用。可以做到弹出带三角形的小控制器和视图的效果。
第十四章
固化(archive)是iOS SDK提供的一种保存和读取对象的机制,使用非常广泛。当固化某个对象时,会将该对象的所有实例变量存入指定的文件。当应用解固(unarchive)某个对象时,会从指定的文件读取相应的数据,然后根据数据还原对象。为了能够固化和解固某个对象,相应的对象必须遵守NSCoding协议,并且实现两个必须方法:encodeWithCoder:和initWithCoder:
当需要编码某个对象时,会向这个对象发送encodeWithCoder:消息。收到消息的对象需要编码自己的实例变量,所以也会向这些实例变量发送encodeWithCoder:消息。因此,对象的编码(固化)过程是一个递归过程,编码中的对象会再编码其他对象。
当应用需要根据编码后的数据初始化某个对象时,会向该对象发送initWithCoder:消息。initWithCoder:应该还原之前通过encodeWithCoder:编码(固化)的所有对象,然后将这些对象赋给相应的实例变量。
有人会问,问什么要这么写?原因很简单,小数据量的持久化都用NSUserDefaults来实现,但是NSUserDefaults只能保存NSString,、NSNumber,、NSDate、 NSArray、NSDictionary这些数据类型,但大多时候,我们想要把某个对象持久化保存起来,所以就需要用到NSCoding协议这样来实现。比如我们需要持久化保存某个Model对象。然后我们再结合NSKeyedArchiver和NSKeyedUnArchiver或者把对象存到应用沙盒中。
xib文件也是基于固化机制的。当读者在Xcode中将某个视图拖拽至画布时,Xcode会创建相应的对象。保存xib文件时,Xcode会将这些视图固化至指定的文件(UIView遵守NSCoding协议)。当应用需要载入xib文件时,就会解固xib文件中的视图。
应用沙盒
应用沙盒包括如下几个目录:
1、应用程序包(application bundle):包含所有的资源文件和可执行文件,是只读目录。(NSBundle mainBundle)。
2、Documents/目录:存放应用运行时生成的需要保存的数据。iTunes和iCloud会在同步设备时备份该目录。当设备发生故障时,可以从iTunes和iCloud恢复该目录中的文件。例如,游戏存档可以保存在此。
3、Library/Preferences/目录:存放所有的偏好设置。iOS中的设置(Settings)应用也会在该目录中查找本应用的设置信息。使用NSUserDefaults类,可以通过plist文件保存数据在此。iTunes和iCloud会在同步设备时备份该目录。
4、Library/Caches/目录:存放应用运行时生成的需要保存的数据。但是iTunes和iCloud不会备份这个目录。不备份数据缓存的原因是因为这个文件夹的数据可能很大,从而延长同步需要的时间。如果数据源是从别处(如web服务器)获取的。就可以保存到本目录。当用户需要恢复设备时,应用只需再次去别处(如web服务器)获取即可。
5、tmp/文件夹:存放应用运行时所需的临时数据。当应用不再需要使用tmp/目录中的文件,就应该改删除这些文件。iOS系统会不定时清理这个目录中的文件。iTunes和iCloud不会备份此目录。
通过NSKeyedArchiver和NSKeyedUnarchiver来把对象数据保存和读取:
archiveRootObject:toFile:方法会先创建一个NSKeyedArchiver对象,然后向跟对象(这里是p1)发送encodeWithCoder:消息,并将之前创建的NSKeyedArchiver对象作为实参传入(NSKeyedArchiver是NSCoder的子类)。p1的encodeWithCoder:方法会向其包含的所有属性发送encodeWithCoder:消息。并传入一个NSKeyedArchiver对象。当所有的对象都完成编码后,NSKeyedArchiver对象就会将数据写入指定的文件中。(我们可以通过打印documents文件夹路径,然后按Command+Shift+G来进入沙盒查看)
当iOS系统认为当前可用的内存过低时,会根据需要终止挂起状态的应用,并且不会发出警告。当系统有足够多的空域内存时,出于挂起状态的应用可以一直保留该状态。当出于挂起状态的应用即将被系统终止时,并不会收到相应的通知,系统会直接将其从内存中移除(终止后的应用,其图标可能还会保留在Dock中,按下图标会重启应用)
隐式变量“_cmd”表示当前方法的选择器。NSStringFromSelector()传入_cmd可以为指定的选择器生成相应的字符串
如果要读写二进制数据,可以使用NSData。如果要读写文本数据,可以使用NSString的实例方法initWithContentsOfFile:读方法和writeToFile:atomically:encoding:error:写方法。和NSString类似,NSDictionary、NSArray也有initWithContentsOfFile:读方法和writeToFile:写方法。
深入学习:应用程序包:Xcode在构建iOS应用时,需要完成的工作是创建应用程序包(application bundle)。应用程序包会包含应用的可执行文件和执行应用所需的全部资源。这些资源包括xib文件、图片和音频文件等。将某个文件加入到项目中,Xcode会自动判断是否应该将该文件加入应用程序包,然后再进行分类。打开应用的沙盒,可以看到应用沙盒中包括以下目录:应用程序包、Documents、tmp、Library。[NSBundle mainBundle]可以获取应用程序包,pathForResource:ofType:可以获取某个文件的全路径。
第十五章
UITableViewCell的两种注册方式:
1、只在tableView: cellForRowAtIndexPath:方法里面这样写:static NSString *cellIdentifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
return cell;
2、UINib *nib = [UINib nibWithNibName:@"UITableViewCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:@"cell"];
tableView: cellForRowAtIndexPath:方法里面:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
return cell;
说明:创建某个UINib对象时,必须指定一个XIB文件。新创建的UINib对象会载入该文件,并始终保留相应的数据。当UINib对象收到instantiateWithOwner:options:消息后,就会解析指定的XIB文件:解固所有的固化对象,并恢复所有的关联。【NSBunlde mainBundle】loadNibName:owner:options:方法,实际上该消息会在内部使用UINib:先创建一个UINib对象,然后根据传入的实参,向UINib对象发送instantiateWithOwner:options:消息。loadNibName:owner:options:和instantiateWithOwner:options:都会返回一个NSArray对象。
第二十二章+第二十三章(动画相关)
CALayer和CAAnimation来进行动画。这两个是Core Animation的两个主要类。而Core Animation在QuanzCore框架中。
每一个UIView都有一个layer的隐式层属性,负责图片和显示的效果。UIView和CALayer的区别是UIView继承自UIResponder,是可以进行事件的响应的。CALayer的代理就是包含它的UIView对象。也就是说,UIView是用类封装起来的一个抽象可视对象,并且可以处理触摸事件,而和绘图有关的功能,放在CALayer中。
CALayer是没有frame属相的,但是仍然可以向其发送setFrame:消息,这时候会根据frame的position和bounds来对layer设置。CALayer有一个anchorPoint属性,默认为(0.5,0.5)。CALayer有一个contents属性,指向一个CGImage对象。zPosition属性决定同级的layer的上下层位置。zPosition越大,越上层。CALayer的很多属性都是隐式可动画的,如position、transform、bounds、contents。
当某个UIView对象收到setNeedDisplay消息后,会将该消息转发给自己的CALayer对象。当应用处理完当前的运行循环后,会将所有标记为“需要重新显示”的层准备一个CGContext对象。CALayer对象在调用绘图函数时,凡是基于CGContext对象的绘图操作,生成的像素最终都会体现在这个层的位图上。
动画对象的作用是,在指定的时间内,持续驱动CALayer属性的变化。
CAAnimationGroup可以保存一组动画对象,其包含的动画对象会并发执行
CATransation可以为CALayer对象提供移出移入屏幕的动画效果。UINavigationController的推入推出的动画效果就是通过CATransation来来实现的。
CALayer的transform属相作用是包含一个变形的3D矩阵。通过应用不同的矩阵,可以完成旋转rotation、缩放scale、变形、倾斜自身frame。
速度控制函数:CAMediaTimingFunction。设置动画对象的速度。CABasicAnimation *basic = [CABasicAnimation animationWithKeyPath:@"transform.rotaion"];
CAMediaTimingFunction *func = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
basic.timingFunction = func;当然,也可以自定义function来控制速度。
控制CAAnimation可以通过delegate的方式。
CAKeyframeAnimation *keyFrame = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
CATransform3D forward = CATransform3DMakeScale(1.3, 1.3, 1.0);
CATransform3D back = CATransform3DMakeScale(0.7, 0.7, 1.0);
CATransform3D forward2 = CATransform3DMakeScale(1.2, 1.2, 1.0);
CATransform3D back2 = CATransform3DMakeScale(0.9, 0.9, 1.0);
[keyFrame setValues:[NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:forward],
[NSValue valueWithCATransform3D:back],
[NSValue valueWithCATransform3D:forward2],
[NSValue valueWithCATransform3D:back2]
, nil]];
[keyFrame setDuration:1.0];
[self.view.layer addAnimation:keyFrame forKey:@"keyFrame"];
要做出炫酷的动画效果,关键是给出合适的键路径,并设置释放的速度控制函数。
第二十四章
UIStroyboard和XIB很相似,但是不同点是,可以用Stroyboard建立多个视图之间的关系。
Storyboard的优点:1、可以很快开发出原型,向客户同事展示界面流程;2、可以替代部分简单的代码;
Storyboard的缺点:1、不利于团队协同开发;2、代码管理工具不容易管理;3、会让简单的开发复杂化;4、降低开发的灵活性。
第二十五章:Block
block对象是存在于栈中的,需要使用copy属性让他保存在堆中。