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的一个说明,大意是指UIAlertViewiOS 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

除了上面的参数外,需要指定支持或者不支持平台的时候,有两个值iosmacosx

使用

- (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)));

这里我们根据123来指定三个参数不能为空,但是我们最后一个参数,并不是指针类型,那是否有问题呢?来看看

nonnull.png

根据提示就知道,参数必须为指针类型

改成

- (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,这里的作用域为{},那么还有那些作用域呢?比如:returnbreakgoto等。

当然我们还可以在自定义类型中进行使用,这一定会很有用

在上面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

我们抛开13方法不看,仔细查看2、4、5、6可以发现,2比其它方法多了一个NS_DESIGNATED_INITIALIZER

#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))

没错,查看其宏定义,可以看到实际上就是objc_designated_initializer属性,方法2就是我们NSDate类的一个全能初始化方法,也就是说,其余的初始化方法在内部实现的时候都要调用该方法,切记是在类的内部实现,这样的话,我们就只需要在全能初始化方法中,进行数据存储,当我们底层数据存储机制改变的时候,只需要修改全能方法中的代码就可以了,而不用更改其他初始化方法。

下面我们就来看看objc_designated_initializer使用时需要注意的问题

我们先定义两个类AnimaDog,并在其中加入一些初始化函数

@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方法,否则会有如下警告
001.png
002.png

Anima继承自NSObject,而initNSObjectobjc_designated_initializer方法,所以会有警告信息,Dog一样的道理

  • 如果初始化方法拥有objc_designated_initializer属性,那么在使用的时候必须调用父类的objc_designated_initializer方法
03.png
04.png

Dog类中的objc_designated_initializer方法initWithKind中,我们没有调用父类的objc_designated_initializer的方法,就得到两条警告信息

  • 如果不是objc_designated_initializer的初始化方法,但是该类拥有objc_designated_initializer初始化方法,那么,必须调用该类的objc_designated_initializer方法或者非objc_designated_initializer方法,不可以调用父类的任何初始化方法
05.png
06.png
07.png
08.png

这里我针对方法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__还有许多其它属性,这里只是一些我认为常用的,通过使用这些属性,代码质量瞬间....如果有什么错误的地方,还望各位多多指点!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容