# Building Objective-C static libraries with categories

Q: How do I fix "selector not recognized" runtime exceptions when trying to use category methods from a static library?

今天在浏览 YYKit 库时，一个宏定义引起了我的注意：YYSYNTH_DUMMY_CLASS,从字面意思看是定义了一个不起任何作用的类。相关描述如下：

/**
Add this macro before each category implementation, so we don't have to use
contain categories and no classes.
*******************************************************************************
Example:
*/
#ifndef YYSYNTH_DUMMY_CLASS
#define YYSYNTH_DUMMY_CLASS(_name_) \
@interface YYSYNTH_DUMMY_CLASS_ ## _name_ : NSObject @end \
@implementation YYSYNTH_DUMMY_CLASS_ ## _name_ @end
#endif

根据 YYKit 作者的描述，在分类中加一个这样的宏定义可以避免一些复杂操作，具体原理作者并没有解释，只是给了一个苹果官方开发文档的连接。这个链接可能因为官方整理文档的原因，URL路径已经失效了。但是在官网搜索 URL 中的关键字找到了这篇资料。根据这篇资料在这里深入了解一下其中的原理和如何解决这类问题。

A: If you're seeing a "selector not recognized" runtime exception when calling a category method that is implemented in a static library, you are hitting the link-time build issue described here, and need to add the -ObjC linker flag to your project, by following these steps:

1. In Xcode, choose View > Navigators > Show Project Navigator, or press ⌘1.
1. Select your project under the PROJECT heading in the Project Navigator, then select the Build Settings tab.
2. Scroll down to the Other Linker Flags build setting under the Linking collection, or type "Other Linker Flags" into the search bar.
3. Set the value of the Other Linker Flags build setting to \$(OTHER_LDFLAGS) -ObjC.

Figure 1: Modifying the Other Linker Flags build setting.

### Troubleshooting

If adding the -ObjC flag isn't fixing the problem, double check that a conflicting Target build setting is not overriding it, by following the above steps, but selecting the current target under "TARGETS" in step 2, instead of the project.

### 解决问题

如果添加-ObjC flag没能解决这个问题，检查一下确保之前的操作没有与 Target build setting 冲突而使其无效。按照上面的步骤，在第二步时选择 "TARGETS"下的target，而不是选择 project。

### Other Causes of selector not recognized Exceptions

The most common causes of a "selector not recognized" exception are:

### No Such Method

The method really does not exist. Check your spelling. Check documentation to verify that the method exists on the version of the operating system your app is using.

### Memory Management

Your app is trying to use an object after it has been deallocated, use the Zombies instrument to debug this kind of problem. You are seeing "selector not recognized" because the memory has been re-allocated as a different kind of object.

### 导致selector not recognized Exceptions异常的其他原因

导致selector not recognized Exceptions异常的原因通常是：

#### 方法不存在

方法实际上是不存在的。检查你的拼写是否有误。查阅文档，确保你调用的方法在当前版本的操作系统内存在。

#### 内存管理

在对象已释放后，你的应用又尝试去访问它，原先存放对象的内存已被重新分配给其他类的对象，所以当你再次访问原先的对象时，就会发生 "selector not recognized"异常。使用Zombies instrument工具来debug这类问题。（这一段文档不通顺，稍作整理）

## What causes those exceptions?

An impedance mismatch between UNIX static libraries and the dynamic nature of Objective-C can cause category methods in static libraries to not be linked into an app, resulting in "selector not recognized" exceptions when the methods aren't found at runtime.

## 导致这些异常的原因

When a C program is compiled, each "source file" is turned into an "object file" that contains executable functions and static data. The linker glues these object files together into a final executable. That executable is eventually bundled into an app by Xcode.

### 链接器

When a source file uses something (like a function) defined in another file, then an undefined symbol is written into the object file, to "stand in" for the missing thing. The linker resolves these symbols by pulling in the object files that include definitions of undefined symbols when building the final executable.

For example, if main.c uses the function foo(), where foo is defined in another file, B.c, then the object file main.o will have an unresolved symbol for foo(), and B.o will include an implementation of foo(). At link time, B.o will be brought into the final executable, so that the code in main.o now references the implementation of foo() defined in B.o.

A UNIX static library is just a collection of object files. Normally the linker only pulls in an object file from a static library if doing so would resolve some undefined symbol. Not pulling in all object files reduces the size of the final executable.

### Objective-C

The dynamic nature of Objective-C complicates things slightly. Because the code that implements a method is not determined until the method is actually called, Objective-C does not define linker symbols for methods. Linker symbols are only defined for classes.

### Objective-C

O-C 的动态特性使这个过程略微复杂了一些。因为实现一个方法的代码直到这个方法被调用时才会被确定下来。Objective-C 并不为方法定义链接符号，链接符号只为 进行定义。

For example, if main.m includes the code [[FooClass alloc] initWithBar:nil]; then main.o will contain an undefined symbol for FooClass, but no linker symbols for the -initWithBar: method will be in main.o.

Since categories are a collection of methods, using a category's method does not generate an undefined symbol. This means the linker does not know to load an object file defining the category, if the class itself is already defined. This causes the same "selector not recognized" runtime exception you would see for any unimplemented method.

YYKit 中的这个宏定义在 分类之前加了一个空类，这样.m文件就包含了类，这样生成的 .o文件就会被链接器打包进最终的可执行文件，从而避免了 "selector not recognized"异常的出现。