0%

1.AnyObject

定义:
public typealias AnyObject

说明:
The protocol to which all classes implicitly conform.

AnyObject can be used as the concrete type for an instance of any class, class type, or class-only protocol.

The flexible behavior of the AnyObject protocol is similar to
Objective-C’s id type.

AnyObject:用于表示任意类,元类的实例的具体类型.

对于”123”或123这些基础数据类型在Swift中是结构体类型,所以这里需要将其强转为AnyObject类型,此时它们的类型将是
OC的NSTaggedPointerString和__NSCFNumber等对象类型.不强转的话编译会出错.

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class FDEItemModel {}
let anyA: AnyObject = FDEItemModel.init() //对象,类的实例
let anyB: AnyObject = FDEItemModel.self //类对象,元类的实例
let anyC: AnyObject = FDEItemModel.init().self

let defaulA = FDEItemModel.init() //FDEItemModel
let defaulB = FDEItemModel.self //FDEItemModel.Type
let defaulC = FDEItemModel.init().self //FDEItemModel

let arr1: [AnyObject] = ["123" as AnyObject, 123 as AnyObject, [123] as AnyObject]

let arr2: [AnyObject] = [NSString("123"),
NSNumber(123),
true as AnyObject,
People.init(),
People.self
]
print(arr2)

...
[123, 123, 1, <OSHybridDemo.People: 0x600002348b90>, OSHybridDemo.People]

在使用AnyObject 对象时要特别注意如果你需要调用它的属性则最好先downcast为实际的类型,如果不downcast那么系统可能获取的是其他类的同名属性,得到的值将是nil。

阅读全文 »

Swift、OC反射

反射即根据类名字符串,生成对应实例对象的过程.

分为两步:

  1. 根据字符串,得到对应的类对象.
  2. 通过类对象调用初始化方法生成一个实例.

获取类对象

OC:

1
2
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");

这俩方法都是返回一个类对象.

Swift:

1
2
aClass.self
type(of: anObject)
阅读全文 »

OC懒加载与Swift懒加载

OC懒加载

1
2
3
4
5
6
7
8
9
- (UIView *)redView
{
if (_redView == nil) {
_redView = [[UIView alloc] init];
_redView.frame = CGRectMake(0, 0, 100, 200);
_redView.backgroundColor = UIColor.redColor;
}
return _redView;
}

注意:在懒加载的实现中尽量避免懒加载循环.即A懒加载B,B又懒加载A.这样会导致互相调用而死循环或者偏离预期结果.

错误示例1:会导致调用死循环.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (UIView *)redView
{
if (_redView == nil) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.blackView.frame.size.width + 100, 200)];
view.backgroundColor = UIColor.redColor;
_redView = view;
}
return _redView;
}

- (UIView *)blackView
{
if (_blackView == nil) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, self.redView.frame.size.height + 100)];
view.backgroundColor = UIColor.blackColor;
_blackView = view;
}
return _blackView;
}

错误示例2:示例2其实和示例1是一回事.也会导致调用死循环.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (UIView *)redView
{
if (_redView == nil) {
_redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.blackView.frame.size.width + 100, 200)];
_redView.backgroundColor = UIColor.redColor;
}
return _redView;
}

- (UIView *)blackView
{
if (_blackView == nil) {
_blackView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, self.redView.frame.size.height + 100)];
_blackView.backgroundColor = UIColor.blackColor;
}
return _blackView;
}

错误示例3:程序会在[self.view addSubview:self.redView];这一行崩溃:Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)

原因在于:对于一个OC对象,alloc之后必须调用init方法完成初始化.对象只有在初始化完成之后才能继续使用.

阅读全文 »

BUG场景

mainTableView嵌套listTableView.mainTableView的headerView里有左右滑动的collectionView(eg:轮播图).此时mainTableView的下拉刷新就会和collectionView的左右滑动起冲突.collectionView左右滑时,mainTableView可能会跟着一起动.

为了实现吸顶效果,mainTableView是允许同时识别多个手势的.而这恰恰是引起手势冲突的原因.

1
2
3
4
5
6
7
8
@implementation MainTableView

// 允许多个手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}

@end

解决

由于mainTableView只能上下滑动,因此可以判断如果otherGestureRecognizer所在的view在左右滑动则不让mainTableView的手势同时识别.

这里有一个不好处理的地方就是在某些精心构造的滑动下,point可能为(0,0),

偶现时的日志:

1
2
3
4
5
6
7
8
9
2019-08-03 18:23:52.661593+0800 xxx[20938:1512558] 
view:<MainTableView: 0x1070c3c00; baseClass = UITableView; frame = (0 0; 414 623); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x282b5dce0>; layer = <CALayer: 0x28258a980>; contentOffset: {0, -3.3333333333333335}; contentSize: {414, 1418}; adjustedContentInset: {0, 0, 0, 0}>
ges:<UIScrollViewPanGestureRecognizer: 0x106d75500; state = Changed; delaysTouchesEnded = NO; view = <GKPageTableView 0x1070c3c00>; target= <(action=handlePan:, target=<GKPageTableView 0x1070c3c00>)>>
otherView:<UICollectionView: 0x1079cea00; frame = (0 0; 414 165.6); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x282b03930>; layer = <CALayer: 0x2825528c0>; contentOffset: {64584, 0}; contentSize: {124200, 165.60000000000002}; adjustedContentInset: {0, 0, 0, 0}> collection view layout: <UICollectionViewFlowLayout: 0x111c20eb0>
otherGes:<UIScrollViewPanGestureRecognizer: 0x106c7aaa0; state = Began; delaysTouchesEnded = NO; view = <UICollectionView 0x1079cea00>; target= <(action=handlePan:, target=<UICollectionView 0x1079cea00>)>; must-fail = {
<UIScrollViewPagingSwipeGestureRecognizer: 0x111c210d0; state = Failed; view = <UICollectionView 0x1079cea00>; target= <(action=_handleSwipe:, target=<UICollectionView 0x1079cea00>)>>
}>
2019-08-03 18:23:52.662988+0800 EmotionCounsel[20938:1512558]
移动的位置:{0, 7}

此时就没办法判断出otherGestureRecognizer.view是不是在左右滑动了.所以加了一个else判断避免这种情况.

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
@implementation MainTableView

// 允许多个手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && otherGestureRecognizer.view != self) {
UIPanGestureRecognizer *otherPanGes = (UIPanGestureRecognizer *)otherGestureRecognizer;
CGPoint point = [otherPanGes translationInView:otherPanGes.view];
UIGestureRecognizerState otherState = otherPanGes.state;
if (otherState == UIGestureRecognizerStatePossible || otherState == UIGestureRecognizerStateBegan) {
if (fabs(point.x) > 0) { //如果在水平左右滑动
return NO;
} else {
if ([otherGestureRecognizer.view isKindOfClass:[UICollectionView class]]) {
UICollectionView *otherView = (UICollectionView *)otherGestureRecognizer.view;
if ([otherView.collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]] && [(UICollectionViewFlowLayout *)otherView.collectionViewLayout scrollDirection] == UICollectionViewScrollDirectionHorizontal) {
NSLog(@"正在滑动左右滚动视图!!!!!!");
return NO;
} else {
NSLog(@"UICollectionView的其他布局");
}
} else {
NSLog(@"其他视图");
}
}
}
}

return YES;
}


@end
阅读全文 »

音频打断处理

音频打断通知:

AVAudioSessionInterruptionNotification

音频会话被系统打断时会收到该通知.打断开始会收到该通知,打断结束也会收到该通知.可以通过userInfo里的AVAudioSessionInterruptionTypeKeykey查看打断的类型:

1
2
3
4
5
6
7
8
9
typedef NS_ENUM(NSUInteger, AVAudioSessionInterruptionType)
{
AVAudioSessionInterruptionTypeBegan = 1, /* the system has interrupted your audio session */
AVAudioSessionInterruptionTypeEnded = 0, /* the interruption has ended */
};

/* keys for AVAudioSessionInterruptionNotification */
/* Value is an NSNumber representing an AVAudioSessionInterruptionType */
AVF_EXPORT NSString *const AVAudioSessionInterruptionTypeKey API_AVAILABLE(ios(6.0), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);

如果是打断结束类型,则userInfo里会有一个AVAudioSessionInterruptionOptionskey,它的值是AVAudioSessionInterruptionOptions枚举类型.用于表示打断结束后是否应该继续播放.

1
2
3
4
5
/* For use with AVAudioSessionInterruptionNotification */
typedef NS_OPTIONS(NSUInteger, AVAudioSessionInterruptionOptions)
{
AVAudioSessionInterruptionOptionShouldResume = 1
};

如果打断是因为应用被挂起,则userInfo里会有一个AVAudioSessionInterruptionWasSuspendedKeykey,并且值是true.(这个key有点奇葩)

使用如下:

注册AVAudioSessionInterruptionNotification通知:

阅读全文 »

iOS 事件分发与手势识别

在讲事件的分发之前,有三个概念不得不提:响应者对象, nextResponder, 响应者链.

响应者对象

在iOS系统中,能够响应并处理事件的对象称之为responder object, UIResponder是所有responder对象的基类。
UIApplication,UIViewController,UIView和所有继承自UIView的UIKit类(包括UIWindow,继承自UIView)都直接或间接的继承自UIResponder,所以它们的实例都是responder object对象。

nextResponder

有UIResponder的文档如下:

The UIResponder class does not store or set the next responder automatically, so this method returns nil by default. Subclasses must override this method and return an appropriate next responder.

UIResponder自身默认返回的是nil.但子类必须重写这个方法并且返回一个合适的nextResponder.UIView的默认实现:通常情况下是它的父视图,但是如果view是作为ViewController的rootView,那么它的nextResponder就是ViewController. ViewController的默认实现:返回它管理的view的父视图.

响应者链

app的视图结构是一个N叉树(一个视图可以有多个子视图,一个子视图同一时刻只有一个父视图),而每一个继承UIResponder的对象都可以在这个N叉树中扮演一个节点。当叶节点成为第一响应者的时候,从这个叶节点开始往其父节点开始追朔出一条链,这一条链就是当前活跃的响应者链。

阅读全文 »

SEL

Defines an opaque type that represents a method selector.

1
typedef struct objc_selector *SEL;

SEL是一种数据类型,代表一个方法选择器(要是觉得不好理解,可以类比一下int,int是整型类型,代表一个整数).方法选择器就是运行时中方法的名称,它是一个注册到(或映射到)Objective-C运行时里的一个C字符串.选择器由编译器生成,在系统加载类的时候由runtime自动进行映射.

按照文档说明,选择器是在编译期间生成的,在加载类的时候映射为runtime中的一个C字符串.

给对象发送一条消息[obj xxxMethod];,其实是调用了objc_msgSend(obj, @selector(xxxMethod));.

objc_msgSend的声明如下:
OBJC_EXPORT id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...);
它的第二个参数的类型就是SEL.说明它是真实存在的.

相关的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//向runtime system注册一个方法名。如果方法名已经注册,则返回已经注册的SEL
SEL sel_registerName(const char *str)

//功能同上
SEL sel_getUid(const char *str)

//使用OC编译器指令.功能同上,也是向runtime system注册一个方法名.
@selector(<#selector#>)

//使用OC字符串构造.功能同上
SEL NSSelectorFromString(NSString *aSelectorName)

//根据Method结构体获取
SEL _Nonnull method_getName(Method _Nonnull m);

//比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
//判断方法名是否映射到某个函数实现上
BOOL sel_isMapped(SEL sel);

eg:

阅读全文 »

1.混编设置

新建一个桥接文件工程名-Bridging-Header.h.到此为止,混编设置差不多就完事了.
该文件在第一次新建Swift文件时,系统会提示是否建立桥接文件,选择是即可.也可自行创建,但自行创建还需要设置路径略微麻烦.

1.1 Swift调用OC

在桥接文件中导入需要调用的OC类的头文件即可.这样就可以在Swift的类里使用OC的类了.

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

//在Swift里调用OC,需要在桥接文件里导入OC头文件
//调用自身module里的OC代码
#import "AppDelegate.h"
#import "ZAEGradientView.h"

//Pod管理的OC库
#import <MJRefresh/MJRefresh.h>
#import <YYModel/YYModel.h>
#import <ZACommon/UIViewController+FRCustomNavigationBarItem.h>
#import <ZACommon/InnerBandCore.h>

注意:对于pod管理的OC库,导入头文件时推荐使用<>而非"".如#import <ZACommon/UIViewController+FRCustomNavigationBarItem.h>,不要写为#import "UIViewController+FRCustomNavigationBarItem.h",否则可能导致编译通不过.

1.2 OC调用Swift

需要在OC的类里写上import "xxx-Swift.h".(xxx为项目名称).也可以写在PCH文件中.
如:#import "EmotionCounsel-Swift.h"

官方文档:

阅读全文 »

今天学习了下Swift中的try-catch,突然发现在json序列化时try-catch不好使了,下面的代码依然会导致崩溃,try-catch并不能catch住.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation

class People: NSObject, Decodable {
@objc var firstName: String
@objc var lastName: String
@objc var age: Int = 0
@objc var height: Float = 0
@objc var weight: Float = 0
@objc var sex: Bool = false

@objc init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
super.init()
}
}

let dict = ["one": People.init(firstName: "x", lastName: "q")]
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
print(jsonData)
} catch let error {
print(error)
}

看了下方法

1
class func data(withJSONObject obj: Any, options opt: JSONSerialization.WritingOptions = []) throws -> Data

的说明:

If obj will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject(_:).

大意是如果obj参数不是一个合法的JSON,那么将抛出一个异常.该异常代表的是一个编程错误而不是解析时的内部错误,它在解析之前就被抛出了.估计这种异常是无法捕获的.所以最好先调用isValidJSONObject判断下是不是一个合法的JSON,再进行序列化.

类似的这样的错误也是捕捉不到的.

1
2
3
4
5
6
7
var ot: String!
do {
let os = try ot!
print(os)
} catch {
print(error)
}

上述代码依旧崩溃.

阅读全文 »

音视频文件格式和编码格式

主要是搞清楚两个概念:编码格式和文件格式。

由于音视频的原始数据都很大,不方便存储与传输,所以就有各种各样的编码技术或算法对其进行编码以达到减少数据的目的,这些编码技术就叫编码格式,有些编码格式解码后能够完全恢复为原始数据,因此这些编码格式也被称为无损压缩,而有些编码格式解码后不能恢复为原来的数据因此也被称为有损压缩。

我们通常说的音视频格式准确地讲应该是音视频文件格式。音视频文件格式它是一个容器里面包含了音频、视频、字幕等,有时也被称为封装格式。有些文件格式只能装载特定编码格式的数据,而有些文件格式既可以装载A编码格式的数据,也可以装载B编码格式的数据,不过这也会带来一些问题比如某个播放器只能播放装载A编码格式的.mp4文件,这时如果你给他一个装载B编码格式的.mp4文件那么这个播放器就会播放失败。对于音频文件,虽然一种音频文件格式可以支持多种编码,例如AVI文件格式,但多数的音频文件仅支持一种音频编码。

音视频文件的名字后面都会有一个扩展名,这个扩展名就代表它是什么文件格式,扩展名的作用就是告诉操作系统应该使用什么样的软件来打开该文件,对于音视频文件改变扩展名并不影响内部数据的编码格式,反而还会误导操作系统,导致文件打开失败,如果想改变编码格式则需要专门的格式转换软件。

编码技术是会不断发展迭代的,所以经常会看到一个编码技术下面可能还会分好几个子类。另外编码格式有各个厂商推出的,也有标准组织推出的,也有个人推出的。这也导致了市面上出现了各种编码格式,每种编码格式又会对应一种文件格式,这就造成了新手在一堆格式名称里晕头转向。我们所说的格式一般指文件格式,但有些时候又是指编码格式,造成这种现象的原因主要是大部分文件格式只支持一种编码格式,这个时候文件格式和编码格式可以看做是同一个东西。

一个音频文件的播放过程:

音频文件——>解封装——->解码———>渲染播放

视频播放则要复杂一些,主要是音频和视频的同步。

如下图:

阅读全文 »