移动安全-iOS(二)

categoery的安全隐患 Objective-C的category机制允许你在运行时给享有的类添加新的方法实现,而且不用重新编译。category可以添加和提替换类的方法实现,然后应用在代码的任意位置,因此,你不用重新实现这个类就能轻松修改它的行为

  • 可以减少单个文件的体积
  • 可以把不同的功能组织到不同的category里
  • 可以由多个开发者共同完成一个类
  • 可以按需加载想要的category 等

category的实现原理

我们不主动引入 Category 的头文件,Category 中的方法都会被添加进主类中。我们可以通过 - performSelector: 等方式 对 Category 中的相应方法进行调用

category完成后系统主要做了两件事

  • 将 Category 和它的主类(或元类)注册到哈希表中;
  • 如果主类(或元类)已实现,那么重建它的方法列表。

在这里分了两种情况进行处理:Category 中的实例方法和属性被整合到主类中;而类方法则被整合到元类中,另外,对协议的处理比较特殊,Category中的协议被同时整合到了主类和元类中。

注意到,不管是哪种情况,最终都是通过调用static void remethodizeClass(Class cls) 函数来重新整理类的数据的

category结构体

structcategory_t {
//类的名字(name)
constchar*name;
//类(cls)
classref_t cls;
//category中所有给类添加的实例方法的列表(instanceMethods)
struct method_list_t *instanceMethods; 
//category中所有添加的类方法的列表(classMethods)
structmethod_list_t *classMethods;
//category实现的所有协议的列表(protocols)
structprotocol_list_t *protocols; 
//category中添加的所有属性(instanceProperties)
structproperty_list_t *instanceProperties;
};
复制代码

从category的定义也可以看出category可以添加实例方法,类方法;可以遵守协议,添加属性;但无法添加实例变量。

注意:如果想为分类增加实例变量,可以通过运行时机制,关联对象的方式实现,ios关联对象不熟悉可以查看这篇文章 关联对象详解01 关联对象详解02

安全隐患

但你看过上面的内容我相信你会很容易理解下面这段话

category最严重的安全问题就是,它可以在任意位置侵入你的类

举个例子:那些只出现在第三方代码库关键功能组件的类,如(TLS)端点验证,都可以被任意第三方库或开原者无意之中覆盖重写。这种事情不常发生,但这种问题的确是存在的,如果开发者小心翼翼的验证完应用中的TLS/SSL的代码之后,开发者引入的第三方库覆盖了这些行为,就会彻底搞乱正常的业务代码,而且开发者不容易发现原因

通过上面的例子,你可以想到,你可以通过分类覆盖掉原有的方法,这是一把双刃剑剑,它会导致一些安全机制失效(类似上面的TLS验证),也可能导致一些难以预料的行为,苹果官方文档的说法如下 官网地址

Because the methods declared in a category are added to an existing class, you need to be very careful about method names. If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.

An application that works with a remote web service, for example, might need an easy way to encode a string of characters using Base64 encoding. It would make sense to define a category on NSString to add an instance method to return a Base64-encoded version of a string, so you might add a convenience method called base64EncodedString.

A problem arises if you link to another framework that also happens to define its own category on NSString, including its own method called base64EncodedString. At runtime, only one of the method implementations will “win” and be added to NSString, but which one is undefined.

Another problem can arise if you add convenience methods to Cocoa or Cocoa Touch classes that are then added to the original classes in later releases. The NSSortDescriptor class, for example, which describes how a collection of objects should be ordered, has always had an initWithKey:ascending: initialization method, but didn’t offer a corresponding class factory method under early OS X and iOS versions.

By convention, the class factory method should be called sortDescriptorWithKey:ascending:, so you might have chosen to add a category on NSSortDescriptor to provide this method for convenience. This would have worked as you’d expect under older versions of OS X and iOS, but with the release of Mac OS X version 10.6 and iOS 4.0, a sortDescriptorWithKey:ascending: method was added to the original NSSortDescriptor class, meaning you’d now end up with a naming clash when your application was run on these or later platforms.

In order to avoid undefined behavior, it’s best practice to add a prefix to method names in categories on framework classes, just like you should add a prefix to the names of your own classes. You might choose to use the same three letters you use for your class prefixes, but lowercase to follow the usual convention for method names, then an underscore, before the rest of the method name. For the NSSortDescriptor example, your own category might look like this:

翻译如下

因为在类别中声明的方法被添加到现有类中,所以您需要非常小心方法名称。

如果在类别中声明的方法的名称与原始类中的方法相同,或者在同一个类(或甚至是超类)上的另一个类别中的方法相同,则关于在哪个方法实现中使用哪个方法实现的行为是未定义的。运行。如果您使用具有自己类的类别,则不太可能成为问题,但在使用类别向标准Cocoa或Cocoa Touch类添加方法时可能会导致问题。

例如,使用远程Web服务的应用程序可能需要使用Base64编码对字符串进行编码的简单方法。定义类别NSString以添加实例方法以返回字符串的Base64编码版本是有意义的,因此您可以添加一个名为的方便方法base64EncodedString。

如果链接到另一个也恰好定义其自己的类别的框架NSString,包括其自己的方法,则会出现问题base64EncodedString。在运行时,只有一个方法实现将“获胜”并被添加到NSString,但哪一个是未定义的。

如果向Cocoa或Cocoa Touch类添加便捷方法,然后将其添加到更高版本的原始类中,则会出现另一个问题。该NSSortDescriptor课为例,介绍如何对象的集合,要责令,一直有一个initWithKey:ascending:初始化方法,但在早期的OS X和iOS版本并没有提供相应的类工厂方法。

按照惯例,应调用类工厂方法sortDescriptorWithKey:ascending:,因此您可能已选择添加类别NSSortDescriptor以提供此方法以方便使用。这可以像你在OS X和iOS的旧版本中所期望的那样工作,但是随着Mac OS X版本10.6和iOS 4.0的发布,sortDescriptorWithKey:ascending:原始NSSortDescriptor类中添加了一个方法,这意味着你现在最终会得到一个在这些或更高版本的平台上运行应用程序时命名冲突。

为了避免未定义的行为,最好在框架类的类别中为方法名称添加前缀,就像您应该为自己的类的名称添加前缀一样。您可以选择使用用于类前缀的相同三个字母,但小写字母按照通常的方法名称约定,然后使用下划线,在方法名称的其余部分之前。对于NSSortDescriptor例如,你自己的类别可能是这样的:

@interface NSSortDescriptor (XYZAdditions)
+ (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;
@end
复制代码

This means you can be sure that your method will be used at runtime. The ambiguity is removed because your code now looks like this:

文档存档开发人员搜索 用Objective-C编程 目录 下一个上一个 自定义现有类 对象应具有明确定义的任务,例如建模特定信息,显示可视内容或控制信息流。正如您已经看到的,类接口定义了其他人希望与对象交互以帮助其完成这些任务的方式。

有时,您可能会发现希望通过添加仅在某些情况下有用的行为来扩展现有类。例如,您可能会发现应用程序通常需要在可视界面中显示一串字符。而不是每次需要显示字符串时创建一些字符串绘图对象,如果有可能让NSString类本身能够在屏幕上绘制自己的字符,那将更有意义。

在这种情况下,将实用程序行为添加到原始的主类接口并不总是有意义的。例如,在应用程序中使用任何字符串对象的大多数情况下,不太可能需要绘制功能,并且在这种情况下NSString,您无法修改原始接口或实现,因为它是框架类。

此外,对现有类进行子类化可能没有意义,因为您可能希望您的绘图行为不仅可用于原始NSString类,还可用于该类的任何子类,例如NSMutableString。而且,虽然NSString在OS X和iOS上都可用,但每个平台的绘图代码需要不同,因此您需要在每个平台上使用不同的子类。

相反,Objective-C允许您通过类别和类扩展将自己的方法添加到现有类。

类别向现有类添加方法 如果您需要向现有类添加方法,可能需要添加功能以便在您自己的应用程序中更轻松地执行某些操作,最简单的方法是使用类别。

声明类别的语法使用@interface关键字,就像标准的Objective-C类描述一样,但不表示子类的任何继承。相反,它在括号中指定类别的名称,如下所示:

@interface ClassName(CategoryName)

@结束 即使您没有原始实现源代码(例如标准Cocoa或Cocoa Touch类),也可以为任何类声明类别。您在类别中声明的任何方法都可用于原始类的所有实例,以及原始类的任何子类。在运行时,类别添加的方法与原始类实现的方法之间没有区别。

考虑XYZPerson前面章节中的类,它具有人名和姓的属性。如果您正在编写一个记录保存应用程序,您可能会发现您经常需要按姓氏显示人员列表,如下所示:

Appleseed,约翰 能,简,简 史密斯,鲍勃 沃里克,凯特 lastName, firstName每次想要显示代码时,不必编写代码来生成合适的字符串,您可以在XYZPerson类中添加一个类别,如下所示:

#import“XYZPerson.h”

@interface XYZPerson(XYZPersonNameDisplayAdditions)

  • (NSString *)lastNameFirstNameString; @结束 在此示例中,XYZPersonNameDisplayAdditions类别声明了一个额外的方法来返回必要的字符串。

类别通常在单独的头文件中声明,并在单独的源代码文件中实现。在这种情况下XYZPerson,您可以在名为的头文件中声明类别XYZPerson+XYZPersonNameDisplayAdditions.h。

即使类别添加的任何方法都可用于类及其子类的所有实例,您还需要在任何源代码文件中导入类别头文件,否则您将使用其他方法,否则您将遇到编译器警告和错误。

类别实现可能如下所示:

#import“XYZPerson + XYZPersonNameDisplayAdditions.h”

@implementation XYZPerson(XYZPersonNameDisplayAdditions)

  • (NSString *)lastNameFirstNameString { return [NSString stringWithFormat:@“%@,%@”,self.lastName,self.firstName]; } @结束 一旦声明了类别并实现了方法,就可以从类的任何实例中使用这些方法,就像它们是原始类接口的一部分一样:

#import“XYZPerson + XYZPersonNameDisplayAdditions.h” @implementation SomeObject

  • (void)someMethod { XYZPerson * person = [[XYZPerson alloc] initWithFirstName:@“John” 名字:@ “Doe的”]; XYZShoutingPerson * shoutingPerson = [[XYZShoutingPerson alloc] initWithFirstName:@“Monica” 名字:@ “鲁滨逊”];

    NSLog(@“这两个人是%@和%@”, [person lastNameFirstNameString],[shoutingPerson lastNameFirstNameString]); } @结束 除了向现有类添加方法之外,您还可以使用类别在多个源代码文件中拆分复杂类的实现。例如,如果几何计算,颜色和渐变等特别复杂,您可以将自定义用户界面元素的绘图代码放在单独的文件中以实现其余部分。或者,您可以为类别方法提供不同的实现,具体取决于您是否正在为OS X或iOS编写应用程序。

类别可用于声明实例方法或类方法,但通常不适用于声明其他属性。在类别接口中包含属性声明是有效的语法,但是不可能在类别中声明其他实例变量。这意味着编译器不会合成任何实例变量,也不会合成任何属性访问器方法。您可以在类别实现中编写自己的访问器方法,但除非已由原始类存储,否则您将无法跟踪该属性的值。

将由新实例变量支持的传统属性添加到现有类的唯一方法是使用类扩展,如类扩展扩展内部实现中所述。

注意: Cocoa和Cocoa Touch包含一些主要框架类的各种类别。

本章简介中提到的字符串绘制功能实际上已经NSString由NSStringDrawingOS X 的类别提供,其中包括drawAtPoint:withAttributes:和drawInRect:withAttributes:方法。对于iOS,该UIStringDrawing类别包括诸如drawAtPoint:withFont:和的方法drawInRect:withFont:。

避免使用类别方法名称冲突 因为在类别中声明的方法被添加到现有类中,所以您需要非常小心方法名称。

如果在类别中声明的方法的名称与原始类中的方法相同,或者在同一个类(或甚至是超类)上的另一个类别中的方法相同,则关于在哪个方法实现中使用哪个方法实现的行为是未定义的。运行。如果您使用具有自己类的类别,则不太可能成为问题,但在使用类别向标准Cocoa或Cocoa Touch类添加方法时可能会导致问题。

例如,使用远程Web服务的应用程序可能需要使用Base64编码对字符串进行编码的简单方法。定义类别NSString以添加实例方法以返回字符串的Base64编码版本是有意义的,因此您可以添加一个名为的方便方法base64EncodedString。

如果链接到另一个也恰好定义其自己的类别的框架NSString,包括其自己的方法,则会出现问题base64EncodedString。在运行时,只有一个方法实现将“获胜”并被添加到NSString,但哪一个是未定义的。

如果向Cocoa或Cocoa Touch类添加便捷方法,然后将其添加到更高版本的原始类中,则会出现另一个问题。该NSSortDescriptor课为例,介绍如何对象的集合,要责令,一直有一个initWithKey:ascending:初始化方法,但在早期的OS X和iOS版本并没有提供相应的类工厂方法。

按照惯例,应调用类工厂方法sortDescriptorWithKey:ascending:,因此您可能已选择添加类别NSSortDescriptor以提供此方法以方便使用。这可以像你在OS X和iOS的旧版本中所期望的那样工作,但是随着Mac OS X版本10.6和iOS 4.0的发布,sortDescriptorWithKey:ascending:原始NSSortDescriptor类中添加了一个方法,这意味着你现在最终会得到一个在这些或更高版本的平台上运行应用程序时命名冲突。

为了避免未定义的行为,最好在框架类的类别中为方法名称添加前缀,就像您应该为自己的类的名称添加前缀一样。您可以选择使用用于类前缀的相同三个字母,但小写字母按照通常的方法名称约定,然后使用下划线,在方法名称的其余部分之前。对于NSSortDescriptor例如,你自己的类别可能是这样的:

@interface NSSortDescriptor(XYZAdditions)
+(id)xyz_sortDescriptorWithKey :( NSString *)键升序:(BOOL)升序;
复制代码

@结束 这意味着您可以确保在运行时使用您的方法。删除歧义,因为您的代码现在看起来像这样:

为了使XYZPerson类能够在内部更改属性,在类扩展中重新声明属性是有意义的,该类扩展在类的实现文件的顶部定义:

 NSSortDescriptor *descriptor =
               [NSSortDescriptor xyz_sortDescriptorWithKey:@"name" ascending:YES];

复制代码

由官方文档可以得出 多个category会定义或者重写相同的方法,但最终只有一个方法被调用,而这个被调用的方法是不确定的

转载于:https://juejin.im/post/5c877b5c5188257b5b2caee5

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值