无法识别的Selector发送给实例的快速排错法

How to solve

Unrecognized selector sent to instance…应该是iOS开发中比较常见的一种问题,但是一般这种错误的报错会指向main.m,一时间很难找到是哪个对象发生了问题。这时,我们可以下一个Debug断点。

在Xcode的菜单栏中选择Debug -> Breakpoints -> Create Symbolic Breakpoint…,在弹出的标签的Symbol栏填入

-[NSObject(NSObject) doesNotRecognizeSelector:]

这时再次运行就会发现真正出现问题的地方了。

Example

今天在给WebView的一个属性赋值的时候一直报错。如果初始化WebView时不涉及这个属性的话,则不会出现问题。虽然我已经定位了问题的所在,但是如何解决依然没有头绪。添加断点后立刻发现,给这个属性传入的对象的某个属性出现了循环引用,改成weak后解决了问题。

ZHRMoe Studio, 2016.

Swift中引入警告的方法

Objective-C引入警告

在OC中,如果我们需要引入警告的时候,一般会写作如下的形式:

 Objective-C Code 
    NSInteger a = 1;

#warning This is a warning.

    NSString *str = @
"This is a string.";


这时,Xcode在编译的时候会在对应行上加上警告,这对于提醒程序员哪里还需要进行后续操作或者注意事项非常有帮助。不过,Swift中暂时还没有官方的方法加入这种警告(据说苹果已经准备引入了),但是我们可以通过骗编译器的方式写警告信息。

Swift引入警告

以下方法来自Stack Overflow中的一篇回答,地址:http://stackoverflow.com/questions/24183812/swift-warning-equivalent

原文的引用信息:

To envoke this functionality with Swift in Xcode today however, you could do the following as outlined by Ben Dodson & Jeffrey Sambells.

首先在项目设置中选择Project Settings -> Build Phases -> "+" -> New Run Script Phase

然后填写如下的代码:

 Srcroot Code 
TAGS=“Warning:"

echo "searching ${SRCROOT} for ${TAGS}"

find 
"${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"


此时,每当你在代码中写以Warning:开头的注释时,系统都会自动加上警告。样例如下:

 Swift Code 
    var a: Int = 1

//Warning: This is a warning.

    var str: String = "This is a string."


如果你想改成TODO:或是其他字符,可以修改代码中TAGS后的值。


ZHRMoe Studio, 2015.

键值编码机制 Key-Value Coding Fundamentals

这篇文章描述了KVC(键值编码)的基本机制。

官方文档

Key-Value Coding Fundamentals

键和键的路径

键是一个描述对象具体属性的字符。原则上,一个键与一个寄存器方法或者随着接收值而变化的实例的名称相符。这些键必须使用ASCII编码,以小写字母开头,同时不包含空格。

以下是一些可以使用的键:payee, openingBalance, transactions 以及 amount.

键的路径是一个用来遍历指定对象的属性序列,这个序列是以点分隔的字符串。这个键里的第一个属性是与接收者相关的,同时随后的每一个键都相对前一个属性的值相关。

例如,键的路径 address.street 可以从接收者对象得到 address 属性当中的值,然后可以查到与 address 对象相关联的 street 属性。

利用KVC机制获取属性的值

valueForKey 方法返回与接收者相关的特定键值。如果相对这个键没有变化的寄存器和实例,那么接收者会给自己发动一条 valueForUndefinedKey: 的消息。默认的 valueForUndefinedKey 的实现会抛出一个NSUndefinedKeyException ,它的子类可以重载这个行为。

相似地, valueForKeyPath: 方法也返回与接收者相关的特定键值。任何在键路径上的非键值编码的键只要符合条件,其对象就会获取一个 valueForUndefinedKey 的信息。

dictionaryWithValuesForKeys: 方法可以获取一个与接收者相关的键值数组。返回的 NSDictionary 包含所有在数组当中的键的值。

阅读笔记:一些泛型类,例如 NSArray, NSSet, 以及 NSDictionary 是不能包含 nil 作为其中的值的。其实,你可以用一个 NSNull 来代替 nilNSNull 可以作为一个单一的实例出现在类属性里用来代替 nildictionaryWithValuesForKeys:setValuesForKeysWithDictionary: 这两个方法的实现可以自动地把 null 转化成 NSNull ,所以你也不必要在类里明确地测试 NSNull 的转化了。

当一个值是返回给一个包含了多对多关系的键,而且这个键并不是路径中的最后一个键时,这个返回的值就会是包含了所有在多对多的键右侧的键的所有值的集合。例如,获取路径 transactions.payee 的值会返回一个包括了所有交易中所有收款人对象的数组。这个规则对多维数组也适用,路径 accounts.transactions.payee 将会返回所有账户的所有交易的所有收款人对象的数组。(译者注:路径中的单词均表达了字面意思。account:账户,transaction:交易,payee:收款人)

利用KVC设置属性的值

setValue:forKey: 方法可以给指定的与接收者相关的键赋值为给定的数值。 setValue:forKey: 方法的实现可以解开 NSValue 的对象,声明纯数据和结构并分配给属性。参考文档 Scalar and Structure Support 可以深入了解关于包装和解开包装的语法和用法。

如果指定的键并不存在,接收者就会被发送 setValue:forUndefinedKey: 的消息。默认的 setValue:forUndefinedKey: 实现会抛出一个 NSUndefinedKeyException。同时,子类可以重载这个方法,用默认的方法处理请求。

setValue:forKeyPath: 和上面的方法做法相似,但是它允许处理一个键的路径甚至是单独一个键。

最后, setValuesForKeysWithDictionary: 可以把给定的字典赋值给接收者的属性,利用字典的键来辨别属性。默认的实现对每个键值对引用 setValue:forKey: ,然后按要求用 NSNull 代替 nil

你还需要额外考虑一件事,在你给一个没有对象的属性赋值为 nil 会发生什么呢?这种情况下,接收者会给自己发送 setNilValueForKey: 的消息。默认的 setNilValueForKey: 实现会抛出一个 NSInvalidArgumentException。你的应用可以重载这个方法来替代默认值或者标记的值,然后用新的值调用 setValue:forKey:

KVC & 点的语法

Objective-C中的点语法和KVC是相互垂直的两种用法。无论是否使用点句法,你都可以使用KVC,也无论你是否使用KVC,你都可以使用点句法。虽然点句法随时都可以使用,但是用法有不同。(译者注:其实就是指Objective-C原生支持点句法,不过官方文档写得真是太有文采了,实在做不到原意翻译)在KVC里,点句法是用来界定键值当中的元素的。一定记住,当你用点句法调用一个属性的时候,你调用的是接收者的基本寄存器方法。

你可以使用键值的方法调用一个属性,下面给出了一个定义类的样例:

@interface MyClass
@property NSString *stringProperty;
@property NSInteger integerProperty;
@property MyClass *linkedInstance;
@end

你可以通过KVC在一个实例中调用它的属性:

MyClass *myInstance = [[MyClass alloc] init];
NSString *string = [myInstance valueForKey:@"stringProperty"];
[myInstance setValue:@2 forKey:@"integerProperty"];

为了分辨点句法在KVC和原生语法之间的区别,你可以参考以下代码:

MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
myInstance.linkedInstance.integerProperty = 2;

以下代码和上例等价:

MyClass *anotherInstance = [[MyClass alloc] init];
myInstance.linkedInstance = anotherInstance;
[myInstance setValue:@2 forKeyPath:@"linkedInstance.integerProperty"];