《iOS Tips 一》

主要内容有三:

  1. copy VS strong
  2. newName
  3. __attribute__

Tip1:对于 NSString 而言,@property 中的 strong 和 copy 有什么区别 ?

在项目中发现有人用 strong,有人用 copy,还有混着用的。
问之,为什么要用strong, 为什么这么写 ?
答曰:一样的都是。
so 真的一样吗?我们细细来看,这里先说有什么区别:

首先,声明一个 MLPerson:

//MLPerson.h
#import <Foundation/Foundation.h>
@interface MLPerson : NSObject
@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy)   NSString *lcopyName;
@end

//MLPerson.m
#import "MLPerson.h"
@implementation MLPerson
- (NSString *)description
{
   return [NSString stringWithFormat:@"strong Name :%@  -- copy Name: %@", _strongName,_lcopyName];
}
@end

如上所示,在 MLPerson 类中添加了两个属性: strongNamelcopyName, 这两个属性分别用 strong 和 copy 修饰, 为了查看方便,重写了 description 方法。


看官却道,咦?这厮为何 copy 修饰的不起一个 copyName 的名字呢?
欲知详细,且看 Tip2


在 main.m 中测试代码如下:

#import <Foundation/Foundation.h>
#import "MLPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MLPerson *jack = [MLPerson new];
        NSMutableString *name = [NSMutableString stringWithString:@"Hello world"];
        jack.strongName = name;
        jack.copyName = name;
        NSLog(@"%@",jack);
        // 修改name
        [name appendString:@" one"];
        NSLog(@"%@",jack);
        
    }
    return 0;
}

控制输出结果:

strong Name :Hello world  -- copy Name: Hello world
strong Name :Hello world one  -- copy Name: Hello world
Program ended with exit code: 0

在 jack 这个对象生成之后,分别为其两个属性赋值,这个值是一个可变字符串,因 NSMutableString 是 NSString 的子类,所以这么做是没有问题的。赋值结束之后,将 name 后面追加了 " one" 这一个字符串,现在 name 的值是: "Hello world one"。 按照面向对象封装思想而言,此时此刻 jack 的这两个属性都不应该改变,因为对对象的修改最好是通过 setter 方法或者公开的方法进行。通过控制台打印的结果来看,strong 修饰的属性是不能满足要求的,而copy则是可以满足要求的。
所以,在开发的过程中,为了让类的封装性不被破坏,针对 NSString 最好使用 copy 来进行修饰,这样的代码会更清晰一些,也不容出错。

原因解释:


图 1-1

如图 1-1 所示,当执行 jack.strongName = name 和 jack.lcopyName = name 的时候,实际上,_strongName 指向的是 ① 的 "Hello world" 而 _lcopyName 指向的确实另一份内容与 name 指向的位置一样的 "Hello world", 后面代码对 name 的追加是修改的 ① 处的 "Hello world" ,结果一目了然。
而对于不可变字符串而言,则没有什么区别。


Tip2: 声明了一个属性名字为 copyName 为什么编译不通过,如果就想使用这个名字该如何去做?

在声明属性的时候,尤其是为了区分两个属性,经常用写成 newName、copyName 或者其他,但是往往 Xcode 编译不通过,并且报错,如图 1-2 所示:

❌ Property follows Cocoa naming convention for returning 'owned' objects
图 1-2

在开发者文档《Memory Management Policy》中有这么一条内存管理策略:

You own any object you create
You create an object using a method whose name begins with "alloc", "new", "copy", or "mutableCopy".
(for example, alloc, newObject, or mutableCopy).

上面这条规则说的是在 MRR(memory retain release)内存管理下的一条规则。
声明属性为 copyName ,也就是会默认产生 setCopyName 和 copyName 这两个方法 setter 和 getter 方法, 然而根据内存规则来说,通过 newXXX 方法就是持有 newXXX 方法返回的对象,getter 方法并不是用来持有对象的,这样就造成了奇异,so 编译器直接报错。
解决这个问题,最简单的方法就是改名,比如:

@property (nonatomic, copy) NSString *theCopyName;

当然有句话就:就不信邪!
如果执意要用这个名字的话,可以修改编译器默认为创建的 getter 方法的名字:

@property (nonatomic, copy, getter = theCopyName) NSString *copyName;

这样编译也是通过的。
还有一种方式就是使用 Function attribute 来修饰 getter 方法

@property (nonatomic, copy)   NSString *copyName ;
- (NSString *)copyName __attribute__((objc_method_family(none)));

上面这种写法也是可以通过验证的。

当然这里又有: __attribute__ 是什么的疑问了?且看 Tip3


Tip3: __attribute__ 是什么?

从上面可以看到,当为 copyName 的 getter 方法添加了 attribute 后面这一段之后,编译器便不再报错。尽管你不知道 attribute 是什么,却仍然可以推断出它让编译器忽略了对这个方法的内存规则检查即其为编译器提供了上下文。
首先,来看下 NSFoundation 框架中 NS_REQUIRES_SUPER 的使用:
NS_REQUIRES_SUPER 是在 NSObjCRuntime.h 中定义的预编译指令,定义如下

#ifndef NS_REQUIRES_SUPER
#if __has_attribute(objc_requires_super)
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
#else
#define NS_REQUIRES_SUPER
#endif
#endif

可以看到第三句中:

#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))

和上面针对属性的使用非常相似,这句话定义了之后,当使用 NS_REQUIRES_SUPER 地方在预编译时期就会被替换为 __attribute__((objc_requires_super)) (这一点在后面来进行验证)
在 MLPerson 类中添加方法 - work :

//MLPerson.h
@interface MLPerson : NSObject

@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy)   NSString *copyName ;
//@property (nonatomic, copy, getter=theCopyName)   NSString *copyName;
- (NSString *)copyName __attribute__((objc_method_family(none)));

- (void)work NS_REQUIRES_SUPER;
@end


//MLPerson.m
#import "MLPerson.h"

@implementation MLPerson

- (NSString *)description
{
    return [NSString stringWithFormat:@"strong Name :%@  -- copy Name: %@", _strongName,_copyName];
}


- (void)work {
    NSLog(@"MLPerson - work method");
}
@end

创建 MLStudent 类继承自 MLPerson 类:

//MStudent.h
#import "MLPerson.h"
@interface MLStudent : MLPerson

@end

//MStudent.m
#import "MLStudent.h"

@implementation MLStudent
- (void)work {
    NSLog(@"MLStudent - work method");
}
@end

在为 MLStudent 类添加 work 方法的时候,可以看到如下警告信息,如图 1-3:

图 1-3

当然如果不调用的话也是可以编译通过的,但是这里会弹出警告⚠️让写此类的人知道此处应该通过 [super work] 调用父类的方法。

  1. 通过观察 * NS_REQUIRES_SUPER* 的使用,对先前 MLPerson 中 copyName 的 getter 方法修饰做出修改,如下:
#if __has_attribute(objc_method_family)
#define ML_OBJC_METHOD_FAMILY_NONE __attribute__((objc_method_family(none)))
#else
#define ML_OBJC_METHOD_FAMILY_NONE
#endif

#import <Foundation/Foundation.h>

@interface MLPerson : NSObject

@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy)   NSString *copyName ;
//@property (nonatomic, copy, getter=theCopyName)   NSString *copyName;
//- (NSString *)copyName __attribute__((objc_method_family(none)));

- (NSString *)copyName ML_OBJC_METHOD_FAMILY_NONE;

- (void)work NS_REQUIRES_SUPER;
@end

这里使用 ML_OBJC_METHOD_FAMILY_NONE 对 __attribute__ 做了预定义处理

  1. 验证预处理阶段的替换
//main.m
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "MLPerson.h"
#import "MLStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
 
        MLPerson *jack = [MLPerson new];
        
        NSMutableString *name = [NSMutableString stringWithString:@"Hello world"];
        
        jack.strongName = name;
        jack.copyName = name;
        NSLog(@"%@",jack);
        [name appendString:@" one"];
        NSLog(@"%@",jack);
        
        
        NSString *newName = @"one world";
        jack.strongName = newName;
        jack.copyName = newName;
        NSLog(@"%@",jack);
        newName = @"xxx";
        NSLog(@"%@",jack);
        
        NSString *str = @"Hello world";   
        
        MLStudent *liLei = [MLStudent new];
        [liLei work];
    }
    return 0;
}

打开终端,进入到项目目录,与 main.m 在同一层级:

 $tree
.
├── MLPerson.h
├── MLPerson.m
├── MLStudent.h
├── MLStudent.m
└── main.m

0 directories, 5 files
$clang -E -fmodules main.m -o main # 对 main.m 执行预处理操作,输出文件名为main
$tree
.
├── MLPerson.h
├── MLPerson.m
├── MLStudent.h
├── MLStudent.m
├── main # 目标文件已经生成
└── main.m
$cat main
# 1 "main.m"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 343 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.m" 2








@import Foundation; /* clang -E: implicit import for "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" */
@import AppKit; /* clang -E: implicit import for "/System/Library/Frameworks/AppKit.framework/Headers/AppKit.h" */
# 1 "./MLPerson.h" 1
# 18 "./MLPerson.h"
@import Foundation; /* clang -E: implicit import for "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" */

@interface MLPerson : NSObject

@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *copyName ;


- (NSString *)copyName __attribute__((objc_method_family(none)));


- (void)work __attribute__((objc_requires_super));
@end
# 12 "main.m" 2
# 1 "./MLStudent.h" 1
# 11 "./MLStudent.h"
@interface MLStudent : MLPerson

@end
# 13 "main.m" 2

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        MLPerson *jack = [MLPerson new];

        NSMutableString *name = [NSMutableString stringWithString:@"Hello world"];

        jack.strongName = name;
        jack.copyName = name;
        NSLog(@"%@",jack);
        [name appendString:@" one"];
        NSLog(@"%@",jack);


        NSString *newName = @"one world";
        jack.strongName = newName;
        jack.copyName = newName;
        NSLog(@"%@",jack);
        newName = @"xxx";
        NSLog(@"%@",jack);

        NSString *str = @"Hello world";

        MLStudent *liLei = [MLStudent new];
        [liLei work];
    }
    return 0;
}

从 #18 的地方截取看到

@import Foundation; /* clang -E: implicit import for "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" */

@interface MLPerson : NSObject

@property (nonatomic, strong) NSString *strongName;
@property (nonatomic, copy) NSString *copyName ;


- (NSString *)copyName __attribute__((objc_method_family(none)));


- (void)work __attribute__((objc_requires_super));
@end

这就是预处理之后 MLPerson 中的内容,我们可以看到注释已经去除,并且宏也已经被替换。
上面通过 clang -E -fmodules main.m -o main 生成了预处理之后的 main 文件
当然通过:gcc -E -Foundation main.m -o main 也是可以做到这一步的,只不过目标文件中导入的 Foudation 会被展开。

至此,我们可以看到 __attribute__ 是为编译器提供上下问的一个工具或者方式,在 Cocoa 中早有使用,目前先了解到此处,后面做专门分析。


end


推荐阅读更多精彩内容

  • 明天我们就要比赛了我心里边非常的激动我的东西们都准备好
    付梦缘阅读 26评论 0 0
  • 文/刘志伟 营销架构师 在我看来,微商营销就做两件事:加固信任和提醒购买。大家用尽所有的力量,都是在解决这...
    刘志伟社群营销学者阅读 268评论 1 4