iOS runtime浅析Property和Ivar

Property

propertyObjective-C的一个语法糖,能帮助你声明成员变量(ivar)(自动声明的变量名前面会加下划线)+自动生成对应属性特性(atomic/nonatomic/copy/strong/weak/assign...)settergetter

通常我们在编写iOS程序的时候声明类的成员变量,习惯使用property

1
2
3
4
5
6
7
8
9
10
11
12
//
// GMYTestObject.h
// ObjectInspector
//
// Created by miaoyou.gmy on 2017/2/22.
// Copyright © 2017年 miaoyou.gmy. All rights reserved.
//
@interface GMYTestObject : NSObject
@property (nonatomic, assign) NSUInteger index;
@property (nonatomic, copy) NSString *str;
@property (nonatomic, copy) NSArray *array;
@end

对应自动声明的_index_str_array

若想声明private的成员变量要借助Class Extensions在原有的实现文件中添加变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
// GMYTestObject.m
// ObjectInspector
//
// Created by miaoyou.gmy on 2017/2/22.
// Copyright © 2017年 miaoyou.gmy. All rights reserved.
//

#import "GMYTestObject.h"

@interface GMYTestObject ()
@property (nonatomic, copy) NSString *testStr;
@property (nonatomic, assign) CGFloat margin;
@property (nonatomic, copy) NSDictionary *dict;
@end

@implementation GMYTestObject
@end

这个技巧可以参阅Class Extensions Extend the Internal Implementation

题外话

property在早些年是不支持声明类变量的。直到2016年Xcode8发布和更新了LLVM,才支持。
WWDC 2016 Session 405 What’s New in LLVM

From the Xcode 8 release notes:

Objective-C now supports class properties, which interoperate with Swift type properties. They are declared as: @property (class) NSString *someStringProperty;. They are never synthesized. (23891898)

额外要注意是,使用property声明类变量是不会自动合成settergetter 的。They are never synthesized.

要使用这个特性的具体实践是:
.h头文件中声明

1
2
3
4
5
6
7
8
9
10
11
12
//
// GMYTestObject.h
// ObjectInspector
//
// Created by miaoyou.gmy on 2017/2/22.
// Copyright © 2017年 miaoyou.gmy. All rights reserved.
//

#import <Foundation/Foundation.h>
@interface GMYTestObject : NSObject
@property (class, nonatomic, assign) NSUInteger instanceCount;
@end

.m实现文件中声明settergetter + 声明static 同类型类变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//
// GMYTestObject.m
// ObjectInspector
//
// Created by miaoyou.gmy on 2017/2/22.
// Copyright © 2017年 miaoyou.gmy. All rights reserved.
//

#import "GMYTestObject.h"
@implementation GMYTestObject
static NSUInteger _instanceCount = 0;
+ (NSUInteger)instanceCount{
return _instanceCount;
}

+ (void)setInstanceCount:(NSUInteger)instanceCount{
_instanceCount = instanceCount; // 重复声明一个类变量是为了实现setter,加_是自有成员变量的传统命名规则
}
@end

Ivar

PropertyIvar+synthesized setter + synthesized getter 的语法糖
Ivar指的就是instance variables

1
2
3
4
5
6
@interface GMYTestObject : NSObject{
@public
NSUInteger _index;
NSString *_str;
NSArray *_array;
}

没有自动合成的settergetter方法。在OOP的封装概念下显得不那么讨喜,渐渐被Property取代。

“原始”的Ivar声明方式也有不少好处:why-would-you-use-an-ivar

runtime

我们再来聊聊如何借助iOS的动态性,获取一个对象的所有成员变量。

<objc/runtime.h>

1
2
3
4
// Describes the properties declared by a class.

OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
1
2
3
4
// Describes the instance variables declared by a class.

OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

根据PropertyIvar 的关系可知。我们使用class_copyIvarList就能获取到所有对象成员变量,包括使用原始声明方式声明的变量。

我们再看看有什么和Ivar相关的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Returns the name of an instance variable.
OBJC_EXPORT const char *ivar_getName(Ivar v);

//Returns the type string of an instance variable.
OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v);

//Returns the offset of an instance variable.
OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v);

//Reads the value of an instance variable in an object.
OBJC_EXPORT id object_getIvar(id obj, Ivar ivar);

//Sets the value of an instance variable in an object.
OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value);

定义GMYTestObject作用测试各个类型的成员变量

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
//
// GMYTestObject.h
// ObjectInspector
//
// Created by miaoyou.gmy on 2017/2/22.
// Copyright © 2017年 miaoyou.gmy. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef enum{
TestA,
TestB,
TestC
}TestEnum;

typedef struct{
float x,y;
}pointNode;

@interface CustomizeObject : NSObject
@property (nonatomic) NSString *name;
@property (nonatomic) NSUInteger age;
@end

@interface GMYTestObject : NSObject
@property (nonatomic) char aChar;
@property (nonatomic) int anInt;
@property (nonatomic) short aShort;
@property (nonatomic) long aLong;
@property (nonatomic) long long aLongLong;
@property (nonatomic) unsigned char anUnsignedChar;
@property (nonatomic) unsigned int anUnsignedInt;
@property (nonatomic) unsigned short anUnsignedShort;
@property (nonatomic) unsigned long anUnsignedLong;
@property (nonatomic) unsigned long long anUnsignedLongLong;
@property (nonatomic) float aFloat;
@property (nonatomic) double aDouble;
@property (nonatomic) BOOL aBooL;
@property (nonatomic) Boolean aBoolean;
@property (nonatomic) bool abool;
@property (nonatomic) char* aCharStar;
@property (nonatomic) int* aIntStar;
@property (nonatomic) void aVoid;
@property (nonatomic) TestEnum aEnum;
@property (nonatomic) pointNode aNode;
@property (nonatomic) SEL aSEL;
@property (nonatomic) id aId;
@property (nonatomic) Class aClass;
@property (nonatomic) NSArray<NSString *> *array;
@property (nonatomic) NSArray<CustomizeObject *> *customizeArray;
@property (nonatomic) NSDictionary *dictionary;
@property (nonatomic) NSSet *aSet;
@property (nonatomic) NSRange range;
@end

使用ivar_getNameivar_getTypeEncoding 打印结果:

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
c _aChar
C _anUnsignedChar
c _aBooL
C _aBoolean
B _abool
s _aShort
S _anUnsignedShort
i _anInt
I _anUnsignedInt
f _aFloat
i _aEnum
q _aLong
q _aLongLong
Q _anUnsignedLong
Q _anUnsignedLongLong
d _aDouble
* _aCharStar
^i _aIntStar
{?="x"f"y"f} _aNode
: _aSEL
@ _aId
# _aClass
@"NSArray" _array
@"NSArray" _customizeArray
@"NSDictionary" _dictionary
@"NSSet" _aSet
{_NSRange="location"Q"length"Q} _range

@note For possible values, see Objective-C Runtime Programming Guide > Type Encodings.

我们还可以更加直观的查看

使用clang rewrite-objc GMYTestObject.m。因为内容过多,这里只放最重要的内容。我们可以清晰的看到 property声明的成员变量是如何定义在C++文件中的。

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#ifndef _REWRITER_typedef_GMYTestObject
#define _REWRITER_typedef_GMYTestObject
typedef struct objc_object GMYTestObject;
typedef struct {} _objc_exc_GMYTestObject;
#endif

extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aId;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aBoolean;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_dictionary;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aEnum;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_anUnsignedChar;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_anInt;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aFloat;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_abool;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aSEL;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aNode;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_customizeArray;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aLongLong;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aChar;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aCharStar;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aDouble;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_anUnsignedShort;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_range;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aLong;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_anUnsignedLong;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_array;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aIntStar;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aBooL;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_anUnsignedInt;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aSet;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aShort;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_anUnsignedLongLong;
extern "C" unsigned long OBJC_IVAR_$_GMYTestObject$_aClass;
struct GMYTestObject_IMPL {
struct NSObject_IMPL NSObject_IVARS;
char _aChar;
unsigned char _anUnsignedChar;
BOOL _aBooL;
Boolean _aBoolean;
bool _abool;
short _aShort;
unsigned short _anUnsignedShort;
int _anInt;
unsigned int _anUnsignedInt;
float _aFloat;
TestEnum _aEnum;
long _aLong;
long long _aLongLong;
unsigned long _anUnsignedLong;
unsigned long long _anUnsignedLongLong;
double _aDouble;
char *_aCharStar;
int *_aIntStar;
pointNode _aNode;
SEL _aSEL;
id _aId;
Class _aClass;
NSArray<NSString *> *_array;
NSArray<CustomizeObject *> *_customizeArray;
NSDictionary *_dictionary;
NSSet *_aSet;
NSRange _range;
};

// @property (nonatomic) char aChar;
// @property (nonatomic) int anInt;
// @property (nonatomic) short aShort;
// @property (nonatomic) long aLong;
// @property (nonatomic) long long aLongLong;
// @property (nonatomic) unsigned char anUnsignedChar;
// @property (nonatomic) unsigned int anUnsignedInt;
// @property (nonatomic) unsigned short anUnsignedShort;
// @property (nonatomic) unsigned long anUnsignedLong;
// @property (nonatomic) unsigned long long anUnsignedLongLong;
// @property (nonatomic) float aFloat;
// @property (nonatomic) double aDouble;
// @property (nonatomic) BOOL aBooL;
// @property (nonatomic) Boolean aBoolean;
// @property (nonatomic) bool abool;
// @property (nonatomic) char* aCharStar;
// @property (nonatomic) int* aIntStar;
// @property (nonatomic) void aVoid;
// @property (nonatomic) TestEnum aEnum;
// @property (nonatomic) pointNode aNode;
// @property (nonatomic) SEL aSEL;
// @property (nonatomic) id aId;
// @property (nonatomic) Class aClass;
// @property (nonatomic) NSArray<NSString *> *array;
// @property (nonatomic) NSArray<CustomizeObject *> *customizeArray;
// @property (nonatomic) NSDictionary *dictionary;
// @property (nonatomic) NSSet *aSet;
// @property (nonatomic) NSRange range;
/* @end */


// @implementation GMYTestObject
// @dynamic aVoid;

static instancetype _I_GMYTestObject_init(GMYTestObject * self, SEL _cmd) {
self = ((GMYTestObject *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("GMYTestObject"))}, sel_registerName("init"));
if(self){

}
return self;
}




static char _I_GMYTestObject_aChar(GMYTestObject * self, SEL _cmd) { return (*(char *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aChar)); }
static void _I_GMYTestObject_setAChar_(GMYTestObject * self, SEL _cmd, char aChar) { (*(char *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aChar)) = aChar; }

static int _I_GMYTestObject_anInt(GMYTestObject * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anInt)); }
static void _I_GMYTestObject_setAnInt_(GMYTestObject * self, SEL _cmd, int anInt) { (*(int *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anInt)) = anInt; }

static short _I_GMYTestObject_aShort(GMYTestObject * self, SEL _cmd) { return (*(short *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aShort)); }
static void _I_GMYTestObject_setAShort_(GMYTestObject * self, SEL _cmd, short aShort) { (*(short *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aShort)) = aShort; }

static long _I_GMYTestObject_aLong(GMYTestObject * self, SEL _cmd) { return (*(long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aLong)); }
static void _I_GMYTestObject_setALong_(GMYTestObject * self, SEL _cmd, long aLong) { (*(long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aLong)) = aLong; }

static long long _I_GMYTestObject_aLongLong(GMYTestObject * self, SEL _cmd) { return (*(long long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aLongLong)); }
static void _I_GMYTestObject_setALongLong_(GMYTestObject * self, SEL _cmd, long long aLongLong) { (*(long long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aLongLong)) = aLongLong; }

static unsigned char _I_GMYTestObject_anUnsignedChar(GMYTestObject * self, SEL _cmd) { return (*(unsigned char *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedChar)); }
static void _I_GMYTestObject_setAnUnsignedChar_(GMYTestObject * self, SEL _cmd, unsigned char anUnsignedChar) { (*(unsigned char *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedChar)) = anUnsignedChar; }

static unsigned int _I_GMYTestObject_anUnsignedInt(GMYTestObject * self, SEL _cmd) { return (*(unsigned int *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedInt)); }
static void _I_GMYTestObject_setAnUnsignedInt_(GMYTestObject * self, SEL _cmd, unsigned int anUnsignedInt) { (*(unsigned int *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedInt)) = anUnsignedInt; }

static unsigned short _I_GMYTestObject_anUnsignedShort(GMYTestObject * self, SEL _cmd) { return (*(unsigned short *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedShort)); }
static void _I_GMYTestObject_setAnUnsignedShort_(GMYTestObject * self, SEL _cmd, unsigned short anUnsignedShort) { (*(unsigned short *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedShort)) = anUnsignedShort; }

static unsigned long _I_GMYTestObject_anUnsignedLong(GMYTestObject * self, SEL _cmd) { return (*(unsigned long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedLong)); }
static void _I_GMYTestObject_setAnUnsignedLong_(GMYTestObject * self, SEL _cmd, unsigned long anUnsignedLong) { (*(unsigned long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedLong)) = anUnsignedLong; }

static unsigned long long _I_GMYTestObject_anUnsignedLongLong(GMYTestObject * self, SEL _cmd) { return (*(unsigned long long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedLongLong)); }
static void _I_GMYTestObject_setAnUnsignedLongLong_(GMYTestObject * self, SEL _cmd, unsigned long long anUnsignedLongLong) { (*(unsigned long long *)((char *)self + OBJC_IVAR_$_GMYTestObject$_anUnsignedLongLong)) = anUnsignedLongLong; }

static float _I_GMYTestObject_aFloat(GMYTestObject * self, SEL _cmd) { return (*(float *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aFloat)); }
static void _I_GMYTestObject_setAFloat_(GMYTestObject * self, SEL _cmd, float aFloat) { (*(float *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aFloat)) = aFloat; }

static double _I_GMYTestObject_aDouble(GMYTestObject * self, SEL _cmd) { return (*(double *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aDouble)); }
static void _I_GMYTestObject_setADouble_(GMYTestObject * self, SEL _cmd, double aDouble) { (*(double *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aDouble)) = aDouble; }

static BOOL _I_GMYTestObject_aBooL(GMYTestObject * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aBooL)); }
static void _I_GMYTestObject_setABooL_(GMYTestObject * self, SEL _cmd, BOOL aBooL) { (*(BOOL *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aBooL)) = aBooL; }

static Boolean _I_GMYTestObject_aBoolean(GMYTestObject * self, SEL _cmd) { return (*(Boolean *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aBoolean)); }
static void _I_GMYTestObject_setABoolean_(GMYTestObject * self, SEL _cmd, Boolean aBoolean) { (*(Boolean *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aBoolean)) = aBoolean; }

static bool _I_GMYTestObject_abool(GMYTestObject * self, SEL _cmd) { return (*(bool *)((char *)self + OBJC_IVAR_$_GMYTestObject$_abool)); }
static void _I_GMYTestObject_setAbool_(GMYTestObject * self, SEL _cmd, bool abool) { (*(bool *)((char *)self + OBJC_IVAR_$_GMYTestObject$_abool)) = abool; }

static char * _I_GMYTestObject_aCharStar(GMYTestObject * self, SEL _cmd) { return (*(char **)((char *)self + OBJC_IVAR_$_GMYTestObject$_aCharStar)); }
static void _I_GMYTestObject_setACharStar_(GMYTestObject * self, SEL _cmd, char *aCharStar) { (*(char **)((char *)self + OBJC_IVAR_$_GMYTestObject$_aCharStar)) = aCharStar; }

static int * _I_GMYTestObject_aIntStar(GMYTestObject * self, SEL _cmd) { return (*(int **)((char *)self + OBJC_IVAR_$_GMYTestObject$_aIntStar)); }
static void _I_GMYTestObject_setAIntStar_(GMYTestObject * self, SEL _cmd, int *aIntStar) { (*(int **)((char *)self + OBJC_IVAR_$_GMYTestObject$_aIntStar)) = aIntStar; }

static TestEnum _I_GMYTestObject_aEnum(GMYTestObject * self, SEL _cmd) { return (*(TestEnum *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aEnum)); }
static void _I_GMYTestObject_setAEnum_(GMYTestObject * self, SEL _cmd, TestEnum aEnum) { (*(TestEnum *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aEnum)) = aEnum; }

static pointNode _I_GMYTestObject_aNode(GMYTestObject * self, SEL _cmd) { return (*(pointNode *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aNode)); }
static void _I_GMYTestObject_setANode_(GMYTestObject * self, SEL _cmd, pointNode aNode) { (*(pointNode *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aNode)) = aNode; }

static SEL _I_GMYTestObject_aSEL(GMYTestObject * self, SEL _cmd) { return (*(SEL *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aSEL)); }
static void _I_GMYTestObject_setASEL_(GMYTestObject * self, SEL _cmd, SEL aSEL) { (*(SEL *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aSEL)) = aSEL; }

static id _I_GMYTestObject_aId(GMYTestObject * self, SEL _cmd) { return (*(id *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aId)); }
static void _I_GMYTestObject_setAId_(GMYTestObject * self, SEL _cmd, id aId) { (*(id *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aId)) = aId; }

static Class _I_GMYTestObject_aClass(GMYTestObject * self, SEL _cmd) { return (*(Class *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aClass)); }
static void _I_GMYTestObject_setAClass_(GMYTestObject * self, SEL _cmd, Class aClass) { (*(Class *)((char *)self + OBJC_IVAR_$_GMYTestObject$_aClass)) = aClass; }

static NSArray<NSString *> * _I_GMYTestObject_array(GMYTestObject * self, SEL _cmd) { return (*(NSArray<NSString *> **)((char *)self + OBJC_IVAR_$_GMYTestObject$_array)); }
static void _I_GMYTestObject_setArray_(GMYTestObject * self, SEL _cmd, NSArray<NSString *> *array) { (*(NSArray<NSString *> **)((char *)self + OBJC_IVAR_$_GMYTestObject$_array)) = array; }

static NSArray<CustomizeObject *> * _I_GMYTestObject_customizeArray(GMYTestObject * self, SEL _cmd) { return (*(NSArray<CustomizeObject *> **)((char *)self + OBJC_IVAR_$_GMYTestObject$_customizeArray)); }
static void _I_GMYTestObject_setCustomizeArray_(GMYTestObject * self, SEL _cmd, NSArray<CustomizeObject *> *customizeArray) { (*(NSArray<CustomizeObject *> **)((char *)self + OBJC_IVAR_$_GMYTestObject$_customizeArray)) = customizeArray; }

static NSDictionary * _I_GMYTestObject_dictionary(GMYTestObject * self, SEL _cmd) { return (*(NSDictionary **)((char *)self + OBJC_IVAR_$_GMYTestObject$_dictionary)); }
static void _I_GMYTestObject_setDictionary_(GMYTestObject * self, SEL _cmd, NSDictionary *dictionary) { (*(NSDictionary **)((char *)self + OBJC_IVAR_$_GMYTestObject$_dictionary)) = dictionary; }

static NSSet * _I_GMYTestObject_aSet(GMYTestObject * self, SEL _cmd) { return (*(NSSet **)((char *)self + OBJC_IVAR_$_GMYTestObject$_aSet)); }
static void _I_GMYTestObject_setASet_(GMYTestObject * self, SEL _cmd, NSSet *aSet) { (*(NSSet **)((char *)self + OBJC_IVAR_$_GMYTestObject$_aSet)) = aSet; }

static NSRange _I_GMYTestObject_range(GMYTestObject * self, SEL _cmd) { return (*(NSRange *)((char *)self + OBJC_IVAR_$_GMYTestObject$_range)); }
static void _I_GMYTestObject_setRange_(GMYTestObject * self, SEL _cmd, NSRange range) { (*(NSRange *)((char *)self + OBJC_IVAR_$_GMYTestObject$_range)) = range; }
// @end

我们都可以看到property 自动生成的gettersetter的实现和声明成员变量的命名符号,是不是很受冲击 - -+

12年Mac Pro机械硬盘更换

12年Mac Pro机械硬盘更换

这是一台12年中的高分雾面屏15寸Mac Pro。在大三(2013年)购买的,现在用起来性能是越发的不行了。开机时间巨久,所以 想着说要不把机械硬盘更换成固态硬盘。

更换之前,先看一下 机械硬盘的读写数据吧:

0x1 准备工作

  1. 一块2.5英寸的 SSD硬盘
  2. 一根SATA转USB 硬盘转接线
    格外一点,购买SSD硬盘之前最好确认一下电脑的SATA 串口是第几代,这个会影响读写性能的。
    我的是 SATA III,对应购买了 TOSHIBA A100 240GB

0x2 备份

  1. 使用 SATA转USB硬盘转接线,将SSD硬盘接入电脑。 使用 磁盘工具 将其 格式化为 Mac OS 扩展
  2. 下载 CCC - Carbon Copy Cloner 将机械硬盘中的所有文件 备份到 固态硬盘中
  3. 按住 option键,重新启动。查看是否可以从固态硬盘启动系统。若可以,则备份成功

0x3 拆机

  1. 断电,拨开电源插在主板上插头。
  2. 卸下锁住硬盘的螺丝,小心拔掉SATA串口线
  3. 安装 固态硬盘。

最后

开启 SSD TRIM设置,能提高SSD性能。
替换为 TOSHIBA A100 SSD的读写数据:

READ方面提升还是蛮明显的

Reveal 调试越狱设备

使用Reveal调试越狱设备

网上有很多实践文章。我自己按照网上的教程实验了一把,在这里记录一下。

  • 一台越狱设备(iOS 7.1)
  • Reveal (1.0.3)

实战

  1. 确认越狱设备 安装 OpenSSHCydia Substrate。若无法安装,可以尝试刷新源,然后重新搜索下载

  2. scp /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib root@your.ip.address.xx:/Library/MobileSubstrate/DynamicLibraries

  3. scp libReveal.plist root@192.168.XXX.XXX:/Library/MobileSubstrate/DynamicLibraries

  4. 重启设备,就可以在电脑查看libReveal.plist中的App UI了。

libReveal.plist文件格式可以为:

1
2
3
4
5
6
{
Filter = {
Bundles = ("com.sina.weibo");
Bundles = ("com.burbn.instagram");
}
}

Bundles里面填写你对应想看的App SoftwareBundleIdentifier。这个Plist文件一开始,我很纠结因为网上的文件格式不同于 Apple的标准Plist XML格式。然而拷贝进越狱设备之后,能起效我也就不在意了。

  1. 我一开始使用的是Reveal 1.6.3版本,拷贝进越狱设备里的也是对应的dylib。没起作用,换了同事的1.0.3安装包就work了。
  2. 连接内网之后,电脑和手机一定要确认在同一个局域网(我猜想 Reveal动态库是在局域网广播UI层级信息的)

ASR&Translator

ASR(Automatic Speech Recognition)

语音识别使用 Apple iOS10 SDK中的 speech.framework即可

语音识别(ASR)领域,业界还有一个leading framework: Nuance

他们主要搞语音和图像方面,卖各种商业解决方案。早年间传闻 iPhone4S 那代的Siri 语音识别使用Nuance服务器

Translator

语音识别之后,我们可以利用文本做一些翻译功能:

这里有些branchmark

不过因为,国内Google被墙了。大多数用户访问不了Google的服务。不用比较,只能选择Microsoft了。。。囧

使用Microsoft Translator服务可以实现 翻译功能
支持文本翻译文本,文本翻译成语音

Getting Started

To access the Microsoft Translator Text Translation API you will need to sign up for Microsoft Azure. Follow these steps.

  1. Sign up for a Microsoft Azure account at http://azure.com
  2. After you have an account go to http://portal.azure.com
  3. Select the + New option.
  4. Select Intelligence from the list of services.
  5. Select Cognitive Services APIs
  6. Select the API Type option.
  7. Select Text Translation.
  8. In the Pricing Tier section select the pricing tier that fits your needs.
  9. Fill out the rest of the form, and select the Create button.
  10. You are now subscribed to Microsoft Translator.
  11. Go to All Resources and select the Microsoft Translator API you subscribed to.
  12. Go to the Keys option and copy your subscription key to access the service.

Translator 文本 API

Plan Description Price
免费 限每月 2M 文字 免费
S1 标准 基于Azure使用量 每 1M 文字 $10
S2 标准 每月 250M 文字 每月 $2,055,超额部分每 1M 文字 $8.22
S3 标准 每月 1B 文字 每月 $6K,超额部分每 1M 文字 $6.00
S4 标准 每月 10B 文字 每月 $45K,超额部分每 1M 文字 $4.5

Translator 语音 API

Plan Description Price
免费 限每月 2小时 免费
S1 标准 基于Azure使用量 每小时 $12
S2 标准 每月 100 小时 每月 $1K,超出部分每小时 $10
S2 标准 每月 1000 小时 每月 $7K,超额部分每小时 $7
S4 标准 每月 10,000 小时 每月 $35K,超额部分每小时 $3.50

PS:还有face api

具体的文档地址:text-translate

一个封装了Microsoft Speech Translator服务的功能库MSTranslateVendor

Obtaining an Access Token

上面举出的Microsoft的API资料都是过期了的文档。不要花时间在这上面-,-。。。

一句话就是:最新的API 已经不需要去注册Azure DataMarket application了,不用client_idclient_secret。直接使用Subscription Key即可

Jumping into SpriteKit

如果你对个人开发者还有一些念想的话,游戏领域可能是个人开发者值得投入精力尝试的。
比起应用来说,游戏没有涉及到那么多的资源如服务端,产品经理,交互。 如果是简单的样式可能美工方面自己现学就可以搞定了
我这里指的是一些简单的游戏,如单场景的消除类:

大体都是一些较为经典的个人开发者作品。盈利模式可以是:App收费,App接入市场广告,App道具收费

好了,下面进入正题。早些年做游戏的话,如果注重跨平台 你的选择可能就是 Cocos2d-X 了。 但其实Apple自身是有套游戏框架: SpriteKit

About SpriteKit

可以按照 Jumping into SpriteKit 这个章节熟悉一下 SpriteKit 。

Getting Started

这里要注意的是 第一步里面一定要将storyboard里面的ViewController 的view object class 设置为 SKView。

Open the storyboard for the project. It has a single view controller (SpriteViewController). Select the view controller’s view object and change its class to SKView.

SpriteKit内容都是绘制在SKView里面的,而SKView渲染出来的东西叫做 场景(scene)SKScene类型。 场景的交互是响应传递链的。

Using Actions to Animate Scenes

每个SKNode都有name属性作为他的描述,给节点起个名字方便你后面查找和指定节点执行动作。 类似UIKit里面的view.tag

SKAction 需要重点了解一下。

Transitioning Between Scenes

这个是区别于UIKit的,没有页面跳转的概念。这里指的 Scene A切换为Scene B。
使用SKTransition可以添加切换动画。查看SKTransition.h,SpriteKit还是自带了蛮多场景切换动画。

Creating Nodes That Interact with Each Other

这个是最有意思的地方: 可以添加SKNode physicsBody属性,让其有物理特性。
这里还提到了 didSimulatePhysics,实际上 SpriteKit中 Scene 是在实时重复绘制的。他的每一帧绘制包括了以下时机回调

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
/**
Override this to perform per-frame game logic. Called exactly once per frame before any actions are evaluated and any physics are simulated.

@param currentTime the current time in the app. This must be monotonically increasing.
*/
- (void)update:(NSTimeInterval)currentTime;

/**
Override this to perform game logic. Called exactly once per frame after any actions have been evaluated but before any physics are simulated. Any additional actions applied is not evaluated until the next update.
*/
- (void)didEvaluateActions;

/**
Override this to perform game logic. Called exactly once per frame after any actions have been evaluated and any physics have been simulated. Any additional actions applied is not evaluated until the next update. Any changes to physics bodies is not simulated until the next update.
*/
- (void)didSimulatePhysics;

/**
Override this to perform game logic. Called exactly once per frame after any enabled constraints have been applied. Any additional actions applied is not evaluated until the next update. Any changes to physics bodies is not simulated until the next update. Any changes to constarints will not be applied until the next update.
*/
- (void)didApplyConstraints NS_AVAILABLE(10_10, 8_0);

/**
Override this to perform game logic. Called after all update logic has been completed. Any additional actions applied are not evaluated until the next update. Any changes to physics bodies are not simulated until the next update. Any changes to constarints will not be applied until the next update.

No futher update logic will be applied to the scene after this call. Any values set on nodes here will be used when the scene is rendered for the current frame.
*/
- (void)didFinishUpdate NS_AVAILABLE(10_10, 8_0);

update_loop

最后

来自 raywenderlich 的学习资料

iOS Responder Chain

Hit-Testing Returns the View Where a Touch Occurred

iOS 使用 hit-testing 找到具体的视图来响应点击。 Hit-testing实现检查了点击手势是落在了哪个视图区域。如果找到了视图,会递归遍历检查该视图下的所有子视图。最终会找到 位于视图层次最靠前(最靠近屏幕)的视图。iOS将其确定为hit-test view,将由它来消费这次touch事件。

Figure 2-1  Hit-testing returns the subview that was touched

更形象点的例子。我们结合Figure 2-1图片来看

1. 当点击手势位于view A。 我们要检查子视图view B 和view C
2. 点击手势没有位于view B,所以我们开始检查view C的子视图view D 和view E
3. 点击手势没有位于view D,所以点击手势被view E响应

hitTest:withEvent:CGPointUIEvent参数找到对应的hit test view

The hitTest:withEvent: method returns the hit test view for a given CGPoint and UIEvent. The hitTest:withEvent: method begins by calling the pointInside:withEvent: method on itself. If the point passed into hitTest:withEvent: is inside the bounds of the view, pointInside:withEvent: returns YES. Then, the method recursively calls hitTest:withEvent: on every subview that returns YES.

If the point passed into hitTest:withEvent: is not inside the bounds of the view, the first call to the pointInside:withEvent: method returns NO, the point is ignored, and hitTest:withEvent: returns nil. If a subview returns NO, that whole branch of the view hierarchy is ignored, because if the touch did not occur in that subview, it also did not occur in any of that subview’s subviews. This means that any point in a subview that is outside of its superview can’t receive touch events because the touch point has to be within the bounds of the superview and the subview. This can occur if the subview’s clipsToBounds property is set to NO.

Note: A touch object is associated with its hit-test view for its lifetime, even if the touch later moves outside the view.
The hit-test view is given the first opportunity to handle a touch event. If the hit-test view cannot handle an event, the event travels up that view’s chain of responders as described in The Responder Chain Is Made Up of Responder Objects until the system finds an object that can handle it.

The Responder Chain Is Made Up of Responder Objects

我们再聊聊如果事件没有被消费掉怎么办?

答案是: 事件会跟随响应链一级一级向上传递。最终给到 Application(继承自UIResponer)。

一个响应者对象(UIApplication/UIViewController/UIView)是能够响应和处理事件的。

第一响应者被设计为能第一个接受事件。要成为第一响应者,需要做以下两件事。

1. 重写 canBecomeFirstResponder 返回YES
2. 处理 becomeFirstResponder 调用。 必要情况可以主动调用这个方法

提示: becomeFirstResponder调用时机最好 在视图都准备到位之后。比如不要在viewWillAppear阶段。应该在viewDidAppear阶段在调用

响应者能处理的事件有:

* 触摸事件(Touch Events): 如果`hit test view`没有消费这个事件。事件按照响应链被向上传递
* 运动事件(Motion Events): UIKit处理这个事件,第一响应者必须要实现` motionBegan:withEvent:` 或 `motionEnded:withEvent:`方法
* 遥控事件(Remote control events): 第一响应者必须实现` remoteControlReceivedWithEvent:`方法
* Action messages: 比如按钮啊、开关啊。如果接受者的action为nil,也会按照响应链向上传递事件
* Editing-menu messages.  
* Text editing. 用户点击输入框或文本视图,UIKit默认自动将其置为第一响应者。接着,模拟键盘被唤起,光标聚焦。

只有Text FieldText View在用户点击之后,UIKit会自动将其置为第一响应者。其他的需要主动的调用becomeFirstResponder方法

响应链向上传递事件是有一些规则的。 看看以下图片的两种情况:

iOS_responder_chain

左图:

1. initial view不处理事件/消息,传递给父级view
2. 父级view接受到事件,不处理的话接着传递给父级view(viewController's view)
3. 父级view(viewController's view)接受到事件,不处理的话传递给viewController
4. viewController接收到事件,不处理传递给windows
5. windows接受到事件,不处理传递给 Application
6. 如果Application最后再不处理的话,事件就被忽略了。。

其中传递链路上任一响应者处理了事件,就中断传递了

右图:

1. initial view不处理事件/消息,传递给父级view(viewController's view)
2. 父级view接受到事件,传递给viewController
3. viewController接受到事件,传递给父级view
    步骤1-3 重复直到找到rootViewController
4. rootViewController 传递给 windows
5. windows 传递给 Application

参考链接

LeetCode

最近整理了自己在LeetCode上所有Accepted的提交并开源在LeetCode-Solver

之前遇到有意思的题目,会专门写篇博客讲讲。现在感觉这要刷完leetcode不得上百篇了,没这必要。。所以后面计划把思路注释和代码写在一起。一并提交到leetcode上。

遇到可以用Swift的题目,也会使用Swift来练练手。解题代码还是多数使用C/C++

QCon 2016 上海站见闻

有幸参与了QCon 2016的上海站会议。官方宣传这是一个中高端会议,专题丰富涵盖了当前互联网各种技术领域。

10月20号周四 10月21号周五 10月22号周五
前端技术实践 玩转大数据 Growth Hacking,IoT & React Native
安全之战 移动开发探索 互联网广告系统实战
新Java,新未来 移动视频 工程团队管理
无处不在的容器 让架构更简单 技术创业
微服务架构,我们该如何实践? 运维与监控 机器学习与深度学习
大数据应用与系统优化实践(厂商共建专题) 大数据服务与应用 用户体验设计
业务上云技术拆解(厂商共建专题) 高并发与实时处理架构设计(厂商共建专题) 研发支撑体系
智能出行 - 高德开放平台专场(厂商共建专题) 微服务实践与架构演进之路(厂商共建专题) 业务系统架构
大数据分析与应用
大规模前端系统

QCon 上海站PPT 下载

其中每个专题会在固定的宴会厅进行,由专题出品人主持专题的会议开展。专题下面是各个议题,时长大致为45分钟包含Q&A环节。

根据自身技术栈出发,我选择了以下专题

  • 20号 前端技术实践
  • 21号 移动开发探索
  • 22号 Growth Hacking & 用户体验设计 & 技术创业

前端技术实践

周四的专题对于 客户端开发来说稍显尴尬,没有与之相关的议题。 我选择了和客户端相对平等的前端专题。 下面重点介绍一下我认为有意思的议题:

Vue 2.0: 渐进式前端解决方案

讲师@尤雨溪,很早就关注他了。这次总算见到真人了。Vue已经在Github上收获了3w+颗star,对于开源项目来说,无疑是相当成功了。

我很认同他对框架的理解。框架的存在是为了帮助我们应对复杂度。但同时框架也有复杂度,Pick the right tool for the job.

开篇的框架理解引出了 Vue ,这个渐进式前端解决方案。可能就是他对当下JS开发环境的一个答案。

框架本身,我不是JS开发,没有使用经历就不做主观评价了。 回到一开始的”框架”,他是如何解决框架复杂度的呢? 答案是”渐进式”。

progressive

Vue框架提供 声明式渲染 核心功能,加上可选的附加库/工具链,来打造弹性复杂度。这个区别于传统框架,集成整合了一整套解决方案。提高框架复杂度,增加开发者的学习使用成本。

这种框架的设计思路,是明智的。

Progressive Web App:反击序章

讲师@黄玄开篇讲述了web在当下移动时代的窘境。有一定的技术高度看待这类行业问题是蛮赞的。不认可web在移动时代只有Hybrid这一种选择。

开始布道 PWA(Progressive Web App),认为这是web针对native的一次反击。
The new application model for Web

  • add To HomeScreen (web 也有像native原生应用一样的桌面图标入口)
  • Instant Loading & ReliableExperience (提供一种缓存机制,类似原生应用的首次下载)
  • Push Notifications (web 也可以像原生应用一样接受通知)

…等等一些特性。让我这种native同学感到十分新鲜。确实在 纯web 到 纯navite 之间有许多可能的点

反观业界,应该很少有企业对自己的网站能支持到这种体验程度。特别是在iOS 9提供了 web mark机制之后,直接把web流量切给了native。不知道有没有统计过用户是愿意留在浏览器 还是 更愿意跳往原生应用

不过在原生应用开发实践中,我个人偏向于 hybrid 方案。这个取决于你的App大多数是什么样的业务场景,需要权衡体验和发布节奏等利益点。

  1. 纯native,体验是有可以保证的。 缺点就是发版受限
  2. hybrid,这里讨论指的是 RN,Weex这种跨端方案。好处是 发布不受限制,一人开发跨两端(iOS,Android) 节省人力。体验稍逊于native
  3. 纯web,包括不经优化,直接套用webview的这种。缺点是体验差,卡顿,load时间久

移动开发探索

这个专题与我息息相关,吸引的我是这些议题

React Native 业务实践和性能优化

who use RN

讲师 携程@赵辛贵。 携程积极拥抱RN技术,多数业务和页面使用RN搭建。
好处明显

  • size优势,RN页面大小计算脱离原生包大小
  • 支持动态发布,跨端节省开发人力
  • RN技术成熟,社区活跃(参会时和旁边的途牛网开发交流,他们动态方案也是选择RN)

携程内部还演化出了CRN(ctrip RN)业务框架。做了性能和稳定性优化(思路参见PPT),规划支持CRN-web实现跨三端(iOS,android,H5)。

外界RN动态方案使用的如火如荼,他们正在证明这是未来移动前端开发的方向。

Weex 极致性能优化

公司同学分享,Weex性能优化的几个思路方向。围绕性能,干货较多。
不过,这种经历只能听听思路。 基本没有实践复用场景 :)
安利Weex性能很好,倒是真的。

蘑菇街 App 的稳定性与性能实践

从用户角度出发看 性能和稳定性问题:

  • 闪退
  • 打开慢
  • 滑动不流畅
  • 耗电
  • 网络不畅/出错
  • 流量大

都是客户端常见的问题,相信各大公司都有自己的答案实践。

他们有一个比较有意思的工具:AppMate(小蘑菇) 提供给测试和业务开发进行开发阶段的一个性能把控。

或许是在阿里的原因,这些东西听来都不足以让我兴奋。

Growth Hacking 最新动态和最佳实践

这个是受团队运营产品委托,刻意留意了这个议题(我所在的天猫team也负责push通道来召回用户,提高留存)

Growth Hacking 最新动态和最佳实践

  1. 数据看板 - 数据分析 - 数据监控
  2. 数据驱动产品决策
  3. AB 测试实验
  4. 灰度渠道发布

其中的数据分析提到:”Core Action” 指到的是核心操作,关键路径到达。
列举了几类APP的”Core Action”

APP类型 Core Action
Facebook connect(连接?相互关注?朋友互动?)
Slack/Wechat 接发信息
Pinterest 晒图
电商 浏览、下单购买
知乎 回答,点赞,收藏,感谢
互联网金融 购买理财产品
二手车 购买,砍价

这里有一个观点 重点关心产品核心操作是否被用户触达
recall
这个模型 描述了 新客或流失客户(最近打开app的时候是在30-60天之前),我们应该积极的通过Push通道来进行用户召回,当用户下沉为忠实用户之后。要谨慎使用push。这个模型是可逆的,也就是说当用户不再活跃的时候,回归上层我们也要通过push相关利益点来进行召回。

分享了业界做病毒式扩散的几个经典例子:

  1. airbnb 分享给朋友,两方都能获得25刀的优惠券
  2. PRISMA 图片滤镜软件 制作图片打上软件水印
  3. Pokémon Go 户外现象级户外捕捉小精灵

这些快速扩散区别于 用户推荐和口口相传。分享者和你分享的时候,并不是在说这个平台或者软件如何如何,还是在和你分享他们得到了什么。分享一定要满足用户一些心理:增加声望、财富、乐趣。

最后病毒式扩散一定要作用在Core Action上。

产品思维和设计思维详解

讲师 @张玉婷,她的设计思路蛮不错的。她认为设计师设计产品交互的时候,一定是从产品架构出发分析产品用户需求,进而推导出产品界面。而不是接到需求在网上一通翻找竞品界面。不同的需求场景有不同的用户语境,进而有不同的视觉交互表达。

比如 从业参与的Weico客户端,是设计驱动的一个产品。目前是最大的微博第三方客户端,跳脱开 官方微博客户端的通用性和功能化,主打个性化和情感化。围绕核心功能(阅读和发送微博)进行 特色的设计扩展。

后面提及高德客户端的交互设计,虽然 App类型不同,设计很难参考。但思路是ok的。要关注 用户与产品发生交互的真实场景,介绍了高德在这方面的交互实践。

如何选好技术初创风口:从0到1,1到100

这个。实话说我睡着了,可能是吃完中饭乏了。恩,是的。

Gas Station

LeetCode - 134.Gas Station

题目大意:

n个加油站分布在 环形路线上。其中每个加油站的储油量为 gas[i]。 你有一辆巨大油箱(可以无限加油)的汽车,从 站点i到下一站i+1,油耗为 cost[i]。一开始的时候,汽车油箱是空的。

如果你能一次开完全程的话,请返回一个起始站点。否则返回-1表示无法完成。

  • 提示: 测试用例保证答案是唯一的。

  • 疑问:题目未明显指出 是顺时针开或逆时针开。但从测试数据理解 站点i 到 站点i+1,应该是顺时针开的。

解题思路:

首先可以明确的是,如果 总油耗sum{cost[0...n]} 大于 总储量sum{gas[0...n]}的话,是无法完成任务的。直接返回-1。

接着,思考一下当 总油耗 小等于 总储量时,是否必然存在可行解。

C(i) + C(i+1) + C(i+2) + C(i+3) … + C(i+n) = C

G(i) + G(i+1) + G(i+2) + G(i+3) … + G(i+n) = G

G >= C

如果 平均分布的话, 则 G(i)>=C(i) 。 必然是ok的。

考虑极限情况,C(i) 油耗巨大。贪心策略保证每站必停(油不加白不加),我们从i站点逆时针选点。基于贪心考虑,逆时针选站点要保证对经过C(i)最大油耗是有利:每一次后退选点对总油耗是正向的。

枚举 (Time Limit Exceeded)

时间复杂度 大致为 O(n^2)。 测试用例数据极为暴力。。

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
 int goRound(int starInx,int *gas,int gasSize,int *cost,int costSize){
int nowGas = 0;
int nowIdx = starInx;
for(int i=0;i<gasSize;i++){
nowGas += gas[nowIdx];
if(nowGas < cost[nowIdx]) return -1;

nowGas -= cost[nowIdx];
nowIdx = (nowIdx+1)%gasSize;
}

return nowGas >= 0 ? starInx : -1;
}

int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {
int ans = -1;
int totalGas = 0, totalCosts = 0;

for(int i=0;i<costSize;i++){
totalCosts += cost[i];
}

for(int i=0;i<gasSize;i++){
totalGas += gas[i];
}

if(totalGas < totalCosts) return ans;

for(int i=0;i<gasSize;i++){
if((ans = goRound(i,gas,gasSize,cost,costSize)) != -1){
break;
}
}

return ans;
}

贪心 (Accepted)

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
 int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {
int totalGas = 0, totalCosts = 0;
int maxCostIdx = -1, maxCost = -1;
for(int i=0;i<costSize;i++){
totalCosts += cost[i];
if(cost[i] > maxCost){
maxCostIdx = i;
maxCost = cost[i];
}
}

for(int i=0;i<gasSize;i++){
totalGas += gas[i];
}

if(totalGas < totalCosts) return -1;

int tmp = maxCostIdx;
int tmpMaxgas = gas[tmp];
int tmpMaxCost = cost[tmp];

while (tmpMaxgas < tmpMaxCost || gas[tmp] >= cost[tmp]) { // 贪心策略
tmp--;
if(tmp == -1) tmp = gasSize - 1;
if(tmp == maxCostIdx) break;


tmpMaxgas += gas[tmp];
tmpMaxCost += cost[tmp];
}


return (tmp+1)%gasSize;
}

Accepted