iOS __attribute__那点小事
今人不见古时月,今月曾经照古人
关于 attribute
__attribute__
机制虽然是GNU C
的一大特色,但是在iOS
中却也被广泛使用,随便举个例子
NS_CLASS_DEPRECATED_IOS(2_0, 9_0, "UIAlertView is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleAlert instead") __TVOS_PROHIBITED
#define NS_CLASS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) NS_CLASS_DEPRECATED(NA, NA, _iosIntro, _iosDep, __VA_ARGS__)
#define NS_CLASS_DEPRECATED(_mac, _macDep, _ios, _iosDep, ...) __attribute__((visibility("default"))) NS_DEPRECATED(_mac, _macDep, _ios, _iosDep, __VA_ARGS__)
上面是对UIAlertView
的一个说明,大意是指UIAlertView
在iOS 2.0
被引入,在iOS 9
废弃,但是废弃并不代表不能用,只是意味着我们应该开始考虑将相关代码迁移到新的API
上面去了。看着这么多宏和其中的参数,有点懵,下面就来看看一些常用的属性。
attribute 使用格式
__attribute__(相关属性)
常用属性
-
availability
:使用版本、平台情况及相关说明信息 -
unavailable
:告诉编译器某方法不可用,如果强行调用编译器会提示错误 -
nonnull
:编译器对函数参数进行检查,不能为null
,参数类型必须为指针类型(包括对象) -
cleanup
:用于修饰一个变量,在它的作用域结束时可以自动执行一个指定的方法(用处挺大的) -
objc_root_class
:顾名思义,表示我们这个类是一个基类 -
objc_designated_initializer
:指定类的初始化方法,并不是对使用者,而是对类内部的实现(具体可以看后面) -
objc_requires_super
:表示子类在重新父类的方法的时候,必须先调用super
方法,否则会有警告 -
objc_subclassing_restricted
:表示该类不能被继承 -
objc_runtime_name
:将类的名字在编译的时候改成另外的名字
具体使用
availability:
关于availability
,我们先来看几个参数
-
introduced
:引进的版本 -
deprecated
:废弃的版本,还能使用,并没有移除,而是提醒用户迁移到其他API
-
obsoleted
:移除的版本,不能再使用 -
unavailable
:那些平台不能用 -
message
:额外提示信息,比如迁移到某某API
除了上面的参数外,需要指定支持或者不支持平台的时候,有两个值ios
和macosx
使用
- (void)testAvailability __attribute__((availability(ios,introduced=2_0,deprecated=7_0,obsoleted=11_0,message="将在ios11进行移除哦")));
availability.png
该函数定义的时候,声明了支持平台、引进版本和废弃版本、移除版本,后面的message
为提示信息,由于当前我所支持的版本为iOS8
,所以会有警告,当改成deprecated=9_0
,警告就会消失
下面在看一个关于unavailable
的使用
- (void)testUnavailableAvailability __attribute__((availability(ios,unavailable,message="iOS平台你不能用的")));
unavailbaleAvai.png
上面例子中,我们定义在ios
平台不能用,如果强行使用就会报错,由于已经用了unavailable
所以其它引进版本废弃版本移除版本参数均不能再添加到后面(可以试试),会在声明函数的时候有警告"unavailable availability overrides all other availability infomation"
unavailable
在系统中也有与其相关的宏定义
#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
#else
#define UNAVAILABLE_ATTRIBUTE
#endif
#if !defined(NS_UNAVAILABLE)
#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
#endif
我们可以直接进行使用,比如下面这样
@interface TestModel : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithSex:(NSString *)sex;
@end
bunengyong.png
当然我们也可以直接使用unavailable
,并且可以在后面追加一些参数提示
@interface TestModel : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithSex:(NSString *)sex;
- (void)testModelAdd __attribute__((unavailable("这个方法无效了,你不能调用了")));
@end
huangefangfa.png
关于unavailable
的使用,用处还是挺大的,比如我们自己建的一些类中,必须要让别人调用自己指定的初始化方法的时候,可以通过该方法来实现。
nonnull
指定参数不能为空,参数必须为制作类型,我们对上面的TestModel
进行改造,如下
- (instancetype)initWithSex:(NSString *)sex address:(NSString *)address age:(NSInteger)age __attribute__((nonnull(1,2,3)));
这里我们根据1
、2
、3
来指定三个参数不能为空,但是我们最后一个参数,并不是指针类型,那是否有问题呢?来看看
根据提示就知道,参数必须为指针类型
改成
- (instancetype)initWithSex:(NSString *)sex address:(NSString *)address age:(NSInteger)age __attribute__((nonnull(1,2)));
表示前两个参数不能为空,使用情况
nonnul1.png当我们传入参数为空的时候,就会有警告信息
cleanup
前面我们说了用这个可以在作用域结束的时候执行指定的方法,下面我们就来仔细看看
其使用方式为 __attribute__((cleanup(...)))
,先看个例子:
{
...
NSString *testCleanString __attribute__((cleanup(printTestString))) = @"测试一下";
}
void printTestString(NSString **string){
NSLog(@" 打印信息string:%@",*string);
}
//输出结果为
打印信息string:测试一下
其中 … 为我们定义的函数,注意这里的函数为c
函数,且函数必须要带参数,而且参数为变量的地址,比如上面中的 string
,我们传的是**string
,这里的作用域为{}
,那么还有那些作用域呢?比如:return
,break
,goto
等。
当然我们还可以在自定义类型中进行使用,这一定会很有用
在上面TestModel
类中,我们增加两个属性
@interface TestModel : NSObject
@property (nonatomic,copy) NSString *sex;
@property (nonatomic,copy) NSString *address;
{
...
TestModel *testModel1 __attribute__((cleanup(testModelCleanUp))) = [[TestModel alloc] initWithSex:@"男" address:@"四川成都" age:1];
}
static void testModelCleanUp(__strong TestModel ** model){
// NSLog(@" 打印信息+++++%s",*model.*sex);
NSLog(@" 打印信息model:%@",*model);
}
//输出结果
打印信息model:<TestModel: 0x608000030b40>
此处有个问题,我本来想通过该方法来输出model
里面的相关信息,然而尝试各种方法都没有实现(如果各位大神有办法,还望告知),为了实现目的,了解到将block
作为参数传递进去,敬请看用法
{
...
__strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^{
NSLog(@" 打印信息:%@ ++ %@",testModel1.sex,testModel1.address);
};
}
static void blockCleanUp(__strong void(^*block)(void)) {
if (*block) {
(*block)();
}
}
//输出结果
打印信息string:测试一下
通过block
终于可以输出想要的信息了,可见block
带来的方便,说了这么多,那么cleanup
到底什么时候用最好呢?比如有下面一个场景,定义一个对象,后面执行了许多其他操作,在这么多操作完成后需要对该对象进行处理,通常的办法就是在最后再对该对象进行处理,那么通过cleanup
的话,我们就可以将改方法放到前面来执行,这样不管后面写多少,都不用担心啦...当然这这是其中一个简单的,其他更复杂的运用,估计只要在实践中实现了。
最后,还有个问题,就是当我们在同一个作用域有多个cleanup
变量的时候,执行顺序又怎么样的呢?
{
NSString *testCleanString __attribute__((cleanup(printTestString))) = @"测试一下";
TestModel *testModel1 __attribute__((cleanup(testModelCleanUp))) = [[TestModel alloc] initWithSex:@"男" address:@"四川成都" age:1];
// [testModel1 testModelAdd];
__strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^{
NSLog(@" 打印信息:%@ ++ %@",testModel1.sex,testModel1.address);
};
}
//输出结果
2017-08-14 17:12:38.810 Demo__For__attribute__[96180:6044949] 打印信息:男 ++ 四川成都
2017-08-14 17:12:38.810 Demo__For__attribute__[96180:6044949] 打印信息model:<TestModel: 0x608000030b40>
2017-08-14 17:12:38.811 Demo__For__attribute__[96180:6044949] 打印信息string:测试一下
这下就全明白了吧,没错就是先入后出的的顺序
objc_root_class
这个表示该类是基类,比如NSObject
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
objc_designated_initializer
在《编写高质量iOS与OS X代码的52个有效方法》中有这么一节,提供全能初始化方法,当时是以NSDate
来说明的,其初始化方法有如下
- (instancetype)init NS_DESIGNATED_INITIALIZER;//1
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;//2
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;//3
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;//4
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;//5
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;//6
我们抛开1
和3
方法不看,仔细查看2、4、5、6
可以发现,2
比其它方法多了一个NS_DESIGNATED_INITIALIZER
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
没错,查看其宏定义,可以看到实际上就是objc_designated_initializer
属性,方法2
就是我们NSDate
类的一个全能初始化方法,也就是说,其余的初始化方法在内部实现的时候都要调用该方法,切记是在类的内部实现,这样的话,我们就只需要在全能初始化方法中,进行数据存储,当我们底层数据存储机制改变的时候,只需要修改全能方法中的代码就可以了,而不用更改其他初始化方法。
下面我们就来看看objc_designated_initializer
使用时需要注意的问题
我们先定义两个类Anima
和Dog
,并在其中加入一些初始化函数
@interface Anima : NSObject
//指定为全能初始化函数
- (instancetype)initWithName:(NSString *)name __attribute__((objc_designated_initializer));
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(NSString *)sex;
@end
#import "Anima.h"
@implementation Anima
//1
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
}
return self;
}
//2
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
{
self = [self initWithName:name];
if (self) {
}
return self;
}
//3
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(NSString *)sex
{
// self = [self initWithName:name];
// self = [self initWithName:name age:age];
self = [super init];
if (self) {
}
return self;
}
@end
#import "Anima.h"
@interface Dog : Anima
- (instancetype)initWithKind:(NSString *)kind __attribute__((objc_designated_initializer));
@end
#import "Dog.h"
@implementation Dog
- (instancetype)initWithKind:(NSString *)kind
{
self = [super initWithName:@""];
if (self) {
}
return self;
}
@end
objc_designated_initializer
遵守原则
- 如果该类有
objc_designated_initializer
的初始化方法,那么它必须覆盖实现父类的objc_designated_initializer
方法,否则会有如下警告
Anima
继承自NSObject
,而init
为NSObject
的objc_designated_initializer
方法,所以会有警告信息,Dog
一样的道理
- 如果初始化方法拥有
objc_designated_initializer
属性,那么在使用的时候必须调用父类的objc_designated_initializer
方法
在Dog
类中的objc_designated_initializer
方法initWithKind
中,我们没有调用父类的objc_designated_initializer
的方法,就得到两条警告信息
- 如果不是
objc_designated_initializer
的初始化方法,但是该类拥有objc_designated_initializer
初始化方法,那么,必须调用该类的objc_designated_initializer
方法或者非objc_designated_initializer
方法,不可以调用父类的任何初始化方法
这里我针对方法3
进行多次修改来测试我们需要的效果,仔细看,我们会发现[self init]
和[super init]
两个方法,[self init]
没有警告,为什么没有呢?因为这里实际上是在执行重新的父类的init
方法,而[super init]
是完全在执行父类的方法。而3
中的最后两种方法也没有警告,正是遵循的调用该类的调用该类的objc_designated_initializer
方法和非objc_designated_initializer
方法
objc_requires_super
此属性表示子类在重新父类方法的时候必须先执行父类的super
方法,否则会有警告,这在我们实际工作中,很有帮助
例子
@interface Person : NSObject
//表示子类在重写的时候 必须先调用父类的方法
- (void)work __attribute__((objc_requires_super));
@end
@implementation Person
- (void)work
{
}
@end
#import "Person.h"
@interface Student : Person
@end
@implementation Student
- (void)work
{
}
@end
mustsuper.png
从图中我们可以看到,如果不实现super
方法,则会有警告信息
objc_subclassing_restricted
有时候,我们如果不想让我们的类被其它类继承,这个时候,我们就可以用该属性来实现。我们对person
类修改下
__attribute__((objc_subclassing_restricted))
@interface Person : NSObject
//表示子类在重写的时候 必须先调用父类的方法
- (void)work __attribute__((objc_requires_super));
@end
#import "Person.h"
@interface Student : Person
@end
noreject.png
从图中我们可以看到,如果我们对其进行强制继承,则会报错
objc_runtime_name
有时候我们不希望比人能看到我们的类的真实名字,那么就可以通过该属性来做代码混淆
__attribute__((objc_runtime_name("GLPerson")))
@interface Person : NSObject
//表示子类在重写的时候 必须先调用父类的方法
- (void)work __attribute__((objc_requires_super));
@end
NSLog(@" 打印信息:%@",NSStringFromClass([Person class]));
//输出结果
打印信息:GLPerson
关于__attribute__
还有许多其它属性,这里只是一些我认为常用的,通过使用这些属性,代码质量瞬间....如果有什么错误的地方,还望各位多多指点!