发布于 

Objc Tips

总结记录 Objective-C 使用过程中一些 Tips。

NS_ENUM 和 NS_OPTIONS

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UIViewAnimationCurve) {
UIViewAnimationCurveEaseInOut, // slow at beginning and end
UIViewAnimationCurveEaseIn, // slow at beginning
UIViewAnimationCurveEaseOut, // slow at end
UIViewAnimationCurveLinear,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

// 用法
UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// 转换为二进制计算
UIViewAutoresizing resizing = 000010 | 010000 = 010010
// 通过 & 判断是否满足条件之一
// resizing & UIViewAutoresizingFlexibleWidth
// -> 010010 & 000010 = 000010
if (resizing & UIViewAutoresizingFlexibleWidth) {
// UIViewAutoresizingFlexibleWidth is set
}

NS_OPTIONS 可以同时选择多个枚举值,使用了移位运算来保证相加结果的唯一性。简而言之,NS_ENUM 在互斥环境下使用;NS_OPTIONS 在多选情况下使用。

简介接口设计模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef NS_ENUM(NSInteger, UserSex) {
UserSexMale,
UserSexFemale
};

@interface DIYUser : NSObject<NSCopying>

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readwrite, assign) UserSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(UserSex)sex;

+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(UserSex)sex;

@end

weak 和 assign 对比

什么情况下使用 weak?

  • 用来避免循环引用
  • OBOutlet 默认为 weak

与 assign 的区别

  • weak 为该属性定义了一种非拥有关系,不影响引用计数,在属性所指对象销毁时,属性值也会自动置为 nil
  • assign 用来修饰基础类型变量(如 CGFloat、NSInteger)
  • assign 可用于非 OC 对象,weak 必须用于 OC 对象

weak 的实现原理

weak 有两种作用:

  • 被 weak 修饰符修饰的弱引用除了不会增加对象的引用计数
  • 引用对象被释放后,这个弱引用会自动失效并置为 nil

实现原理

  • 源码入口 objc_initWeak()
    • 查看对象是否有效,无效置空
    • 调用 storeWeak
  • storeWeak
    • 核心方法 weak_unregister_no_lockweak_register_no_lock
    • 都是对 SideTable 的实例进行操作
  • SideTable
    • 内含有 weak_table_t,是 oc 中 weak 的核心数据结构
    • weak 表通过哈希表实现
    • 通过目标对象的地址作为 key 检索得到对应弱引用变量地址
    • 需要注意:一个 key 可能对应多个弱引用变量地址,放存放在 weak_entry
  • weak_register_no_lock
    • 先校验是否满足校验条件(计数方式、是否在析构,是否能弱引用)
    • 通过弱引用对象的地址,在 weak_table 查找 weak_entry
      • 找到,在 weak_entry 中的 referrers 添加新的弱引用地址
      • 若未找到,新建 weak_entry 并添加
      • 数组中 referrers 起始为静态数组,如果在操作过程中发现静态数组空间不够用切换为动态数组,如果动态数组超过总空间 3/4,扩容一倍
  • weak_unregister_no_lock
    • 根据对象地址,找出 weak_entry
    • 删除 weak_entry 中的弱引用地址
    • 如果最后发现 weak_entry 空了,从 weak_table 移除
  • 自动置为 nil
    • 底层触发 clearDeallocating 方法
    • 先校验对象是否满足弱引用 dealloc
    • 对象 dealloc 时,通过 this 指针(对象指针),找到 SideTable。再通过对象地址在 SideTable 上找到所有的弱引用指针,逐个置 nil

atomic 和 nonatomic

atomic 属性使用了互斥锁。一般情况下不适用 atomic,因为并不能保证线程安全,若要实现线程安全。

如,一个线程在连续多次读取某属性值的过程中有别的线程在同时改写该值,则即使声明为 atomic,也还是会读到不同的属性值。

copy 和 mutableCopy

NSString、NSArray、NSDictionary 常用 copy。因为各自对应了可变类型 NSMutableString、NSMutableArray、NSMutableDictionary,防止内容在不知情的情况下被更改。所以使用 copy 来复制一份不可变的。

对 NSMutableArray 使用 copy

添加、删除、修改组内的元素时,程序会因为找不到对应的方法而崩溃,原因是 copy 复制了一份不可变的 NSArray 对象。错误代码如下:

1
2
3
4
5
@property (nonatomic, copy) NSMutableArray *mutableArray;

NSMutableArray *array = [NSMutableArray arrayWithObjects:@1, @2, nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];

strong 修饰 NSArray 时

1
2
3
4
5
6
7
8
9
10
11
12
13
@property (nonatomic, strong) NSArray *array;

NSArray *array = @[ @1, @2, @3, @4 ];
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];

self.array = mutableArray;
[mutableArray removeAllObjects];;
NSLog(@"%@",self.array);

[mutableArray addObjectsFromArray:array];
self.array = [mutableArray copy];
[mutableArray removeAllObjects];;
NSLog(@"%@",self.array);

打印结果:

1
2
3
4
5
6
7
8
2016-04-03 00:10:10.765 DIYArrayCopyDemo[10681:713670] (
)
2016-04-03 00:10:10.766 DIYArrayCopyDemo[10681:713670] (
1,
2,
3,
4
)

所以 strong 的问题很明显,数据存在被篡改风险。

对象拷贝

  • 声明该类遵循 NSCopying 协议
  • 实现 NSCopying 协议方法 -copyWithZone:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (id)copyWithZone:(NSZone *)zone {
DIYUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [_friends mutableCopy];
return copy;
}

// 对象也进行深拷贝
// 写专门的深拷贝方法
- (id)deepCopy {
DIYUser *copy = [[[self class] alloc]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
}

集合/非集合类对象 copy

非集合类对象:

  • 不可变对象:
    • copy:指针拷贝
    • mutableCopy:内容拷贝
  • 可变对象:
    • copy:内容拷贝
    • mutableCopy:内容拷贝

集合类对象:NSArray、NSDictionary、NSSet 等

  • 不可变对象:
    • copy:指针拷贝
    • mutableCopy:单层深拷贝。(如对 NSArray,是拷贝 array 这个对象,但 Array 内部元素任是指针拷贝)
  • 可变对象:
    • copy:单层深拷贝
    • mutableCopy:单层深拷贝
1
2
3
4
[array copy] //浅拷贝
[array mutableCopy] //单层深拷贝
[mutableArray copy] //单层深拷贝
[mutableArray mutableCopy] //单层深拷贝

@property

@property = ivar + getter + setter,即属性 = 实例变量 + 存取方法。

完成属性定义后,编译器在编译期会自动编写访问这些属性所需要的方法,即“自动合成”。除了生成 getter、setter 外,编译器还会向类中添加适当类型的实例变量,名称为 _propertyName。我们也可以通过 @synthesize 来指定实例变量的名称。

@property 大致的实现过程

属性的源码实现结构:

  • 偏移量
  • setter、getter 实现函数
  • ivar_list:成员变量列表
  • method_list:方法列表
  • prop_list:属性列表

实现过程:

  • 每增加一个属性,系统就会在 ivar_list 中添加一个成员变量;在 method_list 中添加对应的 setter 和 getter 方法;在 prop_list 中添加一个属性。
  • 之后计算该属性在对象中的偏移量,给出 setter、getter 方法对应的实现。setter 方法中从偏移量位置开始赋值;getter 方法中从偏移量开始取值。

@protocol 和 category 如何使用 @property

protocol 中使用 property 只会生成 setter 和 getter 方法声明。我们使用属性的目的是,希望遵循协议的对象能实现该属性。

category 使用 @property 只会生成 setter 和 getter 方法的声明。如果我们需要给 category 增加属性的实现,需要借助 runtime 函数:

  • objc_setAssociatedObject
  • objc_getAssociatedObject

@property 的修饰符归类

  1. 原子性
    • atomic
    • nonatomic
  2. 读写权限
    • readwrite
    • readonly
  3. 内存管理语义
    • assign
    • strong
    • weak
    • unsafe_unretained
    • copy
  4. 方法名
    • getter=,如 getter=isOn
    • setter=
  5. nullable
    • nonnull
    • null_resettable
    • nullable
  6. ARC 下不显示关键字是,默认为:
    • 基本类型
      • atomic
      • readwrite
      • assign
    • 普通 OC 对象
      • atomic
      • readwrite
      • strong

@synthesize 和 @dynamic

  1. @property 如果都没写,默认为:@synthesize = _var;
  2. @systhesize 语义是,默认让编译器加上 setter 和 getter 方法
  3. @dynamic(动态绑定)作用:告诉编译器,属性的 setter、getter 方法不自动生成

什么时候不会 autosynthesis?

  • 同时重写了 setter 和 getter
  • 重写了只读属性的 getter
  • 使用了 @dynamic
  • @protocol 中定义的所有属性
  • category 中定义的所有属性
  • 重写的属性

注意点:

  • 若子类重写了父类属性,必须用 @synthesize 来手动合成 ivar

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @JonyFang 创建,使用 Stellar 作为主题,您可以在 GitHub 找到本站源码。