Skip to content

Commit ea458ce

Browse files
authored
Merge pull request #1 from southpeak/master
拉取新代码
2 parents 46ef84c + 85fb15b commit ea458ce

File tree

17 files changed

+302
-32
lines changed

17 files changed

+302
-32
lines changed

2017/12.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ git push origin HEAD:refs/for/Dev0.0.1
108108

109109
调试的时候,往往底层库会埋一些 `NSLog` 来调试,使用下面这种方式打印出来的函数名称 `__PRETTY_FUNCTION__` 是底层库的函数名。
110110

111-
```
111+
```c
112112
# define LEFLog(fmt, ...) NSLog((@"%s (%d) => " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
113113
```
114114
@@ -121,7 +121,7 @@ git push origin HEAD:refs/for/Dev0.0.1
121121
不太理解?举个例子吧:
122122
每个 APP 都会有一个网络层,业务层会直接与网络层进行交互。调试的时候,我想知道 A 请求是在哪个页面中的哪个函数被调用了,咋么办?前提是 `NSLog` 在底层库。我们可以这样实现:
123123
124-
```
124+
```objc
125125
@implementation LEFLog
126126
+ (NSString *)lastCallMethod
127127
{
@@ -150,13 +150,13 @@ git push origin HEAD:refs/for/Dev0.0.1
150150

151151
然后定义一个宏:
152152

153-
```
153+
```c
154154
# define LEFLog(fmt, ...) NSLog((@"%@, %s (%d) => " fmt), [LEFLog lastCallMethod], __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__
155155
```
156156
157157
打印结果是这样的:在 `LefexViewController` 中的 `viewDidLoad` 调用了 `Network ` 的 `post` 方法,并打印 `I am a log`.
158158
159-
```
159+
```objc
160160
-[LefexViewController viewDidLoad], +[Network post] (22) => I am a log
161161
```
162162

@@ -339,14 +339,14 @@ CoreAnimation 与 pop 的对比
339339

340340
3.当使用 KTVHCLogEnable(HTTPServer, YES) ,将会定义一个名为 `KTVHCLog_ HTTPServer_ConsoleLogEnable` 静态常量,初始值为 YES。
341341

342-
```
342+
```c
343343
#define KTVHCLogEnable(target, console_log_enable) \
344344
static BOOL const KTVHCLog_##target##_ConsoleLogEnable = console_log_enable; \
345345
```
346346

347347
比如我们使用不同的 View 名字创建不同的 View:
348348

349-
```
349+
```objc
350350
#define Name(target) weibo_##target##_name
351351
#define View(target) view##target##Label
352352

@@ -384,4 +384,5 @@ iOS快速解析崩溃日志
384384
385385
- 4、遇到线上用户崩溃,无法拿到完整崩溃日志,可以让用户到【设置->分析->分析数据】里面找到对应时间点的崩溃日志,然后截图,根据一个开源工具 [dSYMTools](https://github.com/answer-huang/dSYMTools),把崩溃栈的关键地址输入到文本框中即可解析出崩溃的那个方法,具体使用方法参考 [ReadMe](https://github.com/answer-huang/dSYMTools)。
386386
387-
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2017/12/10-2.jpg?raw=true)
387+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2017/12/10-2.jpg?raw=true)
388+

2017/目录.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,6 @@
124124
* [7. SourceTree 搭配 Kaleidoscope 进行 Code Review【Vong_HUST】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
125125
* [8. Swift 4.0 中 Dictionary 编码成类数组结构的字符串【南峰子_老驴】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
126126
* [9. CoreAnimation 与 pop 的对比【Vong_HUST】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
127-
* [宏中的 ## 的含义【Lefe_x】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
128-
* [iOS快速解析崩溃日志【Vong_HUST】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
127+
* [10. 宏中的 ## 的含义【Lefe_x】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
128+
* [11. iOS快速解析崩溃日志【Vong_HUST】](https://github.com/southpeak/iOS-tech-set/blob/master/2017/12.md)
129129

2018/01.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# 2018.01
2+
3+
为什么音频播放器突然没声音了呢?
4+
--------
5+
6+
**作者**: [Lefe_x](https://weibo.com/u/5953150140)
7+
8+
9+
做音频的同学可能都会遇到播放的音频突然没声音的情况,遇到这种情况后,一般控制台会抛出下面的错误:
10+
11+
```objc
12+
[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.`
13+
```
14+
15+
遇到这个错误,会导致音频不能正常播放的情况。出现这种情况的主要原因当你设置
16+
17+
```objc
18+
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
19+
```
20+
21+
时还有某些操作占用了 `AVAudioSession` 权限,必须暂停或停止对 `AVAudioSession` 的使用。比如使用 `AVAudioPlayer` 播放某一音频时,此时音频正在播放,直接设置 `AVAudioSession` 的 `active` 为 `NO`,就会报上面提到的错误。而正确的做法是先暂停播放,再设置 `AVAudioSession` 的 `active` 为 `NO`。其正确的做法像下面代码所示,这样的好处是,当遇到设置失败后可以第一时间知道出错的时间点。
22+
23+
```objc
24+
NSError *error;
25+
BOOL isSuccess = [[AVAudioSession sharedInstance] setActive:active withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
26+
if (isSuccess) {
27+
NSLog(@"恭喜你成功设置");
28+
} else {
29+
NSLog(@"设置失败");
30+
}
31+
```
32+
33+
当然如果应用中有多个地方使用 `AVAudioSession`,建议项目中统一处理 `AVAudioSession``active`,这样避免出现错误,一旦出现错误,调试起来就非常费劲。
34+
35+
iOS 中音量控制解惑
36+
--------
37+
38+
**作者**: [Lefe_x](https://weibo.com/u/5953150140)
39+
40+
41+
iOS 中音量中其实也有好多小窍门,这个小集帮你解惑。iOS 中主要有2个地方可以控制音量,一个是系统音量,用户主动按音量键,调整音量,这种方式会显示系统音量提示框;另一个是播放器的音量,比如通过 `AVAudioPlayer` 调整音量,这种不会显示系统提示音量框。
42+
43+
### 如何调节音量时不显示系统音量提示框
44+
45+
主要原理就是获取系统音量 `View`,并把它让用户不可见。但注意一点,你不能把 `MPVolumeView``hidden` 属性设置为 `YES`,这样导致的结果是用户调整音量时任然会显示系统音量提示框,如下代码所示。
46+
47+
```
48+
_volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
49+
_volumeView.backgroundColor = [UIColor yellowColor];
50+
51+
// 如果设置了 Hidden 为 YES,那么修改音量时会弹出系统音量框
52+
_volumeView.hidden = NO;
53+
_volumeView.alpha = 0.01;
54+
for (UIView *view in [_volumeView subviews]){
55+
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
56+
self.volumeSlider = (UISlider*)view;
57+
break;
58+
}
59+
}
60+
[self.view addSubview:_volumeView];
61+
```
62+
63+
### 获取系统音量
64+
65+
方法一:通过 `self.volumeSlider` 获取
66+
67+
如果想获取系统音量,可以通过第一种方式,`self.volumeSlider.value` 来获取,但是你发现第一次为 0,这很纠结,这样导致的结果就是获取的系统音量不准确。这是因为初始 `MPVolumeView` 时,`volumeSlider.value` 还没有赋值,如下图所示:
68+
69+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/2-1.jpg?raw=true)
70+
71+
可以发现,音量是后来通过 `[MPVolumeController updateVolumeValue]` 来更新的。所以我们可以通过监听 `self.volumeSlide` 值改变时的事件,达到获取系统音量的目的。
72+
73+
```objc
74+
[self.volumeSlider addTarget:self action:@selector(sliderValueDidChange:) forControlEvents:UIControlEventValueChanged];
75+
```
76+
77+
方法二:通过 `AVAudioSession` 获取
78+
79+
```objc
80+
[[AVAudioSession sharedInstance] outputVolume];
81+
```
82+
83+
这种方法直接了当。
84+
85+
### 自定义音量控件
86+
87+
如果想自定义音量控件,可以监听音量的变化,并且通过第一种方法隐藏系统音量提示框。通过监听通知,达到监听音量变化的效果。
88+
89+
### 监听音量变化
90+
91+
监听音量变化,通过监听通知
92+
93+
```objc
94+
AVSystemController_SystemVolumeDidChangeNotification
95+
96+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(volumeChanged:) name:@"AVSystemController_SystemVolumeDidChangeNotification" object:nil];
97+
```
98+
99+
最终结果 `AVSystemController_AudioVolumeNotificationParameter` 表示音量的值,这里需要注意的是 `"AVSystemController_AudioVolumeChangeReasonNotificationParameter" = ExplicitVolumeChange;` 这个值,它会由于不同的场景,有不同的值。`ExplicitVolumeChange` 是用户点击音量按钮,`CategoryChange` 是用户按 `home` 键调起 `Siri`,`RouteChange` 这个时路线修改(不太清楚,什么情况下触发的)。
100+
101+
```objc
102+
AVSystemController_SystemVolumeDidChangeNotification; object = <AVSystemController: 0x1c4001dc0>; userInfo = {
103+
"AVSystemController_AudioCategoryNotificationParameter" = "Audio/Video";
104+
"AVSystemController_AudioVolumeChangeReasonNotificationParameter" = ExplicitVolumeChange;
105+
"AVSystemController_AudioVolumeNotificationParameter" = "0.5625";
106+
"AVSystemController_UserVolumeAboveEUVolumeLimitNotificationParameter" = 0;
107+
}}
108+
```
109+
110+
### 注意点
111+
112+
如果通过代码修改了 `self.volumeSlide``value`,那么会显示出系统音量框,如果你发现某个页面突然蹦出一个系统音量框,原因大多数是你修改了这个值。
113+
114+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/2-2.jpg?raw=true)
115+
116+
动态修改应用程序的icon
117+
--------
118+
119+
**作者**: [南峰子_老驴](https://weibo.com/touristdiary)
120+
121+
122+
偶然看到 `Price Tag` 有个替换应用图标的功能,如图,研究了一下。
123+
124+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/3-1.png?raw=true)
125+
126+
这个功能是在 `iOS 10.3` 后新增的,主要的 `API` 如下所示:
127+
128+
```objc
129+
@interface UIApplication (UIAlternateApplicationIcons)
130+
// If false, alternate icons are not supported for the current process.
131+
@property (readonly, nonatomic) BOOL supportsAlternateIcons NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
132+
133+
// Pass `nil` to use the primary application icon. The completion handler will be invoked asynchronously on an arbitrary background queue; be sure to dispatch back to the main queue before doing any further UI work.
134+
- (void)setAlternateIconName:(nullable NSString *)alternateIconName completionHandler:(nullable void (^)(NSError *_Nullable error))completionHandler NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
135+
136+
// If `nil`, the primary application icon is being used.
137+
@property (nullable, readonly, nonatomic) NSString *alternateIconName NS_EXTENSION_UNAVAILABLE("Extensions may not have alternate icons") API_AVAILABLE(ios(10.3), tvos(10.2));
138+
@end
139+
```
140+
141+
只读属性 `supportsAlternateIcons` 用于判断系统是否允许修改 `App` 图标,只有在允许的情况下才能修改。`-setAlternateIconName:completionHandler:` 用于执行修改操作,如果 `iconName` 设置为 `nil`,则恢复为主图标,使用方式如下代码所示:
142+
143+
```objc
144+
- (IBAction)changeIcon:(UIButton *)sender {
145+
if ([[UIApplication sharedApplication] supportsAlternateIcons]) {
146+
147+
NSString *iconName = nil;
148+
if (sender.tag == 1) {
149+
iconName = @"rocket";
150+
} else if (sender.tag == 2) {
151+
iconName = @"pin";
152+
}
153+
154+
[[UIApplication sharedApplication] setAlternateIconName:iconName completionHandler:^(NSError * _Nullable error) {
155+
156+
}];
157+
}
158+
}
159+
```
160+
161+
除了调用 `API` 外,最主要的还需要在 `info.plist` 中配置 `CFBundleIcons` 项,这是一个字典,可包含 `CFBundlePrimaryIcon``CFBundleAlternateIcons``UINewsstandIcon` 三个键。
162+
163+
`CFBundlePrimaryIcon` 为主图标,即 `Assets.xcassets``AppIcon` 的信息,一般置空。`CFBundleAlternateIcons` 即用于设置替换图标,具体的配置项描述可以参考[官方文档](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html) ,通常的配置如图所示。
164+
165+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/3-2.png?raw=true)
166+
167+
这里需要注意的是,替换图标应该放在工程的某个目录下,而不放在 `Assets.xcassets` 中,如图所示。
168+
169+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/3-3.png?raw=true)
170+
171+
172+
iOS 关于音频播放调研
173+
--------
174+
175+
**作者**: [Lefe_x](https://weibo.com/u/5953150140)
176+
177+
178+
由于最近做音频方面的工作,就调研了一下关于音频播放的一些知识,中间也走过不少弯路,希望这篇小集能对关注我们的同学一点启示,少走一些弯路。最后提供一份我看过的资料。这里关于音频播放简单做一个总结。iOS 中音频播放有以下 5 种方式(如果你有更多的方式告诉我,非常感激),它们的使用场景各不同。
179+
180+
[1] 播放小于 30s 的音频:
181+
`AudioServicesPlaySystemSound` 可以播放小于等于 `30s` 的音频,主要用于播放一些提示音,你可以利用 `AudioServicesPlaySystemSoundWithCompletion` 的值播放完成的 `callback`。它有以下特点:
182+
183+
- 使用系统音量,不能修改播放音量;
184+
- 立刻开始播放,不能暂停;
185+
- 不支持快进播放,也不可以循环播放;
186+
- 同一时刻只能播放一个音频;
187+
- 只能通过手机播放音频,不能通过其它设备输出,比如不能通过车载播放。
188+
189+
[查看更多的系统声音ID](http://iphonedevwiki.net/index.php/AudioServices)
190+
191+
[2] `AVAudioPlayer` 播放本地的音频,或者已加载到内存中的音频流,主要用于播放本地的一些音频文件。注意它不能播放网络音频。它有以下特点:
192+
193+
- 可以从任意位置播放,可快进,快退;
194+
- 可以循环播放;
195+
- 可以同时播放多个音频;
196+
- 可以控制播放速率;
197+
198+
[3] `AVPlayer` 可以播放本地和网络音频,也可以播放视频,它支持流媒体播放,也就是说我们可以用它来做边下别播的使用场景。
199+
200+
[4] `AVQueuePlayer``AVPlayer` 的子类,它含有一个队列,主要用来播放一个音视频队列。
201+
202+
[5] `Audio Queue` 主要用来播放音频,录音,它比较底层,会有更多的控制权,如果 `APP` 主要功能是基于音频播放,推荐使用这个。
203+
204+
总的来说,如果普通的本地音频播放,可以选择 `AVAudioPlayer` ,这个不需要了解更多的音频知识,就可以达到一个基本的播放;如果想做流媒体播放,建议使用 `AVPlayer + Local Server` 的方式,类似于唱吧目前开源的方式。当然也可以选择 `Audio Queue`,不过这个难度比较高,需要对音频播放有一个整体的了解,推荐使用三方库 `FreeStream`,不过需要一些 `C++` 的知识,因为使用过程中有一些坑需要填,这样不得不阅读源码。最后推荐一些不错的文章。
205+
206+
[官方 Audio Queue](https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AQPlayback/PlayingAudio.html#//apple_ref/doc/uid/TP40005343-CH3-SW1)
207+
208+
[官方 AudioSession](https://developer.apple.com/library/content/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007875)
209+
210+
[@pp锅的码农生活 博客](https://brownfeng.github.io/2016/07/25/iOS%E9%9F%B3%E9%A2%91%E7%B3%BB%E5%88%97(%E4%B8%80)/)
211+
212+
[@cy_zju 博客](http://msching.github.io/blog/2014/07/08/audio-in-ios-2/)
213+
214+
215+
iOS中NSArray/NSSet的一些巧妙用法
216+
--------
217+
218+
**作者**: [Vong_HUST](https://weibo.com/VongLo)
219+
220+
最近用到很多操作集合类型的方法,这里总结分享一下,也欢迎大家一起补充。
221+
222+
* 假设我们已经有一个 `NSArray<Model *>` 类型的数组,但是我们想把这个数组中的 `Model` 的某个属性取出组成一个新的数组,一般情况下可能是直接去遍历,但是 `NSArray/NSSet` 有一个更便捷的方法 `valueForKey:`,可以快速取出对应属性组成的数组。但是有个问题就是这个方法的效率比 `for` 循环低,数据量不大的时候使用还是没有问题的。如下面两张图:
223+
224+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/4-1.jpg?raw=true)
225+
226+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/4-2.jpg?raw=true)
227+
228+
* 要取两个数组的交集的时候,可以先将 `NSArray` 转换成 `NSMutableSet`,再通过取二者交集即可。但是需要注意一点的是数组中的元素最好复写一下 `isEqual:``hash` 方法,保证取交集后的结果是正确的。
229+
230+
* 要将数组内元素排序或者过滤等操作,可以结合 `NSSortDescriptor``NSPredicate` 使用,可以避免掉大量冗余的 `for` 循环之类的代码。关于 `NSPredicate` 的用法可以参考 [NSHipster](http://nshipster.com/nspredicate/)`Realm`[Cheetsheet](https://academy.realm.io/posts/nspredicate-cheatsheet/)
231+
232+
* 关于图中 `valueForKey:` 的参数为什么不直接用 `@"name"` 而是用 `NSStringFromSelector(@selector(name))`,是因为后者会有代码提示可以避免硬编码带来的错误,同时后续该 `key` 换名字了之后,会有对应的警告。这个也是从 `AFNetworking` 中学到的。如图所示:
233+
234+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/4-3.jpg?raw=true)
235+
236+
对清除图片缓存的思考
237+
--------
238+
239+
**作者**: [高老师很忙](https://weibo.com/517082456)
240+
241+
众所周知,使用 `+[UIImage imageNamed:]` 方法加载图片是会进图片缓存的,清除缓存是系统触发,并没有为我们提供API;使用 `+[UIImage imageWithContentsOfFile:]` 方法加载图片是不会进入图片缓存的。如果想要有图片缓存机制,并且能手动清除图片缓存,我们可以这样做:
242+
243+
`+[UIImage imageWithContentsOfFile:]` 方向下手:
244+
我们可以自己维护一套图片缓存,`Swizzle +[UIImage imageWithContentsOfFile:]` 方法加入缓存机制。加载图片后,加入到 `NSCache` 缓存,再次取该图片时,优先取 `NSCache` 内的缓存,如果缓存内没有再去真正加载。`NSCache``Memory Warning` 的时候会自动清除缓存,我们也可以使用 `-[NSCache removeAllObjects]` 手动清除缓存。当然,你也可以不使用 `Swizzle` ,写一个 `Manager` 也是可以的,我只是提供一种思路。
245+
246+
`+[UIImage imageNamed:]` 方向下手:
247+
`Memory Warning` 或进入后台时,系统会自动帮我们清除使用 `+[UIImage imageNamed:]` 的图片缓存。我们也可以通过模拟发送 `UIApplicationDidReceiveMemoryWarningNotification``UIApplicationDidEnterBackgroundNotification` 来清除图片缓存,风险可以根据实际情况来评估。
248+
249+
还可以从私有API来下手,`+[UIImage imageNamed:]` 系统底层是通过 `UIAssetManager` 来管理图片缓存的,如下两图所示,我们可以模拟调用 `_clearCachedResources` 方法来实现清除缓存。
250+
251+
如果有其他思路的,欢迎提出!
252+
253+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/5-1.jpg?raw=true)
254+
![](https://github.com/southpeak/iOS-tech-set/blob/master/images/2018/01/5-2.jpg?raw=true)
255+
256+

2018/目录.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 2018目录
2+
3+
## 01
4+
5+
* [1. 为什么音频播放器突然没声音了呢?【Lefe_x】](https://github.com/southpeak/iOS-tech-set/blob/master/2018/01.md)
6+
* [2. iOS 中音量控制解惑【Lefe_x】](https://github.com/southpeak/iOS-tech-set/blob/master/2018/01.md)
7+
* [3. 动态修改应用程序的icon【南峰子_老驴】](https://github.com/southpeak/iOS-tech-set/blob/master/2018/01.md)
8+
* [4. iOS 关于音频播放调研【Lefe_x】](https://github.com/southpeak/iOS-tech-set/blob/master/2018/01.md)
9+
* [5. iOS中NSArray/NSSet的一些巧妙用法【Vong_HUST】](https://github.com/southpeak/iOS-tech-set/blob/master/2018/01.md)
10+
* [6. 对清除图片缓存的思考【高老师很忙】](https://github.com/southpeak/iOS-tech-set/blob/master/2018/01.md)
11+
12+

0 commit comments

Comments
 (0)