前言

HTTP的缓存机制,主要体现在HTTP协议头里面的几个字段——Expires、Cache-control、Last-Modified/If-Modified-Since、Etag/If-None-Match等。在移动开发中,了解HTTP的缓存对开发者来说已经必不可少。

HTTP HEAD

Expires

HTTP头中的Expires字段,告诉浏览器在Expires显示的时间前,浏览器可以从缓存中读取,而不需要再次去请求。

Cache-control

Cache-control与Expires一样,都是告诉浏览器有效期。不过它不只是设置过期时间,还可以设置很多选项:

  • no-cache指示请求或响应消息不能缓存
  • no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
  • max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
  • min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
  • max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

如上图:max-age=600,表示600s内不需要重新请求。

Last-Modified

Last-Modified:资源的最后修改时间。

如果本地缓存已经过期,即超过了max-age的缓存时间,缓存的HTTP头中存在Last-Modified,则向服务器发送带有If-Modified-Since字段的请求,后面带上Last-Modified所记录的最后修改时间。

服务器收到带有If-Modified-Since的HTTP请求,会将请求的资源修改时间与If-Modified-Since时间对比:如果这段时间没有被修改过,则返回304,告诉浏览器可以继续使用本地缓存;如果资源已经修改过了,则响应请求的资源数据,返回200 OK.

Etag

Etag:服务器应答数据的时候,生成的当前资源的唯一标识。

如果本地缓存已经过期,即超过了max-age的缓存时间,缓存的HTTP头中存在Etag,则向服务器发送带有If-None-Match字段的请求,后面带上Etag所记录的当前资源的唯一标识。

服务器收到带有If-None-Match的HTTP请求,会将请求的资源唯一标识与If-None-Match的标签的标识对比:如果这段时间没有被修改过,则返回304,告诉浏览器可以继续使用本地缓存;如果资源已经修改过了,则响应请求的资源数据,返回200 OK。

Etag与Last-Modified的区别?

HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
  • 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存
  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器生成的对应资源的唯一标识符,能够更加准确的控制缓存。Last-ModifiedETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified

跳转、刷新、强制刷新

浏览器缓存行为还有用户的行为有关:

用户操作 Expires/Cache-control Last-Moified/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进、后退 有效 有效
F5刷新 无效 有效
Ctrl+F5刷新 无效 无效

总结

当本地存在缓存的时候,一次请求的流程:

HTTP Cache

参考:Cache-Control section of RFC 2616

Blocks

What is blocks

Block语法

Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。

^ 返回值类型 参数列表 表达式

^int (int count) {return count + 1;}

Block语法可以省略几个项目。首先是返回值类型:

^(int count) {return count + 1;}

其次,如果不使用参数,参数列表可以省略:

^{ print(“Block\n”);}

使用Block语法讲Block赋值为Block类型变量:

int(^blk) (int) = ^(int count) {return count + 1;}
int(^blk1) (int) = blk;

使用typedef定义:

typedef int (^blk_t) (int);
blk_t blk = ^(int count) {return count + 1;}
int nResult = blk(10);

Block类型变量可以像C语言中其他类型变量一样使用。

截获自动变量值

Blocks中,Block表达式截获所使用的自动变量的值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。

int a = 10;
int b = 10;
int (^blk) (void) = ^(return a + b;);
b = 2;
blk();

执行结果为:20

__block说明符

若想在Block语法的表达式中将值赋给Block语法外的自动变量,需要在自动变量附加block说明符,该变量成为block变量。

__block int val = 0;
void(^blk) (void) = ^{val = 1;};
blk();
printf("%d",val);

执行结果为: 1

赋值给截获的自动变量会产生编译错误:

id array = [[NSMutableArray alloc] init];
void (^blk) {array = [[NSMutableArray alloc] init]};

会出现编译错误。

另外,在使用C语言数组时,必须小心使用起指针。

const char text[] = "Hello";
void (^blk) (void) = ^{
    printf("%c\n",text[2]);
};

在现在的Block中,截获自动变量的方法并没有实现对C语言数组的截获,因此会编译出错。可以使用指针解决:

const char *text = "Hello";
void (^blk) (void) = ^{
    printf("%c\n",text[2]);
};  

Blocks的实现

// TODO:需要一些时间理解

书名:《Object-C高级编程 IOS与OS X多线程和内存管理》

ARC

在Object-C中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。

  1. 使用Xcode4.2或以上版本
  2. 使用LLVM编译器3.0或以上版本
  3. 编译器选项中设置ARC为有效

所有权修饰符

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

1. __strong修饰符

__strong修饰符为id类型和对象类型默认的所有权修饰符,可以省略。

id __strong obj = [[NSObject alloc] init];

2. __weak修饰符

如果只用__strong修饰符,自动引用计数式内容管理必然会发生“循环引用”的问题。

__weak提供弱引用,弱引用不能持有对象实例,可以解决“循环引用”的问题。

下面这段代码:

id __weak obj = [[NSObject alloc] init];
NSLog(@"%@",obj);

会输出 nil 。因为__weak修饰的obj持用弱引用,在赋值过后,并没有对象强引用它,生成的对象会立即释放。这样obj自动赋值为nil。

再来看看下面这段代码的输出:

id __weak obj0 = nil;
{
    id __strong obj1 = [[NSObject alloc] init];
    obj0 = obj1;
    NSLog(@"%@",obj0);
}
NSLog(@"%@",obj0);

结果:

<NSObject: 0x109420000>
nil

注:weak修饰符只能用于IOS5以上以及OS X Lion以上版本的应用程序。在它们一下的程序可使用unsafe_unretained修饰符来代替。

3. __unsafe_unretained修饰符

unsafe_unretained修饰符,是不安全的所有权修饰符。有unsafe_unretained修饰符的变量不属于编译器的内存管理对象。

id __unsafe_unretained obj = [[NSObject alloc] init];
NSLog(@"%@",obj);

这里和__weak一样,会输出nil。难道这两个修饰符相同吗?再来看第二个例子:

id __unsafe_unretained obj0 = nil;
{
    id __strong obj1 = [[NSObject alloc] init];
    obj0 = obj1;
    NSLog(@"%@",obj0);
}
NSLog(@"%@",obj0);

输出:

<NSObject: 0x10972ced0>
<NSObject: 0x10972ced0>

可以看到结果已经和__weak不同。

这里在第二次打印的时候,obj0已经成为一个野指针。

weak与unsafe_unretained的区别可以简单地说:

当弱引用的对象被释放,weak修饰的对象会自动赋值为nil;而unsafe_unretained修饰的对象不会。

4. __autoreleasing修饰符

在ARC有效的时候,不能使用NSAutoreleasePool。为了实现想非ARC工程的NSAutoreleasePool该怎么办?使用__autoreleasing修饰符。

一下两段代码效果相同:

/* NO ARC */
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

/*  ARC  */
@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}

但是,显式地附加autoreleasing修饰符同显式地附加strong一样罕见。一般不必显示地添加__autoreleasing修饰符。

另外,无论ARC是否有效,调试用的私有方法_objc_autoreleasePoolPrint()都可以使用。利用这个函数可以有效地帮助我们调试注册到autoreleasepool上的对象。

ARC的规则

1. 不能使用retain/release/retainCount/autorelease

2. 不能使用NSAllocateObject/NSDeallocateObject

3. 遵守内存管理的方法命名规则

4. 不要显示调用dealloc

5. 使用@autoreleasepool快代替NSAutoreleasePool

6. 不能使用NSZone

7. 对象型变量不能作为C语言结构体的成员

要把对象型变量加入到结构体成员,可强制转换为void* 或是添加__unsafe_unretained修饰符。

显示转换id 和 void*

下面这段代码,在ARC下会编译错误:

id obj = [[NSObject alloc] init];
void* p = obj;

需要使用“__bridge转换”:

1、 __bridge

/* ARC */
id obj1 = [[NSObject alloc] init];
void* p = (__bridge void*)obj1;

相当于:

/* NO ARC */
id obj = [[NSObject alloc] init];
void* p = obj;

但是使用bridge转换,其安全性与赋值unsafe_unretained修饰符相似,甚至更低。

2、 __bridge_retained

/* ARC */
id obj1 = [[NSObject alloc] init];
void* p = (__bridge_retained void*)obj1;

相当于:

/* NO ARC */
id obj1 = [[NSObject alloc] init];
void* p = obj1;
[(id)p retain];

下面的代码:

void* p = 0;
{
    id obj1 = [[NSObject alloc] init];
    void* p = (__bridge_retained void*)obj1;
}
NSLog(@"%@",p);

由于使用__bridge_retained,p在对后持有该对象,所以会打印出该对象。

3、 __bridge_transfer

/* ARC */
id obj1 = [[NSObject alloc] init];
void* p = (__bridge_transfer void*)obj1;

相当于:

id obj1 = [[NSObject alloc] init];
void* p = obj1;
[(id)p retain];
[obj1 release];

当然可是实现双向的转换。这些转换多数使用在Objec-C对象和Core Foundation对象之间的互相转换。

以下两个函数为系统提供的方法,实现Toll-Free Bridging:

NS_INLINE id CFBridgingRelease(CFTypeRef CF_CONSUMED X) {
return (__bridge_transfer id)X;
}
NS_INLINE CF_RETURNS_RETAINED CFTypeRef CFBridgingRetain(id X) {
return (__bridge_retained CFTypeRef)X;
}

Toll-Free Bridging

属性

ARC中,属性声明的属性与所有权修饰符的关系

属性声明 所有权修饰符
assign __unsafe_unretained
copy __strong(复制对象)
retain __strong
strong __strong
unsafe_unretained __unsafe_unretained
weak __weak

有一种情况不能使用__weak修饰符:

- (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE;
- (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE;

如果NSobject实例上面的两个方法返回NO,绝对不能使用__weak修饰符。

前言

iBeacon作为IOS7的一项新特性,在今年的WWDC大会上正式正式发布。虽然官方对它的描述并不多,WWDC的视频中也没有一个是讲解iBeacon的,但是在WWDC2013的Keynote上还是看到了它的身影。

WWDC2013 Keynote

iBeacon也是我今年WWDC上最喜欢的新特性之一,因为我一直认为更便捷、去中心化的沟通才是未来。在IOS7推出Beta版的时候,我也曾经实践过新推出的Nearby Networking With Multipeer Connectivity。但是相比之下,iBeacon要优秀很多。

目前有不少硬件厂商在生产基于iBeacon的基站,Estimote很早就推出了Estimote Beacons,利用苹果的iBeacon技术,实现Distance、Proximity和Notification三种功能。如果想体验一下,可以在Appstore搜索下载应用Estimote。同时Estimote Beacons提供了一套API,可以方便地进行编程。

Estimote Beacons的API地址

Estimote Beacons

What is iBeacon?

iBeacon通过使用低功耗蓝牙技术(Bluetooth Low Energy,也就是通常所说的Bluetooth 4.0或者Bluetooth Smart),可以创建一个信号区域,当设备进入该区域时,相应的应用程序便会提示用户是否需要接入这个信号网络。通过能够放置在任何物体中的小型无线传感器和低功耗蓝牙技术,用户便能使用iPhone来传输数据。

iBeacon

什么是低功耗蓝牙技术?

低功耗蓝牙技术的最大特点便在于低功耗,从而能使设备拥有更长的续航时间。不过低功耗蓝牙技术仅支持较低的文件传输速率,因此可以用于可穿戴式智能设备之间的信息传送,但却不能完成像传输音频这样的任务。

一个简单的Demo

Demo下载地址

Start a BeaconBroadcast

1、 创建一个BeaconRegion,作为Beacon基站的广播的Region对象。其中UUID为Beacon基站的唯一标识。

NSUUID* myUUID = [[NSUUID alloc] initWithUUIDString:@"E621E1F8-C36C-495A-93FC-0C247A3E6E5D"];
self.targetBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:myUUID identifier:@"My Beacon Demo 20131221"];

2、 创建一个CBPeripheralManager对象

 self.peripheraManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];

3、 开始广播

NSDictionary* peripheraData = [self.boardcastBeaconRegion peripheralDataWithMeasuredPower:nil];
[self.peripheraManager startAdvertising:peripheraData];

开始广播后,可以处理一下回调函数,如:

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error

Start a BeaconMonitor

1、 创建一个BeaconRegion,作为搜索的Region对象。其中UUID为Beacon基站的唯一标识。

NSUUID* myUUID = [[NSUUID alloc] initWithUUIDString:@"E621E1F8-C36C-495A-93FC-0C247A3E6E5D"];
self.targetBeaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:myUUID identifier:@"My Beacon Demo 20131221"];

2、 创建一个CLLocationManager对象

self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;

3、 开始搜索Beacon基站

[self.locationManager startRangingBeaconsInRegion:self.targetBeaconRegion];

接下来可以处理一下回调函数,如:

- (void)locationManager:(CLLocationManager *)manager
    didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
- (void)locationManager:(CLLocationManager *)manager
 didUpdateLocations:(NSArray *)locations
- (void)locationManager:(CLLocationManager *)manager
     didEnterRegion:(CLRegion *)region
- (void)locationManager:(CLLocationManager *)manager
      didExitRegion:(CLRegion *)region
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status

效果图:

CLProximityImmediate

CLProximityNear

CLProximityFar

CLProximityUnknown

didExitRegion

总结

通过Demo,可以对iBeacon有一个简单的了解。iBeacon可以方便实现室内导航、商品消息推送、支付等功能。网上很多文章认为iBeacon会成为NFC杀手。到底iBeacon能发展到什么地步,让我们拭目以待吧。

iOS: Understanding iBeacon

引子

今年的WWDC大会上,苹果推出了IOS7,UI上有了很大变化,向扁平化发展;在新的SDK中,新的特性、新的功能无疑给开发者带来了更大的惊喜。其中最感兴趣的主要有三点,今天的主角TextKit就是其中之一。

TextKit

What is Text Kit?

  1. 快速现代化的文本布局和渲染引擎
  2. 基于core text,拥有core text的所有功能和灵活性,而不需要使用不易用的API(CF type)
  3. 完美地集成在UIKit中

下图是IOS6中的类结构图:

Structure in IOS6

下图是IOS7中的类结构图:

Structure in IOS7

可以看出,iOS7之前,几乎所有的文本和UIWebview都是WebKit来处理的,由它来布局和渲染。在IOS7中,UIWebview还是由WebKit作为排版和渲染引擎,但是文本都由TextKit来处理,TextKit又是基于CoreText之上,这是一个很大的改动。

TextKit结构设计

TextKit的设计符合模型-视图-控制器(MVC)架构:

TextKit结构设计

NSTextStorage:MVC中的模型,用于存储需要显示的文本内容以及他们的属性。它继承自NSMutableAttributedString。当内容变化时,会通知NSLayoutManager;

NSTextContainer:MVC中的视图。每个文本视图都有一个文本容器,用来定义了一个文本可以绘制的区域,即NSTextContainer。

UITextView:实际中的文本视图组件,每一文本视图都有一个文本容器,即NSTextContainer。

NSLayoutManager:MVC中的控制器,用来管理文本的显示:

  1. 这个管理器监听NSTextStorage中文本或属性改变的通知,一旦接收到通知就触发布局进程;
  2. 从NSTextStorage提供的文本开始,它将所有的字符翻译为字形(Glyph);
  3. 一旦字形全部生成,这个管理器向它的文本容器(们)查询文本可用以绘制的区域;
  4. 然后这些区域被行逐步填充,而行又被字形逐步填充。一旦一行填充完毕,下一行开始填充;
  5. 对于每一行,布局管理器必须考虑断行行为(放不下的单词必须移到下一行)、连字符、内联的图像附件等等;
  6. 当布局完成,文本的当前显示状态被设为无效,然后文本管理器将前面几步排版好的文本设给文本视图。

Text Layout

什么是TextLayout?

TextLayout = Glyph + Location
Glyph = Font + String

什么是Glyph?

Glyph

  1. 字符的图形表示(A Graphical Representation of Characters)。
  2. Character + font –> glyph
  3. 图形系统的Glyph IDs: CGGlyph

有了TextLayout可以实现的功能:

1.字距调整(Kerning)

Kerning

2.连写

Ligature

3.断字

Break

4.隐藏文字

Truncation

5.更加完美的排版

DEMO演示

下载地址:Demo地址

Demo1:BaseInteraction

对文本内容进行基本的检测,包括地址、电话、URl等。

-(BOOL) textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
    //...
}

通过回调函数,对URL的点击进行处理。

BaseInteraction

Demo2:ExclusionPaths

为TextView中的文字设置ExclusionPaths,即设置一个不可填充文本的区域。

- (UIBezierPath *)translatedBezierPath
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //从预先设置好的plist中读取
        _originalButterflyPath = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"butterflyPath" ofType:@"plist"]];
    });

    CGRect butterflyImageRect = [self.textView convertRect:self.imageView.frame fromView:self.view];
    UIBezierPath *newButterflyPath = [self.originalButterflyPath copy];
    [newButterflyPath applyTransform:CGAffineTransformMakeTranslation(butterflyImageRect.origin.x, butterflyImageRect.origin.y)];

    return newButterflyPath;
}

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    self.textView.textContainer.exclusionPaths = @[[self translatedBezierPath]];
}

ExclusionPaths

Demo3:TextColoring

实现对文本特殊字段的高亮处理,对URL类型字符串添加边框等。

self.textStorage = [[TextColoringTextStorage alloc] init];
OutliningLayoutManager* layoutManager = [[OutliningLayoutManager alloc] init];
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)];
[layoutManager addTextContainer:textContainer];
[self.textStorage addLayoutManager:layoutManager];

UITextView* textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) textContainer:textContainer];
[self.view addSubview:textView];

//设置需要处理的文本片段
self.textStorage.tokens = @{ @"Alice" : @{ NSForegroundColorAttributeName : [UIColor redColor] },
                             @"once" : @{ NSForegroundColorAttributeName : [UIColor greenColor] },
                             TKDDefaultTokenName : @{ NSForegroundColorAttributeName : [UIColor blackColor],NSFontAttributeName:[UIFont fontWithName:@"Helvetica" size:18.] } };

TextColoring

Demo4:FontType

UIFontDescriptor的简单使用。

UIFontDescriptor* fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
UIFontDescriptor* boldFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
UIFont* boldFont = [UIFont fontWithDescriptor:boldFontDescriptor size:0.0];

总结

通过几个例子,TextKit已经初步展示了它强大的处理能力,但这只是冰山一角。在WWDC的视频中关于自动布局、更加精细的排版方面例子,Demo中并没有实现。

参考资料:

WWDC2013视频: https://developer.apple.com/wwdc/videos/

Session 210: Introducing Text Kit

Session 223: Using Fonts with Text Kit

Session 220: Advanced Text Layouts and Effects with Text Kit

WWDC 2013 sample code: https://developer.apple.com/downloads/index.action?name=WWDC%202013

objc.io原文地址: http://www.objc.io/issue-5/getting-to-know-textkit.html

译文地址: http://blog.jobbole.com/5196

引子

ios6中默认提供了大概200种字体,有时为了让应用中的文字更好看,希望能有其他的字体可以选择。今天主要来介绍一下如何为IOS应用添加字体。

方法一、将字体包打包到工程

步骤如下:

1.下载字体包

在网上搜索字体包,可以找到很多资源,我下载了一个名字叫 DIN-Medium.otf 的字体包。(有一些特殊的字体可能涉及到版权的问题,所以在使用之前要确认清楚。)

2.将字体包添加到工程

将字体包添加到工程

添加字体包

注:如果不选 Show Raw Keys/Values,需要添加的Key值为 Fonts provided by application

3.修改plist文件

在工程的plist文件中添加key值为UIAppFonts:

添加键值

这样就可以使用我们新加的字体了。

使用新增字体

效果图:

效果图

方法二、动态下载字体库

在IOS6之后,苹果支持动态字体库下载。这样的好处可以不必增加程序包大小,不过下载的字体只能从官方支持的列表中选择:

iOS 6: Font list

iOS 7: Font list

苹果提供的Demo

具体实现方法可以看一看Demo。

在Readme中有一句很奇怪的话:

Fonts downloaded from applications in this manner are not installed permanently. 
The system may decide to remove them under certain usage scenarios.

按照Readme中的说法,系统可能会删除我们下载的字体,所以在每次程序启动前都需要检查一遍,使用的时候要确定字体已经可以使用。

什么场景下会删除?Readme中并没有详细说。

Demo效果图:

Demo效果图

总结

将字体包打包到工程的方法

适用情况:字体包比较小

存在的问题:对字体包大小有限制;版权的问题

动态下载字体库的方法

适用场景:字体包比较大,无法打包到工程(一个完整的字体包要10M多);不用担心版权的问题。

字体会下载一个系统的路径/private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/,一次下载,所有的App都能使用(在被系统删除之前)。

存在的问题:

  1. 需要预先下载10几M的字体包,对用户来说是一个负担
  2. 每次应用重新启动,即使已经下载,都需要“预热”一次。需要先到上面所说的下载路径进行匹配,检查是否已经下载;如果已经下载,就会将字体缓存到另外一个cache目录下,才能使用,这个过程需要3s左右的时间。(我试过手动删除下载的字体包,但不去改里面的XML文件,结果匹配出错返回错误结果,但是不再会重新下载)
  3. 可选择的字体有限制,只能从苹果提高的列表中选择
  4. 只支持IOS6以上(按照目前的统计来看,这个慢慢不是问题了)

引子

在iOS开发中,工程中会用到很多第三方的开源库,像ASIHttpRequest、AFNetworking、JSONKit等等。要添加一个第三方库的:

  1. 先查找并下载到本地,然后添加到工程
  2. 添加第三方开源库所依赖的framework
  3. 一些第三方开源库需要修改OtherLinerFlag的参数

一旦开始一个新的项目,这些工作又要重新做一遍。另外还需要自己管理第三方库更新的问题。

有没有一个能够帮助我们自动完成这一系列工作的方法呢?没错,就是今天的主角——CocoaPods。

CocoaPods介绍

CocoaPods为你的Xcode工程管理库的依赖。你的工程的第三方库依赖由一个单独的叫做Podfile的文本文件管理。CocoaPods可以获取第三方库源代码、解决库的依赖,然后将你的工程与它放在一个新创建的workspace中。最终的目标是通过创建一个更集中的生态系统,以提高第三方的开源库可发现性和参与性。

官网地址:http://cocoapods.org/

GitHub地址:https://github.com/cocoapods/cocoapods

安装CocoaPods

CocoaPods使用Ruby创建,需要预先安装ruby。Mac下自默认安装好了ruby,可以通过命令查看:

ruby --version

使用ruby的gem命令安装:

sudo gem install cocoapods
pod setup

修改gem源

默认的gem源为https://rubygems.org/,国内的网络有可能被墙。如果无法访问这个网址,可以使用淘宝的RubyGems 镜像。修改方法:

$ gem sources --remove https://rubygems.org/
$ gem sources -a http://ruby.taobao.org/

查看gem源:

gem source -l

修改成功后,可以按照上面的步骤安装。

更新CocoaPods

更新已安装的CocoaPods,只需要输入:

sudo gem update cocoapods

参见:CocoaPods Getting Started

使用CocoaPods查找第三方库

CocoaPods可以很方便地查找第三方库:

pod search ReactiveCocoa

将CocoaPods添加到Xcode工程

创建一个Podfile文件,添加你所需要的第三方库文件:

platform :ios, '6.0'
pod 'AFNetworking', '~> 2.0'  
pod 'ObjectiveSugar', '~> 0.5'

保存并放在工程的根目录,然后:

pod install

这样就会生成一个MyApp.xcworkspace,以后经通过使用这个打开工程,而不是原来的MyApp.xcodeproj.

如果需要更新工程使用的第三方库,只需要编辑Podfile文件,然后再次执行pod install 即可。

集成到一个已存在的workspace中

如果想将CocoaPods集成到一个已经存在的workspace中,只需要在Podfile中增加一句:

workspace 'MyWorkspace'

这一系列命令后到底做了什么

使用pod install命令,实际做了:

  1. 创建或者更新workspace
  2. 将你的工程添加到这个workspace中
  3. 如果需要将CocoaPods静态库工程添加到workspace中
  4. 添加libPods.a
  5. 将CocoaPods的Xocde configurations file添加到你的工程
  6. 将你的工程的target configurations改变为急于CocoaPods的
  7. 添加一个编译参数来拷贝pods中的资源文件到你的app bundle。i.e. a ‘Script build phase’ after all other build phases with the following: Shell: /bin/sh Script: ${SRCROOT}/Pods/PodsResources.sh

参见:Using CocoaPods

定义私有库

在开发的过程中,可以定义将通用的部分定义成私有的库,便于快速开发。

Podspec

.podspec文件是一个库的描述文件。.podspec文件可以标识该第三方库所需要的源码文件、依赖库、编译选项,以及其他第三方库需要的配置。

在安装pods的时候,会从https://github.com/CocoaPods/CocoaPods下载,里面包含了所有第三方库的.podspec文件。可以通过查看当前用户根目录下的.cocoapods/repos/master/ 路径。

以AFNetworking为例:

Pod::Spec.new do |s|
s.name     = 'AFNetworking'
s.version  = '2.0.2'
s.license  = 'MIT'
s.summary  = 'A delightful iOS and OS X networking framework.'
s.homepage = 'https://github.com/AFNetworking/AFNetworking'
s.authors  = { 'Mattt Thompson' => 'm@mattt.me' }
s.source   = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => "2.0.2", :submodules => true }
s.requires_arc = true

...
...

end

创建pods私有库描述文件

创建命令:

$ pod spec create REPO_NAME

会生成一个.podsepc文件。根据注释可以方便理解各个配置项的意义。

创建本地podsepc

在.cocoapods/repos/master/ 路径下,创建文件,名称为新创建的私有库名称,在目录下创建不同版本路径,然后将对应的.podsepc文件拷贝到对应目录下。

然后使用pods搜索:

$ pod search REPO_NAME

看看是否能够搜索到新创建的库。

删除pods私有Repo

输入命令:

pod repo remove [name]

总结

使用CocoaPods管理第三方库,能够帮助完成很多之前繁琐的工作,提高工作效率。第三方库的更新更是省心了不少,不再需要再一个一个查看,哪个第三方库更新了没有。

另外有一款APP,Podlife,让我们了解Pods上第三方库更新的情况,很不错。

2013杭州西部越野是一次环杭州城山脊越野跑:从西湖区转塘风水洞出发,穿越周浦、龙坞、西湖三大山脊,最终到达终点浙大玉泉校区。这条线路也被称为“风浙线”,全程约42公里。

2013杭州西部越野路线图

起因

今天碰到了一个很有趣的问题,大概现象是这个样子的:在一个可以动态增加、删除tablviewcell的页面,每个cell上有一个输入框,当离开页面的时候,记录cell个数、输入框内容,下次进入自动填充显示。

发现之前在ios6上运行完全没有问题,但是在ios7上,一旦删除过一条后,退出页面,再进来,第一个行的输入框记录的内容会变成删除的那个cell的输入框内容。

另外一个值得注意的地方是,cell个数很少,没有超过tableview的高度。

难道又是ios7兼容的问题,原来一直都没有问题啊?

好吧,开始查看代码,单步调试,看看到底是哪一步逻辑出错。

哪里出了问题

在退出页面,保持内容的时候,大概有这样一段代码逻辑:

for(UIView* v in self.tableView.subviews)
{
    if([v isKindOfClass:[UITableViewCell class]])
    {
        UITextField* textField = cell.myTextField;
        // 取出textField.text 根据cell的 indexPath值 保存到data model
    }
}

单步调试的时候,发现tableview上竟然存在一个为nil的cell。不错,就是我们删除的那个cell。程序内并没有非空检测,结果取它的的indexPath.row ,得到的值为零。这样就将它的textField内容保存了下来。

删掉的cell为什么还在

这是该到我们的利器大显身手了——Reveal。 我写了一个demo,最初有6个cell,功能是点击cell,就让cell的个数减一。当我删除五行后,在Reveal中可以看到:

Reveal截图

是的,那个cell还在!还在tableview上,只不过被隐藏掉了。 打印tableview上的cell:

<UITableViewCell: 0x755be20; frame = (0 220; 320 44); text = '5'; hidden = YES; autoresize = W; layer = <CALayer: 0x755bf50>>
<UITableViewCell: 0x755b680; frame = (0 176; 320 44); text = '4'; hidden = YES; autoresize = W; layer = <CALayer: 0x755b480>>
<UITableViewCell: 0x755aee0; frame = (0 132; 320 44); text = '3'; hidden = YES; autoresize = W; layer = <CALayer: 0x755ae10>>
<UITableViewCell: 0x755a870; frame = (0 88; 320 44); text = '2'; hidden = YES; autoresize = W; layer = <CALayer: 0x755a730>>
<UITableViewCell: 0x7559f60; frame = (0 44; 320 44); text = '1'; hidden = YES; autoresize = W; layer = <CALayer: 0x755a090>>
<UITableViewCell: 0x7556f70; frame = (0 0; 320 44); text = '0'; hidden = YES; autoresize = W; layer = <CALayer: 0x75570f0>>

可以看到,还是存在6个cell对象。

好吧,那为什么当初在ios6上没有问题呢

ios6上运行没有问题,不代表代码逻辑没有问题,只不过问题被巧妙的掩藏过去了。

问题的关键在于查找到的顺序:在ios7中,被删除的cell会最后被遍历,会覆盖row为0的内容;而在ios6中,被删除的cell最先被遍历,虽然会先写入错误的内容,但是后面会被真正的第0行内容覆盖,所以才看起来没有问题。

终于,又解决了一个有趣的bug。

总结

这种遍历的方式本来就有问题,应该根据indexPath,遍历查找cell。另外程序缺少异常情况的处理,cell为空时,没有做异常处理。对UITableView的机制缺乏了解,通过这个bug又有了新的认识。

Reveal乃居家旅行必备神器。

也许很多时候,我们埋下了陷阱,由于某种巧合,表面上安然无事。但是终究有一天会暴漏出来。要不断提高代码质量。

(ps:如果没有埋下这个错误,也许就没有了今天解决问题的乐趣,就不会又进一步的了解。就像那句话说的:无知让我感到痛苦,无知为我带来快乐。)