-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
359 lines (359 loc) · 201 KB
/
search.xml
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[iOS代码规范]]></title>
<url>%2F2020%2F05%2F15%2FiOS%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83%2F</url>
<content type="text"><![CDATA[编程核心原则:原则一:代码应该简洁易懂,逻辑清晰 因为软件是需要人来维护的。这个人在未来很可能不是你。所以首先是为人编写程序,其次才是计算机: 不要过分追求技巧,降低程序的可读性。 简洁的代码可以让bug无处藏身 原则二:面向变化编程,而不是面向需求编程。 需求是暂时的,只有变化才是永恒的。 本次迭代不能仅仅为了当前的需求,写出扩展性强,易修改的程序才是负责任的做法,对自己负责,对公司负责。 原则三:先保证程序的正确性,防止过度工程 过度工程(over-engineering):在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。 先写出可用的代码,反复推敲,再考虑是否需要重用的问题。 先写出可用,简单,明显没有bug的代码,再考虑测试的问题。 Objective-C 编程规范if 语句不要使用过多分支,分支比较多的情况下要善于使用return来提前返回 推荐: 123456- (void)someMethod { if (!goodCondition) { return; } //Do something} 不推荐这样写: 12345- (void)someMethod { if (goodCondition) { //Do something }} 举个典型的例子 12345678910111213141516171819202122232425262728293031323334353637-(id)initWithDictionary:(NSDictionary*)dict error:(NSError)err{ //方法1. 参数为nil if (!dict) { if (err) *err = [JSONModelError errorInputIsNil]; return nil; } //方法2. 参数不是nil,但也不是字典 if (![dict isKindOfClass:[NSDictionary class]]) { if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."]; return nil; } //方法3. 初始化 self = [self init]; if (!self) { //初始化失败 if (err) *err = [JSONModelError errorModelIsInvalid]; return nil; } //方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO) if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) { return nil; } //方法5. 核心方法:字典的key与模型的属性的映射 if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) { return nil; } //方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回 if (![self validate:err]) { return nil; } //方法7. 终于通过了!成功返回model return self; 条件表达式如果很长(超过2个表达式),则需要将他们提取出来赋给一个BOOL值 推荐 12345678Bool isContains = sessionName.hasPrefix("Swift")Bool isCurrentYear = sessionDateCompontents.year == 2014let isSwiftSession = nameContainsSwift && isCurrentYear && user.isLoginif (isSwiftSession) { //Do something} 不推荐 123if (sessionName.hasPrefix("Swift") && sessionDateCompontents.year == 2014 && user.isLogin ){ //Do something} 一个函数的长度必须限制在50行以内 通常来说,在阅读一个函数的时候,如果视需要跨过很长的垂直距离会非常影响代码的阅读体验。如果需要来回滚动眼球或代码才能看全一个方法,就会很影响思维的连贯性,对阅读代码的速度造成比较大的影响。最好的情况是在不滚动眼球或代码的情况下一眼就能将该方法的全部代码映入眼帘。 一个函数只做一件事(单一原则) 每个函数的职责都应该划分的很明确(就像类一样)。 推荐: 12dataConfiguration()viewConfiguration() 不推荐: 12345void dataConfiguration(){ ... viewConfiguration()} 函数命名要清晰 命名应该尽可能的清晰和简洁,但在 Objective-C 中,清晰比简洁更重要。由于 Xcode 强大的自动补全功能,我们不必担心名称过长的问题 推荐 123- (void) insertObject:(UInteger)atIndex;- (void) removeObjectAtIndex:(UInteger)atIndex; 不推荐 12- (void) insert:(int)at;- (void) remove:(int)at; 在 init 和 dealloc 中不要用存取方法访问实例变量当 init、dealloc 方法被执行时,类的运行时环境不是处于正常状态的,使用存取方法访问变量可能会导致不可预料的结果,因此应当在这两个方法内直接访问实例变量。 1234567891011121314151617181920212223242526//正确,直接访问实例变量- (instancetype)init { self = [super init]; if (self) { _bar = [[NSMutableString alloc] init]; } return self;}- (void)dealloc { [_bar release]; [super dealloc];}// 错误,不要通过存取方法访问- (instancetype)init { self = [super init]; if (self) { self.bar = [NSMutableString string]; } return self;}- (void)dealloc { self.bar = nil; [super dealloc];} 不要用点分语法来调用方法,只用来访问属性。这样是为了防止代码可读性问题。 推荐 1view.backgroundColor = [UIColor orangeColor]; 不推荐 1[view setBackgroundColor:[UIColor orangeColor]]; 注释 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。 容易产生歧义的代码 设计以上三种情况下。都必须写好注释。在xcode中自带快捷注释方法。选好要注释的方法或类。option + command + / 系统常用类作实例变量声明时加入后缀 类型 后缀 UIView View UILabel Lbl UIButton Btn UIImage Img UIImageView ImgView NSArray Ary NSMutableArray Mary NSDictionary Dict NSMutableDictionary Mdict NSString Str NSMutableString Mstr NSSet Set NSMutableSet Mset CGRect函数 其实iOS内部已经提供了相应的获取CGRect各个部分的函数了,它们的可读性比较高,而且简短,推荐使用 推荐 123456CGRect frame = self.view.frame; CGFloat x = CGRectGetMinX(frame); CGFloat y = CGRectGetMinY(frame); CGFloat width = CGRectGetWidth(frame); CGFloat height = CGRectGetHeight(frame); CGRect frame = CGRectMake(0.0, 0.0, width, height); 不推荐 123456CGRect frame = self.view.frame; CGFloat x = frame.origin.x; CGFloat y = frame.origin.y; CGFloat width = frame.size.width; CGFloat height = frame.size.height; CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size }; 范型 如果容器元素唯一时,建议在定义NSArray、NSDictionary、NSSet,指定容器的元素 推荐 12@property (nonatimic, readwrite, copy) NSArray <DSRUser*> *userAry;@property (nonatimic, readwrite, copy) NSDictionary <NSString *,DSRUser*> *userDict 不推荐 12@property (nonatimic, readwrite, copy) NSArray *userAry;@property (nonatimic, readwrite, copy) NSDictionary * userDict 尽量使用字面量值来创建 NSString , NSDictionary , NSArray , NSNumber 这些不可变对象: 推荐 1234NSArray *nameAry = @[@"zhangsan", @"lishi", @"wangwu"];NSDictionary *productDict = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"}; 不推荐 12NSArray * nameAry = [NSArray arrayWithObjects:@"zhangsan", @"lishi", @"wangwu", nil];NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys:@"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill"]; 属性的命名使用小驼峰 推荐: 1@property (nonatomic, readwrite, copy) NSString * tipStr; 属性的关键字推荐按照 原子性,读写,内存管理的顺序排列推荐: 12@property (nonatomic, readwrite, copy) NSString *nameStr;@property (nonatomic, readonly, assign) NSUInterger old; Block属性应该使用copy关键字 推荐: 12typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock; 形容词性的BOOL属性的getter应该加上is前缀 推荐 1@property (nonatomic, getter=isEditable, assign) BOOL editable; 建议尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写,避免多处地方修改属性值,方便管理代码 12在头文件中,设置对象属性为 `readonly`在实现文件中设置为 `readwrite` 方法名中不应使用and 推荐 1- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height; 不推荐 1- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height; 方法实现时,如果参数过长(参数超过3个),则令每个参数占用一行,以冒号对齐 123456- (void)doSomethingWith:(NSString *)theFoo rect:(CGRect)theRect interval:(CGFloat)theInterval{ //Implementation} 方法名前缀 刷新视图的方法名要以refresh为首。 更新数据的方法名要以update为首 点击事件的方法实现要以click为首 推荐 12- (void)refreshHeaderViewWithCount:(NSUInteger)count;- (void)updateDataSourceWithViewModel:(ViewModel*)viewModel; 类遵循代理过多的时候(超过3个协议),换行对齐显示 1234567@interface ShopViewController () <UIGestureRecognizerDelegate, HXSClickEventDelegate, UITableViewDelegate, UITableViewDataSource>{ ... } 代理的方法需要明确必须执行和可不执行 代理方法在默认情况下都是必须执行的,然而在设计一组代理方法的时候,有些方法可以不是必须执行(是因为存在默认配置),这些方法就需要使用@optional关键字来修饰: 123456@protocol HomeCellViewDeleagte <NSObject>@optional- (void)switchValueChanged:(id)sender;- (void)clickButtonToDetail:(id)sender;@end 使用定时器时。要实现startTimer 和 stopTimer 两个函数;实现函数尽量不要加入其他的业务逻辑 123456789101112- (void) startTimer{ if (_timer){ [self stopTimer]; } _timer = ...}- (void) stopTimer{ [_timer invalidate]; _timer = nil;} 方法与参数名,一般都以小写字母开头;对于缩写字符串,可以大写字符开头 1+ (NSURL *)URLWithString:(NSString *)URLString; 参考文献https://juejin.im/post/5940c8befe88c2006a468ea6#heading-29 https://github.com/QianKaiLu/Objective-C-Coding-Guidelines-In-Chinese]]></content>
<categories>
<category>Objective-C</category>
</categories>
</entry>
<entry>
<title><![CDATA[iOS面试题Swift部分]]></title>
<url>%2F2020%2F04%2F21%2FiOS%E9%9D%A2%E8%AF%95%E9%A2%98Swift%E9%83%A8%E5%88%86%2F</url>
<content type="text"><![CDATA[Swift基础面试题1、Class和Struct 的区别 类是引用类型,结构体是值类型。结构体不可以继承值类型被赋予给一个变量、常量或者被传递给一个函数时。其值会倍拷贝。引用类型在被赋予到一个变量、常量或者被传递到一个函数时、其值不会被拷贝。 2、defer的用法。 使用defer代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出异常,这段代码都会被执行。 defer语句块中的代码,会在当前作用域结束前掉用。每当一个作用域结束就进行该作用域defer执行。 3、什么是高阶函数。 一个函数如果可以一某一个个函数作为参数,或者是返回值。那么这个函数就称之为高阶函数。 4、什么是copy on write时候 写时复制,指的是swift中的值类型,并不会一开始赋值的时候就去复制,只有再需要修改的时候,才去复制。 5、String 与 NSString 的关系与区别 String 是结构体,值类型,NSStirng 是类,引用类型NSString 与 String之间可以随意转换 6、associatedtype 的作用 简单来说就是protocol 使用的泛型 实现协议的时候,可以使用typealias 指定特定的类型,也可以自动推断 7、final关键字 final用于限制继承和重写,如果只是需要在某一个属性前加一个final。如果需要限制整个类无法被继承,那么可用用类名之前加一个final 8、public 和 open 的区别 这两个都用于模块声明需要对外界暴露的函数,区别在于,public 修饰的类,在模块外无法被继承,而open则可以任意继承,公开度来说,open>public 9、定义静态方法时关键字 static 和 class 有什么区别 static 定义的方法不可以被子类继承,class则可以 10、throws 和 rethrows 的用法与作用 throws 用在函数上,表示这个函数会抛出异常 rethrows 与 throws 类似,不过只适用于参数中有函数,且函数会抛出异常的情况下,rethrows 可以用throws替换,反过来不行.如 11、mutating关键字 结构体和枚举类型中修改 self 或其属性的方法必须将该实例方法标注为 mutating,否则无法在方法里改变自己的变量。 实现协议中的 mutating 方法时,若是类类型,则不用写 mutating 关键字。而对于结构体和枚举,则必须写 mutating 关键字。 12、必包是引用类型吗 必包是引用类型。如果一个必包被分配给一个变量。这个变量复制给另一个变量,那么他们引用的是同一个必包,他们的捕捉列表也会被复制。]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>面试题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iOS面试题Object-C部分]]></title>
<url>%2F2020%2F04%2F21%2FiOS%E9%9D%A2%E8%AF%95%E9%A2%98Object-C%E9%83%A8%E5%88%86%2F</url>
<content type="text"><![CDATA[iOS面试题 Object-C部分Object-C 基础题1、NSSet 一般用什么关键字修饰。为什么?2、浅拷贝和深拷贝的区别?浅拷贝:只复制指向对象的指针,而不复制引用对象本身。深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。 3、使用Block时需要注意哪些问题。 在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针: __weak typeof(self) weakSelf = self; 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。 __strong typeof(self) strongSelf = weakSelf; 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量 4、UIView 和 CALayer 是什么关系?UIView 继承自UIResponder类,可以响应事件。CALayer直接继承NSObject类。不可以响应事件。UIView时CALayer的代理。UIView主要处理事件,CALayer负责绘制。 5、iOS中数据持久化方案有哪些?NSUserDefault 简单数据快速读写Property list (属性列表)文件存储Archiver (归档)SQLite 本地数据库 6、单个viewController的生命周期? initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib) loadView:加载view viewDidLoad:view加载完毕 viewWillAppear:控制器的view将要显示 viewWillLayoutSubviews:控制器的view将要布局子控件 viewDidLayoutSubviews:控制器的view布局子控件完成 viewDidAppear:控制器的view完全显示 viewWillDisappear:控制器的view即将消失的时候 viewDidDisappear:控制器的view完全消失的时候 dealloc 控制器销毁 7、Objective-C 如何对内存管理的?答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池 1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。 2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。 3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。 8、继承、分类和类扩展 分类有名字,类扩展没有分类名字,是一种特殊的分类。 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。 继承可以增加,修改或者删除方法,并且可以增加属性。 9、通常对象为啥要用strong?用strong就是要持有他的指针,指针就是指向内存,就是叫被引用的内存不被释放 10、instancetype和id的异同?1.相同点都可以作为方法的返回类型2.不同点1.instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;2.instancetype只能作为返回值,不能像id那样作为参数 11、isKindOfClass和isMemberOfClass的区别isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员 isMemberOfClass只能确定一个对象是否是当前类的成员 12、delegate 和 notification 的区别二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。 13、iOS多线程技术有哪几种方式?pthread、NSThread、GCD、NSOperation 14、什么是懒加载,懒加载有什么作用用到时才初始化,延迟加载。延时加载可以避免内存过高 15、有哪些常见的Crash场景 访问了僵尸对象 访问了不存在的方法 数组越界 16、说一下静态库和动态库之间的区别动态库形式:.dylib 和 .framework 静态库形式:.a和.framework 区别: 静态库:链接时,静态库会被完整的复制到可执行文件中 系统动态库:链接时不复制,程序运行时有系统动态加载到内存,提供内存掉用,系统只加载一次,多个程序共用,节省内存。 17、使用atomic一定是线程安全的吗?不一定。只有在执行setter或getter方法时是线程安全的。 18、什么是 KVO 和 KVC?KVC(key-value-coding)键值编码,是一种间接访问实例变量的方法。提供一种机制来间接访问对象的属性 KVO是一种基于KVC实现的观察者模式。当指定的被观察的对象的属性更改了,KVO会以自动或手动方式通知观察者。 19 堆、栈栈:由系统管理分配和释放。一般存放函数的参数值,和局部变量(函数中的基本数据类型)。栈区的操作方式类似数据结构中的栈(先进后出) 堆:由程序员管理(分配释放),若程序员不释放,程序结束时可能由系统回收。一般由程序员 alloc、new 出来的对象。堆的操作方式与数据结构中的堆不同,操作方式类似于链表 Object-C 进阶题:1、什么是 RunLoop?2、说的OC是动态运行时语言是什么意思?主要是将数据类型的确定由编译时,推迟到了运行时。 简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。 3、isa指针?对象的isa指针指向所属类。类的isa指针指向所属的元类。元类的isa指针指向了根元类;跟元类指向了自己 4.Objective C都有哪些锁机制1.@synchronized 同步锁2.NSLock 对象锁3.NSRecursiveLock 递归锁4.NSConditionLock 条件锁5.pthread_mutex 互斥锁(C语言)6.dispatch_semaphore 信号量实现加锁(GCD) 5、NSNotificationCenter是在哪个线程发送的通知?6、load和initialize方法分别在什么时候调用的? 7、说一下hash算法。 8、什么是method swizzling? 9、runtime 如何实现 weak 属性 要实现 weak 属性,首先要搞清楚 weak 属性的特点: weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 那么 runtime 如何实现 weak 变量的自动置nil? runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。 10、dispatch_barrier_async的作用是什么? 栏栅函数 计算机基础题1、get 和 post 的区别 get用来获取数据,post用来提交数据 get参数有长度限制,而post无限制。 get是明文传输,post是放在请求体中 get请求会保存在浏览器历史记录中,还可能保存在web服务器的日志中 1、HTTP与HTTPS的区别? HTTPS协议需要到CA申请证书 HTTP是明文传输,HTTPS则是具有安全性的SSL加密传输 HTTP和HTTPS使用的端口也不一样,前者是80,后者是443 HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。 3、说一下TCP和UDP UDP协议是全称是用户数据协议,在OSI模型中,在第四层网络层。UDP有不提供数据包分组、组装和不对数据包进行排序的缺点,也就是说,当报问发送之后,是无法得知器是否安全完整的达到。 TCP协议全称是传输控制协议。是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP是面向连接的、可靠的、流协议。流就是指不间断的数据结构,你可以把它想象成排水管里面的水流。 实战应用相关的题目1、麻烦你设计个简单的图片内存缓存器(移除策略是一定要说的) 2、如何检测内存泄漏 3、如何优化App的性能。 4、抓包工具的使用。 5、苹果掉单问题如何解决 7、有一个数组,我要把里面的元素去重。你会怎么做? 8、Git与SVN之间的比较。]]></content>
<categories>
<category>Object-C</category>
</categories>
<tags>
<tag>面试题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SourceTree记住密码]]></title>
<url>%2F2019%2F11%2F15%2FSourceTree%E8%AE%B0%E4%BD%8F%E5%AF%86%E7%A0%81%2F</url>
<content type="text"><![CDATA[SoucreTree 每次pull 和 push 的时候都会弹出输入密码的提示框,挺烦的。 简单的设置流程 1、菜单->仓库->仓库设置->远程仓库->选中Url->点击编辑 2、Url路径修改比如http://xxxx/xxxx.git 改为 http:username:[email protected] 3、确定 到这里。以后这个仓库的代码就不用再输入密码了]]></content>
<tags>
<tag>工具</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iOS组件化开发(一):远程私有库的基本使用]]></title>
<url>%2F2019%2F11%2F06%2FiOS%E7%BB%84%E4%BB%B6%E8%AF%9D%E5%BC%80%E5%8F%91%E4%B8%80%2F</url>
<content type="text"><![CDATA[随着项目功能的不断增加,越来越多的开发人员,业务主线也随之越来越多,造成耦合越来越严重,编译越来越慢,测试不独立等一系列问题。为了解决此类情况,我们可以考虑组件话开发 概念: 组件话就是将一个单一的工程项目,分解成为各个独立的组件,然后按照某种方式,任意组织成一个拥有完整业务逻辑的工程 优势 独立:独立编写、编译、运行、测试 重用:功能代码的重复使用。比如不同项目使用同一功能模块 高效:任意增删模块,实现高效迭代 组件化还可以配合二进制化,提高项目编译速度 组件分类大体上分为三类:基础组件、功能组件和业务组件 基础组件:也称为公共组件,存放平时定义的宏、常量、协议、分类、对必要的第三方的封装类,以及各种处理工具类,如时间、日期、设备信息、文件处理、沙盒管理等 功能组件:自定义视图控件、一些特定功能的封装(如录音、播放音频封装) 业务组件:各种业务线 步骤归纳1、创建远程索引库和私有库 2、将远程索引库添加到本地 pod repo add 索引库名称 索引库地址 3、在本地创建一个pod模版 pod lib create 组件名称 将框架的核心代码添加到Classes目录下 4、本地安装测试核心代码是否可用 pod install。修改Spec描述文件,将修改好的模版库上传至远程私有库 5、上传代码 git init git add . git commit -m "提交描述" git remote add origin 远程私有库地址 git push origin master 6、打标签 git tag '0.1.0' git push --tags 7、提交spec至私有索引库 pod lib lint –private pod spec lint –private pod repo push 索引库名称 xx.podspec 8、使用 12source 官方索引库urlsource 私有库索引url 一、创建私用索引库这里以码云为例,创建一个TGSpecs的私有索引库,这玩意的作用如其名,就是用来索引的 二、本地添加私有索引库一、查看本地索引库1pod repo 如图,目前本地仅有github上的那个公有索引库 2、添加私有索引库将我们刚刚新建的私有索引库TGSpecs添加到本地 1234// pod repo add 索引库名称 索引库地址// pod repo remove 索引库名称 /*删除本地索引*/pod repo add TGSpecs https://gitee.com/zhongqing05/TGSpecs.git 再查看一下本索引 pod repo 现在本地就有两个索引库。到这里,索引库的的事情就先放一边了 三、创建组件库1、创建远程代码仓库创建TGBase为例,创建一个基础组件 2、本地快速创建模版库到合适的位置创建一个与组件名相同的文件夹,cd 进去后,使用命令 123//pod lib create 组件名pod lib create TGBase 这里会让你配置一些信息,根据自己的情况自行配置即可。 3、添加组件内容创建完成后,会自动帮我们打开相应的Example项目,TGBase目录中会出现如图这些文件,我们把基础组件相关的东西丢到Classes文件夹中,并且把ReplaceMe.m 文件删除 note:默认Classes文件夹中存放的文件就是pod install 时要下载下来的文件,当然可以通过修改spec文件的配置来更改位置 4、安装与测试本地库cd到 Example目录下,打开Podfile文件可以看到 1pod 'TGBase', :path => '../' 模版库已经默认帮我们在Podfile中指定了TGBase.podspec 的位置,使组件可以正常安装使用和方便测试 1pod install 如上图安装成功,Xcode打开项目,也能看到刚刚添加的文件 到这里,本地测试组件没问题后,我们接下来要将podspec文件进行修改,然后上传至私有索引库 5、修改Spec具体的配置说明可以参考Cocoapods创建第三方框架 主要修改内容 123456789 s.name = 'TGBase' s.version = '0.1.0' s.summary = 'TGBase.' s.description = <<-DESCTODO: TBase is Tiange Technology Basic component DESC s.homepage = 'https://gitee.com/zhongqing05/TGBase' s.source = { :git => 'https://gitee.com/zhongqing05/TGBase.git', :tag => s.version.to_s } s.source_files = 'TGBase/Classes/**/*' 四、上传组件代码1、将代码提交到组件仓库123456git add .git commit -m '提交说明'git remote add origin https://gitee.com/zhongqing05/TGBase.git//第一次push如果报错的话可以加上-f//git push -f origin mastergit push origin master 2、打标签12git tag '0.1.0'git push --tags 五、提交podspec到私有索引库在上传spec文件前我们可以做一个验证来节省时间,不然每次推送很久结果还是验证失败,会气死人的 1、本地验证Spec的必填字段12// 本地验证不会验证 s.source 中的tagpod lib lint 2、远程验证12// 远程验证会验证 s.source 中的tag,如果此时没有打上相应的标签则会报错pod spec lint 如果你刚才没有打标签,并上传至远程私有库进来进行验证,肯定是会报错 note:如果验证的是私有库,则后面加上 --private,否则会有警告,你可以选择 --alllow-warnings 来忽略警告 3、提交podspec123//pod repo push 私有索引库名称 spec名称.podspecpod repo push TGSpecs TGBase.podspec 这里的操作过程:先将我们的代码直接push到本地索引库TGSpecs,推送后自动会帮我们同步到远程索引库 再来看看码云上的私有索引库TGSpecs 来测试下我们的组件 1pod search TGBase 六、使用私有库现在我们可以试试通过pod的形式来添加TGBase,创建一个新项目,cd到工程目录下 1、添加Podfile文件1pod init 2、在Podfile的最顶部添加索引库Url12source 'https://github.com/CocoaPods/Specs.git'source 'https://gitee.com/zhongqing05/TGSpecs.git' 3、添加使用组件1pod 'TGBase' 4、安装组件1pod install 查看工程。从远程下载过来的代码 本文参考:https://juejin.im/post/5ac5d5abf265da2396129e63]]></content>
<categories>
<category>iOS组件化学习</category>
</categories>
</entry>
<entry>
<title><![CDATA[Xcode 报错:errsecinternalcomponent]]></title>
<url>%2F2019%2F10%2F30%2Ferrsecinternalcomponent%2F</url>
<content type="text"><![CDATA[前言前些天看了一篇技术大佬的博客,关于提升提升mac终端效率的。原文地址:https://juejin.im/post/5d91a07d6fb9a04de96e7cb0。昨天在跟同事的聊天中突然聊到了如何提高百度云盘的效率。所以今天就找到前些天到看的这篇文章,自己尝试弄好之后分享给同事。 百度云破解步骤1、按照这片文章的这段命令进行操作 1git clone https://github.com/CodeTips/BaiduNetdiskPlugin-macOS.git && ./BaiduNetdiskPlugin-macOS/Other/Install.sh 看到这段命令后,一眼就知道这个是从github下载过来的一个叫Install.sh的脚本文件。所以我也直接去github上看了。 2、按照步骤弄好之后,打开 百度网盘,点击登录。后面就弹出了一个访问钥匙串的弹框。 3、在第二部卡了非常久,输入电脑开机密码、和百度网盘密码也没用,这个弹框是要输入访问钥匙串的密码。这里有点懵逼,关于这种密码完全没有印象。所以只好再度google。看到这篇文章 https://www.mac69.com/mac/3477.html。 根据这篇文章去尝试输入mac密码,大概输入了4次之后就吧百度云盘给弄闪退了,我试了2次,后面看了一下这篇的相关评论,说这个工具已经没用了。好吧,到这里我就放弃了。 4、回到这个工程的github地址,按照方法进行卸载,到这里。百度网盘以破解失败介绍。1个小时就耗在这里了。上午就过去了 Xcode 所有工程包错:errsecinternalcomponent1、运行Xcode项目,包错 errsecinternalcomponent 具提内容没有截图这种错误第一眼看上去就是证书签名的问题。我仔细的检查了一下Xcode工程相关的设置,以及profile相关的文件,都没发现问题。就连钥匙串也检查了一遍。 2、检查了自身的问题只后,没折了,只好再次求助Google爸爸。在到相关的一些答案。看到最多的还是这个 1234xattr -rc .实在不行在删除 这个目录/Users/denny/Library/Developer/Xcode/DerivedData/ 或者 12cd ~/Library/Developer/Xcode/DerivedDataxattr -rc . 这2个方法是目前Goole爸爸给了最多的答案,但是还是没用,想了半天。才想起来那个钥匙串访问的弹框。后面我再运行一个其他的项目,结构报错一样。在这里我就猜到肯定是上午安装 百度云破解 所导致的问题 回到Github 搜索找到 BaiduNetdiskPlugin-macOS 这个开源库从这里,才是找到解决问题的方法。 1、查看这个库的的Issuse。很快的发现有人也提了相关的issuse。相关连接果然和我遇到的问题一样。好吧,最后的答案就是: 卸载此插件,然后重启电脑 写了这么多,就这一句话。记录一下整个安装破解百度云破解所带出的相关问题]]></content>
<categories>
<category>Error记录</category>
</categories>
<tags>
<tag>Error记录</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MLeaksFinder的使用介绍和原理]]></title>
<url>%2F2019%2F10%2F27%2FMLeaksFinder%E7%9A%84%E4%BD%BF%E7%94%A8%E4%BB%8B%E7%BB%8D%E5%92%8C%E5%8E%9F%E7%90%86%2F</url>
<content type="text"><![CDATA[MLeaksFinder 的主要用途检测UIViewController和UIView内存泄露的第三房库。引进MLeaksFinder后,可以向往常一样的开发。调试业务逻辑的过程并且自动的发现并提示内存泄露。开发者无需打开Instrument等工具,也无需为了找内存泄漏去跑额外的流程。这不仅很大的节省了开发者查找内存泄漏的时间成本,而且还能很及时的让开发这意识到哪了的代码有问题,并且修复。 下载地址点击这里使用方式把 MLeaksFinder 目录文件添加到你的项目中,就可以在运行时(debug模式下)帮你检测项目里的内存泄漏了,无需修改任何业务代码,而且只在debug下开启,完全也不影响你的release包 引入 MLeaksFinder 可选择 CocoaPods 安装,安装时注意有没有 warnings,特别是 OTHER_LDFLAGS 相关的warnings。如果有warnings,可以在工程的 Build Settings -> Other Linker Flags 加上 -ObjC 也可以手动引入,直接把MLeaksFinder的代码引入的引入到项目里即生效。如果把MLeaksFinder 做为子工程,需要在主工程的 Build Settings -> Other Linkers Flags 加上 -ObjC 引入后,先检验是否引入成功,在 UIViewController+MemoryLeak.m 的 +(void)load方法中打上断点,运行项目时该方法进入便引入成功。 引入 MLeaksFinder 的代码之后即可检测内存泄漏,但查找循环引用的功能还未生效。可以再手动的加入 FBRetainCycleDetector 代码,然后把 MLeaksFinder.h 里的 //#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 0 打开 MLeaksFinder 默认只在debug下生效,当然也可以通过 MLeaksFinder.h 里的 //#define MEMORY_LEAKS_FINDER_ENABLED 0 来手动控制开关。 MLeaksFinder -原理]]></content>
</entry>
<entry>
<title><![CDATA[iOS重签名]]></title>
<url>%2F2019%2F10%2F22%2FiOS%E9%87%8D%E7%AD%BE%E5%90%8D%2F</url>
<content type="text"><![CDATA[第一步:获取脱壳的ipa包1、越狱手机导出 2、各类网站上下载的ipa。 3、各类助手上下载越狱版ipa包 第二步:命令行实现重签名1、 将ipa解压缩后前往.app所在的目录,输入 1codesign -d -vv IpaDownloadTool.app 然后输出可执行文件的签名信息 12345678910111213Executable=/Users/tgkj/Downloads/Payload/IpaDownloadTool.app/IpaDownloadToolIdentifier=cn.zxlee.IpaDownloadToolFormat=app bundle with Mach-O universal (armv7 arm64)CodeDirectory v=20400 size=4444 flags=0x0(none) hashes=130+5 location=embeddedSignature size=4798Authority=iPhone Distribution: Siyuan Zhang (XW95KAQH96)Authority=Apple Worldwide Developer Relations Certification AuthorityAuthority=Apple Root CASigned Time=Aug 20, 2019 at 2:06:16 PMInfo.plist entries=33TeamIdentifier=XW95KAQH96Sealed Resources version=2 rules=10 files=49Internal requirements count=1 size=188 这里可以看到签名相关的信息 2、查看Mac本地的证书列表: 1security find-identity -v -p codesigning 输出 12357950B1A87C9A8E42F39B6E87EF37B34972FF4F4 "iPhone Distribution: Ji.....BE01D5144626C1DC4330FF66B0B8924DA13AA06B "iPhone Develo.... 记下你要用来签名的证书双引号(包括双引号)中的字符串,一会儿会用到 3、确认ipa包是否已经脱壳,输入: 123cd IpaDownloadTool.appotool -l IpaDownloadTool | grep crypt 输出 123456 cryptoff 16384cryptsize 376832 cryptid 0 cryptoff 16384cryptsize 409600 cryptid 0 cryptid为0即为已脱壳,为1为加密状态。这里有两组数据是因为这是个支持两种CPU架构的可执行文件。可输入file IpaDownloadTool查看可执行文件支持的架构 输出 123IpaDownloadTool: Mach-O universal binary with 2 architectures: [arm_v7:Mach-O executable arm_v7] [arm64:Mach-O 64-bit executable arm64]IpaDownloadTool (for architecture armv7): Mach-O executable arm_v7IpaDownloadTool (for architecture arm64): Mach-O 64-bit executable arm64 4、删除无法签名的插件文件:PlugIns文件夹、Watch文件夹note:没有这2个文件夹跳过这步 5、对.app文件夹内的Frameworks文件夹中的每一个framework强制重签名 1codesign -fs 步骤2中记下的证书信息 要签名的.framework note:这里Frameworks要签名的直接跳过这步 6、将自己的描述文件名改为embedded.mobileprovision,并拖入到.app中,再将.app中info.plist文件里的Bundle identifier改成我们自己的BundleID 7、在.app同级目录下新建一个entitlements.plist文件 1touch entitlements.plist 在.app目录下查看描述文件内容:security cms -D -i embedded.mobileprovision将Entitlements节点下的 1234<dict> ... ...</dict> 复制粘贴到刚刚新建的entitlements.plist文件中 8、最后一步,对整个包签名,回到.app所在目录,输入 1codesign -fs 步骤2中记下的证书信息 --no-strict --entitlements=entitlements.plist WeChat.app 9、打包 1zip -ry IpaDownloadTool.ipa Payload 以上,就是通过命令行一步步的实现应用重签名。 用脚本实现签名1、创建 appSign.sh 文件 1touch appSign.sh 2、拷贝一下内容到 appSign.sh文件中 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657#${SRCROOT} 它是工程文件所在的目录TEMP_PATH="${SRCROOT}/Temp"#资源文件夹,我们提前在工程目录下新建一个APP文件夹,里面放ipa包ASSETS_PATH="${SRCROOT}/APP"#目标ipa包路径TARGET_IPA_PATH="${ASSETS_PATH}/*.ipa"#清空Temp文件夹rm -rf "${SRCROOT}/Temp"mkdir -p "${SRCROOT}/Temp"#----------------------------------------# 1. 解压IPA到Temp下unzip -oqq "$TARGET_IPA_PATH" -d "$TEMP_PATH"# 拿到解压的临时的APP的路径TEMP_APP_PATH=$(set -- "$TEMP_PATH/Payload/"*.app;echo "$1")# echo "路径是:$TEMP_APP_PATH"#----------------------------------------# 2. 将解压出来的.app拷贝进入工程下# BUILT_PRODUCTS_DIR 工程生成的APP包的路径# TARGET_NAME target名称TARGET_APP_PATH="$BUILT_PRODUCTS_DIR/$TARGET_NAME.app"echo "app路径:$TARGET_APP_PATH"rm -rf "$TARGET_APP_PATH"mkdir -p "$TARGET_APP_PATH"cp -rf "$TEMP_APP_PATH/" "$TARGET_APP_PATH"#----------------------------------------# 3. 删除extension和WatchAPP.个人证书没法签名Extentionrm -rf "$TARGET_APP_PATH/PlugIns"rm -rf "$TARGET_APP_PATH/Watch"#----------------------------------------# 4. 更新info.plist文件 CFBundleIdentifier# 设置:"Set : KEY Value" "目标文件路径"/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER" "$TARGET_APP_PATH/Info.plist"#----------------------------------------# 5. 给MachO文件上执行权限# 拿到MachO文件的路径APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\>|cut -f1 -d\<`#上可执行权限chmod +x "$TARGET_APP_PATH/$APP_BINARY"#----------------------------------------# 6. 重签名第三方 FrameWorksTARGET_APP_FRAMEWORKS_PATH="$TARGET_APP_PATH/Frameworks"if [ -d "$TARGET_APP_FRAMEWORKS_PATH" ];thenfor FRAMEWORK in "$TARGET_APP_FRAMEWORKS_PATH/"*do#签名/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" "$FRAMEWORK"donefi 3、然后将 appSign.sh 直接丢到.xcodeproj同级目录中 4、Xcode –> Build Phases –> New Run Script Phase 5、Run note:这里Xcode编译可能会报的错 1: appSign.sh: Permission denied 解决办法:在终端上直接运行命令: chmod 777 appSign.sh 2: Xcode Internal launch error: process launch failed: Unspecified 网上找的重启手机,或者重新连接Xcode都没用。后面再找找答案 本文转载:https://juejin.im/post/5c460908f265da6167209b87]]></content>
<tags>
<tag>iOS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[class-dump的安装和使用]]></title>
<url>%2F2019%2F10%2F18%2Fclass-dump%2F</url>
<content type="text"><![CDATA[本文转载 https://www.jianshu.com/p/1e3fe0a8c048 class-dump 用来dump目标文件的class信息的工具。它利用Objective-C语言的runtime的特性,将存储在mach-O文件中的@interface和@protocol信息提取出来,并生成对应的.h文件。 安装步骤1、下载地址:http://stevenygard.com/projects/class-dump/ 2、打开终端输入 1open /usr/local/bin 3、把dmg文件中的class-dump文件复制到/usr/local/bin 4、更改权限:终端输入 1sudo chmod 777 /usr/local/bin/class-dump 到这里就安装好了。 这里为什么是777? https://juejin.im/post/5c46dcad518825260d7ee605 这篇文章说得很清楚 显示class-dump的用法和版本 1class-dump --help 使用方法1、自己建项目生成一个ipa文件,更改文件为zip格式,然后解压之后得到.app的目标文件 2、用终端输入命令class-dump -H [.app文件的路径] -o [输出文件夹路径] 1class-dump -H /Users/mac/Desktop/Payload/Kt.app -o /Users/mac/Desktop/Payload 就可以得到所有的.h文件了(在输出的文件夹里) ps:自己编译的项目没有加密,能够解析出来。class-dump不能直接将AppStore上的app的头文件导出来,你只会导出CDStructures.h这个头文件,而这里边基本是没有信息的。相当于Apple在app上加了一层壳(加密了),需要把这层壳砸破。(Dumpdecrypted破壳)]]></content>
<tags>
<tag>命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[内存对齐]]></title>
<url>%2F2019%2F10%2F16%2F%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90-1%2F</url>
<content type="text"><![CDATA[内存1、为什么要内存对齐 ? 在Xcode编译器下。一下2个结构体的大小是多少? 123456789101112131415struct StructOne { char a; //1字节 double b; //8字节 int c; //4字节 short d; //2字节} MyStruct1;struct StructTwo { double b; //8字节 char a; //1字节 short d; //2字节 int c; //4字节} MyStruct2;NSLog(@"%lu---%lu--", sizeof(MyStruct1), sizeof(MyStruct2));上述代码打印出来的结果为 为什么相同的结构体,只是交换了变量 ab 在结构体中的顺序他们的大小就改变了呢?这就是“内存对齐”的现象 内存对齐规则每个特定的平台上的编译器都有自己的默认“对齐系数”,程序员可以通过预编译命令 #pragma pack(n) n = 1,2,4,8,16 来改变这一系数,其中的n就是你要指定的对齐系数. 提示Xcode 中默认为#pragma pack(8)。如果在代码执行前加一句#pragma pack(1) 时就代表不进行内存对齐,上述代码打印的结果就都为 16。 1、1数据成员对齐规则: 结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset(偏移)为0的地方,以后每个数据成员的对齐按照 #pragma pack 指定的数值和这个数据成员自身长度中,比较小的那个进行 1、2结构(或联合)的整体对齐规则: 在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照 #pragma pack 指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。 例如:#pragma pack(4)系数 = min(max(成员),n )整体对齐系数 = min((max(int,short,char), 4) = 4 1、3结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从整体对齐系统的整数倍地址开始存储。 1234567891011121314151617struct StructOne { 长度 对齐 偏移 区间 char a; 1 < 8 1 0 [0] double b; 8 = 8 8 8 [8, 15] int c; 4 < 8 4 16 [16, 19] short d; 2 < 8 2 20 [20, 21]} MyStruct1;解读:1、数据成员的对齐按照#pragma pack-8和自身长度中比较小的那个进行-- char a 的自身长度为 1, 1 < 8, 按 1 对齐2、第一个数据成员放在offset为0的地方-- char a 的偏移为 13、整体对齐系数 = min((max(int,short,char,double), 8) = 8, 将 21 提升到 8 的倍数,则为 24,所以最终结果为 24 个字节 123456789101112struct B { 长度 对齐 偏移 区间 char e[2]; 1 < 8 2 0 [0, 1] short h; 2 < 8 2 2 [2, 3] struct A { //重这里开始,要考虑整体对齐系数,初始区间必须是整体对齐系数的倍数。 int a; 4 < 8 4 8 [8, 11] double b; 8 = 8 8 16 [16, 23] float c; 4 < 8 4 24 [24, 27] }; // 整体对齐系数 = min((max(int,double ,float), 8) = 8 // 将内存大小由28补齐到8的整数倍32,∴ result = 32}; ###为什么要内存对齐 之所以要内存对齐,有2方面的原因: 平台原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台的某些特定类型的数据只能从某些特定地址开始存取。——-比如,有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么这种架构的编译器必须保证字节对齐。 性能原因:内存对齐可以提高存取效率。—–比如。有些平台每次读取都是从偶地址开始,如果一个int型(假设为32为系统)如果存放在偶地址开始的地方,那么一个读周期就可以都出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高地位进行拼凑才能得到改32位数据。]]></content>
<tags>
<tag>iOS、Objective-C</tag>
</tags>
</entry>
<entry>
<title><![CDATA[genstring命令]]></title>
<url>%2F2019%2F10%2F16%2Fgenstring%E5%91%BD%E4%BB%A4%2F</url>
<content type="text"><![CDATA[一、用 genstrings 命令查找。genstring 官方介绍 比如工程中用到的本地字符串 1title.text = NSLocalizedString(@"text","text") 实践过程 1、打开终端,进入当前项目文件。 12例如:cd /Users/tgkj/Desktop/Cat 2、创建中英文做在的文件夹目录。 1234mkdir zh-Hans.lprojmkdir en.lproj注意:这里有好多网有说这个文件夹必须是 语言+.lproj,这里我测试的时候随意取过这个文件夹的名字,但也执行成功了。 3、使用genstrings 命令查找工程的本地字符串 123find ./ -name *.m -print0 | xargs -0 genstrings -o en.lproj注意,这个是会扫描文件夹下的所用文件的。 网上好多粘贴这个命令的 123genstrings -o en.lproj *.mgenstrings命令只扫描这个文件夹下的文件,并不是递归的。 4、命令执行后生成Localizable.strings文件 12/* text */"text" = "text";]]></content>
<tags>
<tag>命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[lipo的基本用法]]></title>
<url>%2F2019%2F10%2F16%2Flipo%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95%2F</url>
<content type="text"><![CDATA[简单介绍lipo 是管理Fat File 的工具,可以查看cpu 架构,提取特定架构,整合和拆分库文件 命令用法 查看静态库指出cpu的架构 12345lipo -info 静态库路径.framework/xxxx 或 静态库路径.a举例:lipo -info /Users/tgkj/Desktop/TeeagerGreenModel.framework/TeeagerGreenModelArchitectures in the fat file: /Users/tgkj/Desktop/TeeagerGreenModel.framework/TeeagerGreenModel are: armv7 i386 x86_64 arm64 合并静态库 1234lipo -create 静态库路径1 静态库路径2 -output 存放路径举例:lipo -create /Users/tgkj/Library/Developer/Xcode/DerivedData/ TeeagerGreenModel-bjryvkwvkylloqeqscwurstbubsa/Build/Products/Release-iphoneos/TeeagerGreenModel.framework/TeeagerGreenModel /Users/tgkj/Library/Developer/Xcode/DerivedData/TeeagerGreenModel-bjryvkwvkylloqeqscwurstbubsa/Build/Products/Release-iphonesimulator/TeeagerGreenModel.framework/TeeagerGreenModel -output /Users/tgkj/Library/Developer/Xcode/DerivedData/TeeagerGreenModel-bjryvkwvkylloqeqscwurstbubsa/Build/Products/Release-iphoneos/TeeagerGreenModel.framework/TeeagerGreenModel 移除指定架构 1234lipo 静态库路径 -remove CPU架构名称 -output 移除后存放路径举例:lipo /Users/tgkj/Library/Developer/Xcode/DerivedData/ TeeagerGreenModel-bjryvkwvkylloqeqscwurstbubsa/Build/Products/Release-iphoneos/TeeagerGreenModel.framework/TeeagerGreenModel -remove armv7 -output /Users/tgkj/Library/Developer/Xcode/DerivedData/ TeeagerGreenModel-bjryvkwvkylloqeqscwurstbubsa/Build/Products/Release-iphoneos/TeeagerGreenModel.framework/TeeagerGreenModel 静态库拆分 1lipo 静态库源文件路径 -thin CPU架构名称 -output 拆分后文件存放路径]]></content>
<tags>
<tag>命令</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SwiftUI简单介绍]]></title>
<url>%2F2019%2F06%2F04%2FSwiftUI%2F</url>
<content type="text"><![CDATA[SwiftUI 核心理念 更好的应用程序,更少的代码 SwiftUI 是一种创新、极其简单的方法,可以利用Swift的强大功能在所有Apple平台上构建用户界面。只需要使用一组工具和Api,即可为任何Apple设备构建用户界面。通过易于阅读和自然编写的申明性Swift语法,SwiftUI与新的Xcode设计工具无缝协作,使您的代码和设计保持完美同步。自动支持动态类型、Dark模式、本地化和辅助功能。意味着您的SwiftUI代码的第一行已经是您编写过得最强大的UI代码。 工具要求: Xcode11 声明语法SwiftUI使用声明性语法,因此您只需要说明用户界面执行的操作即可。例如,您可以编写所需的项目列表,其中包含文字段,然后每个字段的对齐方法、字体和颜色。您的代码比以往任何时候都更简单、更易于阅读,从而节省了您的时间和维护。 12345678910111213141516import SwiftUIstruct Content : View { @state var model = Themes.ListModel var boby: some View { List(model.items, action: model.selectItem){ Image(item.image) VStack(aligment: .leading){ Text(item.title) Text(item.subtitle) } } }} 这种声明样式甚至使用与复杂的动画。只需要几行代码,即可轻松的将动画添加到几乎任何概念中,并选择一组随时可用的效果。在运行时,系统处理创建平滑运动所需要的所有步骤,甚至可以处理中断以保持应用的稳定性。随着动画的简单,你将寻找新的方法来使你的App变的更灵动。 设计工具Xcode11包括直观的新设计工具,使SwiftUI构建见面与拖放一样简单。当您的设计画布工作时,编辑的所有内容都与相邻编辑器中的代码完全同步。代码在您键入时立即显示为预览,并且您对该预览所做的任何更改都会立即显示在代码中。Xcode会立即重新编译你的更改,并将其插入到应用的运行版本中,在任何时候都是可见和可编辑的。 拖放: 只需要在画布上拖动控件,即可排列用户界面中的组件。单击以打开检查器可以选择字体、颜色、对齐方式和其他设计选项,并轻松使用光标重新排列控件。代码编辑中还提供了许多可视化编辑器。因此您可以使用检查器为每个控件发现新的修饰符。及时您更喜欢界面的手工编码部分也是如此。您可以从库中拖动控件,并将其方置画布上或者直接放在代码上。 动态更换: Swift编辑器和运行时完全嵌入到整个Xcode中,因此你的应用不断的构建和运行。您看到的画布不仅仅是用户界面的近似值,而是您的实体应用。Xcode可以直接在您的实时应用中编辑过得代码与Swift中的一项新功能‘动态替换’交换。 预览: 现在,您可以创建任何SwiftUI视图的一个或多个预览以获取示例数据,并配置用户能看到的几乎任何内容,如大字体、本地化或深色模式。预览还可以在任何设备任何方向显示您的UI 在所有Apple平台上的原生SwiftUI是建立在数十年来创造世界上最具创新性和最直观的用户界面的经验至少的。用户喜爱的有关Apple生态系统的所用内容(如控件和特定于平台的体验)在您的代码中都很没。SwiftUI是真正的原生技术,因此你的应用可以通过少量的代码和交互设计画布直接访问每个平台的技术。 以上内容来自苹果官方网站]]></content>
<categories>
<category>Swift</category>
</categories>
</entry>
<entry>
<title><![CDATA[socket]]></title>
<url>%2F2019%2F05%2F28%2Fsocket%2F</url>
<content type="text"></content>
</entry>
<entry>
<title><![CDATA[泛型]]></title>
<url>%2F2019%2F05%2F19%2Ffanxing%2F</url>
<content type="text"><![CDATA[泛型代码让你能够自定义要求,编写出适用于任意类型、灵活可重用的函数及类型。它能够让你避免代码重复,用一种清晰抽象的方式来表达意图。 泛型函数12345func swapTwoValues<T>(_ a: inout T, _ b: inout T){ let temporaryA = a a = b b = temporaryA} 这个尖括号告诉Swift那个T是swapTwoValues函数定义的一个占位类型名,因此Swift不会去查找名为T的实际类型 类型参数你可以提供多个类型参数,将他们都写在尖括号中,用逗号隔开 命名类型参数大多数情况下,命名参数具有一个描述性名字,例如 Dictionary<Key, Value> 中的Key和Value,以及Array中的Element 泛型类型除了泛型函数,Swift还允许你定义泛型类型. 123456789struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element){ items.append(item) } mutating func pop() -> Element { return items.removeLast() }} Element 为待提供的类型定义了一个占位名。 123456var stackOfString = Stack<String>()stackOfString.push("uno")stackOfString.push("dos")stackOfString.push("tres")let fromThePop = stackOfStirng.pop() 扩展一个泛型类型1234567891011extension Stack { var topItems: Element? { return items.isEmpty ? nil : items[items.count -1] }}//这个扩展并没有定义一个类型参数列表。相反的,Stack类型已有的类型参数名称Element,被用在扩展中表示计算型topItems的可选类型。if let topItem = stackOfString.topItem { print("The top item on the stack is \(topItem)")} 类型约束类型约束是指定一个类型参数必须继承指定类,或者符合一个特定的协议或协议组合。例如,Swift中的Dictionary类型对字典的键类型做了限制。字典的键必须是可哈希(hashable)的。 类型约束语法123func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U){} 类型约束实践12345678func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind{ return index } } return nil} 上面所写的函数无法通过编译。问题出在相等性检查上。不是所有的Swift类型都可以用等式符进行比较。Swift标准库中定义了一个Equatalbe协议。该协议要求任何遵循的类型必须实现等式符(==)及不等符(!=),从而能对改类型的任意两个值进行比较 1234567func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int ? { for (index, value) in array.enumerated() { if value == valueToFind { return index } }} 关联类型关联类型为协议中的某个类型提供一个占位名,其代表的实际类型在协议被采纳时才会被指定。你可以通过 associatedtype关键字来指定关联类型。 实践1234567//定义一个Container 协议,该协议定义了一个关联类型Itemprotocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get }} 定义一个非泛型的IntStack类型。采纳并符合了Container协议 123456789101112131415161718192021struct IntStack: Container { //原始实现 var items = [Int]() mutating func push(_ item: Int){ items.append(item) } mutating func pop() -> Int { return items.removeLast() } //Container 协议实现部分 typealias Item = Int mutating func append(_ item: Int){ self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Int { return items[i] }} 指定Item为Int类型,即typealias Item = Int,从而将Container协议中抽象的Item类型转换为具体的Int类型 下面定义泛型Stack结构体并且遵循Container协议 1234567891011121314151617181920struct Stack<Element>: Container{ //Stack<Element> 的原始实现部分 var items = [Element]() mutating func push(_ item: Element){ items.append(item) } mutating func pop() -> Element { return items.removeLast() } //Container 协议实现部分 mutating func append(_ item: Element){ self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element{ return items[i] }} 通过扩展一个已经存在的类型来指定关联类型Swift的Array类型已经提供了Container协议所有的属于、方法、以及下标的实现。这意味着你只需要简单的申明Array采纳协议就可以扩展Array 1extension Array: Container {} 定义了这个扩展之后,你可以将任意的Array当做Container 给关联类型添加约束123456protocol Container { associatedtype Item: Equatable mutating func append(_ item: Item) var count: Int { get } subscript(i: Int)-> Item { get }} 为了遵循Container协议,Item类型也必须遵循Equatable协议 在关联类型约束里使用协议协议可以作为自身的要求出现 1234protocol SuffixableContainer : Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix} 这个协议中的Suffix是个关联类型。它有2个约束。它必须遵循SuffixableContainer协议(就是当前定义的协议),以及它的Item类型必须和容器里的Item类型相同 123456789extension Stack: SuffixableContainer { func suffix(_ size: Int) -> Stack { var result = Stack<Int>() for index in (count-size)..<count { result.append(self[index]) } return result }} 泛型Where语句类型约束让你能够为泛型函数,下标,类型的类型参数定义一些强制要求。where子句跟一个或多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系 1234567891011121314func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { //检查容器是否相同 if someContainer.count != anotherContainer.count { return false } //检查每一对元素是否相等 for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } //所有元素都匹配,返回true return true} 函数参数列表的两个类型的要求 C1必须符合 Container 协议 C2必须符合 Container 协议 C1的Item和C2的Item类型相同 C1的Item必须符合Equatable协议 具有泛型Where子句的扩展12345678extension Stack where Element: Equatalbe { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else{ return false } return topItem == item }} //如果尝试在其元素不符合Equatable协议栈上调用isTop(_:)方法,则会编译报错 使用where字句去扩展一个协议 where子句要求Item符合协议 1234567891011extension Container where Item: Equatable { func startsWith(_ item: Item) -> Bool { return count > 1 && self[0] == item }}if [8, 9, 9].statsWith(43){ print("Start Wiht 43")}else { print("Start with something else")} where字句要求Item为特定的类型 12345678910extension Container where Item == Double { func average() -> Double { var sum = 0.0; for index in 0..<count { sum += self[index] } return sum / Double(count) }}print([125.0, 23.00, 323.00].average()) 具有泛型Where字句的关联类型你可以在关联类型后面加上具有泛型where的子句 12345678protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator} 迭代器(Iterator)的泛型where字句要求,无论迭代器是什么类型,迭代器中的元素必须和容器项目的类型保持一致 泛型下标下标是能够泛型的,他们能够包含泛型where子句 123456789extension Container { subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result = [Item]() for index in indices { result.append(self[index]) } return result }}]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[协议]]></title>
<url>%2F2019%2F04%2F30%2FProtocol%2F</url>
<content type="text"><![CDATA[协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性、以及其他需要的东西。 协议的语法 12345678910protocol SomeProtocol { //协议的定义部分}//某个自定义类型遵循协议struct SomeStructure : SomeProtocol, AnotherProtocol {}拥有父类的类在遵循协议时,应该将父类名放在协议名之前class SomeClass : SomeSuperClass, FirstProtocol, AnotherProtocol { // 这里是类的定义部分} 属性要求协议可以要求遵循的类型提供特定名称和类型的实例属性或类型属性。协议不知道是存储属性还是计算属性,它指定属性的名称和类型 123456789protocol SomeProtocol { var mustBeSetterble: Int { get set} var doseNotNeedToBeSettable: Int {get}}协议定义类型属性时,总是使用static关键字做前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字来声明类型属性protocol AnotherProtocol { static var someTypeProtocol: Int { get set}} 123456789101112131415161718192021//遵循次协议必须有一个可读String类型的实例属性fullNameprotocl FullyName { var fullName: String { get }}struct Person: FullyNamed { var fullName: String}let john = Person(fullName: "John")//john.fullName 为 “John”class StarShip: FullyNamed { var prefix: String? var name: String init(name: String, prefix: String? = nil){ self.name = name self.prefix = prefix } var fullName: String { return (prefix != nil ? prefix! + "":"") + name }} 方法要求在协议中定义类方法的时候,总是使用static 关键字作为前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字 1234567protocol SomeProtocol { static func someTypeMethod()}//定义一个只含有实例方法的协议protocol RandomNumberGenerator { func someFun() -> Int} 异变方法要求有时候需要在方法中改变方法所属的实例。例如,在值类型的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改所属的实例以及实例任意属性的值。 注意:实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须mutating关键字 123456789101112131415161718protocol Togglable { mutating func toggle()}enum OnOffSwitch: Togglable { cass off , on mutating func toggle(){ switch self { case .off self = .on case .on self = .off } }}var lightSwitch = OnOffSwitch.offlightSwitch.toggle()//lightSwitch 现在是 .on 构造器要求协议可以要求遵循协议类型实现指定构造器。 123protocol SomeProtocol { init(someParameter: Int)} 协议构造器要求的类实现你可以遵循协议的类中实现构造器,无论作为指定构造器还是便利构造器,都必须为构造器标上required修饰符 12345678910111213141516171819class SomeClass : SomeProtocol { required init(someParameter: Int){}}//required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。//如果类已经标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类//如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 required 和 overrideprotocol SomeProtocol{ init()}class SomeSuperClass{ init(){}}class SomeSubClass: SomeSuperClass, SomeProtocol { //因为遵循协议,需要加上required //因为继承自父类,需要加上override required override init(){}} 可失败构造器要求协议还可为遵循协议类型的类型定义可失败构造器 协议作为类型协议本身并不实现任何功能,但是协议可以被当做一个成熟的类型来使用。协议可以像其他普通类型一样使用。 作为函数、方法或构造器中的参数类型或返回值类型 作为常量、变量或属性的类型 作为数组、字典或其他容器的元素类型 1234567891011121314protocol RandomNumberGenerator { func random() -> Double}class Dice { let sides: Int let generator: RandomNumberGenerator init(sides: Int, generator: RandomNumberGenerator){ self.sides = sides self.generator = generator } func roll() -> Int { return Int(generator.random * Double(sides)) }} 委托 现实中最常见的用法委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。 在扩展里添加协议遵循扩展可以为已有类型添加属性(计算属性)、方法、下标以及构造器, 123extersion SomeClass: SomeProtocol { //实现遵循的协议} 有条件地遵循协议泛型类型可能只在某些情况下满足一个协议的要求,比如当类类型的泛型形式参数遵循对应协议时。你可以通过扩展类型列出限制泛型类型有条件的遵循某协议。在你采纳协议的名字后面写泛型where分句。 12345678910protocol SomeProtocol { var textOutput: String { get }}extension Array : SomeProtocol where Element: String { // var textOutput: String{ let text = self.map { $0.textOutput } return "[" + text + "]" }} 在扩展里声明采纳协议当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时。可以通过空扩展体的扩展采纳该协议。 12345struct Hamster { //完全实现了协议 SomeProtocol}//这里显示的声明extersion Hamster : SomeProtocol{} 注意:即使满足了协议的所有要求,类型也不会自动遵循协议,必须显示地遵循协议 协议类型的集合12//集合中的3个元素都遵循SomeProtocol协议的实例let things: [SomeProtocol] = [param1,param2,param3] 协议继承协议能够继承一个或多个其他协议 123protocol SomeProtocol: FirstProtocol,AnthorProtocol { //定义部分} 类专属的协议通过添加AnyObject 关键字到协议的继承列表,就可以限制协议只能被类类型采纳。protocol SomeClassProtcol: class { //专属类的协议定义部分} 协议合成要求一个类型同时遵循多个协议是很有用的。你可以使用协议组合来复合多个协议到一个要求里。 协议组合使用 SomeProtocol & AnotherProtocol 的形式。 1234567891011121314protocol Named { var name: String { get }}protocol Aged { var age: Int { get }}struct Person: Named, Aged { var name: String var age: Int}//函数参数 celebrator 类型为 Name & Aged。这意味着同时要遵循这2个协议func wishHappyBirthday(to celebrator: Name & Aged){ print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")} 1234567class Location {}class City: Location, Named {}//这意味这方法只接受 Location & Named 的参数func beginConcert(in location: Location & Named){} 检查协议一致性is 和as操作符来检查协议一致性,即是否复合某协议,并且可以转换到指定的协议类型。 is 用来检查实例是否复合某个协议 as? 返回一个可选值,当实例复合某个协议是,返回类型为协议类型的可选值。 as! 将实例强制向下转换到某个协议,如果转换失败,会引发运行时错误 可选的协议要求协议可以定义可选要求,遵循协议类型可以选择是否实现这些要求。协议中使用 optional 关键字作为前缀来定义可选要求。可选要求用在你需要和Objective-C打交道的代码中。协议和可选要求都必须带上 @objc 属性。标记 @objc特性的协议只能被继承自 Objective-C类或者遵循。 使用可选要求时,他们的类型会自动变成可选的。比如,一个类型为 (Int) -> String 的方法会变成 ((Int) -> String)? ,这里是函数类型可选,不是函数的返回值可选 协议扩展协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现 12345extension RandomNumberGenerator { func randomBool() -> Bool { retrun random() > 0.5 }} 通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外的修改 提供默认实现可以通过协议扩展来为协议的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展的默认实现被使用。 为协议扩展添加限制条件在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足某些限制条件,才能获得协议扩展提供的默认实现。使用where字句来描述 12345678910extension Collection where Element: Equatable { func allEqual() -> Bool { for element in self { if element != self.first { return false } } return true }}]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[扩展]]></title>
<url>%2F2019%2F04%2F28%2Fextension%2F</url>
<content type="text"><![CDATA[扩展就是为一个已有的类、结构体、枚举或者协议添加新功能。 Swift中的扩展 添加计算型属性和计算型类型属性 定义实例方法和类方法 提供新的构造器 定义下标 定义和使用新的嵌套类型 使一个已有类型符合某个协议 扩展语法使用关键字 extension来声明扩展 123extension SomeType { // 为 SomeType扩展的新功能写到这里} 通过扩展一个已有类型,使其采纳一个或多个协议 123extension SomeType : SomeProtocol, AnotherProtocol { //协议实现写到这里} 计算型属性扩展可以为已有类型添加计算型实例属性和计算型类型属性 12345678910extension Double { var km: Double { return self * 1000.0 } var m: Double { return self } var cm: Double { return self / 100.0 } var mm: Double { return self / 1000.0 } var ft: Double { return self / 3.28084 }}let oneInch = 25.4.mmlet aMarathon = 42.km + 19.5m 构造器扩展可以为已有类型添加新的构造器。扩展能为类添加新的便利构造器,但不能为类添加新的指定构造器。指定构造器必须总是由原始的类来实现提供 注意如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何指定构造器且所有存储属性提供了默认值,那么我们可以再扩展中的构造里调用默认构造器和逐一成员构造器。 12345678910111213struct Size { var width = 0.0, height = 0.0}struct Point{ var x = 0.0, y = 0.0}struct Rect { var origin = Point() var size = Size()}let defaultRect = Rect()let memberRect = Rect(origin: Point(x: 0.0, y: 2.0), size: Size(width: 5.0, height: 5.0 )) 12345678extension Rect { init(center: Point, size: Size) { let originX = center.x - size.width / 2 let orginY = center.y - size.height / 2 self.init(origin: Point(x: orginX, y: originY), size: size) }}let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(x: 5.0, y: 5.0)) 方法扩展可以为已有类型添加实例方法和类型方法 可变实例方法通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改self或其属性方法方法必须将改实例方法标注mutating 1234567extension Int { mutating func square (){ return self * self }}var someInt = 3somInt.square() 下标扩展可以为已有类型添加新下标 12345678910extension Int { subscript(digitIndex: Int) -> Int { var decimalBase = 1 for _ in 0..<digitIndex { decimalBase *= 10 } return (self / decimalBase) %10 }}746[0] 嵌套类型扩展可以为已有类型的类、结构体和枚举添加新的嵌套类型: 123456789101112131415extension Int { enum Kind { case negative, zero, positive } var kind: Kind { switch self { case 0: return .zero case let x where x > 0 : return .positve default: reutrn .negative } }} 例子为Int添加了嵌套类型,这个名为Kind的枚举表示特定整数的类型。]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Swift 相关面试题]]></title>
<url>%2F2019%2F04%2F28%2FSwift%2F</url>
<content type="text"><![CDATA[本文复制 https://www.jianshu.com/p/23d99f434281 class 和 struct 的区别 class 为类,struct 为结构体,类是引用类型,结构体是值类型。结构体不可以继承 不通过继承,代码复用(共享)的方式有哪些 扩展,全局函数 Set 独有的方法有哪些? 1234567891011//定义一个setlet setA: Set<Int> = [1, 2, 3, 4, 4] //{1, 2, 3, 4} 顺序可能不一致,同一个元素只有一个值set setB: Set<Int> = [1, 3, 5, 7, 9] //{1, 3, 5, 7, 9}//取并集let setUnion = setA.union(setB) //{1, 2, 3, 4, 5, 7, 9}//取交集let setIntersect = setA.intersection(setB) //{1, 3}//取差集 A - Blet setRevers = setA.subtraction(setB). //{2, 4}//取对称差集let setXor = letA.symmentricDifference(setB) //{2, 4, 5, 7, 9} 实现一个 min 函数,返回两个元素较小的元素 123func myMin<T: Comparable>(_a: T, _ b: T) -> T { return a<b ? a:b} map、filter、reduce 的作用map 用于映射,可以将一个列表转换为另一个列表 12[1, 2, 3].map{"\($0)"} //数字数组转换为字符串数组["1", "2", "3"] filter 用于过滤,可以筛选出想要的元素 12[1, 2, 3].filter{$0 %2 == 0} //筛选偶数//[2] reduce 合并 12[1, 2, 3].reduce(""){$0 + "\($1)"} //转换为字符串拼接// "123" map 与 flatmap 的区别flatmap 有两个实现函数实现 123456public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult ?) -> rethrows -> [ElementOfResult]//这个方法,中间的函数返回值为一个可选类型,而flatmap会丢掉那些返回值为nil的值,例如["1", "@", "2", "3", "a"].flatMap{Int($0)}// [1, 2, 3]["1", "@", "2", "3", "a"].map{Int($0) ?? -1}//[Optional(1), nil, Optional(2), Optional(3), nil] 另一个实现 123456789public func flatMap<SegmentOfResult>(_ transform: (Element) throws ->SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element] where SegmentOfResult : Sequence//中间的函数,返回值为一个数组,而这个flatmap返回的对象则是一个与自己元素类型相同的数组func someFunc(_ array:[Int]) -> [Int] { return array}[[1],[2,3],[4,5,6]].map(someFunc)// [[1], [2, 3], [4, 5, 6]][[1], [2, 3], [4, 5, 6]].flatMap(someFunc)// [1, 2, 3, 4, 5, 6] 什么事copy on write时候 写时复制,指的是swift中的值类型,并不会一开始赋值的时候就去复制,只有再需要修改的时候,才去复制。这里有相关说明http://www.jianshu.com/p/7e8ba0659646 如何获取当前代码的函数名和行号 #file 用于获取当前文件文件名#line 用于获取当前行号#column 用于获取当前编号function 用于获取当前函数名以上这些都是特殊的字面量,多用于调试输出日志。具体app文档https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html 这里有中文翻译http://wiki.jikexueyuan.com/project/swift/chapter3/04_Expressions.html 如何声明一个只能被类conform的protocol申明协议的时候,加一个class即可:如 123protocol SomeClassProtocl : class { func someFunction()} guard使用场景guard和if类似,不同的是,guard总是有一个else语句,如果表达是假或绑定失败的时候,就会执行else语句,而在else语句中一定要停止函数调用 1234guard 1+1 == 2 ? else { fatalError("something wrong")}//常使用场景为,用户登录的时候,验证用户是否有输入用户名密码等 defer使用场景 defer语句块使用中的代码,会在当前作用域结束前调用,常用场景如异常退出后,关闭数据库连接 1234567891011121314func someQuery() -> ([Result], [Result]){ let db = DBOpen("xxx") defer{ db.close() } guard results1 = db.query("query1") else{ return nil } guard results2 = db.query("query2") elee{ return nil } return (results1, results2)}**注意,如果有多个defer,那么后加入的先执行** String 与 NSString 的关系与区别 String 是结构体,值类型,NSStirng 是类,引用类型NSString 与 String之间可以随意转换 1234let someString = "123"let someNSSting = NSString(string: "123")let stirngToNSString = someString as NSStringlet nsstringToString = someNSSting as String 怎么获取一个 String 的长度 不考虑编码,只是想知道字符的数量,用 characters.count 123"hello".count//如果想知道在某个编码下占多少字节,可以用"hello".lengthOfBytes(using: .utf8) 如何截取 String 的某段字符串 swift中,有三个取字符串函数substring:to, substring:from, substring:with 1234567let simpleString = "Hello,world"simpleString.substring(to: simpleString.index(simpleString.startIndex, offsetBy:5))// hellosimpleString.substring(from: simpleString.endIndex, offsetBy:-5)// worldsimpleString.substring(with: simpleString.index(simpleString.startIndex, ofsetBy: 5) ..< simpleString.index(simpleString.endIndex, ofsetBy: -5))// , throws 和 rethrows 的用法与作用 throws 用在函数上,表示这个函数会抛出异常有两种情况下会抛出错误,一种是直接使用throw抛出,另一种是调用其他抛出异常的函数时,直接使用try xx没有处理异常。如 12345678910111213enum DivideError: Error { case EqualZeroError}func divide(_ a: Double, _ b: Double) throws -> Double{ guard b! = Double(0) else { //throw 抛出一定要实现Error 协议 throw DivideError.EqualZeroError } return a / b}func split(pieces: Int) throws -> Double { retrun try divide(1, Double(pieces))} rethrows 与 throws 类似,不过只适用于参数中有函数,且函数会抛出异常的情况下,rethrows 可以用throws替换,反过来不行.如 123func processNumber(a: Double, b: Double, function: (Double, Double) throws -> Double) rethrows -> Double { return try function(a, b)} try? 和 try! 是什么意思 这两个都用于处理可抛出异常的函数,使用这两个关键字不用写 do catch,区别在于 try?在用于处理可抛出异常函数时,如果函数抛出异常,则可以返回nil,否则返回函数值的可选值,如: 1234print(try? divide(2, 1))// Optional(2.0)print(try? divide(2, 0))// nil 而try! 则在函数抛出异常的时候奔溃。否则则返回函数的返回值。如 1234print(try! divide(2, 1))// 2.0print(try! divide(2, 0))// 奔溃 associatedtype 的作用 简单来说就是protocol 使用的泛型 例如定义一个列表协议 123456protocol ListProtocol{ associatedtype Element func push( _ element: Element) func pop(_ element: Element) -> Element?}被associatedtype关键字修饰的变量,相当于一个占位符,而不能表示具体的类型。具体的类型需要让实现的类来指定。 实现协议的时候,可以使用typealias 指定特定的类型,也可以自动推断,如 1234567891011121314151617181920class IntList: ListProtocol { typealias Element = Int //使用 typealias 指定为 Int var list = [Element]() func push (_ element: Element) { self.list.append(element) } func pop(_ element: Int) -> Int? { return self.list.popLast() }}class DoubleList : ListProtocol { var list = [Double]() func push (_ element: Double) {//自动推断 self.list.append(element) } func pop(_ element: Double) -> Double? { return self.list.popLast() }} 使用泛型也可以 123456789class AnyList<T>: ListProtocol{ var list = [T]() func push(_ element: T){ self.list.append(element) } func pop(_ element: T) -> T? { return self.list.popLast() }} 可以使用where字句限定Element类型,如: 12345extension ListProtocol where Element == Int { func isInt() -> Bool { return true }} 什么时候使用 final final用于限制继承和重写,如果只是需要在某一个属性前加一个final。如果需要限制整个类无法被继承,那么可用用类名之前加一个final public 和 open 的区别 这两个都用于模块声明需要对外界暴露的函数,区别在于,public 修饰的类,在模块外无法被继承,而open则可以任意继承,公开度来说,open>public 声明一个只有一个参数没有返回值闭包的别名 1234typealias SomeClosuerType = (Stirng) -> ()let somClosuer: SomeClosuerType = { (name: String) in print("hello," name)} Self 的使用场景 self 通常在协议中使用,用来表明实现者或者实现者的子类类型 123protocol CopyProtocol { func copy() -> Self} 如果是结构体去实现,要将Self转换为具体类型 123456struct SomeStruct : CopyProtocol{ let value: Int func copy() -> SomeStruct{ return SomeStruct(value: self.value) }} 如果是类去实现,则有点复杂,需要有一个required初始化方法,具体可以看这里 123456class SomeCopyableClass : CopyProtocol { func copySelf() -> Self { return type(of: self).init() } required init(){}} dynamic的作用 由于swift是一个静态语言,所以没有Objective-C中的消息发送这些动态机制,dynamic的作用就是让swift代码也能有Object-C中的动态机制,常用的方法就是KVO了,如果要监控这一个属性,则必须要标记dynamic。可以参考这篇文字http://www.jianshu.com/p/ae26100b9edf 什么时候使用 @objc @objc 用途是为了在Objective-C 和Swift混编的时候,能够正常的调用Swift代码,可以修饰类,协议,方法,属性。 常用的地方是在定义deleagte 协议中,会将协议中部分方法申明为可选方法,需要用到 @objc 1234567891011@objc protocol OptionalProtocol { @objc optional func optionalFunc() func normalFunc()}class OptionProtocolClass : OptionalProtocol { func normalFunc(){ }}let someOptionalDeleagte: OptionalProtocol = OptionProtocolClass()someOptionalDeleagte.optionalFunc?() Optional(可选型) 是用什么实现的 Optional 是一个泛型枚举,大致定义如下 1234enum Optional<Wrapped> { cass none case some(Wrapped)} 如何自定义下标获取 实现subscript即可,如 12345678910111213extension AnyList { subscript(index: Int) -> T { return self.list[index] } subscript(indexString: String) -> T? { guard let index = Int(indexString) else { return nil } return self.list[index] }}除了索引数字外,其他类型也是可以的 ?? 的作用 可选类型的默认值,但可选类型为nil的时候,会返回后面的值,如let someValue = optional1 ?? 0 lazy 的作用 懒加载,当属性要使用的时候,才去完成初始化。如 12345678910111213141516171819class LazyClass { lazy var someLazyValue: Int = { print("lazy init value") return 1 }() var someNormalValue : Int = { print("normal inti value") return 2 }()}let lazyInstance = LazyClass()print(lazyInstance.someNormalValue)print(lazyInstance.someLazyValue)// 打印输出// normal init value// 2// lazy init value// 1 一个类型表示选项,可以同时表示有几个选项选中(类似UIViewAnimationOptions ),用什么类型表示 需要实现自OptionSet,一般使用struct实现,由于OptionSet要求有一不可失败的构造器init(rawValue:),而枚举无法做到这一点(枚举的原始值构造器是失败的,而且有些组合值,是没有办法用一个枚举值表示的) 1234567struct SomeOption: OptionSet{ let rawValue: Int static let option1 = SomeOption(rawValue: 1 << 0) static let option2 = SomeOption(rawValue: 1 << 1) static let option3 = SomeOption(rawValue: 1 << 2)}let options: SomeOption = [.option1, .option2] inout 的作用 输入输出参数 Error 如果要兼容 NSError 需要做什么操作 其实直接转换就可以,例如:SomeError.someError as NSError 但是这样没有错误码,描述等等,如果想和NSError一样有这些东西,只需要实现LocalizedError 和 CustomNSError协议,有些方法有默认实现,可以略过。如: 12345678910111213141516171819202122232425262728293031323334enum SomeError : Error , LocalizedError, CustomNSError { case error1, error2 public var errorDescription: String? { switch self { case .error1: return "error description error1" case .error2: return "error description error2" } } var errorCode: Int { switch self{ case .error1: return 1 case .error2: return 2 } } public static var errorDomain: String { return "error domain SomeError" } public var errorUserInfo: [String : Any] { switch self{ case .error1: return ["info" :"error1"] case .error2: return ["info" :"error2"] } }}print(SomeError.error1 as NSError)//Error Domain=error domain SomeError Code=1 "error description error1" UserInfo={info=error1} 下面的代码都用了哪些语法糖 [1, 2, 3].map{ $0 * 2 } [1, 2, 3]使用了Array实现的ExpressibleByArrayLiteral 协议,用于接收数组的字面值。 map{***} 使用了闭包作为最后一个参数时,可以直接写在调用后面,而且,如果是唯一参数的话,圆括号也可以省略。 闭包没有声明函数参数,返回值类型,数量,依靠的是闭包类型的自动推断。 闭包中语句只有一句时,自动将这一句的结果作为返回值 $0 没有声明参数列表的时候,第一个参数名称为$0,后续参数以此类推 什么是高阶函数 一个函数如果可以以某一个函数作为参数,或者返回值,那么这个函数就称之为高阶函数,如 map, reduce, filter 如何解决引用循环 转换为值类型,只有类会存在引用循环,所以如果能不用类,是可以解决引用循环的。 deleagte 使用 weak属性 闭包中,对有可能发生循环引用的对象,使用weak或者unowned修饰 下面的代码会不会崩溃,说出原因 1234var mutableArray = [1, 2, 3]for _ in mutalbeArray { mutalbeArray.removeLast()} 不会,原理不清楚,就算是把removeLast(),换成removeAll(),这个循环也会执行三次,估计是一开始,for in就对mutalbeArray进行了一次值捕获,而Array是一个值类型,removeLast() 并不能修改捕获值 给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明 使用where子句,限制 Element为String 123456指定Array集合元素类型为String的扩展。如果Array集合的元素非String类型,扩展里的方法就不能使用extension Array where Element == Stirng { var isStringElement: :Bool { return true; }} 定义静态方法时关键字 static 和 class 有什么区别 static 定义的方法不可以被子类继承,class则可以 12345678class AnotherClass { static func staticMethod(){} class func classMethod(){}}class ChildOfAnotherClass : AnotherClass{ ovveride class func classMethod(){}} 一个Sequence的索引是不是一定从0开始不一定,两个for in 并不能保证都是从0开始,且输出结果一致,官方文档如下 Repeated Access The Sequence protocol makes no requirement on conforming types regardingwhether they will be destructively consumed by iteration. As aconsequence, don’t assume that multiple for-in loops on a sequencewill either resume iteration or restart from the beginning: 12345678> for element in sequence {> if ... some condition { break }> }> > for element in sequence {> // No defined behavior> }> 举例 123456789101112131415161718192021222324252627class Countdown: Sequence, InteratorProtocol { var count: Int init(count: Int){ self.count = count } func next() -> Int?{ if count == 0 { return nil }else{ differ { count -=1 } return count; } }}var countDown = Countdown(count:5)print("begin for in 1")for c in countDown { print(c)}print("end for in 1")print("begin for in 2")for c in countDown { print(c)}print("end for in 2") 最后输出结果是 123456789begin for in 154321end for in 1begin for in 2end for in 2 很明显,第二次没有输出任何结果,原因就是第二次for in的时候,并没有将 count 重值。 数组都实现了哪些协议 mutableCollection ,实现了可修改的数组,如 a[1] = 2ExpressibleByArrayLiteral 实现了数组可一次从[1,2,3]这种字面初始化的能力 如何自定义模式匹配 这部分不太懂,贴个链接吧 http://swifter.tips/pattern-match/ autoclosure 的作用 自动闭包,会自动将某一个表达式封装为闭包,如 1234func autoClosureFunction(_ closure: @autoclosure () -> Int){ closure()}autoClosureFunction(1) 详细可参考http://swifter.tips/autoclosure/ 编译选项 whole module optmization 优化了什么 编译器可以跨文件优化编译代码,不局限于一个文件http://www.jianshu.com/p/8dbf2bb05a1c 下面代码中mutating的作用是什么 1234567struct Person{ var name: String{ mutating get { return store } }} 让不可变对象无法访问属性name 如何让自定义对象支持字面量初始化有几个协议,分别是ExpresssibleByArrayLiteral 可以由数组形式初始化ExpressibleByDictionaryLiteral可以由字典形式初始化ExpressibleByNilLiteral 可以由nil值初始化ExpressibleByIntegerLiteral 可以由整数型初始化ExpressibleByFloatLiteral 可以由浮动数初始化ExpressibleByBooleanLiteral 可以由布尔值初始化 dynamic framework 和 static framework 的区别是什么 静态库是每一个程序单独打包一份,而动态库则是多个程序之间共享]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>面试题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[可选链 、错误处理、类型转换、嵌套类型]]></title>
<url>%2F2019%2F04%2F25%2Foption%2F</url>
<content type="text"><![CDATA[可选链(Optional Chaining)式调用是一种可以在当前值为nil的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么会调用成功;如果可选值是nil,那么调用将返回nil 使用可选链式调用代替强制展开通过调用的属性、方法或下标的可选值后面放一个问号 ‘?‘,可以定义一个可选链。这一点很像在可选值后面放一个叹号’!‘来强制展开它的值。它们主要的区别在于可选值为空时可选链式调用只会调用失败,然后强制展开会将触发运行时错误。 可选链式调用定义模型通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标 通过可选链式调用访问属性1234567//这里没有去创建相关的类。let john = Person()if let roomCount = john.residence?.numberOfRooms {}else {} 通过可选链式调用方法12345if john.residence?.printNumberOfRooms() != nil { print("It was possible to print the number of rooms")}else { print("It was not possible to print teh number of rooms")} 通过可选链式调用访问下标通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。 12345if let firstRoomName = john.residence?[0].name { }//用可选链调用来赋值john.residence?[0] = Room(name: "Bathroom") 链接多层可选链式调用可选通过连接多个可选链式调用在更深的模型层次中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回可选值的可选层次。 通过可可选链式访问一个Int值,将返回Int?,无论使用了多层可选链式调用。 123if let johnsStreet = john. john.residence?.address?.street {} 错误处理错误处理是响应错误以及从错误中恢复的过程。 表示并抛出错误在Swift中,错误用符合Error协议的类型的值来表示。这个空协议表明该类型可以用于错误处理 Swift枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。 12345enum VendingMachineError : Error { case invalidSelection //选择无效 case insufficientFunds( coinsNeeded: Int) //金额不足 case outOfStock //缺货} 抛出错误使用 throw关键字。 1throw VendingMachineError.insufficientFunds(coinsNeeded: 5) 处理错误Swift中有4种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用 do-catch 语句处理错误、 将错误作为可选类型、或者断言此错误根本就不会发生。 在调用一个能抛出错误的函数、方法或者构造器之前,加上 try 关键字,或者 try? 或 try! 这种变体。 用 throwing函数 传递错误为了标识一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 throws 关键字。一个标有 throws关键字的函数称做 throwing函数。 1func canThrowErrors() throws -> String 一个throwing函数可以在其内部抛出错误,并将错误传递到函数被调用的作用域。 注意:只有throwing 函数可以传递错误。任何在某个非throwing函数内部抛出的错误只能在函数内部处理 123456func vend(name: String) throws { guard name.isEmpty else { throw VendingMachineError.invalidSelection }} 因为vend(name:)方法会传递出它抛出的任何错误。在你的代码中调用此方法要么直接处理这些错误(使用do-catch,try?或try!);要么继续将错误传递下去。 1234func buyFavouriteSnack(person: String, vendingMachine: VendingMachine) throws { try vendingMachine.vend(name: snackName)}//将错误继续传递下去 用Do-Catch处理错误如果在do子语句中的代码抛出了一个错误,这个错误会与catch子语句做匹配。从而决定哪条子句能处理它 123456789do { try expression statements} catch pattern 1 { statements} catch pattern 2 where condition { statements}如果do代码块有错误抛出,相应的执行会马上转移到catch子句中,并判断这个错误是否要继续传递下去。如果没有错误抛出,do子句中余下的语句会被执行 将错误转换成可选值可以使用try?通过错误转换成一个可选值来处理。 1234567891011func someThrowingFunction() throws -> Int { //...}let x = try? someThrowingFunction()let y: Int?do { y = try someThrowingFunction()} catch { y = nil} 如果someThrowingFunction() 抛出错误,x和y的值都是nil。否则x和y的值是函数的返回值。无论someThrowingFunction()的返回值是什么类型,x,y都是这个类型的可选类型。 禁用错误传递有时你知道某个throwing函数实际上运行时是不会抛出错误的,在这种情况下,你可以再表达式前面写try!来禁止用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果有错误抛出,你会得到一个运行时错误 1let phone = try! loadImage(atPath: "./Resources/John") 指定清理操作你可以使用 defer语句在即将离开当前代码块是执行一系列语句。该语句能执行一些必要清理工作。 defer语句块将代码的执行延迟到当前作用域退出之前。延迟执行的语句不能包含任何控制转换语句,例如break、return语句或是抛出一个错误。 1234567891011func processFile(filename: String) throws { if exists(filename) { let file = open(filename) defer { close(file) } while let line = try fiel.readline() { // 处理文件 } }} 类型转换类型转换可以判定实例的类型,也可以将实例看做是其父类或者子类的实例。 类型转换在Swift中使用is和as操作符实现。你可以用这2个操作符去检查值的类型和转换它的类型,也可以用它来检查是否实现了某个协议 为类型转换定义类的层次1234567891011class MediaItem {}class Movie: MediaItem {}class Song: MediaItem {}let library = [ Movie() Song()]//数组 library 的类型被推断为[MediaItem] 检查类型用is来检查实例是否属于某个特定的子类。 123456for item in library { if item is Movie { // is Movie }else if item is Song { }} 向下转型某类型的一个常量或变量可能在幕后实际上是属于一个子类。当确定这种情况时,你可以尝试向下转到它的子类型。用 as? 或 as! 向下转型可能会失败。as?返回一个你试图向下转成的类型的可选类型。as!是试图向下转型和强制解包转换结果结为一体。如果失败会触发运行时错误 123456for item in library { if let movie = item as? Movie { //is Movie }else if let song = item as? Song { }} Any 和 AnyObjectSwift 中提供了2中特殊的类型别名: Any 可以表示任何类型,包括函数类型 AnyObject 可以表示任何类类型的实例 12345678var things = [Any]()things.append(0)things.append(0.0)things.append("hello")let optionalInt : Int? = 3things.append(optionalInt) //有警告things.append(optionalInt as Any) //没有警告 注意:Any类型可以表示所有类型的值,包括可选类型。Swift会在你用Any类型来表示一个可选类型的时候,会给你一个警告。如果你确实想使用Any类型来承载一个可选值,你可以使用as 操作符显示转换为Any 嵌套类型枚举被常用于为特定的类或结果实现某些功能。类似的,枚举可以方便的定义工具类或结构体。Swift 允许定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体 嵌套类型实践123456789101112131415161718192021222324252627282930313233343536373839struct BlackjackCard { //嵌套的 Suit 枚举 enum Suit: Character { case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣" } //嵌套的Rank类型 enum Rank: Int { case two = 2, three, four, five, six, seven, eight, nine, ten case jack , queen, king, ace struct Values { let first: Int, second: Int? } var values: Values { switch self { case .ace: return Values(first: 1 ,second: 11) case: .jack, .queen, .king return Values(first: 10, second: nil) default: return Values(first: self.rawValue, second: nil) } } } let rank: Rank, suit: Suit var description: String { var output = "suit is \(suit.rawValue)," output += "values is \(rank.values.first)" if let second = rank.values.second { output += "or\(second)" } return output }}let theAceOfSpades = BlackjackCard(rank: .ace, suit:.spades);print("\(theAceOfSpades.description)")]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[构造过程与析构过程]]></title>
<url>%2F2019%2F04%2F24%2Finit%2F</url>
<content type="text"><![CDATA[构造过程构造过程是使用类、结构体、枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储属性的初始值和执行它必须设置或初始化的工作。 存储属性的初始赋值类和结构体在在创建实例时,必须为所有存储属性设置合适的初始值。存储属性的值不能处于一个位置的状态。你可以在构造器中为存储属性赋初始值,也可以再定义属性是设置其默认值。 注意当你为存储属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察器 构造器构造器在创建某个特定类型的新实例时被调用。以关键字init命名 123init(){ // 在此执行构造过程} 默认属性值你可以在构造器中为存储属性设置初始值,也可以在属性声明时设置器默认值。提倡后者 构造参数构造参数的语法跟函数和方法的参数相同 1234567891011struct Celsius { var temperatureInCelsius : Double init(fromFahrenheit fahrenheit: Double){ temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } init(fromKelvin kelvin: Double){ temperatureInCelsius = kelvin - 273.15 }}let boilingPointOfWater = Clesius(fromFahrenheit: 200)let freezingPointOfWater = Clesius(fromKelvin: 273) 参数名和参数标签跟函数和方法参数相同,构造参数也拥有一个构造器内部使用的参数和一个在调用构造器时使用的参数标签。 如果定义构造器没有提供参数标签,Swift会为构造的每一个参数自动生成一个参数标签。 不带参数标签的构造器如果你不希望构造器的某个参数提供参数标签,你可以使用’ _ ‘显示的来描述他的外部名。 123456789101112131415struct Color { let red, green, blue: Double init(red: Double, green: Double, blue: Double){ self.red = red self.green = green self.blue = blue } init(_ white: Double){ red = white green = white blue = white }}let magenta = Color(red: 1.0, green: 1.0 blue: 1.0)let white = Color(2.0) 可选属性类型如果你定制的类包含一个逻辑上允许取值为空的存储属性。 构造过程中常量属性的赋值在构造过程中任意时间点给一个常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。 注意对于类实例来说,它的常量属性只能在定义他的类的构造过程中修改,不能在子类修改 默认构造器如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么Swift会给这些结构体或类提供一个默认构造器。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。 123456class ShoppingListItem { var name: String ? var quantity = 1 var purchased = false}var itme = ShopingListItem() 结构体的逐一成员构造器除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。 1234struct Size { var width = 0.0 , height = 0.0}let size = Size(width: 5.0, height: 2.0) 值类型的构造器代理构造器可以通过调用其他构造器来完成实例的部分构造过程,这一过程称为构造器代理,它能避免多个构造器间的代码重复。 对于值类型,你可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 self.init 注意 如果你为某个值类型定义了自定义构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问成员逐一构造器)。 假如你己自定希望默认构造器、逐一成员构造器以及你自义构造器都能用来创建实例,可以将自定义的构造器写到扩展中,而不是写在值类型的原始定义中 1234567891011121314151617181920struct Size { var width = 0.0, height = 0.0}struct Point{ var x = 0.0, y = 0.0}struct Rect { var origin = Point() var size = Size() init(){} init(origin: Point, size: Size){ self.origin = origin self.size = size } init(center: Point, size: Size){ let originX = center.x - size.width / 2 let originY = center.y - size.height / 2 self.init(origin: Point(x: originX, y: originY, size: size)) }} 类的继承和构造过程类里面的所有存储属性(包括所有继承自父类的属性)都必须在构造过程中设置初始值。Swift为类提供了指定构造器和便利构造器来确保实例中所有存储型属性都能获得初始值 12345678//指定构造器语法 跟值类型的简单构造器一样init(parameters){ statements}//便利构造器语法convenience init(parameters){ statements} 类的构造器的代理规则 指定构造器必须调用其直接父类的指定构造器 便利构造器必须调用同类中定义的其他构造器 便利构造器最后必须调用指定构造器 两断式构造过程Swift中类的构造过程包含两个阶段。第一阶段,类中的每一个存储型属性赋一个初始值。当每个存储型属性的初始值赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性 构造器的继承和重写与OC中的子类不同,Swift中的子类默认情况不会继承父类构造器。 注意父类的构造器会在安全和适当的情况下被继承。 假如你希望自定义子类能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。 当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是重写父类的这个指定构造器。因此,你必须在子类构造时带上override修饰符 相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器。因此,你的子类并未对一个父类构造器提供重写。不需要加 override修饰符 注意子类可以在初始化时修改继承来的变量属性,但不能修改继承来的常量属性 构造器的自动继承如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。 假设你为子类中引入的所有新属性都提供了默认值。以下2个规则适用: 如果子类没有定义任何指定构造器,它将自动继承父类中的所有指定构造器 如果子类提供了所有父类指定构造器的实现–无论是通过规则1继承过来的还是提供了自定义实现。它将自动继承所有便利构造器 注意对于规则2,子类可以将父类的指定构造器实现为便利构造器 指定构造器和便利构造器实践1234567891011121314151617181920212223242526272829303132333435363738394041424344454647class Food { var name : String init(name: String){ self.name = name } convenience init(){ //便利构造器一定要调用指定构造器 self.init(name: "[Unnamed]") }}let nameMeat = Food(name: "Bacon")let mysteryMeat = Food()class RecipeIngredient: Food { var quantity: Int init(name: String, quantity: Int){ self. quantity = quantity super.init(name: name) } //这里重写了父类的指定构造器 要用 override 修饰 //实现了父类所有指定构造器,将自动继承父类的便利构造器 override convenience init(name: Stirng){ self.init(name: name, quantity: 1) }}let oneMysteryItem = RecipeIngredient()let oneBacon = RecipeIngredient(name: "Bacon")let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)class ShopingListItem: RecipeIngredient { var purchased = false var description: String { var output = "\(quantity) * \(name)" output += purchased ? "1" : "2" return output }}//由于自己引入的属性提供了默认值,并且自己也没有指定构造器,因此ShopingListItem将自动继承父类所有指定构造器和便利构造器var breakfastList = [ShopingListItem(),ShoppingListItem(name: "Bacon"),ShoppingListItem(name: "Eggs", quantity: 6)]breakfastList[0].name = "Orange juice"breakfastList[0].purchased = turefor item in breakfastList { print(item.description)} 可失败构造器你可以在一个类、结构体或者枚举类型定义中,添加一个或多个可失败构造器。其语法为在 init关键字后面添加问号(init?) 注意:可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名和参数类型相同 可失败构造器创建一个类型为自身类型的可选类型对象。通过 return nil语句来表明可失败构造器在何种情况下应该失败 123456789struct Animal { let species: String init?(species: String){ if species.isEmpty { return nil } self.species = species }} 枚举类型的可失败构造器你可以通过一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败 123456789101112131415enum TemperatureUnit { case Kelvin, Celsius, Fahrenheit init?(symbol: Character){ switch symbol{ case "K": self = .Kelvin case "C": self = .Celsius case "F": self = .Fahrenheit default: return nil } }} 带原始值的枚举类型的可失败构造器带原始值的枚举类型会自动带一个可失败构造器 init?(rawValue:),该失败构造器有一个名为rawValue的参数,其类型和枚举类型的原始值类型一致。 1234enum TemperatureUnit: Character{ case kevlin = "K", Celsius = "C", Fahrenheit = "F"}let fahrenheitUnit = TemperatureUnit(rawValue: "F") 构造失败的传递类、结构体和枚举的可失败构造器可以横向代理到同类型中其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。 无论是向上代理还是横向代理,如果你代理到其他可失败构造器触发构造失败,整个构造器就立即终止 123456789101112131415class Product { let name: String init?(name: String){ if name.isEmpty { return nil } self.name = name }}calss CartItem: Product { let quantity: Int init?(name: String, quantity: Int){ if auantity < 1 { return nil } self.quantity = quantity super.init(name: name) }} 重写一个可失败构造器你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。 当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败的唯一方式是对父类的可失败构造的反回值强行解包 12345678910111213141516171819202122232425262728293031class Document { var name : String ? init {} init?(name: String){ self.name = name if name.isEmpty { return nil } }}class AutomaticallyNameDocument: Document { override init(){ super.init() self.name = "[Untitled]" } override init(name: String){ super.init() if name.isEmpty { self.name = "[Untitled]" } else { self.name = name } } }class UntitledDocument : Document { override init() { //强行解包 super.init(name: "[Untitled]")! }} init!可失败构造器init!将会建立一个对应类型的隐式解包可选类型对象 必要构造器在类的构造器前面添加 required 修饰符表明所有的该子类都必须实现该构造器 12345class SomeClass { required init(){ //构造器的实现代码 }} 子类重写父类必要构造器是,必须在子类构造器前添加required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加 override 修饰符 12345class SomeSubclass : SomeClass { required init () { // 构造器的实现 }} 通过闭包或函数设置属性的默认值如果存储属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值 123456class SomeClass { let someProperty: SomeType = { //someValue 必须和SomeType类型相同 return someValue }()} 注意闭包结尾后面接了一对空的小括号。这里用来告诉Swift立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身赋值给了属性,而不是将闭包的返回值赋值给属性。 注意:如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其他部分还没初始化。这意味着你不能在闭包里访问其他属性,即使这些属性有默认值。同样,你也不能使用隐式的self属性,或者调用其他任何实例方法 1234567891011121314151617struct Chessboard { let boardColors: [Bool] = { var temporaryBoard = [Bool]() var isBlack = false for i in 1...8 { for j in 1..8{ temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squarIsBlackAt(raw: Int, column: Int) ->Bool { return boardColors[(raw * 8) + column] }} 析构过程析构器只适用于类类型,当一个类的实例被释放之前,析构器会立即调用。析构器用关键deinit来标识 析构过程原理Swift 会自动释放不再需要的实例以释放资源 123deinit { //执行析构过程} 析构器在实例释放前会被自动调用。你不能主动调用析构器。 直到实例析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性。 析构器实践1234567891011121314151617181920212223class Bank { static var coinsInBank = 10000; static func distribute(coins numberOfCoinsRequested: Int) -> Int { let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank) coinsInbank -= numberOfCoinsToVend return numberOfCoinsToVend } static func recive(coins: Int){ coinsInBank += coins }}class Player : Int { var coinsInpurse: Int init(coins: Int){ coinsInpurse = Bank.distribute(coins: coins) } func win(cons: Int){ coinsInpurse += Bank.distribute(coins: coins) } deinit{ Bank.recive(coins: coinsInpurse) }}]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[方法 下标 继承]]></title>
<url>%2F2019%2F04%2F23%2Fmethod_subscripte%2F</url>
<content type="text"><![CDATA[方法方法是与某些特定类型相关联的函数。 实例方法(Instance Methods)实例方法是属于某个特定类、结构体或枚举类型实例的方法。 self属性类型的每一个属性都有一个隐含属性叫做self,self完全等同于该实例本身。 在实例方法中修改值类型结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。 但是,如果你确实需要在某个特定的方法中修改结构体或者枚举属性,你可以为这个方法选择可变(mutating)行为 1234567Struct Point { var x = 0.0 y = 0.0 mutating func moveByX(_ delateX: Double,_ delateY: Double){ x += delateX y += delateY }} 在可变方法中给self赋值可变方法能够赋给隐含属性self一个全新的实例。 123456struct Point { var x = 0.0 , y = 0.0 mutating func moveByX(x delataX: Double, y delataY: Double){ self = Point(x: x + delateX, y: y + delataY) }} 类型方法定义类型本身调用的方法叫类型方法。static关键字。类还可以用关键字class允许子类重写父类的方法实现。 123456class SomeClass { class func someTypeMethod() { // 在这里实现类型方法 }}SomeClass.someTypeMethod() 下标下标可以定义在类、结构体和枚举中,是访问集合、列表或有序列表中元素的快捷方式。someArray[index] somDictionary[key] 一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义多个入参的下标满足自定义类型的需求。 下标语法下标定义使用 subscript 关键字。下标可以设定读写或只读。 123456789subscript(index: Int) -> Int{ get{ //返回一个适当的Int 类型的值 } set(newValue){ //执行适当的赋值操作 }}//newValue 的类型和返回类型相同 如果只读计算型属性,可以省略只读下标的 get 关键字 123subscript(index: int) -> Int{ //返回一个适当的Int 类型的值} 123456struct TimesTable { let multiplier: Int subscript(index: Int) -> Int{ return multipliter * index }} 下标用法12var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]numberOfLegs["bird"] = 2 Swift 中Dictionary类型的下标接受并返回可选类型的值。Dictionary之所如此实现,是因为不是每个键都有个对应的值。同时这也提供了一种通过键删除对应值的方式,只需要将键对应的值赋值为nil即可 下标选项下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型 123456789var list = [Double]()subscript(row: Int, column: Int) -> Double { get{ return list[row * column] } set{ list[row *column] = newValue }} 继承一个类可以继承另一个类的方法,属性和其他特性。继承是区分类与其它类型的基本特征。 可以为类中继承来的属性添加属性观察器,无论它原本被定义为存储属性还是计算属性 定义一个基类不继承其它类的类,称之为基类 子类生成子类生成指的是在一个已有类的基础上创建一个新的类。子类继承父类的特性,并且可以进一步完善。 重写子类可以为继承来的实例方法,类方法,实例属性,或下标提供自己定制的实现。这种行为叫重写。 如果要重新某个特性,你需要在定义的前面加上override关键字。 访问父类的方法,属性及下标 在方法 someMethod() 的重写实现中,可以通过 super.someMethod() 来调用父类版本的someMethod()方法 在属性 someProperty() 的getter 或 setter实现中,可以通过 super.someProperty 来访问父类的属性 在下标的重写实现中,可以通过 super[someindex]来访问父类版本中的相同下标 重写方法重写属性你可以提供定制的getter或setter来重写任意继承来的属性,无论继承来的属性是存储型属性还是计算型属性。 注意: 你可以将一个继承来的只读属性重写为一个读写属性,只需要在重新版本的属性里提供getter和setter即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性 如果你的重写属性中提供了setter,那么你也一定要提供getter。如果你不想在重写版本中的getter里修改继承来的属性值,你可以通过super.someProperty来返回继承来的值。 123456789class Car : Vehicle { var gear = 1 override func someOverrideFunc(){ // } override var desciption: String { return super.description + "in gear\(gear)" }} 重写属性观察器可以通过重写属性为一个继承来的属性添加属性观察器 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以为他们提供willSet或didSet实现是不恰当的。 1234567class AutomaticCar :Car { override var currentSpeed: Double{ didSet { gear = Int(currentSpeed / 10.0) + 1 } }} 防止重写你可以通过方法、属性、下标前面添加final关键字来防止他们被重写:final var 、final func、final class func、final subscript]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[类和结构体 属性]]></title>
<url>%2F2019%2F04%2F23%2FSwift-ClassAndStruct%2F</url>
<content type="text"><![CDATA[类和结构体类和结构体的对比共同点: 定义属性用于存储值 定义方法用于提供功能 定义下标操作通过下标语法可以访问它们的值 定义构造器用于生成初始化值 通过扩展增加默认实现的功能 遵循协议提供某种标准功能 与结构体相比,类还有如下附加功能: 继承:允许一个类继承另一个类的特征 类型转换:允许在运行时检查和解释一个类实例的的类型 析构器:运行一个类实例释放任何其所被分配的资源 引用计数:允许对一个类多次引用 定义语法1234567class SomeClass{ //在这里定义类}struct SomeStructure{ //}注意命名风格 类和结构体实例123456struct Resolution{ var width = 0 var height = 0}let someResolution = Resolution()let someVideoMode = VideoMode() 结构体和类都使用构造器来生成新的实例。构造器最简单的形式就是在后面跟随一对空括号。通过这种方式其属性均会被初始化默认值。 属性访问通过点语法访问实例的属性。也可通过点语法为变量属性进行赋值 结构体类型的成员逐一构造器所有的结构体都有一个自动生成的成员逐一构造器,用于初始化新构体实例中成员的属性 1let var = Resolution(width: 640, height: 4800) 结构体和枚举是值类型值类型被赋予一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。 类是引用类型引用类型被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此引用的是已存在的实例本身,不是其拷贝。 恒等运算符因为类是引用类型,有可能多个常量和变量在幕后同时引用同一个类实例。运用以下2个运算符检测两个常量或者变量是否引用同一个实例 等价于(===) 不等价于(!==) 类和结构体的选择当符合一条或者多条以下条件时,请考虑构建结构体 该数据结构的主要目的是用来封装少量相关简单数据值 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用 该数据结构不需要去继承另一个既有类型的属性或者行为 注意:Swift中拷贝行为看起来总会发生,然而,Swift在幕后只用在决定必要时才执行实际拷贝。Swift管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化 属性存储属性存储属性就是存储在特定类或结构体实例里的一个常量或变量。可以在定义存储属性的时候指定特定默认值。 123456struct FixedLengthRange { var firstValue: Int let length: Int}var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)rangeOfThreeItems.firstValue = 6 常量结构体的存储属性如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行。 12let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)rangeOfFourItems.firtValue=6//这里会报错 这种行为由于结构体属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量 延迟存储属性延迟存储属性是值当第一次调用的时候才会去计算其初始值的属性。在属性声明前使用lazy关键字。 必须将延迟存储属性声明成变量,因为属性的初始值可能在实例构造完成后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。 计算属性除存储属性外,类、结构体、和枚举可以定义计算属性。计算属性不直接存储值,而是通过一个getter和一个可选的setter来间接获取和设置其他属性或变量的值 123456789101112131415161718192021222324struct Point { var x = 0.0, y = 0.0}struct Size { Var width = 0.0, height = 0.0}struct Rect { var origin = Point() var size = Size() var center: Point{ get { let centerX = orign.x + (size.width / 2) let centerY = orign.y + (size.height / 2) return Point(x: centerX, y: centerY) } set(newCenter){ orign.x = newCenter.x - (size.width / 2) orign.y = newCenter.y - (size.height / 2) } }}var square = Rect(origin: Point(x: 0.0, y0.0), size: Size(width: 10.0, height: 10.0))let initCenter = square.centersquare.center = Point(x: 15.0, y: 15.0) 简化Setter声明如果计算属性的setter没有定义表示新值的参数名,则可以使用默认名称 newValue。 123456789101112131415struct AlternativeRect{ var orign = Point() var size = Size() var center: Point { get { let centerX = origin.x + (size.width / 2) let centerY = origin.y + (size.height / 2) return Point(x: centerX, y: centerY) } set { orign.x = newValue.x - (size.width / 2) orign.y = newValue.y - (size.height / 2) } }} 只读计算属性只有geter 没有setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值。只读计算属性的声明可以去掉 get关键字和大括号 123456struct Clubid{ var width = 0.0, height = 0.0, depth = 0.0 var volume: Double { return width * height * depth }} 属性观察器属性观察器监控和响应属性值的变化,每次属性设置的时候都会调用属性观察器。 willSet在新的值被设置之前调用 didSet在新的被设置之后立即调用 注意:父类的属性在子类的构造器中被赋值时,它在父类中的willSet 和didSet 观察器会被调用,随后才会调用子类的观察器 12345678910111213class SetpCounter { var totalSteps: Int = 0 { willSet(newTotalSteps){ print("About to set totalSteps to \(newTotalSteps)") } didSet{ if totalSteps > oldValue { print("Added \(totalSteps - oldValue) steps") } } }} 全局变量和局部变量计算属性和属性观察器所描述的功能也可以用于全局变量和局部变量 全局变量和局部变量都属于存储型变量,跟存储属性类似。另外全局和局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也一样 注意 全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局常量或变量不需要标记lazy修饰符。 局部范围的常量或变量从不延迟计算 类型属性给类型定义属性,无论你创建多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性 存储型类型属性可以是变量或常量,计算行类型属性跟实例的计算型属性一样只能定义成变量属性 类型属性语法使用关键字 static定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class来支持子类对父类的实现进行重写。 12345678910111213141516171819202122232425struct SomeStructure { static var storedTypeProperty = "Some Value" static var computedTypeProperty: Int { return 1 }}enum SomeEnumeration { static var storedTypeProterty = "Some Value" static var computedTypeProperty: Int { return 6 }}class SomeClass { static var storedTypeProperty = "SomeValue" static var computedTypeProperty: Int { return 27 } cass var overridebaleProperty: Int { return 107 }}注意:跟实例的存储属性不同,必须给存储属性指定默认值。因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。存储型类型属性是延迟初始化的,他们只有再第一次访问的时候才会被初始化。即使他们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用`lazy`修饰符 获取和设置类型属性的值跟实例属性一样,类型属性也是通过点语法访问。但是,类型属性是通过类型本身来访问,而不是通过实例 1let type = SomeClass.computedTypeProperty]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[枚举]]></title>
<url>%2F2019%2F04%2F19%2Fenum%2F</url>
<content type="text"><![CDATA[枚举为一组关联的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值 枚举语法12345enum someEnumration{ // 枚举定义放在这里}注:与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。 使用 Switch 语句匹配枚举值关联值12345678910111213141516171819202122232425//表示两种商品条形码的枚举enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String)}var productBarcode = Barcode.upc(8, 32342, 1323, 3)productBarcode = .qrCode("ABCDEFGHIJKLMNOP")//这时,原始的 Barcode.upc 和其整数关联值被新的 Barcode.qrCode 和其字符串关联值所替代//用swift case 提取关联值switch productBarcode { case .cup(let numberSystem, let manufacturer, let product, let check): print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") case .qrCode(let productCode): print("QR code: \(productCode).")}//为了简洁,也可以在成员名称前标注一个let或者varswitch productBarcode {case let .upc(numberSystem, manufacturer, product, check): print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")case let .qrCode(productCode): print("QR code: \(productCode).")} 原始值使用 ASCII 码作为原始值的枚举: 123456enum ASCIIControlCharacter: Character { case tab = "\t" case lineFeed = "\n" case carriageReturn = "\r"} 注意:原始值和关联值是不同的,原始值在定义枚举是被预先填充的值,像上述三个ASCII码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或者变量时才设置的值,枚举成员的关联值可以变化 原始值的隐式赋值在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值 123enum Planet: Int { case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune} 使用枚举成员的 rawValue 属性可以访问该枚举成员的原始值: 12let earthsOrder = Planet.earth.rawValue// earthsOrder 值为 3 使用原始值初始化枚举实例如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做rawValue的参数,参数类型即为原始值类型,返回值则是枚举成员或nil 1let possiblePlanet = Planet(rawValue: 7) 注意:原始值构造器是一个可失败的构造器,因为并不是每一个原始值都有与之对应的枚举成员。 递归枚举递归枚举是一种枚举类型,它有一个或者多个枚举成员使用该枚举类型的实例作为关联值。你可以再枚举成员前面加上indirect来表示该成员可递归。 12345enum ArithmeticExpression { case number(Int) indirect case addition(ArithmeticExpression, ArithmeticExpression) indirect case multiplication(ArithmeticExpression, ArithmeticExpression)}]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[闭包]]></title>
<url>%2F2019%2F04%2F17%2Fclosure%2F</url>
<content type="text"><![CDATA[闭包定义闭包是自包含的函数代码块,可以在代码中被传递和使用。 闭包可以捕获和存储所在上下文任意的常量和变量的引用 全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一 全局函数是一个有名字但不会捕捉任何值的闭包 嵌套函数是一个有名字可以捕获其封闭函数域内值的闭包 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量的匿名闭包 闭包表达式语法1234//闭包表达式语法有如下的一般形式:{ (parameters) -> return type in statements} 闭包表达是可以是in-out参数,但不能设定默认值 单表达式闭包隐式返回单行表达式闭包可以省略 return关键字来隐士的返回单行表达式的结果。 sorted(by:{s1, s2 in s1 > s2})因为这里只包含了一个单一表达式 s1>s2 参数名称缩写Swift自动为内联函数提供了参数名称缩写功能,直接通过$0,$1,$2 来顺序调用闭包的参数。如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成 1var reversert = names.sorted(by,{ $0 < $1 }) 运算符函数实际上还有一种更简短的方式来编写上面列子中的闭包表达式。Swift的String类型定义了 (>) 的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:)方法的参数需要的函数类型符合。因此,你可以使用更简单的传递一个大于号,Swift可以自动推断出你想使用大于号的字符串的函数实现 1var reversert = names.sorted(by:>) 尾随闭包:尾随闭包是一个写在函数括号之后的闭包表达式,函数将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签: 123someFunctionThatTakesAClosure(){ //闭包主体} 闭包是引用类型逃逸闭包:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回后才被执行,我们称该改闭包从函数中逃逸。@escaping 标记这个闭包是允许“逃逸”出这个函数的 1234var completeionHandles: [() -> Void] = []func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void){ completeionHandles.append(completionHandler)} 将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 1234567891011func someFunctionWithNonescapingClosure(closure: () -> Void){ closure()}class SomeClass{ var x = 10 func doSomething(){ someFunctonWithEscapingClosure{ self.x = 100 } someFunctionWithNonescapingClosure{ x = 200 } }} 自动闭包自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候会返回被包装在其中的表达式 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行 1234567var customersInLine = ["Chris","Alex", "Ewa", "Barry","Daniella]print(customersInLine.count)// 打印出 "5"let customerProvider = { customersInLine.remove(at: 0) }print(customersInLine.count)// 打印出 "5"print("Now serving") 如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure和 @escaping属性。 123456789101112131415var customersInLine = ["Barry","Daniella"]var customerProviders: [() -> String] = []func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String){ customerProviders.append(customerProvider)}collectCustomerProviders(customersInLine.remove(at: 0))collectCustomerProviders(customersInLine.remove(at: 0))print("Collected \(customerProviders.count) closures.")// 打印 "Collected 2 closures."for customerProvider in customerProviders { print("Now serving \(customerProvider())!")}// 打印 "Now serving Barry!"// 打印 "Now serving Daniella!"]]></content>
<categories>
<category>Swift</category>
</categories>
<tags>
<tag>Swift</tag>
</tags>
</entry>
<entry>
<title><![CDATA[浅谈iOS中的Block、oc中为什么会用copy修饰block、block和deleagte的区别]]></title>
<url>%2F2019%2F04%2F16%2Fblock%2F</url>
<content type="text"><![CDATA[block 的修饰符为什么选copy想必很多开发人员知道一般用copy修饰block,接下来讲解为什么需要用copy,甚至会讲到其实用strong修身符也是可以的.在Objective-C 语言中,一共有3中类型的block: _NSConcreteGlobalBlock 全局静态block,不会访问外部局部变量 _NSConcreteStackBlock 保存在栈区中的block,但函数返回时会被销毁. _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁. 总结: block内部没有调用外部局部变量时存放在全局区(ARC和MRC下均是) block使用了外部局部变量,这种情况下也是我们平时常用的方式.MRC:Block的内部地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续在次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.所以在使用block属性时使用copy修饰.但ARC中block都会在堆上的,系统默认会对block进行copy操作. 使用copy,strong修身block在ARC和MRC都可以的,都是在堆区 补充: 一个block要使用self.会在block外部申明一个weak变量指向self,然而为何有时会block里又申明一个strong变量指向weakSelf? 原因: block会把写在block里面的变量copy一份,如果直接在block里面使用self,(self对变量默认是强引用)self对block持有,block对self持有,导致循环引用.所以这里需要声明一个弱引用weakSelf,让block引用weakSelf,打破循环引用.而这样会导致另外一个问题,因为weakSelf对self是弱引用,如果这个时候控制器pop或者其他方式使引用计数为0,就会释放,如果block是异步调用,而且调用的时候self已经释放了,这个时候weakSelf已经变成nil了.当控制器(也可以是其他控制器)pop回来之后(或者一些其他原因导致释放),网络请求完成,如果这个时候需要控制器作出反应,需要strongSelf再对weakSelf强引用一下.但是,你可能会疑问,strongSelf对weakSelf强引用,weakSelf对self弱引用,最终也是对self进行了强引用,会导致循环引用吗?不会的,因为strongSelf是在block里声明的指针,当block执行结束后,strongSelf会释放,这个时候不将不再强引用weakSelf,所以self会正确的释放. Bolck 和 Deleagte 的区别block 和delegate 本质上都是回调, 在编码风格上 block更轻型,使用简单,能够直接访问上下问.使用block代码使代码更加紧凑,降低了代码的分散程度.使用deleagte,首先要去申明protocol协议,并且写上回调方法.然后在要实现的协议类中去实现.编码风格计较零散. 源头上的区别: delegate运行成本低,block的运行成本高.block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是引用计数+1,使用完成或者block置nil后才能销毁.deleagte只保存了一个对象指针(要用weak修饰deleagte,不然也会造成循环引用),直接回调,没有其他额外的消耗. 使用场景上的区别 多个相关方法.假如每个方法都设置一个block,这样会更麻烦.而deleagte让多个方法分成一组,只需要设置一次,就可以多次回调了.当多于3个方法时就应该优先采用delegate.当1,2个回调时则使用block deleagte更安全些,比如:避免循环引用.使用block时稍微不注意就形成循环引用,导致对象释放不了.这种循环引用,一旦出现就比较难检查出来.而deleagte的方法时分开的.并不会引用上下文,因此更安全些.]]></content>
<categories>
<category>Objective-C</category>
</categories>
<tags>
<tag>Objective-C</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HTTP的简单介绍]]></title>
<url>%2F2019%2F04%2F13%2FHTTPIntroduce%2F</url>
<content type="text"><![CDATA[原文链接 https://juejin.im/entry/5981c5df518825359a2b9476 HTTP网络请求的基本介绍HTTPH (Hyper Text Transfer Protocol超文本传输协议)是一种应用层协议,它通过TCP实现了可靠的数据传输,能够保证该数据的完整性,正确性。而TCP对于数据传输控制的优点也能够体现在HTTP上,使得HTTP数据传输吞吐量,效率得到保证。对于移动开发来说,网络应用基本上都是C/S架构。客户端通过向服务端发起特定的请求,服务器返回结果,客户端解析结果,然后将结果展示在UI上,他们的之间的交互图如下 详细的交互步骤: 客户端执行网络请求,从URL中解析出服务器的主机名 将服务器的主机名转换成服务器的IP地址 将端口从URL中解析出来 建立一条客户端与Web服务器的TCP连接 客户端通过输出流向服务器发送一条HTTP请求 服务器向客户端发送一条HTTP响应报文 客户端从输入流解析报文 客户端解析报文,关闭连接 客户端将结果显示在UI上 HTTP的请求方式GET请求GET是最常见的方法,它的作用是获取服务器中某个资源,下图显示了一个例子,在这个例子中,客户端用GET方法发起了一次HTTP请求,然后服务端将对应的资源返回给客户端 注意:GET请求的参数都需要放到请求的URL中。 POST请求POST方法起初始用来向服务器传递数据的,实际上,POST请求通常会用来提交HTML的表单,表单中填好的数据会被传输给服务器,然后由服务器对这些数据进行处理,下图显示了一个用POST请求的执行流程。 比较: GET GET请求能够被缓存 GET请求有长度限制 GET请求会保存在浏览器的浏览记录中 GET请求的URL能够保存为浏览器书签 POST POST请求不能够被缓存 POST请求没有长度限制 POST请求不会保存在浏览器的浏览记录中 POST请求的URL无法保存为浏览器书签 PUT请求与GET从服务器中读取资源相反,PUT方法会向服务器写入资源,有些发布系统允许用户创建Web页面,并用PUT直接向其传输到Web服务器中。如下图所示 PUT方法的语义就是让服务器用请求的主体部分来创建一个由所请求的URL命名的新文档,如果那个URL已经存在的话,就用这个资源来代替它。 DELETE请求顾名思义,DELETE方法所做的事情就是请求服务器删除请求URL所指定的资源,但是客户端应用程序无法保证删除操作一定会被执行,因为HTTP规范允许服务器在不通知的情况下撤销请求,下图显示了一个DELETE方法实例。 注:DELETE方式与GET请求一样,参数都是需要放在请求的URL中。 HEAD请求HEAD方法与GET方法的行为很类似,但服务器在响应中只返回首部,不会放回实体的主体部分,这就允许客户端在未获取实际资源的请求下,对资源的首部进行检查,使得HEAD可以在不获取资源的情况下了解资源的情况(比如,判断其类型),通过看响应中的状态码,看看某个对象是否存在;通过查看首部,测试资源是否被修改了。 TRACE 请求客户端发起一个请求,这个请求可能要穿过防火墙,代理,网关或者其他一些应用程序,每个中间节点都可能会修改原始的HTTP请求,TRACE方法允许客户端在最终将请求发送给服务器时,看看它变成了什么样子。 TRACE请求会在目的服务器端发起一个“环回”诊断。形程最后一站的服务器会弹回来一条TRACE响应,并在响应主体携带它收到的原生报文,这样客户端就可以查看所有在HTTP应用层组成的请求、响应链、原生报文是否以及如何被毁坏或修改过。如下图所示 TRACE方法主要用户诊断,也就是说,用于验证请求是否如愿穿过请求、响应链。它是一种很好的工具,可以查看代理和其他应用程序对用户请求所产生的效果。 OPTIONS请求OPTIONS方法请求Web服务器告知其支持的各种功能。可以询问服务器通常支持哪些方法,或者对某些特殊资源支持哪些方法(有些服务器可能只支持一些特殊的对象使用特定的操作)。这对客户端应用程序提供了一种手段,使其不用实际访问那些资源就能判定访问各种资源的最优方式。下图显示了一个OPTIONS方法请求。 HTTP请求的几大要素请求行 请求行包含请求方法(Methode)、请求统一资源标识符(RUL)、HTTP版本号。URL就是URL中排除Host剩下的部分,也就是资源在服务器本地上的路径 请求头 请求头主要存放客户端想给服务端的附近信息。HTTP请求在iOS中用NSURLRequest与NSMutableRequest表示;HTTP响应用NSHTTPURLResponse表示 Host:目标服务的网络地址 Accept:让服务器知道客户端能接受的数据类型,如 text/html/json Content-Type:body中的数据类型,如 application/json; charest=UTF-8 Accept-Language:客户端的语言环境,如 zh-cn Accept-Encoding:客户端支持的数据压缩格式:如gzip User-Agent:客户端的软件环境 Connection:keep-alive,该字段是从HTTP1.1才开始有的,用来告诉服务端着是个持久链接,“请服务端不要在发出响应之后立即断开TCP连接” Content-Length:body的长度,如果body为空则该字段值为0。该字段一般在POST请求中才会有 Cookie:记录者用户信息的保存在本地的用户数据,如果有会被自动附上。 Field: 请求体 真正需要给服务器端的数据,在使用POST-multipart上传请求中请求提就是上传文件的二进制NSData类型数据;在GET请求中请求体为空;在普通的POST请求中请求提就是一些表单数据。在iOS中一般用NSMutableRequest 的HTTPBody属性表示,添加body用 [NSMutalbeURLRequest setHTTPBody] 响应状态行 响应状态行是服务器返回给客户端状态的信息,包含HTTP版本号、状态码、状态码对应的英文名称。 HTTP版本介绍 HTTP1.1之前 1、不支持持久连接。一旦服务器对客户端发出响应就立刻断开TCP连接。 2、无请求头跟响应头 3、客户端的前后请求是同步的,下一个请求必须等上一个请求从服务端拿到响应后才能发出,有点类似多线程的同步机制。 HTTP1.1(主流版本,1999发布) 1、增加请求头跟响应头 2、支持持久连接。客户端通过请求中指定Connection为keep-alive告知服务器不要在完成响应后立即释放连接。HTTP是基于TCP的,在HTTP1.1中一次TCP连接可以处理多次HTTP请求 3、客户端不同请求之间是异步的,下一个请求不必等到上一个请求回来后再发出,而可连续发出请求,有点类似多线程异步处理。 HTTP2.0(2015年) 1、HTTP2.0采用二进制格式而非文本格式 2、HTTP2.0是完全多路复用的,而非有序阻塞的。只需一个连接即可实现并行。 3、使用报头压缩,HTTP2.0降低了开销 4、HTTP2.0让服务器可以将响应主动推送到客户端缓存中。 HTTPS与HTTP的一些区别HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。HTTP协议运行在TCP之上,所有的传输的内容都是明文,在HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443.HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。]]></content>
<categories>
<category>通信</category>
</categories>
<tags>
<tag>Http</tag>
</tags>
</entry>
<entry>
<title><![CDATA[使用cocoapod托管代码。]]></title>
<url>%2F2019%2F04%2F10%2FCocoapods%2F</url>
<content type="text"><![CDATA[一、新建一个项目1、在github上新建一个项目,新加项目是要勾选许可证类型。 注:如果是已有项目,可以从其他地方拷贝一个LICENSE文件,放到项目的跟目录下(与READEME.md同级) 2、使用GitHub DeskTop 工具把代码克隆到本地。如果还没有安装的同学。赶紧去官方网站下载。很好用的工具注:这里也可以使用git命令行把远程代码clone到本地。我个人是比较习惯可视化工具 3、在当前文件夹新建一个项目。 到这里你的项目就已经准备好了。 二、构建版本注:我这里主要在github上构建release版本 123451、进入你刚刚在github上创建的项目。如果你还没有构建过版本。release一定是02、点击release。编写版本号.(这里填写的版本号要跟podspec文件里填的版本号一致,一般是从0.0.1开始填写)。3、然后填写release title 和 describe this release4、然后把你要发布的文件拖进来,或者点击进去选择文件。5、检查完成后,点击 Publish release 发布。 附:使用tag git命令来构建新版本 重点( tag 标记上传至 github ): 此处默认以上操作完成(终端完成以下操作,前提路径在该仓库下) git tag ‘xxx’(xxx必须是你在 .podspec 文件中的 version(版本号)) git add . (添加) git commit -m ‘提交内容’ git push origin xxx(xxx是你的tag号) 三. 创建podspec文件1、打开终端,cd 到你当前文件夹下。比如:cd cd Desktop/JPAlbumPicker/ 2、创建.podspec文件 12pod spec create '项目名'比如:pod spec create JPAlbumPicker JPAlbumPicker是我的项目名 3、打开你刚刚创建的podspec文件。并进行修改。 我这里建议一下作为初学者,这里稍微花点心思去了解一下大概有哪些功能。然后全部给删了,把官方写的黏贴进来,进行对应的修改。这样相对简洁。 常用的 123456789101112131415161718- s.name : 类库的名称- s.version : 类库的版本号- s.summary : 类库的摘要(简介)- s.description : 描述, 用于生成标签和提高搜索结果, 主要是类库的功能等, 尽量简短; 注意, 需要写在两个DESC中间- s.homepage : 类库的地址链接- s.screenshots : 效果图链接- s.license: 许可类型, 常用的是 MIT- s.author :作者- s.social_media_url : 个人主页地址- s.platform : 平台 例如: iOS , tvOS, watchOS等,- s.resource = "icon.png":单个资源- s.resources = "Resources/*.png" : 多个资源- s.source_files : 文件的相对路径- s.public_header_files : 公开的头文件, 如果不设置这个, 默认公开所有的头文件- s.exclude_files = "Classes/Exclude" 排除不需要的文件- s.requires_arc = true 需要ARC- s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 项目配置- s.dependency "JSONKit", "~> 1.4" 需要其他第三方库的支持 4、编写完成之后校验一下你填写的podspec文件有没有问题。 12pod lib lint '.podspec'比如:pod lib lint JPAlbumPicker.podspec 这里可能出现错误。你根据错误从新打开podspec文件修改正确就好了。另外如果错误不明确的话,后面跟上 –verbose。输出的错误内容更详细 1pod lib lint JPAlbumPicker.podspec --verbose 把错误改完之后基本上也就剩下一些警告了。如果是一些无关紧要的警告导致验证不通过。后面加上 —allow-warnings进行忽略即可。 1pod lib lint JPAlbumPicker.podspec --allow-warnings 基本上到这里podspec文件也就通过验证了。在终端上会打印 1‘项目名’ passed validation 四、注册cocoapods账户(已有账号可以忽略这步骤)输入 pod trunk me 检查你有没有注册过账号 123- Name: zhongqing- Email: [email protected] Since: December 17th, 2018 20:34 首次使用。注册 pod trunk register [email protected] ‘zhongqing’ –description=’test’ 正常情况下回收到邮件,根据提示验证即可: 五、上传代码注:这里还可以再次校验 podrepo文件。通过之后直接发布就好了。 pod trunk push JPAlbumPicker.podrepo 这里上传时间可能会有点久,耐心等待,上传成功。]]></content>
<categories>
<category>github</category>
</categories>
<tags>
<tag>git</tag>
<tag>cocoapod</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构的一些定义]]></title>
<url>%2F2019%2F04%2F06%2Fdata_struct%2F</url>
<content type="text"><![CDATA[什么是数据结构数据结构是存在一种或多种特定关系的数据元素的集合。 逻辑结构:是指数据对象中元素之间的相互关系。从逻辑上分有以下四种: 集合结构 线性结构 数形结构 图形结构 物理结构:是指数据结构在计算机中的存储形式:从物理上分有以下2种: 顺序存储结构链式存储结构 什么是算法定义:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作 算法的特点:有穷性,确定性,可行行,输入,输出。 算法的设计要求: 正确性、可读性、输入、输出 线性表定义:线性表是零个或多个具有相同元素的有限序列。线性表分为顺序存储结构和链式存储结构 线性表的顺序存储结构:线性表的顺序存储结构,指的是连续的存储单元依次存储线性表的数据元素 优点: 无需为表中元素之间的逻辑关系而增加额外的存储空间。 可以快速的存取表中任一位置的元素 缺点: 插入和删除操作需要移动大量的元素 当线性表长度变化较大时,难以确定存储空间的容量 造成存储空间的碎片 线性表的链式存储结构:结点:我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中的存储的信息称做指针或链。这两部分信息组成的数据元素ai的存储映像。称为结点 单链表 n个结点链接成一个链表,即为线性表的链式存储结构,因为此链表的每一个结点中只包含一个指针域,所以叫单链表。 静态链表用数组描述的链表叫做静态链表 循环链表 将单链表中终端结点的指针由空指针指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。 双向链表 双向链表是在单链表的每一个结点中,再设置一个指向前驱结点的指针域 栈与队列栈栈是限定仅在表尾进行插入和删除的操作的线性表。我们把允许插入和删除的一端称为栈顶,另一端称为栈底。栈又是后进先出的线性表,简称LIFO(last in first out) 队列队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表.队列是一种先进先出的线性表,简称FIFO(first in first out)。允许插入的一端称为队尾,允许删除的一端称为队头 他们均可用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。因此他们各自有各自的技巧来解决这个问题 对于栈来说,如果两个数据类型相同的栈,可以用数组的两端作栈底的方法让两个栈共享数据,这就可以最大地利用数组空间。 对于对列来说,为了避免数组插入和删除需要移动数据,于是引入了循环队列,使得队头和队尾可以在数组中循环变化。解决了移动数据的时间消化,使得本来插入和删除是O(n)的时间复杂度变成了O(1)。 他们也都可以通过链式存储结构来实现。实现原则上与线下表基本相同。 串串(string)是由零个或多个字符组成的有限序列,又名字符串。 注: 串的顺序存储有一些变化,串值的存储空间可在程序执行过程中动态分配而得。比如在计算机中存在一个自由存储区。叫做”堆“。这个堆可由C语言动态分配函数malloc()和free()来管理 串的链式存储结构除了在连接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序存储结构好。 树树(Tree)是n(n>=0)个结点的有限集。当n=0时称为空数。在任意一棵非空树中:有且仅有一个特定的结点称为根(root)结点;当(n>1)时,其余的结点可分为m(m>0)个互不相交的有限集T1、T2、T3…Tm,其中每一个集合本身又是一棵树,并且称做根的子数(SubTree) 其他定义 结点拥有的子树称为结点的度。度为0时称为叶结点或终端结点。度不为0时称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。 结点的层次从根开始定义起,根为第一层,根的孩子为第二层。双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度或高度。 如果将树中结点的各个子树看成从左至右是有序的,不能互换的,则称该树为有序树,否则称为无序树。 森林是m(m>0)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。 树的存储结构: 双亲表示法:在每一个结点中,附设一个指示器指示双亲结点在数组中的位置。 孩子表示法:把每个结点的孩子排列起来,以单链表作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。 孩子兄弟表示法:任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设计两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。 二叉树二叉树是n(n>=0)个结点的有限集合,该集合或为空集(称为空二叉树),或者由一个根节点和两颗互不相交的,分别称为根结点的左子树和右子树的二叉树组成。 二叉树的特点: 每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。注意不是只有两颗子树,而是最多有。没有子树或者有一颗子树都是可以的。 左子树和右子树是有顺序的,次序不能任意颠倒。 即使树中的某一个节点,也要区分是左子树还是右子树 二叉树具有5种基本形态 空二叉树 只有一个根结点 根结点只有左子树 根结点只有右子树 根结点既有左子树又有右子树 特殊二叉树 斜树:所有的结点都只有左子树的二叉树叫做左斜树。所有节点都只有右子树的二叉树叫做右斜树。左斜树和右斜树统称为斜树。 满二叉树:如果所有的分支节点都存在左子树和右子树,并且所有的叶子都在同一层上。这样的二叉树叫做满二叉树。 完全二叉树:对一颗具有n个结点的二叉树按层序标号,如果编号为i(1 <= i <=n )的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵树称为完全二叉树。(不好理解)]]></content>
<categories>
<category>数据结构</category>
</categories>
<tags>
<tag>数据结构</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CocosPod 常见错误]]></title>
<url>%2F2019%2F04%2F04%2FCocosPodError%2F</url>
<content type="text"><![CDATA[从cocospod下载一个16年的老项目,然后pod install,安装一下第三方依赖。然后提示以下错误。 1234567Re-creating CocoaPods due to major version update.Analyzing dependencies[!] The dependency `SnapKit (~> 0.17.0)` is not used in any concrete target.The dependency `AVOSCloud (~> 3.1.3.6)` is not used in any concrete target.The dependency `MJExtension` is not used in any concrete target.The dependency `Kingfisher (~> 1.8)` is not used in any concrete target.The dependency `MJRefresh` is not used in any concrete target. 我老的podfile文件是这样的 12345678source 'https://github.com/CocoaPods/Specs.git'platform :ios, '8.0'pod 'SnapKit', '~> 0.17.0'pod 'AVOSCloud', '~> 3.1.3.6'pod 'MJExtension'pod 'Kingfisher', '~> 1.8'pod 'MJRefresh' 修改后 1234567891011source 'https://github.com/CocoaPods/Specs.git'platform :ios, '8.0'target "MJianshu" dopod 'SnapKit', '~> 0.17.0'pod 'AVOSCloud', '~> 3.1.3.6'pod 'MJExtension'pod 'Kingfisher', '~> 1.8'pod 'MJRefresh'end 重新pod install 就能下载了。 因为老的格式没有按下面的这个语法写。新的major版本不支持了。 123target "你的项目名" do//这里填写你要添加的第三方依赖。end 执行 pod setup 很慢很慢的解决方案 前期准备(安装过可以省略):1.查看ruby 2.更新 gem 3.安装cocoapods。 pod repo remove master pod repo add master https://gitcafe.com/akuandev/Specs.git 会出现 [!] To setup the master specs repo, please run `pod setup`这里不用管它,继续这一步把https改成git 速度上会快一点 git clone git://gitclub.cn/CocoaPods/Specs.git ~/.cocoapods/repos/master pod repo update //到这里结束大概20来分钟,你可以试一下 pod search masonry]]></content>
<categories>
<category>CocoaPods</category>
</categories>
<tags>
<tag>CocoaPods</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iOS中几种常用的锁]]></title>
<url>%2F2019%2F04%2F01%2Flock%2F</url>
<content type="text"><![CDATA[多线程编程中,应该尽量避免资源在线程之间共享,以减少线程之间的相互作用,但是总有多个线程相互干扰的情况下(如多个线程访问一个资源)。在线程必须交互的情况下,就需要一些同步工具,来确保它们交互是安全的。 锁是线程编程同步工具的基础。iOS开发中常用的锁用一下几种: @synchronized NSLock 对象锁 NSRecuisiveLock 递归锁 NSConditionLock 条件锁 pthread_mutex 互斥锁 dispatch_semaphore 信号量加锁(GCD) @synchoronized关键字加锁(互斥锁),性能较差不推荐使用。 12345678 @synchoronized(self){ //这里添加要加锁的代码}注意点:1. 加锁代码尽量少2. 添加OC对象必须在多个线程中都是同一对象3. 有点是不需要显示的创建锁对象,便可以实现锁的机制。3. @synchronized块会隐藏的添加一个异常处理程序来保护代码,该处理层会在异常抛出是自动释放互斥锁。所以如果不想让隐试的异常处理例程带来额外开销,你可以考虑锁对象。 以面通过买票的例子展示使用 12345678910111213141516171819202122232425262728//设置票的张数-(void)startSaleTickets{ _tickets = 5;//设置票的总数 dispatch_async(_saleticket_queue, ^{ [self saleTickets]; }); dispatch_async(_saleticket_queue, ^{ [self saleTickets]; });}//@synchronized-(void)saleTickets{ while (1) { @synchronized (self) { [NSThread sleepForTimeInterval:1]; if (_tickets > 0) { _tickets -- ; NSLog(@"剩余票数:%d, Thread:%@",_tickets,[NSThread currentThread]); }else{ NSLog(@"票已经卖完:Thread:%@",[NSThread currentThread]); break; } } }} 以下是打印 2019-04-01 14:19:27.397588+0800 LockTest[4435:147099] 剩余票数:4, Thread:<NSThread: 0x600000c8c540>{number = 3, name = (null)} 2019-04-01 14:19:28.398071+0800 LockTest[4435:147099] 剩余票数:3, Thread:<NSThread: 0x600000c8c540>{number = 3, name = (null)} 2019-04-01 14:19:29.398409+0800 LockTest[4435:147099] 剩余票数:2, Thread:<NSThread: 0x600000c8c540>{number = 3, name = (null)} 2019-04-01 14:19:30.398952+0800 LockTest[4435:147099] 剩余票数:1, Thread:<NSThread: 0x600000c8c540>{number = 3, name = (null)} 2019-04-01 14:19:31.403696+0800 LockTest[4435:147099] 剩余票数:0, Thread:<NSThread: 0x600000c8c540>{number = 3, name = (null)} 2019-04-01 14:19:32.403928+0800 LockTest[4435:147099] 票已经卖完:Thread:<NSThread: 0x600000c8c540>{number = 3, name = (null)} 2019-04-01 14:19:33.405796+0800 LockTest[4435:147102] 票已经卖完:Thread:<NSThread: 0x600000c8d040>{number = 4, name = (null)} NSLock 互斥锁,不能多次调用lock方法,会造成死锁 在Cocoa程序中NSLock实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking定义的,它定义了lock和unlock方法,你使用这些方法获取或者释放该锁。 NSLock类还增加了tryLock和lockBeforeDate:方法。 tryLock试图获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反,它只是返回NO。 lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变成非阻塞状态。(或者返回NO) 123456789101112131415161718192021222324252627-(void)startSaleTickets{ _tickets = 5;//设置票的总数 dispatch_async(_saleticket_queue, ^{ [self saleTickets]; }); dispatch_async(_saleticket_queue, ^{ [self saleTickets]; });}//@synchronized-(void)saleTickets{ while (1) { @synchronized (self) { [NSThread sleepForTimeInterval:1]; if (_tickets > 0) { _tickets -- ; NSLog(@"剩余票数:%d, Thread:%@",_tickets,[NSThread currentThread]); }else{ NSLog(@"票已经卖完:Thread:%@",[NSThread currentThread]); break; } } }} 打印如下 2019-04-01 14:53:04.285814+0800 LockTest[4552:172571] 剩余票数:4, Thread:<NSThread: 0x60000320d940>{number = 3, name = (null)} 2019-04-01 14:53:06.584844+0800 LockTest[4552:172572] 剩余票数:3, Thread:<NSThread: 0x60000320d7c0>{number = 4, name = (null)} 2019-04-01 14:53:07.585406+0800 LockTest[4552:172571] 剩余票数:2, Thread:<NSThread: 0x60000320d940>{number = 3, name = (null)} 2019-04-01 14:53:08.443847+0800 LockTest[4552:172572] 剩余票数:1, Thread:<NSThread: 0x60000320d7c0>{number = 4, name = (null)} 2019-04-01 14:53:10.613102+0800 LockTest[4552:172571] 剩余票数:0, Thread:<NSThread: 0x60000320d940>{number = 3, name = (null)} 2019-04-01 14:53:12.610755+0800 LockTest[4552:172572] 票已卖完,Thread:<NSThread: 0x60000320d7c0>{number = 4, name = (null)} 2019-04-01 14:53:13.614076+0800 LockTest[4552:172571] 票已卖完,Thread:<NSThread: 0x60000320d940>{number = 3, name = (null)} NSRecursiveLock 递归锁使用递归锁最容易犯的一个错误就是在递归或循环中造成死锁如下代码中,因为在线程1中的递归block中,锁会被多次lock,所以自己也被阻塞了 12345678910111213141516-(void)deadLockTest{ dispatch_async(_saleticket_queue, ^{ static void(^LockTest)(int); LockTest = ^(int value){ [self->_lock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; NSLog(@"deadLockTest Value=%d",value); LockTest(value--); } [self->_lock unlock]; }; LockTest(5); });} 此处,将NSLock换成NSRecursizeLock便解决问题.NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多次次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。 123456789101112131415161718-(void)recursizeLockTest{ _recursizeLock = [[NSRecursiveLock alloc] init]; dispatch_async(_saleticket_queue, ^{ static void(^TestLock)(int); TestLock = ^(int value){ [self->_recursizeLock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; NSLog(@"recursizeLockTest Value=%d",value); value--; TestLock(value); } [self->_recursizeLock unlock]; }; TestLock(5); });} NSConditionLock 条件锁 12345678910111213141516171819-(void)condictionLockTest{ NSConditionLock *theLock = [[NSConditionLock alloc] init]; //线程1 dispatch_async(_saleticket_queue, ^{ for (int i=0; i<= 3; i++){ [theLock lock]; NSLog(@"thread:%d",i); sleep(1); [theLock unlockWithCondition:i]; } }); //线程2 dispatch_async(_saleticket_queue, ^{ [theLock lockWhenCondition:2]; NSLog(@"thread2111111"); [theLock unlock]; });} 在线程1中加锁使用了lock,是不需要条件的,所以顺利的就锁住了,unlockWithConditin:在开锁的同时设置了一个整形条件2.线程2則需要一把被标识为2的钥匙,所以当线程1循环到i=2时,线程2的任务才执行。NSConditionLock也跟其他的锁一样,是需要lock与unlock对应的,只是lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的。当然这是与你需求相关的。 pthread_mutex 互斥锁 12345678910111213141516171819- (void)pthread_mutexTest{ __block pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); //线程1 dispatch_async(_saleticket_queue, ^{ pthread_mutex_lock(&mutex); NSLog(@"任务1"); sleep(2); pthread_mutex_unlock(&mutex); }); //线程2 dispatch_async(_saleticket_queue, ^{ sleep(1); pthread_mutex_lock(&mutex); NSLog(@"任务2"); pthread_mutex_unlock(&mutex); });} 打印如下 2019-04-01 17:28:03.606464+0800 LockTest[5075:272170] 任务1 2019-04-01 17:28:05.611833+0800 LockTest[5075:272169] 任务2 dispatch_semaphore 信号量实现加锁 GCD也提供了一种信号机制。使用它我们也可以来构建一把“锁”(从本质意义上来讲,信号量与锁是有区别的) 123456789101112131415161718- (void)dispatch_semaphoreTest{ //创建信号量 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); //线程1 dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"任务1"); sleep(10); dispatch_semaphore_signal(semaphore); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(1); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"任务2"); dispatch_semaphore_signal(semaphore); });} 打印如下: 2019-04-01 17:33:14.204755+0800 LockTest[5090:275909] 任务1 2019-04-01 17:33:24.206795+0800 LockTest[5090:275911] 任务2 信号量和互斥锁的区别 信号量用在多线程任务同步的,一个线程完了某一个动作就通过信号量来告诉被的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才能开始利用这个资源。比如全局变量的访问,有时要加锁,操作完了再解锁。有时候锁和信号量会同时使用的。也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如有A、B两个线程,B线程要等A线程完成某一个任务以后再进行自己下面的步骤,这个任务不一定是锁定某一资源,还是可以进行一些计算或者数据处理之类。而线程互斥则是“锁住某一资源的概念”,在锁定期间内,其他线程无法对被保护的数据进行操作。有些情况下两者可以互换。]]></content>
<categories>
<category>多线程</category>
<category>锁</category>
</categories>
<tags>
<tag>Objective-C</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iOS 多线程实现 --GCD使用详解]]></title>
<url>%2F2019%2F04%2F01%2FGCD%2F</url>
<content type="text"><![CDATA[本文来自:https://www.jianshu.com/p/33151a5bac28 介绍GCD,英文全称是Grand Central Dispatch(功能强悍的中央调度器),基于C语言编写的一套多线程开发机制,因此使用时会以函数形式出现,且大部分函数以dispatch开头,虽然是C语言的但对于苹果其他多线程实现方式,抽象层次更高,使用起来也更加方便。 它是苹果为应对多核的并行运算提出的解决方案,它会自动利用多核并发处理和运算,它能提供系统级别的处理,而不再局限于某个进程、线程,官方声明会更快、更高效、更灵敏,且线程由系统自动管理(调度、运行),无需程序员参与,使用起来非常方便。 任务和队列GCD有2个核心:任务和队列 任务:要执行的操作或方法函数, 队列:存放任务的集合,而我们要做的就是将任务添加到队列然后执行,GCD会自动将队列中的任务按先进先出的方式提取并交付给对应的线程。注意任务的取出是按照先进先出的方式,这也是队列的特性,但是取出后执行的顺序不一定,下面会详细讨论。 1任务任务是一个比较抽象的概念,可以简单的认为是一个操作、一个函数、一个方法等等。在实际的开发中大多是以block的形式,使用起来更加灵活。 2队列queue 有2种队列:串行队列和并行队列。串行队列:同步执行,在当前线程执行;并行队列:可由多个线程异步执行,但任务取出还是FIFO的。 队列创建,根据函数的第二个参数来创建串行或并行队列 123// 参数1 队列名称// 参数2 队列类型 DISPATCH_QUEUE_SERIAL/NULL 串行队列,DISPATCH_QUEUE_CONCURRENT代表并行队列dispatch_queue_t serialQ = dispatch_queue_create("队列名称",NULL); 另外系统还提供了两种队列:全局队列和主队列 全局队列属于并行队列,只不过由系统创建没有名字,且在全局可见(可用)。获取全局队列 12345/* 取得全局队列 第一个参数:线程的优先级,设为默认即可,个人习惯写0,等同于默认 第二个参数:标记参数,目前没有用,一般传入0*/serialQ = dispatch_get_global_queue(DISPATCH_QUEQE_PRIORITY_DEFAULT, 0); 主队列属于串行队列,也由系统创建,只不过运行在主线程(UI线程)。获取主队列 1serialQ = dispatch_get_main_queue(); 关于内存:queue属于一个对象,也是占用内存的,也会使用引用计数,当queue添加一个任务时就会将这个queue retain一下,引用计数+1,直到所有任务都完成内存才会释放。(我们在声明queue属性时要用strong)。 3执行方式–2种同步执行和异步执行同步执行:不会开启新的线程,在当前线程执行。异步执行:GCD管理的线程池中有空闲线程就会从队列中取出任务执行,会开启线程。 下面为实现同步和异步的函数,函数功能为:将任务添加到队列并执行 1234567/* 同步执行 第一个参数:执行任务的队列:串行、并行、全局、主队列 第二个参数:block任务*/void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);//异步队列void dispatch_async(dispatch_queue_t queue, dispathc_block_t block); 注意:默认情况下,新线程都没有开启runloop,所有当block任务完成后,线程都会自动被回收,假设我们想在新开的线程中使用NSTimer,就必须启用runloop。可以使用[[NSRunLoop currentRunLoop] run] 开启当前线程,这时就需要自己管理线程的回收等工作。 另外还用2个方法,实际开发中用的并不是太多dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);和 dispatch_barrier_async(dispatch_queue_t queue, disaptch_block_t block); 加了一个barrier,意义在于:队列之前的block处理完成之后才开始处理队列中barrier的block,且barrier的block必须处理完成之后,才能处理其他的block。 三、几种类型很明显两种执行方式,两种队列。那么就有4种情况:串行队列同步执行、串行队列异步执行、并行队列同步执行、并行队列异步执行。哪一种会开启新的线程?开几条?是否并发?记忆起来比较绕,但是只要抓住基本的就可以,为了方便理解,现分析如下: 串行队列,同步执行——串行队列意味着顺序执行,同步执行意味着不开启线程(在当前线程执行) 串行队列,异步执行——串行队列意味着任务顺序执行,异步执行说明要开线程。(如果开多个线程的话,不能保证串行队列顺序执行,所以只能开一个线程) 并行队列,异步执行——并行队列意味着执行顺序不确定,异步执行意味着会开启线程,而并行队列又不允许不按顺序执行,所以系统为了提高性能会开启多个线程,来队列提取任务(队列中的任务取出仍然要顺序取出的,只是线程执行无序)。 并行队列,同步执行——同步执行意味着不开线程,责肯定是顺序执行的。 死锁—-程序执行不出来 四、死锁举例主队列死锁:这种死锁最常见,问题也最严重,会造成主线程卡住.原因:主队列,如果主线程正在执行代码,就不调度任务;同步执行:一直执行第一个任务直到结束.两者相互等待造成死锁,示例代码如下. 123456789- (void)mainThreadDeadLockTest{ NSLog(@"begin"); dispatch_sync(dispatch_get_main_queue(),^{ // 发生死锁下面的代码不会执行 NSLog(@"middle"); }); // 发生死锁下面的代码不会执行,当然函数也不会返回,后果也最为严重 NSLog(@"end");} 其他线程死锁,这种不会影响主线程:原因: serialQueue 为串行队列,当代码执行到block1时正常,执行到dispatch_sync时.dispatch_sync等待block2执行完才能返回,而serialQueue是串行队列,它正在执行block1,只有等block1执行完毕之后才会去执行block2,相互等待造成死锁. 12345678910111213141516- (void)deadLockTest { //其他线程死锁 dispatch_queue_t serialQueue = dispatch_queue_create("serial_queue",DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue,^{ //串行队列block1 NSLog(@"begin"); dispatch_sync(serialQueue,^{ //串行队列block2 发生死锁,下面的代码不会执行 NSLog(@"middle"); }); // 不会打印 NSLog(@"end"); }); //函数会返回,不影响主线程 NSLog(@"return");} 五、常用举例 线程间通讯 比如,为了提高用户体验,我们一般在其他线程(非主线程)下载图片或其它网络资源,下载完成后我们要更新UI,而UI更新必须在主线程执行,所以我们经常会使用 12345678// 同步执行,不阻塞直到下面block中的代码执行完毕diapatch_sync(dispatch_get_main_queue,^{ // 主线程,UI更新});//异步执行dispatch_async(dispatc_get_main_queue,^{ //主线程,UI更新}) 信号量的使用也属于线程间通讯,下面的举例是经常使用到的场景,在网络访问中,NSURLSession类都是异步的(找了好久没有找到同步的方法),而有时我们希望能够像NSURLConnection一样可以同步访问,即在网络block调用完之后做一些操作.那我们可以使用dispatch的信号量来解决. 123456789101112131415///用于线程间通讯,下面是等待一个网络完成- (void)dispatchSemaphore { NSString *urlString = [@"https:www.baidu.com" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; //设置缓存策略为每次都从网络加载 超时时间30秒 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error){ //处理完之后发,送信号量 NSLog(@"正在处理"); dispatch_semaphore_signal(semaphore); }] resume]; //等待网络处理完成 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"处理完成");} 在上面的举例中, dispatch_semaphore_signal 的调用必须是在另一个线程调用,因为当前线程已经 dispatch_semaphore_wait阻塞.另外,dispatch_semaphore_wait最好不要在主线程调用 全局队列,实现并发: 123dispatch_async(dispatch_get_global_queue(0,0), ^{ //要执行相关代码}) 六、Dispatch Group使用调度组,可以轻松实现一下任务完成后,做一些操作.比如具有顺序性要求的生产者消费者等等. 1234567891011121314151617- (void)groupTest { // 创建一个组 dispatch_group_t group = dispatch_group_create(); NSLog(@"开始执行"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ //任务1 //等等一段时间执行 [NSThread sleepForTimeInterval:1]; NSLog(@"task1 runing in %@",[NSThread currentThread]); }); dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{ //任务2 NSLog(@"task2 runing in %@",[NSThread currentThread]); }); });} 点击屏幕后打印如下,可以看到任务1虽然等待了1s,任务2也不执行,只有任务1执行完成之后才去执行任务2. 示例2:其实示例1并不常用,真正用到的是监控多个任务完成之后,回到主线程更新UI,或者做其它事情 123456789101112131415161718192021222324252627-(void)groupTest1 { dispatch_group_t group = dispatch_group_create(); NSLog(@"开始执行"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ //关联任务1 NSLog(@"task 1 running in %@",[NSThread currentThread]); }); dispatch_group_async(group,dispatch_get_global_queue(0,0), ^{ //关联任务2 NSLog(@"task2 runing in %@",[NSThread currentThread]); }); dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{ //关联任务3 NSLog(@"task3 runing in %@",[NSThread currentThread]); }); dispatch_group_async(group,dispatch_get_global_queue(0, 0), ^{ //关联任务4 [NSThread sleepForTimeInterval:1]; NSLog(@"task4 runing in %@",[NSThread currentThread]); }); dispatch_group_notify(group,dispatch_get_main_queue(), ^{ //回到主线程执行 NSLog(@"mainTask running in%@",[NSThread currentThread]); }); });} 点击屏幕打印如下,无论其他任务的执行完成顺序,mainTask等待他们执行完成后才执行. 019-03-30 21:15:13.903164+0800 GDC[11358:631441] 开始执行2019-03-30 21:15:13.903705+0800 GDC[11358:631487] task 1 running in <NSThread: 0x600000b6d240>{number = 4, name = (null)}2019-03-30 21:15:13.903754+0800 GDC[11358:631488] task2 runing in <NSThread: 0x600000b60340>{number = 3, name = (null)}2019-03-30 21:15:14.906108+0800 GDC[11358:631490] task4 runing in <NSThread: 0x600000b6d340>{number = 5, name = (null)}2019-03-30 21:15:15.903843+0800 GDC[11358:631489] task3 runing in <NSThread: 0x600000b6d480>{number = 6, name = (null)}2019-03-30 21:15:15.904085+0800 GDC[11358:631441] mainTask running in<NSThread: 0x600000b3e900>{number = 1, name = main} 关于GCD的内存管理问题根据上面的代码,可以看出有关dispatch的对象并不是OC对象,那么,用不用像对待Core Foundation框架的对象一样,使用retain、release来管理它呢?答案是不用的. 如果是ARC环境,我们无需管理,会像对待OC对象一样自动内存管理.如果是MRC环境,不是使用retain、release,而是使用dispath_retain/dispatch_release 来管理.]]></content>
<categories>
<category>多线程</category>
</categories>
<tags>
<tag>Objective-C</tag>
<tag>多线程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[NSDictionary 的实现原理]]></title>
<url>%2F2019%2F03%2F29%2FDictionary%2F</url>
<content type="text"><![CDATA[NSDictionary使用原理1、NSDictionary (字典)是使用hash表来实现key和value之间的映射存储的,hash函数设计的好坏影响着数据查找访问效率 2、Objective-C 中的字典NSDictionary底层其实就是一个哈希表,实际上绝大多数语言中字典都是通过哈希表实现, 哈希原理1哈希概念:哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。 哈希表的存储过程: 根据key计算出它的哈希值h。 假设箱子的个数为n,那没这个键值对应该放在第(h % n)个箱子中。 如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突 1234在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所用键值都会排列在链表中。哈希表还有一个重要的属性:负载因子(load foctor),它用来衡量哈希表的空、满程度,一定程度上也是可以体现查询效率,计算公式为负载因子 = 总键值对数/箱子个数负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是1,或者0.75)时,哈希表将自动扩容。 重哈希概念: 哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使key的哈希值不变,对箱子个数取余结果也会发生改变,因此所有键值对的存放位置可能发生改变,这个过程称为重哈希(rehash) 哈希表的扩容并不是总能够有效的解决负载因子过大的问题。假设所有的key的哈希值都一样,那么即使扩容以后他们的位置也不会变化。因为负载因子会降低,但实际存储在每个箱子中的链表长度并不发送改变,因此也就不能提高哈希表的查询性能 总结1、如果哈希表中本来箱子就比较多,扩容时需要重新设计哈希并移动数据,性能影响较大。2、如果哈希函数设计不合理,哈希表在极端情况下会变成线性表。性能极低。]]></content>
<categories>
<category>实现原理</category>
</categories>
<tags>
<tag>Objective-C</tag>
</tags>
</entry>
<entry>
<title><![CDATA[iOS 内存的几大区域]]></title>
<url>%2F2019%2F03%2F29%2Fios_memory%2F</url>
<content type="text"><![CDATA[本文参考:https://www.jianshu.com/p/6fe8bf22acfb 堆栈(stack):由系统管理分配和释放。一般存放函数的参数值,和局部变量(函数中的基本数据类型)。栈区的操作方式类似数据结构中的栈(先进后出) 堆区(heap):由程序员管理(分配释放),若程序员不释放,程序结束时可能由系统回收。一般由程序员 alloc、new 出来的对象。堆的操作方式与数据结构中的堆不同,操作方式类似于链表 全局区(又称静态去)(static)由编译器管理(分配和释放),程序结束后由系统释放。存放全局变量和static修饰的静态变量。静态区由2块区域组成,一块是存放未初始化的全局变量和静态变量,另一块是初始化已经完成的全局变量和静态变量,这两块是相邻的 文字常量区:由编译器管理(分配释放),程序结束后由系统存放。存放常量字符串。 程序代码区存放函数的二进制代码 几大区域详解 申请方式栈区:系统自动分配。例如,在函数申明中一个局部变量(int a;),系统会自动在栈区开辟空间 堆区:需要程序员自己申请,并指明大小,例如(char p1 = (char *)malloc(10); char p2 = new char[10];)注意p1、p2、本身是栈区,他俩指向地址是在堆区 申请后系统的响应 栈区:只要栈区的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出 堆区:操作系统会有一个记录空闲内存地址的链表,当系统收到程序申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点。然后将该节点重空闲链表中删除,并将改节点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地处记录本次分配的空间的大小,这样,后续代码中的delete语句就可以正确的释放该内存空间。还有找到改堆节点的大小不一定正好等于申请的大小,系统会自动将多余的内存空间重新放入空闲链表中。 申请大小的限制 栈区:栈区是向低地址扩展的数据结构,是一块连续的内存。栈顶的地址和栈的最大容量是系统预先规定好的,是一个在编译是就确定的常数,如果申请的空间超过栈的剩余空间时,会有错误提示。因此,能从栈获得空间较小。 堆区:堆区是搞地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空内存地址的,自然不是连续的。而链表的遍历反向是由低地址向高地址。堆区的大小受限于计算机系统的虚拟内存,由此可见,堆区获得的空间较灵活,空间较大。 存取效率比较 堆区:堆区是运行时刻赋值的 栈区:栈区也是运行时刻赋值的,栈区的读写速度比堆区块 全局区:编译的时候就确定 其余的现在不确定 申请效率的比较栈区:栈区由系统自动分配,速度快,但程序员无法控制堆区:堆区是alloc(c++中使用,即创建新对象)分配的内存,一般速度较慢,而且容易产生内存碎片,不过使用起来方便。 结语: 栈区是点菜(只管吃,其他都不用管),堆区是自助餐(都自己来)]]></content>
<categories>
<category>原理</category>
</categories>
<tags>
<tag>iOS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据持久化的方案]]></title>
<url>%2F2019%2F03%2F27%2FData_persistence%2F</url>
<content type="text"><![CDATA[本文来自: https://casatwy.com/iosying-yong-jia-gou-tan-ben-di-chi-jiu-hua-fang-an-ji-dong-tai-bu-shu.html 1 NSUserDefault一般来说,小规模数据,弱业务相关数据,都可以放到NSUserDefault里面,内容比较多的数据,强业务相关的数据就不太适合NSUserDefault了。 2 keychainKeyChain 是苹果提供的带有可逆加密的存储机制,普遍用在各种存密码的需求上。另外,由于App卸载只要系统不重装,KeyChain中的数据依旧能够的到保留,依旧可被iCloud同步特性,大家都会在这里存储用户唯一标识串。所有有需要加密,需要存储iCloud的敏感小数据,一般都会放在Keychain。 3 文件存储文件存储包括了Plist,archive,Stream等方式。一般结构化的数据或者需要方便查询的数据,都会以Plist的方式去持久化。Archive方式适合存储平时不太经常使用但很大量的数据,或者读取之后希望直接对对象的数据,因为Archive会将对数据及其对象关系序列化,以至于读取数据的时候需要Decode很花时间,Decode的过程是解压,也可以是序列化,这个可以根据具体中的实现来决定。Stream就是一般的文件存储了。一般用来存存图片啥的。适用于经常使用,然而数据量又不算非常大的那种。 4 数据库存储数据库存储,样式就比较多了。苹果自带了一个CoreData,当然业界也有无数代替可选的方案,不过真正用在iOS领域的除了CoreData外,就是FMDB比较多了。数据库方案主要是为了便于增删改查,当数据有状态和类别的时候最好还是采用数据库方案比较好,而且尤其是这些状态和类别都是强业务相关的时候,就更加采用数据库方案了。因为你不可能通过文件系统遍历文件去甄别你需要获取某个状态或类别的数据,这么做成本太大了。当然,特别大量的数据也不合适直接存储数据库,比如图片或者文章这样的数据,一般来说,都是数据库存一个文件名,然后这个文件名指向的是某个图片或者文章的文件。如果真的要做全文索引这种需求,建议最好还是挂个API丢到服务端去做。]]></content>
<categories>
<category>Objective-C</category>
</categories>
</entry>
<entry>
<title><![CDATA[内存管理]]></title>
<url>%2F2019%2F03%2F26%2FMRC-ARC%2F</url>
<content type="text"><![CDATA[MRC 与 ARCObjective-C中提供了两种内存管理机制:MRC(MannulReference Counting)和 ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求。现在苹果推荐使用 ARC 来进行内存管理。 MRC对象操作的四个类别 操作对象 OC中对应的方法 对应的retainCount变化 持有并生成对象 alloc/new/mutableCopy等 +1 持有对象 retain +1 释放对象 release. -1 废弃对象 dealloc 注意1 这些对象操作的方法并不包含在OC中,而是包含在cocoa框架下的Foundation框架中。从iOS7开始,这些方法被移到Runtime当中,可以objc4-680 NSObject.h 找到2 对象的 reatinCount 属性并没有实际上的参考价值,参考苹果官方文档《Practical Memory Management》. 四个法则 自己生成的对象,自己持有 非自己生成的对象,自己也能持有 不在需要自己持有的对象的时候,释放 非自己持有的对象无需释放 autorelease使得对象在超出生命周期后能正确的释放(通过调用release方法)调用release后对象会被立即释放,而调用outorelease后对象不会立即释放,而是注册到autoreleasepool中,经过一段时间后pool结束,此时调用release方法,对象被释放。 在MRC的内存管理模式下,对变量的相关的方法有:reatin ,release和 autorelease。reatin和release方法操作是引用计数,当引用计数为0时,便自动释放内存。并且可以用NSAutoreleasePool对象,对加入自动释放池(autorelease调用)的变量进行管理,当drain时回收内存 ARCARC是苹果引入的自动内存管理机制。会根据引用计数自动监测对象的生命周期,实现方式是编译时期自动在已有代码中合适的内存管理代码以以及在Runtime做一些优化。 变量标识符 __strong __weak ___unsafe_unretained __autoreleasing __strong是默认使用标识符 只要还有一个强指针指向某个对象,这个对象就会一直存活 __weak 声明这个引用不会保存被引用对象的存活,如果对象没有强引用了,弱引用会被置为nil __unsafe_unretained声明这个引用不会保存引用对象的存活,如果对象没有强引用了,它不会被置为nil。如果它引用的对象被回收掉了,该指针就变成野指针。 __autoreleasing 用于标识使用引用传值的参数(id*),在函数返回时会被自动释放掉。 变量标识用法如下 1Number* __strong num = [[Number alloc] init]; 注意 __strong 的位置应该放到 * 和变量名中间,放到其它位置严格上说不正确的,但编译不会报错 属性标识符assign 表明setter仅仅是一个简单的赋值操作,通常用于基本数据类型。 strong表明属性定义一个拥有者关系。当给属性设定一个新值的时候,首先这个值进行retain,旧值进行release,然后进行赋值操作 weak表明属性定义一个非拥有者关系,当给属性定义一个新值的时候,这个值不会进行retain,旧值也不会进行release,而是进行类似assign的操作。不过当属性指向的对象被销毁时,该属性会被置为nil unsafe_unreatained 的语义和assign类似,不过是用于对象类型。表示一个非拥有(unretained)的,同时也不会在对象销毁时置nil的(unsafe)关系 copy类似strong,不过赋值是进行copy操作不是retain操作。通常在需要保留某个可变对象(NSString最常见),并且防止它被意外的串改。 unsafe_unretained 的用处unsafe_unretained 差不多是实际使用最少的一个标识符了,在使用中它的用处主要有下面几点:1 兼容性考虑。iOS4以及之前还没有引入weak,这种情况下表达弱引用的语义只能使用unsafe_unretained。现在这种情况很少使用了。2 性能考虑。使用weak对性能有一些影响,因此对性能要求高的话可以考虑使用unsafe_unretained 替换weak。 引用循环当2个对象相互持有对方的强引用,并且这2个对象的引用计数都不是0的时候,便造成了引用循环。要想破除引用循环,可以从以下几点入手 注意变量作用域,使用autorelease 让编译器来处理引用。 使用弱引用weak 当实例变量完成工作后,将其置为nil AutoRelease PoolAutoRelease Pool 提供一种可以允许你向一个对象延迟发送release操作的机制。当你想放弃一个对象的所有权,同时又不希望这个对象立马被释放掉(例如在一个方法中返回一个对象时),AutoRelease Pool的作用就显现出来了 所谓延迟发送 release 消息是指,当我们把一个对象标记为autorelease时: 1NSString* str = [[[NSString alloc] initWithString:@"hello"] autorelease]; 这个对象的retainCount会+1,但并不会发送release。当这段语句所处的autoreleasepool 进行drain操作时,所标记了autorelease的对象的retainCount会被-1。即release消息发送被延迟到pool释放的时候。 在ARC环境下,苹果引入了@autoreleasepool 语法,不再需要手动调用autorelease 和 drain 等方法。 AutoReleasePool的用处在ARC下,我们并不需要手动调用autorelease有关的方法,甚至可以完全不知道autorelease的存在,就可以真确的管理好内存。因为Cocos Touch的RunLoop中,每个runloop circle中系统都自动加入AutoReleasePool的创建和释放。 当我们创建和销毁大量对象时,使用手动创建AutoReleasePool 可以有效的避免内存峰值的出现。在这种情况不手动创建的话,外层系统创建的pool会在整个runloop circel结束之后drain,手动创建的话,会在block结束之后进行drain操作。下面是一个普遍的例子: 12345678 for (int i = 0; i < 100000000; i++){ @autoreleasepool { NSString* string = @"abc"; NSArray* array = [string componentsSeparatedByString:string]; }} 如果不使用autoreleasepool,需要在循环结束之后释放100000000个字符串,如果使用的话,则会在每次循环结束的时候都进行release操作。 AutoRelease Pool drain的时机如上面所说,系统在runloop 中创建的autoreleasepool 会在runloop一个event结束时进行释放操作。我们手动创建的autoreleasepool会在block执行完成之后进行drain操作。需要注意的是: 当block以异常(exception)结束时,pool不会被drain, pool的drain操作会把所用标记为autorelase的对象进行引用计数-1,但并不意味着这个对象一定会被释放掉,我们可以在autorelease pool中手动retain 对象,以延长它的生命周期(在MRC中)。]]></content>
<categories>
<category>原理</category>
</categories>
<tags>
<tag>Objective-C</tag>
</tags>
</entry>
<entry>
<title><![CDATA[苹果证书如何转pem文件]]></title>
<url>%2F2019%2F03%2F22%2Fpost-title%2F</url>
<content type="text"><![CDATA[openssl x509 -in apns_miaobozhibo.cer -inform der -out apns_miaobozhibo.pem注:apns_miaobozhibo.cer:推送证书apns_miaobozhibo.pem:要生的pem文件 p12转pem文件openssl pkcs12 -nocerts -out apns_miaobozhiboKey.pem -in apns_miaobozhibo.p12注:apns_miaobozhibo.p12文件apns_miaobozhiboKey.pem 要生成的pem文件]]></content>
</entry>
<entry>
<title><![CDATA[Hello World]]></title>
<url>%2F2019%2F03%2F22%2Fhello-world%2F</url>
<content type="text"><![CDATA[Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment]]></content>
</entry>
</search>