Ruby和Objective-C似乎是两种不同的语言:一种是动态的,另一种是静态的;一种是解释型语言,另一种是编译型语言;一个有简洁的语法,另一个有点冗长。从优雅的角度来看,Ruby似乎给了我们更自由的编程体验,所以很多人都放弃了Objective-C。但这是一个不幸的笑话。Objective-C并不是某些人认为的紧身衣,它像Ruby一样受到Smalltalk的影响,并且具有许多Ruby开发人员喜欢的语言特性——动态方法查找、鸭子类型、开放类和常见案例这些特性诸如高度可变的运行时也存在于Objective-C中,甚至是那些鲜为人知的技术。Objective-C的这些特性都归功于它的IDE和编译器,但也正是因为它们,你才不能自由地编写代码。但是等等,你怎么能说Objective-C是一种动态语言呢?不是基于C语言吗?您可以在Objective-C代码中包含任何C或C++代码,但这并不意味着Objective-C仅限于C或C++代码。Objective-C中所有有趣的类操作和对象自省都来自称为Objective-C运行时的东西。这个Objective-C运行时相当于Ruby解释器。它包含强大的元编程所需的所有重要功能。事实上,C语言和Ruby一样支持这些特性。使用property_getAttributes或method_getImplementation方法将选择器映射到具体的实现(一个选择器处理一个方法),判断对象是否可以响应选择器,然后遍历子类Tree。在Objective-C的众多方法中,最重要的是objc_msgSend方法,它促进了应用程序中每条消息的发送。消息传递Smalltalk是第一个名副其实的面向对象语言。它用“从一个对象向另一个对象发送信息”的新概念取代了“调用函数”的旧概念,对后来的语言发展产生了很大的影响。产生了深远的影响。你可以在Ruby中发送消息:receiver.the_messageargumentObjective-C的实现方式与Ruby相同:这些消息实现了鸭子类型,也就是说关注的重点不是这个对象的类型或者类本身,而是这个对象是否能响应消息。发送消息真的很酷,但它的价值只有在消息传输数据时才会最大化:receiver.send(:the_message,argument)and[receiverperformSelector:@selector(theMessage:)withObject:argument];正如Ruby中的方法需要符号支持一样,Objective-C中的选择器也需要字符串支持。(在Objective-C中没有符号。)这允许您动态地使用方法。您甚至可以使用NSSelectorFromString方法从字符串创建选择器并在对象上执行它。同样,我们也可以在Ruby中创建一个字符串或符号,并将其传递给Object#send方法。当然,不管你使用什么语言,一旦你向一个无法处理消息的对象发送消息,默认情况下就会抛出异常,应用程序就会崩溃。当你想在调用方法之前判断对象是否可以执行该方法时,可以在Ruby中使用respond_to吗?检查方法:ifreceiver.respond_to?:the_messagereceiver.the_messageargumentendObjective-C有一个类似的方法:if([receiverrespondsToSelector:@selector(theMessage:)]){[receivertheMessage:someThing];}如果你想添加方法,它会变得更加动态你想要一个不能修改的类(比如系统类),那么Objective-C中的类别不会让你失望——很像Ruby中的“开放类”。例如,如果想在NSArray类中加入Rails中的to_sentence方法,我们只需要扩展NSArray类:@interfaceNSArray(ToSentence)-(NSString*)toSentence;@end@implementationNSArray(ToSentence)-(NSString*)toSentence{if(self.count==0)return@"";if(self.count==1)return[selflastObject];NSArray*allButLastObject=[selfsubarrayWithRange:NSMakeRange(0,self.count-1)];NSString*result=[allButLastObjectcomponentsJoinedByString:@","];BOOLshowComma=self.count>2;result=[resultstringByAppendingFormat:@"%@and",showComma?@",":@""];result=[resultstringByAppendingString:[selflastObject]];returnresult;}@endCategory在编译时向程序添加方法——让我们在运行时动态捕获它们怎么样?有些消息可以嵌套数据,例如Rails的动态查找器。Ruby首先通过覆盖method_missing和respond_to这两个方法来匹配模式,然后将新方法的定义添加到这个对象中。Objective-C中的过程类似,但是我们没有重写doesNotRecognizeSelector:方法(相当于Ruby中的method_missing方法),而是在resolveClassMethod:方法中捕获Category添加的方法。假设我们有一个名为+findWhere:equals:的类方法,它可以获取属性的名称和值,那么很容易通过正则表达式找到属性的名称,并通过块注册选择器。+(BOOL)resolveClassMethod:(SEL)sel{NSString*selectorName=NSStringFromSelector(sel);NSRegularExpression*regex=[NSRegularExpressionregularExpressionWithPattern:@"^findWhere(\\w+)Equals:$"选项:0error:nil];NSTextCheckingResult*result=[regexfirstMatchInString:selectorNameoptions:0range:NSMakeRange(0,selectorName.length)];if(result){NSRangepropertyNameRange=[resultrangeAtIndex:1];NSString*propertyName=[selectorNamesubstringWithRange:propertyNameRange];IMImplementation=imp_implementationWithBlock((id)^idself,idarg1){return[selffindWhere:propertyNameequals:arg1];});ClassmetaClass=object_getClass(self);class_addMethod(metaClass,sel,implementation,"@@:@@");returnYES;}return[superresolveClassMethod:sel];这个方法的好处是我们不需要重写respondsToSelector:,因为类中注册的每个选择器都会调用这个方法。现在让我们调用[RGSongfindWhereTitleEquals:@"Mercy"]。第一次调用findWhereTitleEquals:的时候,runtime不知道这个方法,所以会调用resolveClassMethod:,然后我们动态添加findWhereTitleEquals:方法,第二次调用findWhereTitleEquals:的时候,因为已经添加了,所以resolveClassMethod:将不再被调用。这里有一些其他的方法来实现捕获动态方法。您可以将消息传递给不同的对象或通过覆盖resolveClassMethod:和resolveInstanceMethod:方法(如上)接管“调用”,并在消息传递之前执行您希望消息执行的任何操作。这些方法会导致运行成本的增加,尤其是在-forwardInvocation:时会达到顶峰,这种情况下我们必须实例化一个对象来执行它们。-forwardInvocation:方法中默认调用doesNotRecognizeSelector方法,导致应用频繁异常或崩溃。Introspection动态方法解析不仅仅是对Ruby、Objective-C等语言的技术支持。您还可以在运行时以有趣的方式操作这些对象。就像在Ruby中调用MyClass#instance_methods一样,您可以在Objective-C中调用class_copyMethodList([MyClassclass],&numberOfMethods)来获取对象中的方法列表。您还可以通过class_copyPropertyList方法获取类中的属性列表,这可以在您的模型中实现令人难以置信的内省。例如,在这个RapGenius应用程序中,我们使用此功能将JSON中的字典映射到本机对象。(如果你非常喜欢Ruby中的mixins,Objective-C强大的动态支持也可以达到同样的效果。VladimirMitrovic有一个名为Objective-Mixin的库,它可以将一个类中的实现复制到另一个类中。)你现在可以使用的所有动态工具都可以用来创建类似CoreData的东西,这是一个有点像ActiveRecord的持久化对象图。在CoreData中,关系是“有缺陷的”,这意味着它们仅在被其他对象访问时才加载。每个属性的访问器和修改器都在运行时重写(使用我们上面提到的动态方法解析)。如果我们访问一个尚未加载的对象,框架会动态地从持久存储中加载该对象并返回它。它保持低内存利用率,避免在获取任何对象时必须将实体对象图加载到内存中的情况。当调用CoreData实体上的更改器时,系统将标记该对象以进行清理,而无需重写每个属性getter和setter。这就是元程序,羡慕呀!什么是编译器?显然,Objective-C和Ruby不是同一种语言,迄今为止最大的区别在于Objective-C是一种编译型语言。这是最需要注意这些技术的地方。编译时,编译器会先判断你的应用程序使用的每个选择器是否在应用程序中。如果你正在处理的对象有类型信息,编译器也会检查以确保在头文件中声明了选择器。这样做是为了防止在对象中调用未声明的选择器。有一些方法可以绕过这些讨厌的限制,包括关闭相关的编译警告。这是练习元编程Objective-C***的练习。您可以通过将选择器类型存储为未知类型或ID,从对象中删除此类型信息。由于编译器不知道这种类型,它只能假设您的程序可以接受发送给它的任何消息(假设这些消息在应用程序的其他地方声明并且相关的编译标志已打开)。好的建议:如果我们关闭编译器标志并将对象保存为id类型,那将是非常危险的!事实上,Objective-C中最好的东西之一就是编译器(是的,比元程序更好)。类型检查确保我们更快地编写和重构代码,并且我们在编程时犯的错误更少。由于没有人会关闭这些警告,因此很难与这些id类型共享您的代码。大多数Objective-C开发人员仍然更喜欢使用更强大的类型而不是元程序。事实证明,Objective-C更受约束——但因为编译器可以提高更多的安全性和速度,我们只能选择这种方式并承担后果。再一次,事实告诉我们这些语言非常相似,Ruby开发人员应该喜欢Objective-C,即使那些方括号让我们望而却步。原文链接:SoroushKhanlou译文:MoriSunshine译文链接:http://blog.jobbole.com/63628/
