重温“iOS编程”

几年前初学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属性让他保存在堆中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容