断言(NSAssert)的使用

NSAssert和assert是断言,主要的差别是assert在断言失败的时候只是简单的终止程序,而NSAssert会报告出错误信息并且打印出来.所以尽管的使用NSAssert,可以不去使用assert.
iOS中用的最多的是两对断言, NSAssert/NSCAssert 和 NSParameterAssert/NSCparameterAssert. 要知道他们的区别,我们先来看看他们定义.

#if !defined(NS_BLOCK_ASSERTIONS)
#if !defined(_NSAssertBody)
#define NSAssert(condition, desc, ...)	\
  do {				\
  __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
  if (!(condition)) {		\
    [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
    object:self file:[NSString stringWithUTF8String:__FILE__] \
      lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
  }				\
    __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
  } while(0)
#endif
#if !defined(_NSCAssertBody)
#define NSCAssert(condition, desc, ...) \
  do {				\
  __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
  if (!(condition)) {		\
    [[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
    file:[NSString stringWithUTF8String:__FILE__] \
      lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
  }				\
    __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
  } while(0)
#endif

##NSAssert/NSCAssert  两者的
差别通过定义可以看出来, 前者是适合于Objective-C的方法,_cmd 和 self 与运行时有关. 后者是适用于C的函数.
NSParameterAssert/NSCparameterAssert 两者的区别也是前者适用于Objective-C的方法,后者适用于C的函数.
NSAssert/NSCAssert  和 NSParameterAssert/NSCparameterAssert 的区别是前者是所有断言, 后者只是针对参数是否存在的断言, 所以可以先进行参数的断言,确认参数是正确的,再进行所有的断言,确认其他原因.
NSAssert的用法

int a = 4;
NSAssert(a == 5, @"a must equal to 5"); //第一个参数是条件,如果第一个参数不满足条件,就会记录和打印第二个参数
//回记录并打印断言日志    
*** Assertion failure in -[AppDelegate application:didFinishLaunchingWithOptions:], /Users/admin/Desktop/storyboard/storyboard/AppDelegate.m:36
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'a must equal to 5

##NSParameterAssert的用法

- (void)assertWithPara:(NSString *)str
{
    NSParameterAssert(str); //只需要一个参数,如果参数存在程序继续运行,如果参数为空,则程序停止打印日志
    //further code ...
}
// 如果str 为空则有如下类似的日志
*** Assertion failure in -[AppDelegate assertWithPara:], /Users/admin/Desktop/storyboard/storyboard/AppDelegate.m:45<pre name="code" class="objc">*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: str'

Xcode 已经默认将release环境下的断言取消了, 免除了忘记关闭断言造成的程序不稳定.
NSAssertionHandler:自定义处理方法,程序不会直接崩溃
NSAssertionHandler实例是自动创建的,用于处理错误断言。 如果 NSAssert和NSCAssert条件评估为错误,会向 NSAssertionHandler实例发送一个表示错误的字符串。每个线程都有它自己的NSAssertionHandler实例。
自定义NSAssertionHandler的子类

@interface MyAssertHandler : NSAssertionHandler
@end
#import "MyAssertHandler.h"
@implementation MyAssertHandler
//处理Objective-C的断言
- (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,...
{
  NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%li", NSStringFromSelector(selector), object, fileName, (long)line);
}
//处理C的断言
- (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format,...
{
  NSLog(@"NSCAssert Failure: Function (%@) in %@#%li", functionName, fileName, (long)line);
}
@end

给线程添加处理类

NSAssertionHandler *myHandler = [[MyAssertHandler alloc] init];
    //给当前的线程
    [[[NSThread currentThread] threadDictionary] setValue:myHandler
                                                   forKey:NSAssertionHandlerKey];
                                                   

实现这些以后,程序能够获得断言失败后的信息,但是程序有可能继续运行,不会强制退出程序.

03/29/2016 13:56 下午 posted in  Xcode

简单了解XCode工程模板的创建知识

使用Xcode 6新建工程时,Apple准备了好些模板,这些模板写个Demo还是没有问题的,但是用来组织项目文件还是太弱了,所以情况经常是不得不每次去新建各种目录,这种重复性的劳动一来乏味,二来浪费时间。那么我们能不像创建自己的模板呢?这样新建的工程就能按自己的想法包含各种目录和文件。好消息是可以,坏消息是Apple没有提供相应的文档。虽然没有文档,还是试着来创建一个模板,每次都重复实在太烦(就是这么任性)。

既然没有文档,我们就把Apple的模板复制一份,在它的基础上修改成我们需要的样子。

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/ 

有iOS所有工程模板。用户自定义的模板建议放到~/Library/Developer/Xcode/Templates/ ,目录如果不存在就创建。模板至少要包含两部分:一是扩展名为 .xctemplate 的文件夹;二是名称为 TemplateInfo.plist 的属性列表文件。好了,我们来创建一个自定义模板:

// Step 1:
$ mkdir ~/Library/Developer/Xcode/Templates/CocoaBite.xctemplate/

// Step 2:
$ cp /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/Single\ View\ Application.xctemplate/* ~/Library/Developer/Xcode/Templates/CocoaBite.xctemplate/

现在我们有了一个和Single View Application一样的模板,但这和我们目标还相差很远。接下来我们要做就是修改 TemplateInfo.plist ,让模板为我们做更多准备工作。

Keys Advice
Ancestors No Import settings from another Project Template.
Concrete Recommended Visible or hide Template form New Project Window.
Definitions No Work with workplace. Can write to file example source code.
Description Recommended New Project Window – Project Template Description.
Identifier Yes Project Template Unique Identifier.
Kind Yes XCode Template Kind. Project or File.
Nodes Recommended Create or Copy Files to Project. Copy works
Options Recommended New Project Wizard >> Choose Options for Project. Add Text Fields, Combo Boxes.
Platforms Recommended Set Platform.
Project Yes Set Project Build Settings.
Targets Recommended Set Build Settings, Build Phases for Targets. Link Libraries.

上面列出了TemplateInfo.plist大部分键,详细介绍在 这里

我自己新建的模板主要用到Definitions和Nodes,它们俩组合起来可以控制模板会新建哪些文件。例如我想让模板包含Models目录:

// Step 1:
$ cd ~/Library/Developer/Xcode/Templates/CocoaBite.xctemplate/

// Step 2:
$ mkdir -p Models

// Step 3: 编辑TemplateInfo.plist 如下图所示。

完整的模板放在 这里 Xcode-6-Project-Templates

Reference

Creating Custom Xcode 4 Project Templates
About XCode 4 Project Template (How To Create Custom Project Template)
Xcode-6-Project-Templates
链接

02/19/2016 16:39 下午 posted in  Xcode

Xcode升级后插件失效的原理与修复办法

Xcode 的插件大大丰富了 Xcode 的功能,而且有了 Alcatraz ,插件的管理也非常容易,像我这种 Vim 党完全离不开 XVim。但是有个非常恼人的问题:一旦升级 Xcode ,插件就失效!

之前 Xcode 升级到6.2的时候遇到过插件失效的问题,Google 之后把一段很长命令复制到 Terminal 后运行一下即可,当时一看解决了,顿时觉得满足感爆棚,自己可以拯救地球了~就没有再深入,结果升级到6.3时又遇到了。“同样的招式对圣斗士是不能使用第二次的!”,同样的坑对有节操的程序员是不能掉进去第二次的!因此这一次一定要搞清楚为什么会这样,以后再次遇到了如何解决。

问题原因

Xcode 的插件放置在 ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins 目录下,为 .xcplugin 格式。通过 Show Content 可以看到 xcplugin 中存在一个 Info.plist,其中有一项为 DVTPlugInCompatibilityUUIDs,而这就是插件失效的原因。

由于 Apple 没有公开插件开发的相关资料,这里我只能通过命名跟值猜测 DVTPlugInCompatibilityUUIDs 的作用:插件通过 DVTPlugInCompatibilityUUIDs 来指定能够运行此插件的 Xcode 版本。因此,DVTPlugInCompatibilityUUIDs 中存放的是 Xcode 版本对应的 UUID,Xcode 在启动加载控件时,将当前 UUID 同插件 Info.plist 中 DVTPlugInCompatibilityUUIDs 存放的 UUID 数组进行匹配,如果没有匹配项,说明此插件无法在该版本的 Xcode 运行,插件也就失效了。

解决办法

解决办法非常简单:将当前版本的 UUID 加到 DVTPlugInCompatibilityUUIDs 中即可。但是插件比较多(1个及以上)的情况下,一个个的打开修改非常无聊跟低效,作为“懒惰”的程序员,这时候就要用上命令行,让重复劳动自动化。思路为将命令分为两部分:

通过 find 命令在插件目录下找到所有插件的 Info.plist 文件。
通过 xargs 命令对上一步的搜索结果进行“for 循环”(就这样理解吧),针对每一个 Info.plist 文件,利用 defaults write 命令将当前版本的 UUID 加到 DVTPlugInCompatibilityUUIDs 中。
此时问题来了,挖掘机技术。。。不对,是如何获取当前版本 Xcode 的 UUID 呢?首先关掉 Xcode,打开 Terminal,输入 tail -f /var/log/system.log,再次打开 Xcode,就能看到如下 log 信息:

[MT] PluginLoading: Required plug-in compatibility UUID 9F75337B-21B4-4ADC-B558-F9CADF7073A7 for plug-in at path ‘~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/Alcatraz.xcplugin’ not present in DVTPlugInCompatibilityUUIDs

可以看到,log 信息表明 Xcode 加载插件失败的原因,并且能够看到当前版本(6.3)Xcode 的 UUID 为 9F75337B-21B4-4ADC-B558-F9CADF7073A7。经过 @Kyrrr 的提醒,有一种更好的方式来获取当前版本 Xcode 的 UUID:通过 defaults read 命令从 Xcode 的 Info.plist 读取 DVTPlugInCompatibilityUUID。

最终的命令为:

	
find ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add `defaults read /Applications/Xcode.app/Contents/Info.plist DVTPlugInCompatibilityUUID`

在 Terminal 中运行上述命令就解决了插件失效的问题,在插件 Info.plist 的 DVTPlugInCompatibilityUUIDs 中也能看到新增的 UUID 了。

from:http://joeshang.github.io/2015/04/10/fix-xcode-upgrade-plugin-invalid/

11/03/2015 14:37 下午 posted in  Xcode

Xcode的断点调试技巧

转载:http://supermao.cn/duan-dian-shen-ru-liao-jie/
编码不能没调试,调试不能没断点(Break Point)。XCode的断点功能也是越来越强大。

##基本断点

如下图,这种是最常用的断点,也是最容易设置。左键点击一下就可以设置。

1438224910359355

##编辑断点

断点是可以编辑的。

1438224927240670

断点有下面几个属性可以设置:

  1. Condition
  2. Ignore
  3. Action
  4. Options

###Condition

这里可以输入条件表达式,满足条件的时候断点就会生效。例如上面输入a == 50。这个是非常有用的设置,特别在循环体内调试的时候,用着真的是爽。

###Ingore

在这里可以设置忽略断点次数。

###Action

Action是这里最复杂的,最强大的功能了。Action有6中类型。如下图

1438225007147421

  1. AppleScript
  2. Capture GPU Frame
  3. Debugger Command
  4. Log Message
  5. Shell Command
  6. Sound

常用的就是Log Message和Debugger Command

##Log Message

在这里填写的东西可以打印到控制台。例如我做了如下设置:

1438225128874847

%B会打印断点的名字,%H会打印断点的调用次数,@@中间可以输入表达式。 上面的设置在控制台的输出如下:

1438225371364390

##Debugger Command

这里可以输入调试命令,也就是po(打印对象信息),bt(打印函数栈),expression(表达式)这些调试命令。看图就明白了:

1438225401812904

image 控制台输出如下:

1438225450194964

##Options

勾选Automatically continue after evaluating actions之后程序会在断点产生后继续运行。这个属性是相当有用的,可以输入调试信息至于不暂停程序。

出了上面的基本断点外,XCode还提供了下面四种断点,需要点击断点面板左下角的+号添加。

1438225565768320

  1. Exception Breakpoint
  2. OpenGL ES Error Breakpoint
  3. Symbolic Breakpoint
  4. Test Failure Breakpoint

##Exception Breakpoint

Exception Breakpoint是一个非常有用的断点项。正如名字所示,当程序抛出异常的时候就回产生断点。通常程序崩溃会停在崩溃的地方,但有时候并不能准确停在引起异常的地方。比如数组越界!比如我下图所示,会引起数组越界访问。

1438225625879689

1438225651155831

程序运行的时候就会崩溃。但是崩溃停在了main函数里面,就算看了栈信息也不能马上定位到到底是那个数组越界访问了。为什么崩溃不能停在数组越界哪里?这是因为数组越界访问不一定会导致程序崩溃的,数组越界访问会导致异常抛出,而抛出的异常没有得到处理才会导致程序崩溃。因此最后会导致崩溃停在CoreFoundation框架里面。这个时候就需要设置Exception Breakpoint产生断点来定位错误了。

1438225734539767

1438225742253959

##OpenGL ES Error Breakpoint

这个主要是OpenGL ES的断点调试,这个个人没用到过。

##Symbolic Breakpoint

Symbolic Breakpoint,符号断点,真的是调试神器啊。当程序运行到特定符号的时候就会产生断点。通过这种方式添加断点,就不需要在源文件中添加,也不需要知道断点设置在文件的第几行。如图:

1438225777399846

比普通断点多了两个属性Symbol和Module。

##Symbol

Symbol的内容,可以有如下几种:

  1. 方法名称:会对所有具有此方法名称的类方法生效。例如 initWithFrame: 。

  2. 特定类的方法:OC类和C++类都适用,例如 ,[UIView initWithFrame:]或者 Shap::draw()。

  3. 函数名称。例如普通C函数。

通过设置Symbol来调试,好用根本停不下来,想怎么断点就怎么断点。

##Test Failure Breakpoint

这个类型的断点会在test assertion 失败的时候暂停程序的执行。

08/17/2015 18:34 下午 posted in  Xcode

iOS开发者证书-详解/生成/使用

转载:http://nuoerlz.is-programmer.com/posts/47670.html
本文假设你已经有一些基本的Xcode开发经验, 并注册了iOS开发者账号.

#相关基础

##加密算法

现代密码学中, 主要有两种加密算法: 对称密钥加密公开密钥加密.

###对称密钥加密

对称密钥加密(Symmetric-key algorithm)又称为对称加密, 私钥加密, 共享密钥加密.

这类算法在加密和解密时使用相同的密钥.

例如: 最常见的应用场景 - 系统登陆.

要成功登陆系统, 你必须输入正确的密码, 这密码是 唯一的, 是与创建时一样的. 同样 的, 其他人要成 功登陆, 他也要输入这唯一的正确的密码.

###公开密钥加密

公开密钥加密(public-key cryptography, 也称为非对称密钥加密).

这类算法在加密和解密时使用不相同的密钥.

这类加密算法对应有两个密钥: 公钥和私钥. 公钥, 是公开的, 任何人都可以获得; 私钥 , 是私密的, 只由持有者所有.

这类加密算法的特点是: 用公钥加密的内容只能用私钥解密, 用私钥加密的内容只能 用公钥解密.

这类加密算法的特点决定了, 它即可以用于实现数据加密, 又可以用于实现身份认证 (数字签名).

###加密 & 认证

我们需要区分加密和认证这两个基本概念.

  1. 加密是将数据资料加密, 使得非法用户即使取得加密过的资料, 也无法获取正确的资料 内容, 所以数据加密可以保护数据, 防止监听攻击. 其重点在于数据的安全性.

  2. 身份认证是用来判断某个身份的真实性, 确认身份后, 系统才可以依其身份给予相应的 权限. 其重点在于用户的真实性.

要实现这两个要求(加密和认证), 都需要用到加密算法, 但并不是所有的算法都可实现身 份认证. 身份认证现在一般使用公开密钥加密算法.

下面将重点说明如何使用公开密钥加密算法实现加密和认证.

###如何实现加密?

对于某一用公钥加密的数据, 只能由对应的私钥解密.

例如, 我要给你发一封加密邮件. 我必须有你的公钥, 我用你的公钥给邮件加密. 这样 就能保证邮件不被别 人看到, 而且不被篡改. 因为只有你可以用你自己的 私钥解密.

###如何实现认证?

对于某一用私钥加密的数据, 只能由对应的公钥解密. (记住, 私钥只由持有人所有)

用我(A1)的私钥给邮件加密, 发给你(B1)之后, 你 能用我的公钥解密. 这样就能保证 邮件是我(A1)发的.

##数字身份证 & 数字证书(Certificates) & 数字证书认证机构(CA) & 根证书

###数字身份证

数字身份证, 是身份标识方式的一种, 是一对"钥匙", 即一对公钥&私钥. 它一般 用本地系统工具生成.

###数字证书认证机构

数字证书认证机构(CA, Certificate Authority), 是负责发放和管理数字证书的权威机构, 并作为交易中受信任的第三方, 承担公钥体系中公钥的合法性 检验的责任.

###数字证书

数字证书, 是一种用于计算机的身分识别机制. 数字证书不是数字身份证 , 而是身份认证机构(数字证书认证机构)在数字身份证上加上数 字签名. 这一行为表示身份认证机构已认定这个持证人. 这里的"认定"是怎么做到 的呢? 参考'如何验证数字证书的有效性?'.

数字证书一般含这样一些信息:

证书发布者
证书持有者
有效期(证书在这个时期之前或之后无效)
证书持有者的公钥()
证书扩展, 包含一些额外信息
所使用的哈希算法
数字签名, 该数字签名是对以上信息的哈希值用CA的 私钥加密生成(
)

###如何验证数字证书的有效性?

即, 如何确保数字证书是经过CA认证的呢?

注意: 对于某一用私钥加密的数据, 只能由对应的公钥解密.

原理: 计算数据证书的数据信息的哈希值H1, 对证书上的数字签名用CA的公钥解密得 H2, 如果H1等于H2, 则该证书有效, 且是经CA认证的.

CA的公钥如何获得呢? 它包含在根证书里.

###根证书

根证书, 是CA给自己颁发的数字证书, 是信任链的起始点. 它一 般放在CA网站上, 供任何人下载.

###数字签名

数字签名, 是一种类似写在纸上的普通的物理签名, 但是使用了公钥加密领域的 技术实现, 用于鉴别数字信息的方法.

数字签名具有完整性, 不可抵赖性(即不可否认性).

从'如何实现认证?'一节, 我们知道可以利用公开加密算法的特点实现"认证", 也就是签 名. 但是这会有一个问题, 任何人都可以或可能冒充我(A1)! 即, 任何人都可以冒充我 (A1)把他经用他的私钥加密的数据和他的公钥发给你. 也就是说, 你无法确保你接收到的 公钥就是我(A1)的!

所以, 这就需要一个机制来保证. 这机制就是基于前面所说的"数字证书"和"CA". 它可确 保我(A1)是经过CA认证的.

###数字签名原理

1.签名:

发送者先对要发送的"数据"计算一个哈希值, 再用自己的私钥对这个哈希值加密生成一 个"签名(值)", 同时发送者要拥有一个向CA申请得到的"数字证书"(记住, 其记录有公 钥), 最后发送者把"数据", "签名(值)"和"数字证书"一起发送给接收者.

2.验证签名:

接收者接收到发来的"数据", "签名(值)"和"数字证书"后, 会作一系列验证来判断这一数 字签名是否有效:

打开并验证"数字证书"的有效性;
计算"数据"的哈希值H1, 用"数字证书"记录的公钥对"签名(值)"进行解密得到值H2, 如 果H1==H2, 则该"数字签名"有效.

Digital_Signature_diagram

##Xcode代码签名相关

###Keychain

MAC下用于存储和管理密钥等私密信息的工具.

###Identifiers / Bundle ID / App ID

这是应用的唯一标识.

###Device UUID

这是设备的唯一标识.

###Provisioning Profiles

这就是我们最后要生成的 Profiles, 它记录了 App ID, UUID 和其所信任的证书.

当Xcode要把一个应用部署到真机上时, 会作相应检验:

Keychain中是否有相匹配的有效证书? 参考'如何验 证数字证书的有效性?'
Profiles是否有效? 参考'数字签名原理'
要部署的App的App ID是否与Profiles记录的App ID相匹配?
UUID是否相匹配?
只有在所有检验都通过了, 才能部署到真机上.

##Start

###生成密钥

request_cer

request_cer_info

填上Email和Name, 并选择"Saved to disk".

最后在"keys"下生成一对新的密钥, 为了以后分辨方便, 最好对它重新命名, 双击可以重 命名. 同时也会生成一个CRS文件.

request_cer_done

###安装根证书

这个证书叫做 Worldwide Developer Relations Certificate Authority, 通过这个链接 可以下载: https://developer.apple.com/certificationauthority/AppleWWDRCA.cer, 一般下载下来的文件名为: AppleWWDRCA.cer.

该证书一般由 Xcode 自动安装.

###向Apple(CA)申请开发者证书

登陆苹果开发者中心, 到 Certificates, Identifiers & Profiles | iOS Apps | Certificates | Development 下, 新增一个Certificates:

cer_add

点下一步会出现, 要求你先生成一个CRS文件, 这就是我们在"生成密钥"时做的:
cer_req

直接下一步, 并把刚才生成的CRS文件上传, 最后提交生成证书. 把它下载下来, 双击, 其会自动添加到Keychains中.

cer_in_keys

cer_in_cers

###创建 App ID

在Identifiers下创建一个新的App ID.

###把设置的UUID加入Devices注册列表

在Devices下添加.

###生成Profiles

在 Certificates, Identifiers & Profiles | iOS Apps | Provisioning Profiles | Development 下, 点击添加一个新的Profiles.

到"选择证书"页面时, 有一点要注意, 最好只选上刚刚生成的证书, 不要选择所有. 因为 "选择所有"在Keychain中已有别的证书下, 容易出现各种奇葩问题, 后面会有详细说明.

profile_add

最后, 把它下载下来, 双击添加到Xcode中.

profile_done

##配置到Xcode

xcode_profile_configure

至此, 一切OK!

可能遇到的错

如果你按上面的执行下来, 最后一编译应用发现还是不行, 报类似这种错,

cer_error_1

在XcodeOrganizer中也会显示出错:

cer_error_2

这错一般是由于证书不匹配, 要检查:

保证生成Profile时, 选择且只选择了一个证书;
保证Keyschain里没有重复的证书.

08/15/2015 20:16 下午 posted in  Xcode