Effective Objective-C 2.0

读Effective Objective-C 2.0有感,在读这边书之前OC的基础并不是很好,可以说一窍不通,所以不乏一些很简单的东西。

Accustoming Yourself to Objective-C

Item 4: Typed Constants

主要介绍的是#define、static、const、extern。

  • const修饰常量。
  • static修饰静态变量

    这里OC和Swift是有区别的,OC的static修饰的变量不会创建外部符号,仅在自己的实现文件中进行类似于#define的替换。在Swift中,static表示类变量或者是类方法。

典型使用🌰

1
2
3
4
5
/// xx.m
const NSString *ABCNotificationName = @"ABCNotificationName";

/// xxx.m
extern const NSString *ABCNotificationName;

这样就不会在两个无关的实现文件导入相关头文件。在这里不能添加static修饰符,否则会导致extern失败。

Objects, Messaging, and the Runtime

Item 6: Understand Properties

这里介绍了属性的相关知识,当添加一个实例变量之后,存储实例变量的内存结构发生变化(实例变量在类中offset发生变化)。为了这样的情况发生,OC采用运行时动态查找实例变量的offset来实现稳定的ABI

You can even add instance variables to classes at runtime.这句话让我百思不得其解,不知道如何实现。(objc_setAssociatedObject也不是向类中添加实例变量啊)
Item 27中有这样一段话,Therefore, instance variables do not have to be defined in the public interface, since consumers of the class do not have to know their layout.说的是Extension,可能作者想说的是Extension可以添加实例变量,但是Extension不是运行时。这里不知道是不是作者的错误。

Item 9: Class Cluster

在看Effective Objective-C的时候看到类簇这部分,让我对Objective-C中的工厂模式有了一定的认识,也解开了以前的疑惑(比如UIButton、NSArray等)。

比如在lldb调试的过程中看到的NSArray类型并不是NSArray,可能是一个__NSSingleObjectArrayI也可能是__NSArrayI,都是Apple封装在内部的私有类,他们都是NSArray子类,意味着NSArray仅仅是一个壳而已。

1
2
3
4
id idArray = @[@1];
if ([idArray class] == [NSArray class]) {
/// 不会执行。因为[idArray class]返回不会是NSArray类本身。
}

推荐从NSArray看类簇这篇文章。

Item 12: Message Forwarding

  1. 首先调用resolveInstanceMethod或者resolveClassMethod,表明是否能够新增一个方法来处理。

    Dynamically provides an implementation for a given selector for an instance method.
    Dynamically provides an implementation for a given selector for a class method.

  2. 当上一步遍历到NSObject的时候还是返回NO,就会调用forwardingTargetForSelector,在这里可以将消息转发给另一个对象。

    Returns the object to which unrecognized messages should first be directed.
    ⚠️ 这里不要返回self,否则会陷入死循环。常用的操作是将消息转发给内部的隐藏的对象。

  3. 当上一步继续返回nil的时候,会调用forwardInvocation进行完整的消息转发。

    Overridden by subclasses to forward messages to other objects.

resolveInstanceMethod || resolveClassMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import <objc/runtime.h>
#import <objc/message.h>

@interface ViewController ()
- (void)failedDymamic;
@end

@implementation ViewController

+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(failedDymamic)) {
class_addMethod(self, sel, (IMP) dynamicMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}

void dynamicMethod(id self, SEL sel) {
printf("Dynamic Method\n");
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self failedDymamic];
}

@end
  • 第一次点击:

由于没有failedDymamic的实现,导致无法在method list中找到对应实现,所以调用resolveInstanceMethod向method list中加入新的dynamicMethod

  • 第二次点击:

不经过resolveInstanceMethod,因为method list中已经有对应的方法,所以直接调用。

⚠️ 但是method list中对应的sel依旧是faile淡定ymamic,但是实际调用的是dynamicMethod,相当于相应的方法被替换。

forwardingTargetForSelector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// 假设我们有一个隐藏的属性不想让外部看到,但是外部需要接口访问,其中装逼的方法可以进行消息转发。
@interface PrivateView: UIView
@end
@implementation PrivateView
- (void)changeColor: (UIColor *)color {
self.backgroundColor = color;
}
@end

@interface ViewController ()
@property(nonatomic, strong) PrivateView *privateView;
- (void)changeColor: (UIColor *)color;
@end

- (void)viewDidLoad {
[super viewDidLoad];
self.privateView = [[PrivateView alloc] initWithFrame:self.view.frame];
[self.view addSubview:self.privateView];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(changeColor:)) {
/// 消息转发,将消息转发到内部变量,自己并不处理该事件。
return self.privateView;
}
return [super forwardingTargetForSelector:aSelector];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self changeColor:[UIColor redColor]];
}

@end

⚠️ 当一次forwardingTargetForSelector成功之后,再次发送该消息的时候并不会重新从resolveInstanceMethod开始,而是直接forwardingTargetForSelector。猜测应该是做了缓存。

forwardInvocation

以上都没有结果,就会调用forwardInvocation进行完整的消息转发过程。

⚠️ 此时必须要重写methodSignatureForSelector返回对应方法签名,如果方法签名返回为nil,则不会调用forwardInvocation

1
2
3
4
5
6
7
8
9
10
11
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([anInvocation selector] == @selector(changeColor:)) {
[anInvocation invokeWithTarget:self.privateView];
} else {
[super forwardInvocation:anInvocation];
}
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [self.privateView methodSignatureForSelector:aSelector];
}

上面仅仅是消息的简单转发,在这里其实我们可以做更多,比如修改参数、修改返回值等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([anInvocation selector] == @selector(changeColor:)) {
UIColor *color = [UIColor greenColor];
/// 修改参数,将背景色从red修改成green 0:self 1:_cmd
[anInvocation setArgument:&color atIndex:2];
[anInvocation invokeWithTarget:self.privateView];
} else {
[super forwardInvocation:anInvocation];
}
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [self.privateView methodSignatureForSelector:aSelector];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self changeColor:[UIColor redColor]];
}

由于执行到该消息转发的时候并没有缓存,所以每次都会执行。

methodSignatureForSelector

To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.

在上面class_addMethod的时候出现了"v@:"东西。

  1. @encode(BOOL) (c) for the return type
  2. @encode(id) (@) for the receiver (self)
  3. @encode(SEL) (:) for the selector (_cmd)
  4. @encode(NSString *) (@) for the first explicit argument

每个方法都有两个默认的参数,一个是self,一个是_cmd。而那个字符串表示的就是类型编码(Type Encodings)。

"v@:"表示的就是返回值为void,第一个参数是self,第二个参数为_cmd。

doesNotRecognizeSelector

当上面步骤都走完了,还是没有找到相关的消息接受者,就会调用NSObject的doesNotRecognizeSelector来抛出异常。既”Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason:”

Item 13: Method Swizzling

在了解了消息转发之后,就很容易理解方法交换。就是修改Methos Lists。

在这里我们需要注意的几点:

  1. 为什么要在load中实现方法交换。
  2. load和initialize调用时机。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// main.m
int main(int argc, const char * argv[]) {
NSLog(@"Begin");
NSLog(@"End");
return 0;
}

/// ObjectA.m
@implementation ObjectA

+ (void)load {
NSLog(@"Load");
}

+ (void)initialize {
NSLog(@"initialize");
}

+ (instancetype)newCreate {
return [[self alloc] init];
}

@end

1
2
3
4
5
6
int main(int argc, const char * argv[]) {
NSLog(@"Begin");
ObjectA *newCreate = [[ObjectA alloc] init];
NSLog(@"End");
return 0;
}

load调用:

  1. Load在镜像加载的时候调用。
  2. Load先于main调用,这个时候Framework已经加载了(C++静态库没有)。
  3. 父类先于子类调用。
  4. 类先于carthage调用。

initlizate调用:

  1. 第一次初始化类,或者是调用类方法等(个人理解就是创建元类的时候,因为元类也可以看成单例)。
  2. 如果没有调用则不会调用initlizate。

你真的了解 load 方法么?

Item 14: Objective-C Object

1
NSString *string = @"This is NSString";

这个是一个很简单的字符串

Type string @”This is NSString”
内存分配
类型 objc_object objc_class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;


struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

Class中的isa指向其元类,也是类方法定义的地方,只有一个实例关联元类(可以认为类方法的调用类似于单例调用实例方法)。所以下面的判断方法是正确的。

1
2
3
4
id object = /*...*/;
if ([object class] == [AClass class]) {
// 'object' is an instance of AClass
}

⚠️ 这种方式仅对NSObject子类有效,如果类是继承于NSProxy,则不适用。

Interface and API Design

Item 18: Prefer Immutable Objects

Swift中我觉得最好的是权限控制,由于Swift以文件为单位进行权限控制。例如privatefileprivate等。内部读写,外部读取我们可以使用private(set)fileprivate(set)这样的方式。刚接触OC的时候很是困惑,直到我明白OC的动态性和Runtime等相关知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// A.h
@interface A
/// 只读属性
@property(nonatomic, readonly) NSString *string;
@end

/// A.m
@interface A ()
@property(nonatomic, copy, readwrite) NSString *string;
@end

@implementation
- (instancetype)init {
self = [super init];
if (self) {
self.string = @"Jack";
}
return self;
}
@end

下面提供个人的理解:

  1. Runtime的存在使得OC具有动态性,直到运行时才知道如何进行消息派发,在编译的时候是并不知道的。
  2. 头文件的作用就是告诉编译器我有这个方法,其他人可以尝试调用,但是有没有实现我并不告诉别人,直到运行时才告诉你。
  3. Extension其实是在编译的时候进行的,目的就是隐藏具体的细节。

实现文件用来编译,头文件用来链接其他文件。

我们再来分析上面的代码,在头文件声明属性,编译器会自动生成getString方法,其他人看到也就只有这个。在实现文件中声明相同属性,编译器会声明getString和setString两个方法,只有自己知道。所以也就实现了类似于Swift的内部读写,外部读取。但是这个setString方法在Runtime的是可以在方法表中找到的,意味着我们向其发送setString消息,是能够响应的(或者通过KVC)。

1
2
3
4
5
6
7
ObjectA *a = [[ObjectA alloc] init];
NSLog(@"%@", a.string);
/// 向其发送setString消息,These functions must be cast to an appropriate function pointer type before being called.
((void (*)(id, SEL, id))objc_msgSend)(a, sel_registerName("setString:"), @"Sparrow");
NSLog(@"%@", a.string);
[a setValue:@"Jack" forKey:@"string"];
NSLog(@"%@", a.string);

Advances

objc_msgSend

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

从objc_msgSend定义看出,其并不是一个具体的函数,而是在调用的时候需要给出指定的函数指针类型。

下面我们看看编译器是如何为我们解决代码到消息的转换。

1
2
3
4
5
6
7
/// main.h
int main(int argc, const char * argv[]) {
ObjectA *a = [[ObjectA alloc] init];
/// 其实明并没有做任何事,仅仅在头文件声明,让编译器知道有这个方法
[a setA: @"Sparrow"];
return 0;
}
1
$ clang -rewrite-objc main.m
1
2
3
4
5
6
/// main.cpp
int main(int argc, const char * argv[]) {
ObjectA *a = ((ObjectA *(*)(id, SEL))(void *)objc_msgSend)((id)((ObjectA *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ObjectA"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)a, sel_registerName("setA:"), (NSString *)&__NSConstantStringImpl__var_folders_8j_r22m5hzd7lj4qz9prr3q1qlw0000gn_T_main_1cdde1_mi_0);
return 0;
}

函数指针类型是(void (*)(id, SEL, NSString *)sel_registerName通过给定的"setA:"字符串生成一个SEL,之后是传递的参数。这样就实现了消息发送。

runtime objc_msgSend使用

Extension(Class-continuation Category) && Category

  1. Extension(编译期间),Category(运行时期间)。

    所以Extension可以添加实例变量而Category不能。

  2. Extension和Category都可以添加属性。

    但是只有Extension可以自动生成对应的实例变量和存储方法,而Category不会生成实例变量和存储方法,只有一个存储方法的声明,需要自己去手动实现。
    如果没有手动实现就会出现警告Property ‘privateString’ requires method ‘privateString’ to be defined - use @dynamic or provide a method implementation in this category

  3. 如果Category添加的方法和原类中的方法是同名的,那么会覆盖(最后在运行时加载的时候Category的方法表会在原方法表前面,导致被覆盖的现象,其实原方法还是存在的)原有类中方法的实现。

我们再来看看这个结构,则objc_ivar_list指向的是实例变量表,objc_method_list指向的实例方法表指针。两者的区别导致了我们只能在运行时动态修改实例方法表,而不能修改实例变量表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;


struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;



struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;


struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;

int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}

在运行期,对象内存布局已经确定。

从category中可以看出并不能添加实例变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;

method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}

property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

深入理解Objective-C:Category

Memory Management

Item 30 ARC

当方法名字是以下面名字为开头,则返回的对象交由调用者管理。如果不是,则返回的对象为autorelease对象。

  1. alloc
  2. new
  3. copy
  4. mutableCopy

举个🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// ObjectA.m
/// 很简单,两个类方法,返回当前实例,注意命名
@interface ObjectA : NSObject

+ (instancetype)newCreate; // return [[self alloc] init];
+ (instancetype)create; // return [[self alloc] init];
- (void)testFunction; // NSLog(@"Test Function");

@end

/// main.m
int main(int argc, const char * argv[]) {
ObjectA *newCreate = [ObjectA newCreate];
ObjectA *create = [ObjectA create];
return 0;
}
1
2
# 用clang将ObjectA.m文件编译成LLVM中间文件。
$ clang -S -fobjc-arc -emit-llvm ObjectA.m -o ObjectA.ll
1
2
3
4
5
6
7
8
9
10
11
12
define internal i8* @"\01+[ObjectA newCreate]"(i8*, i8*) #0 {
%7 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %5, i8* %6)
%9 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %7, i8* %8)
ret i8* %11
}

define internal i8* @"\01+[ObjectA create]"(i8*, i8*) #0 {
%7 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %5, i8* %6)
%9 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %7, i8* %8)
%12 = tail call i8* @objc_autoreleaseReturnValue(i8* %11) #2
ret i8* %12
}
1
2
# 用clang将main.m文件编译成LLVM中间文件。
$ clang -S -fobjc-arc -emit-llvm main.m -o main.ll
1
2
3
4
5
6
7
8
define i32 @main(i32, i8**) #0 {
%11 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)
%16 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %15, i8* %14)
%17 = call i8* @objc_retainAutoreleasedReturnValue(i8* %16) #2
call void @objc_storeStrong(i8** %19, i8* null) #2
call void @objc_storeStrong(i8** %20, i8* null) #2
ret i32 %21
}

这里我们可以看出同一个实例仅仅是命名不同,编译的结果是不一样的。
new开头的方法是直接返回对象的,意味着由调用者管理返回对象的内存,对象为strong类型,所以返回的对象引用计数为1。
非new开头的方法返回的是一个autorelease对象(引用计数为0),但是由于create对象为
strong类型,所以做了一次objc_retainAutoreleasedReturnValue操作。
相同点是在函数结束的时候调用objc_storeStrong将对象释放。

接下来我们看看这两个方法的实现,再结合书中的代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Same as objc_retainAutorelease but suitable for tail-calling 
// if you don't want to push a frame before this point.
__attribute__((noinline))
static id
objc_retainAutoreleaseAndReturn(id obj)
{
return objc_retainAutorelease(obj);
}

// Prepare a value at +1 for return through a +0 autoreleasing convention.
id
objc_autoreleaseReturnValue(id obj)
{
if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

return objc_autorelease(obj);
}

// Prepare a value at +0 for return through a +0 autoreleasing convention.
id
objc_retainAutoreleaseReturnValue(id obj)
{
if (prepareOptimizedReturn(ReturnAtPlus0)) return obj;

// not objc_autoreleaseReturnValue(objc_retain(obj))
// because we don't need another optimization attempt
return objc_retainAutoreleaseAndReturn(obj);
}

id
objc_retainAutorelease(id obj)
{
return objc_autorelease(objc_retain(obj));
}

在这里我们可以看出编译器对autorelease进行了优化。

  1. 如果该对象返回的对象是__strong类型,则直接返回该对象。
  2. 如果该对象返回的对象是__weak类型,则返回的对象为autorelease对象。

weak

首先我们证实一下上面的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// main.m

int main(int argc, const char * argv[]) {
/// ⚠️ main.m:13:21: warning: assigning retained object to weak variable; object will be released after assignment。
ObjectA *__weak newCreate = [ObjectA newCreate];
[newCreate testFunction];
return 0;
}


int main(int argc, const char * argv[]) {
ObjectA *__weak create = [ObjectA create];
[create testFunction];
return 0;
}
1
2
3
4
5
6
7
8
9
define i32 @main(i32, i8**) #0 personality i8* bitcast (i32 (...)* @__objc_personality_v0 to i8*) {
%12 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %11, i8* %10)
%16 = call i8* @objc_initWeak(i8** %14, i8* %15) #2
call void @objc_release(i8* %17) #2, !clang.imprecise_release !7
%19 = call i8* @objc_loadWeakRetained(i8** %18) #2
invoke void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*)*)(i8* %22, i8* %21)
...
call void @objc_destroyWeak(i8** %25) #2
}
1
2
3
4
5
6
7
8
9
10
11
define i32 @main(i32, i8**) #0 personality i8* bitcast (i32 (...)* @__objc_personality_v0 to i8*) {
%12 = call i8* bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to i8* (i8*, i8*)*)(i8* %11, i8* %10)
%13 = call i8* @objc_retainAutoreleasedReturnValue(i8* %12) #2
%17 = call i8* @objc_initWeak(i8** %15, i8* %16) #2
call void @objc_release(i8* %18) #2, !clang.imprecise_release !7
%20 = call i8* @objc_loadWeakRetained(i8** %19) #2
invoke void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*)*)(i8* %23, i8* %22)
to label %24 unwind label %28
...
call void @objc_destroyWeak(i8** %25) #2
}

两个同样的实例一个有警告无任何的输出结果,一个没有警告,正常输出。

原因就在于第二个创建的实例是一个autorelease对象,而第一个创建的对象由于引用计数为0,所以一创建就被释放掉了。

探究ARC

Objective-C Automatic Reference Counting (ARC)

黑幕背后的Autorelease