Swift中内存管理

强引用循环和 weak 关键字

房东

1
2
3
4
5
6
7
8
9
10
11
12
class Landlord {
var name: String
var house: House?

init(name: String) {
self.name = name
}

deinit {
print("Landlord内存释放")
}
}

房子

1
2
3
4
5
6
7
8
9
10
11
12
class House {
var name: String
var landlord: Landlord?

init(person: String) {
self.name = person
}

deinit {
print("House内存释放")
}
}

循环引用

1
2
3
4
5
6
7
8
9
10
11
func strongRetain() {
var landlord: Landlord? = Landlord(name: "老李")
var house: House? = House(person: "望京府邸")
// 房东有个房子
landlord?.house = house
// 房子有个主人
house?.landlord = landlord

landlord = nil
house = nil
}
  • 虽然 landlord = nil 和 house = nil,但是 Landlord 和 House 内存空间并没有释放 (并未执行 deinit 函数)

  • 原因:因为两个内存空间相互引用,引用计数不为0,形成了强循环引用

  • 解决办法:使用weak 关键字来修饰两者中其中一个变量

    • 使用weak修饰的变量必须是可选类型,可以赋值为nil
    • 使用weak修饰必须是var 类型变量
1
2
3
4
5
6
7
8
9
10
11
12
class House {
var name: String
weak var landlord: Landlord?

init(person: String) {
self.name = person
}

deinit {
print("House内存释放")
}
}

unowned 关键字的使用

相互循环引用的两个变量有一个是可选的

有个身份证的类,每个身份证肯定对应一个人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ID {
var number: String
let person: Landlord

init(person: Landlord, number: String) {
self.person = person
self.number = number
}

deinit {
print("ID内存释放")
}
}

```

房东

class Landlord {
var name: String
var house: House?
/// 身份证可以为nil,
var id: ID?

init(name: String) {
    self.name = name
}

deinit {
    print("Landlord内存释放")
}

}

1
2

循环引用

func unownedTest() {
var person: Landlord? = Landlord(name: “老王”)
var id: ID? = ID(person: person!, number: “10010”)

person?.id = id
person = nil
id = nil

}

1
2
3
4
5
6
7
8

- person 和 id 之间有强引用关系
- 每个id必须有一个对应的人并且是固定的只能使用let 修饰,不能为nil, 而id可以为空
- unowned和 weak 作用相同都是弱引用,只是unowned 修改的变量不能是nil (可选型的)
- unowned只能使用在 class 类型上,不能修饰函数类型,例如闭包存在循环引用时不能使用unowned修饰
- 解决方法:
- 此时可以通过使用 weak 修饰 id 的方式,因为 id 可以为nil
- let 修饰并且不能为nil 这种情况可以使用 unowned 修饰解决
class ID {
    var number: String
    unowned let person: Landlord

    init(person: Landlord, number: String) {
        self.person = person
        self.number = number
    }

    deinit {
        print("ID内存释放")
    }
}
1
2
3
4
	
**unowned有一定的危险性**

因为unowned 修饰的对象不能赋值为nil,所以执行下面的代码会crash,

func unownedTest() {
var person: Landlord? = Landlord(name: “老王”)
var id: ID? = ID(person: person!, number: “10010”)

person?.id = id

// 提前释放 person 内存
person = nil

// 获取身份证对应的人
print(id?.person) // 此处奔溃,
let owner = id?.person
id = nil

}

1
2

正确的写法因该是先将🆔设置为nil,再将 Person 设置为nil,再调用 print(id?.person) 就不会再报错

func unownedTest() {
var person: Landlord? = Landlord(name: “老王”)
var id: ID? = ID(person: person!, number: “10010”)

person?.id = id

id = nil

// 提前释放 person 内存
person = nil

// 获取身份证对应的人
print(id?.person) // 此处奔溃,
let owner = id?.person

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#### 相互循环引用的两个变量都不是可选的

使用隐式可选类型

### 闭包中的强引用循环

- 并不是所有的闭包都会产生循环引用,只有相互引用的两个对象会产生循环引用

- 使用swift闭包捕获列表,在闭包的声明参数之前使用 [unowned self], 不需要解包

- 使用 unowned表示这个对象不能为空,可能存在风险,如果对象为空就会crash


使用 unowned 对象不能为空,可能存在风险

class Closure {
var closure: ((Int) -> Void)?

init() {
    closure = { [unowned self] index in
        print(self)
        print(index)
    }
}

}

1
2

使用 weak, 此时需要解包

closure = { [weak self] index in
// 解包
if let self = self {
print(self)
print(index)
}
}

1
2

强制解包

closure = { [weak self] index in
// 强制解包
print(self!)
print(index)
}
`

ReactNative 中使用 TypeScript

从RN0.57版本开始已经可以直接支持typescript,无需任何配置。在保留根入口文件index.js的前提下,其他文件都可以直接使用.ts或.tsx后缀。但这一由babel进行的转码过程并不进行实际的类型检查,因此仍然需要编辑器和插件来共同进行类型检查

TypeScript和JavaScript对比

两者深度对比

最大的区别:

  • TypeScript是强类型语⾔和静态类型语⾔(需要指定类型)
  • JavaScript是弱类型语⾔和动态类型语⾔(不需要指定类型)

TypeScript

由微软开发的自由和开源的编程语言,是JavaScript的一个严格超集,并添加了可选的静态类型和基于类的面向对象编程

TypeScript定义

安装及语法

教程

安装
1
npm install -g typescript
安装 typescript 依赖管理器 typings
1
npm install -g typings
安装 typings 依赖
1
2
typings install npm~react --save
typings install dt~react-native --globals --save
使用 yarn 安装

如果已经安装了 yarn 安装如下:

1
2
yarn add tslib @types/react @types/react-native
yarn add --dev react-native-typescript-transformer typescript
创建 tsconfig.json 配置文件
  • cd 到 ReactNative 项目根文件夹下

    1
    tsc --init
  • 修改 tsconfig.json 文件

    如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录。 tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项

    tsconfig.json文件配置详解

    TypeScript配置文件tsconfig简析

    务必设置”jsx”:”react”
    注意多余的注释可能会不兼容,需要移除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    "compilerOptions": {
    "target": "es6",
    "allowJs": true,
    "jsx": "react",
    "outDir": "./dist",
    "sourceMap": true,
    "noImplicitAny": false
    },

    // "files"属性 :指定一个包含相对或绝对文件路径的列表。 "include"和"exclude"属性指定一个文件glob匹配模式列表

    "include": [
    "typings/**/*.d.ts",
    "src/**/*.ts",
    "src/**/*.tsx"
    ],
    "exclude": [
    "node_modules"
    ]
    }
  • cd 到项目根目录中新建存放 typescripe 源代码的文件夹

建立 rn-cli.config.js 文件,使用支持typescript的transfomer
1
2
3
4
5
6
7
8
module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
}
}
修改 .babelrc 文件
1
2
3
4
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}
修改项目中文件导入
  • 入口index.jsApp.js的文件名请不要修改,项目根目录下新建src文件夹
  • src文件夹下新建 .tsx 后缀的文件
  • .tsx 后缀的文件引用react的写法有所区别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import * as React from 'react'
    import { Text, View } from 'react-native';
    import { Component } from 'react';

    export default class HelloWorld extends Component{
    render() {
    return (
    <View>
    <Text>Hello!</Text>
    <Text>Typescript</Text>
    </View>
    );
    }
    }
  • App.js中使用

    • App.js 导入方式修改为如下方式

      1
      2
      3
      4
      	import './src';
      ```

      - 导入 .tsx 类型的组建

      import HelloWorld from “./src/HelloWorld”;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11

      ### 配置jsconfig

      `jsconfig.json`目录中存在文件表明该目录是JavaScript项目的根目录。该jsconfig.json文件指定根文件和JavaScript语言服务提供的功能选项

      [jsconfig详解](https://code.visualstudio.com/docs/languages/jsconfig)

      [jsconfig.json](https://jeasonstudio.gitbooks.io/vscode-cn-doc/content/md/%E8%AF%AD%E8%A8%80/javascript.html?q=)


      ##### 新建 `jsconfig.json` 文件

touch jsconfig.json

1
2

##### 编辑 `jsconfig.json` 文件

{
“compilerOptions”: {
“allowJs”: true,
“allowSyntheticDefaultImports”: true,
“emitDecoratorMetadata”: true
},
“exclude”: [
“node_modules”,
“rnApp/Tools” // 项目根目录下新建的存放RN中所用的一些工具类
]
}

1
2
3


### package.json 文件配置

{
“name”: “RNProject”,
“version”: “0.0.1”,
“private”: true,
“scripts”: { // 通过设置这个可以使npm调用一些命令脚本,封装一些功能
“start”: “node node_modules/react-native/local-cli/cli.js start”,
“test”: “jest”
},
“dependencies”: { // 发布后依赖的包
“@types/react”: “^16.4.16”,
“@types/react-native”: “^0.57.4”,
“react”: “16.5.0”,
“react-native”: “0.57.2”,
“react-transform-hmr”: “^1.0.4”,
“tslib”: “^1.9.3”
},

“devDependencies”: { // 开发中依赖的其它包
“babel-jest”: “23.6.0”,
“jest”: “23.6.0”,
“metro-react-native-babel-preset”: “0.48.0”,
“react-native-typescript-transformer”: “^1.2.10”,
“react-test-renderer”: “16.5.0”,
“typescript”: “^3.1.2”
},

“jest”: { // JavaScript 的单元测试框架
“preset”: “react-native”
}
}

1
2
3
4
5
6
7
8
9

##### `–save` 和 `-–save-dev` 区别

- --save参数表示将该模块写入dependencies属性,
- --save-dev表示将该模块写入devDependencies属性。

##### 添加开发依赖库

- 类型检查

npm install @types/jest --save-dev
npm install @types/react --save-dev
npm install @types/react-native --save-dev 

1
- 装饰器转化核心插件
npm install babel-plugin-transform-decorators-legacy --save-dev
1
2
	
- 测试框架
npm install react-addons-test-utils --save-dev npm install react-native-mock --save-dev
1
2

##### 删除依赖库

npm uninstall @types/jest –save

1
2


npm uninstall @types/jest –save-dev

1
2

##### 彻底删除,-d 表示 devDependencies, -O 表示 optionalDependencies 中

npm uninstall -s -D -O @types/jest

1
2
3
4
5
6
7
8
9
10

### 报错汇总

##### 错误1 `Cannot find module 'react-transform-hmr/lib/index.js'`

[解决1](https://stackoverflow.com/questions/50142561/cannot-find-module-react-transform-hmr-lib-index-js?noredirect=1&lq=1)

[解决2](https://github.com/facebook/react-native/issues/21530)

- 安装 `react-transform-hmr`
npm install react-transform-hmr --save
1
2

- 清除缓存重新启动服务
npm start --reset-cache ```

《52个方法-第一章》

一、OC起源

  • OC使用的是“消息结构”而不是“函数调用”
  • 两者区别:
    • 消息结构:在运行时所执行的代码由运行环境来决定,无论是否多态总会在运行时才会去查找所要执行的方法
      1
      2
      NSObject *objc = [NSObject new];
      [objc performWith: parameter1 and: parameter2];
- 函数调用:由编译器决定,如果函数多态,编译时就要查处到底应该执行那个函数实现

1
2
Object *objc = new Object;
objc -> perform(parameter1, parameter2);
  • 消息结构中编译器不会关心接收消息的对象是何种类型,接收消息的对象问题也要在运行时处理,此过程叫做“动态绑定”

要点:

  1. OC语言使用的动态绑定的消息结构,在运行时所执行的代码由运行环境来决定,无论是否多态总会在运行时才会去查找所要执行的方法,
  2. OC 中对象总是分配在“堆空间”,而绝不会分配在”栈“上,不带 “*”的变量或者结构体等可能会使用“栈空间”

二、类头文件尽量少导入其他头文件

要点:

  1. 除非必要,否则不要引入头文件,使用向前声明降低类之间的耦合
  2. 有时无法使用向前声明,应该把该类遵守协议的声明放到“class-continuation分类”(匿名分类)中。
  3. 如果“class-continuation分类”也不行的话,就把协议单独放在一个文件中,再将其引入### 三、多用字面量语法,少用与之等价的方法

三、多用字面量语法,少用与之等价的方法

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
- (void)tempMethond {
// OC1.0 起,支持简单方式创建基本类型对象

// - 使用字符串字面量:简介易读
NSString *someString = @"Effective Objective-C 2.0";
// 传统方式
NSString *someStrongOld = [NSString stringWithFormat:@"Effective Objective-C 2.0"];


// - 字面数值
NSNumber *someNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.1415926;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
// 字面量语法用于表达式
int x = 5;
float y = 6.23f;
NSNumber *expressionNumber = @(x * y);


// - 字面量数组
NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
NSString *dog = animals[1];

// arrayWithObjects 和 字面量 的区别:
// arrayWithObjects 方法依次处理各个参数,直到发现nil为止
// 字面量数组遇到nil直接抛出异常
NSArray *animalsOld = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];
NSString *dogOld = [animalsOld objectAtIndex:1];


// - 字面量字典
// 如果有nil直接抛出异常
NSDictionary *personData = @{@"firtName": @"Matt",
@"lastName": @"Galloway",
@"age": @28};
NSString *lastName = personData[@"lastName"];

// dictionaryWithObjectsAndKeys 方法依次处理各个参数,直到发现nil为止
NSDictionary *personDataOld = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt",@"firtName",
@"Galloway", @"lastName",
[NSNumber numberWithInteger:28], @"age", nil];

NSString *lastNameOld = [personData objectForKey:@"lastName"];


// - 可变数组和字典
NSMutableArray *mutableArray = animals.mutableCopy;
mutableArray[1] = @"dog";

NSMutableDictionary *mutableDict = [personData mutableCopy];
mutableDict[@"lastName"] = @"Galloway";
}

要点:

  1. 使用字面量语法创建字符串,数值,数组,字典简明扼要
  2. 通过下标操作获取数组,字典对应的元素
  3. 使用字面量创建数组,字典时若有nil ,会抛出异常,确保值里不含nil### 五、用枚举表示状态、选项、状态码

四、多用类型常量,少用#define预处理指令

宏定义
  • 缺点:
    • 可以把所有相同的字符串值都替换为定义的值,
      • 定义出来的常量没有具体的类型信息
      • 如果有人重新定义了宏定义,不会有警告或者提醒,导致程序中值不一样
类型常量
  • 命名规则:

    • 如果只是在实现文件.m 中使用,则在前面加字母k
    • 如果此常量在类之外也可见,则以类名为前缀,(第19条详细了解命名习惯)
  • 声明位置:

    • 总是喜欢在头文件中声明宏定义,可能和常量名冲突,
    • 所有引入这个头文件的类都会出现宏定义或者常量,相当于声明了一个名叫kAnimationDuration的全局变量
    • 应该加上前缀,表明其所属的具体类,例如:EOCViewClassAnimationDuration
  • 常量从右往左解读依次是: 具体类型,不可修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    	// const 修饰的是常量不希望别人修改, NSString * 指向NSString对象
    extern NSString *const EOCStringConstant;


    // 两者写法都可以
    extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
    extern NSTimeInterval const EOCAnimatedViewAnimationDuration1;
    ```

    - 如果不打算公开常量,可以定义在 .m 实现文件里

    static const NSTimeInterval kAnimationDuration = 0.3;

    1
    2
    3

    - static关键字
    - static 修饰意味着该变量仅在定义此变量的编译单元(实现文件.m)中可见,作用域就是当前.m文件
    static const NSTimeInterval kAnimationDuration = 0.3;
    
    1
    2
    3
    - 如果需要声明一个外部可见的常值变量,需要在头文件中声明并使用 extern 关键词修饰 "extern 

    在 .h 文件中
    extern NSString *const EOCStringConstant; // 两者写法都可以 extern const NSTimeInterval EOCAnimatedViewAnimationDuration; extern NSTimeInterval const EOCAnimatedViewAnimationDuration1;
    1
    2

    在 .m 文件中
    NSString *const EOCStringConstant = @"VALUE"; const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3; NSTimeInterval const EOCAnimatedViewAnimationDuration1 = 0.3;
    1
    2
    3
    4
    5
    6
    7
    8
    9

    **要点:**

    1. 定义常量时static const定义编译单元(实现文件.m)中可见的常量,无需为其名称加前缀
    2. 在头文件中使用extern 声明全局常量,并在实现文件中定义其值,这种常量要出现在全局符号表中,所以加相关类作为前缀

    ### 五、用枚举表示状态、选项、状态码

    - 普通枚举

    enum EOCConnectionState {

    EOCConnectionStateDisconnected, // 断开连接
    EOCConnectionStateConnecting,   // 连接中...
    EOCConnectionStateConnected,    // 已连接
    

    };

    1
    普通枚举如果要使用如下:

    enum EOCConnectionState status = EOCConnectionStateConnected;

    1
    2

    如果每次都不需要输入enum关键字,只需要typedef关键字重新定义枚举类型

    EOCConnectionState status1 = EOCConnectionStateConnecting;

    1
    2

    - 设置枚举的初始值

    enum EOCConnectionStateConnectionState {

    EOCConnectionStateConnectionStateDisconnected = 1, // 断开连接
    EOCConnectionStateConnectionStateConnecting,   // 连接中...
    EOCConnectionStateConnectionStateConnected,    // 已连接
    

    };

    1
    2
    3
    4
    5

    - 可选枚举

    - << 位操作符(左移), 表示往左移动N位,使用 位运算符 | (或)来组合使用
    - 使用位运算符 &(与) 判断是否开启了某个选项

    enum UIViewAutoresizing {

    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
    

    }

    1
    2
    3
    4

    - 使用宏定义枚举

    普通枚举类型(老式语法定义枚举)

    typedef enum : NSUInteger {

    EOCConnectionState2DisConnected,
    EOCConnectionState2DisConnecting,
    EOCConnectionState2Connected,
    

    } EOCConnectionState2;

    1
    2

    NS_ENUM 宏定义的枚举展开后:

    typedef enum EOCConnectionState_3 : NSUInteger EOCConnectionState_3;
    enum EOCConnectionState_3 : NSUInteger {

    EOCConnectionState_3DisConnected,
    EOCConnectionState_3DisConnecting,
    EOCConnectionState_3Connected,
    

    };

    1
    2

    - 定义新特性枚举

    typedef NS_ENUM(NSUInteger, EOCConnectionState3) {

    EOCConnectionState3DisConnected,
    EOCConnectionState3DisConnecting,
    EOCConnectionState3Connected,
    

    };

    1
    2
    3
    4

    - 枚举使用

    .h文件

    #import <UIKit/UIKit.h>

    typedef enum EOCConnectionState EOCConnectionState;

    typedef NS_ENUM(NSUInteger, EOCConnectionState3) {

    EOCConnectionState3DisConnected,
    EOCConnectionState3DisConnecting,
    EOCConnectionState3Connected,
    

    };

@interface EOCEnum : NSObject
@property (nonatomic, assign) enum EOCConnectionState state;
@property (nonatomic, assign) EOCConnectionState3 state3;
@end

1
2

.m 文件中
#import "EOCEnum.h" @implementation EOCEnum - (void)testEnum { // 未使用typedef 关键字 enum EOCConnectionState status = EOCConnectionStateConnected; NSLog(@"%u", status); // 使用typedef 关键字 EOCConnectionState status1 = EOCConnectionStateConnecting; NSLog(@"%u", status1); UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; if (resizing & UIViewAutoresizingFlexibleWidth) { // 设置了 UIViewAutoresizingFlexibleWidth 约束 } } // UIKit 试图所支持的设置显示方向 - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft; } // 枚举使用 - (void)userEnum { switch (_state3) { case EOCConnectionState3DisConnected: // ... break; case EOCConnectionState3DisConnecting: // ... break; case EOCConnectionState3Connected: // ... break; default: // 最好不用,防止以后增加一种状态机之后,编译器不发出警告 break; } } @end ```

要点:

  1. 使用枚举表示状态机的状态、传递给方法的选项以及状态码等值,给枚举值起名时要注重易懂
  2. 如果一个类型可以使用状态机的状态来表示,并且多个选项可同时使用,那么就定义为可选的枚举类型,枚举各值定义为2的幂,以便通过“按位或”操作组合
  3. 使用NS_ENUM 和 NS_OPTIONS 宏来定义枚举,并指明底层的数据类型。这样可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型
  4. switch语句处理枚举类型时不要实现default 分支,如果以后加入枚举的新状态之后,编译器会发出警告

iOS通过URL获取HTML中标题

获取标题

String扩展一个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private let patternTitle = "(?<=title>).*(?=</title)"
extension String {
static func parseHTMLTitle(_ urlString: String) -> String? {
let url = URL(string: urlString)!
do {
let html = try String(contentsOf: url, encoding: .utf8)
let range = html.range(of: patternTitle, options: String.CompareOptions.regularExpression, range: html.startIndex..<html.endIndex, locale: nil)
guard let tempRange = range else { return "" }
let titleStr = html[tempRange]
return String(titleStr)
} catch {
return ""
}
}
}
iOS 中简单正则表达式

iOS 中 正则表达式中的\ 需要使用 \\ 来表示

// 获取
let testStr = "我到底是[iOS]开发还是[产品]经理"

// 获取 "[" 和 "]" 之间的所有内容, 左边[ 起一直到 右边 ] 结束的所有内容 
let pattern1 = "\\[.*\\]" // [iOS]开发还是[产品]

// 获取 "[" 和 "]" 之间的所有内容,包括[]
let pattern2 = "\\[.*?\\]"    // [iOS]

// 获取第一个 "[" 和 "]" 之间的所有内容 不包括[] 
let pattern3 = "(?<=\\[).*?(?=\\])" // iOS

// 获取html中 title 标签中内容
let pattern5 = "(?<=title>).*(?=</title)" 
获取范围截取子串
let testRange1 = testStr.range(of: pattern1, options: String.CompareOptions.regularExpression, range: testStr.startIndex..<testStr.endIndex, locale: nil)
    let result1 = String(testStr[testRange1!])


let testRange2 = testStr.range(of: pattern2, options: String.CompareOptions.regularExpression, range: testStr.startIndex..<testStr.endIndex, locale: nil)
let result2 = String(testStr[testRange2!])


let testRange3 = testStr.range(of: pattern3, options: String.CompareOptions.regularExpression, range: testStr.startIndex..<testStr.endIndex, locale: nil)
let result3 = String(testStr[testRange3!])

print(result1) 
print(result2) 
print(result3) 
正则表达式

通过URL解析html中的标题

  • 获取html字符串
  • 正则匹配
  • 生成字符串Range
  • 截取字符串

匹配一次直接返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 生成表达式
let regular = try NSRegularExpression(pattern: pattern5, options: .caseInsensitive)


// 开始匹配
let matchResult1 = regular.firstMatch(in: html, options: NSRegularExpression.MatchingOptions.reportProgress, range: NSMakeRange(0, html.count))

// 获取匹配结果范围
let matchRange = matchResult1?.range
let range = Range(matchRange!)!

// 生成String.Index 类型范围
let startIndex = html.index(html.startIndex, offsetBy: range.lowerBound)
let endIndex = html.index(html.startIndex, offsetBy: range.upperBound)
let range3 = Range(uncheckedBounds: (lower: startIndex, upper: endIndex))

// 截取字符串
let result1 = String(html[range3])
print(result1)

全部匹配后返回结果时一个集合

1
2
3
4
5
6
7
8
9
10
let matchResult2 = regular.matches(in: html, options: .reportProgress, range: NSMakeRange(0, html.count))
matchResult2.forEach { (result) in
let range = Range(result.range)!

let startIndex = html.index(html.startIndex, offsetBy: range.lowerBound)
let endIndex = html.index(html.startIndex, offsetBy: range.upperBound)

let subStr2 = html[Range(uncheckedBounds: (startIndex, endIndex))]
print(String(subStr2))
}

将匹配到的内容提还

1
let str3 = regular.stringByReplacingMatches(in: html, options: .reportProgress, range: NSMakeRange(0, html.count), withTemplate: "====")

OC 语言特性

分类

原理

由运行时来决议的,不同分类当中含有同名方法 谁最终生效取决于谁最终参与编译,最后参编译的同名分类方法会最终生效,假如分类方法中添加的方法和宿主类中的某一个方法名相同,分类中的方法会覆盖宿主类中的同名方法,覆盖是指在消息传递过程中优先查找数组靠前的元素,如果查找到了同名方法就直接调用,实际上宿主类的同名方法实现仍然是存在的,我们可可以通过一些手段调用到原有类的同名方法的实现

使用场景

  • 申明私有方法,分类的 .m 文件中申明私有方法啊,对外不暴露
  • 对类中的代码进行抽取分解
  • 把Framework的私有方法公开化

特点

  • 运行时决议,运行时通过runtime 才把分类中的方法添加到了宿主类上
  • 可以为系统类添加方法
  • 有多个分类中存在同名方法时,最后编译的方法会最先生效
  • 分类中有和宿主类同名方法时,底层中通过内存copy将分类方法“覆盖”宿主类方法
  • 名字相同的分类编译会报错

分类中可以添加的内容

  • 实例方法
  • 类方法
  • 协议
  • 属性,实际上只是声明了 getter / setter 方法,并没有添加成员变量

关联对象技术

关联对象由 AssocicationsManager 管理并在 AssociationsHashMap 上存储
所有对象的关联内容都在同一个全局容器中

  • 给分类添加成员变量,通过关联对象的方式

扩展

使用场景

  • 声明私有属性
  • 声明私有方法
  • 声明私有成员变量

特点

  • 编译时决议
  • 只以声明的形式存在,多数情况下寄生在宿主类的.m中
  • 不能为系统添加扩展

代理

  • 代理设计模式,传递方式是一对一
  • 使用weak 避免循环引用

通知

使用观察者模式来实现的用于跨层传递消息的机制

KVO

KVO 是系统对观察者模式的又一实现,使用isa 混写技术(isa - swizzling)来动态运行时为某一个类添加一个子类重写了它的setter 方法,同时将原有类的isa指针指向了新创建的类上

  • KVO 是OC对观察者模式的又一实现
  • apple 使用isa 混写技术(isa - swizzling)来实现KVO
    • 给我A类注册一个观察者时本质上是调用系统的 Observer for keyPath 方法,系统会创建一个NSKVONotifiying_A 的类
  • 使用 setter 方法设置值KVO才能生效
  • 使用setValue:forKey: 改变值KVO才能生效
  • 使用下划线的成员变量直接修改值需要手动添加KVO才能生效(增加 willChangeValueForKey 和 didChangeValueForKey 两个方法)

KVC

apple提供的键值编码技术

  • (nullable id)valueForKey:(NSString *)key;
  • (void)setValue:(nullable id)value forKey:(NSString *)key;

    以上两个方法中的key是没有任何限制的,只要知道对应的成员变量名称,就可以对私有的成员变量设置和操作,这违背了面向对象的编程思想

(void)setValue:(nullable id)value forKey:(NSString *)key; 方法调用流程

  • 先判断是否有跟key 相关的setter 方法,如果有就直接调用,结束调用
  • 如果没有再判断是否存在实例变量,如果存在直接给赋值,结束调用
  • 如果说实例变量不存在,会去调用 - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; 抛出异常

属性关键字

读写权限

  • readonly

  • readwrite 系统默认

原子性

  • atomic (系统默认)保证赋值和获取(对成员属性的直接获取和赋值)时线程安全,并不代表操作和访问,如果atomic修饰的一个数组,只能保证独具改数组的读取和赋值时线程安全,并不能保证对数据操作(增加/删除数组元素)时是安全的
  • nonatomic

引用技术

  • assign
    • 修饰基本数据类型,
    • 修饰对象时不改变其引用计数,
    • 会产生悬垂指针
  • weak
    • 不改变被修饰对象的引用计数,
    • 所指对象被释放之后指针自动置为nil

两者区别:

  • weak可以修饰对象,而assign 既可以修饰对象也可以修饰基本数据类型
  • assign 修饰的对象,对象释放后指针仍然指向原对象的内存地址,而weak 修饰的对象,释放后会自动置为nil

问题1

weak 修饰的对象为什么释放后会自动置为nil

问题2

@property (copy) NSMutableArray *array; 有什么问题

  • 被copy修饰后,如果赋值过来的是NSMutableArray,copy之后就是NSArray,如果赋值过来的是NSAarray,copy 之后仍然是NSArray,有可能会调用array的添加元素方法会导致crash

copy 关键词

  • 浅拷贝:内存地址的复制,让目标对象指针和源对象指向同一块内存空间,
    • 会增加对象的引用计数
    • 并没有一个新的内存分配
  • 深拷贝:目标对象指针和源对象指针分别指向两块内容相同的内存空间
    • 不会增加源对象的引用计数
    • 有新对象的内存分配

MRC 重写 retain修饰的变量的setter 方法

1
2
3
4
5
6
7
8

- (void)setObj:(id)obj {
if (_obj != obj) {
[_obj release];
}
_obj = [obj retain];

}

判断是为了防止异常处理,如果不做 if 判断,当传入的 obj 对象正好是原来的_obj 对象,对原对象尽行releas 操作,实际上也会对传入的对象进行releas 操作进行释放,此时如果再通过obj指针访问废弃的对象时就会导致carsh

iOS动态设置tableHeaderView高度

需求

动态的设置tableHeaderView的高度

遇到的问题:

  • 设置tableHeaderViewframe无法实现,有时候会出现tableView 中间的 cell 不显示
  • 有时候会导致headerViewcell内容重叠

  • 通过计算控件高度设置frame方式麻烦

正确的步骤

  • 创建HeaderView
  • 取出UITableViewtableHeaderView
  • 设置frame大小,调整headerView布局
  • 重新给UITableViewtableHeaderView

使用AutoLayout

UITableView增加分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extension UITableView {
/// set tableHeaderView
func setTableHeaderView(_ headerView: UIView) {
headerView.translatesAutoresizingMaskIntoConstraints = false
self.tableHeaderView = headerView
// autolayout
NSLayoutConstraint(item: headerView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: headerView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1.0, constant: 0).isActive = true
}
/// update
func updateHeaderView() {
guard let headerView = self.tableHeaderView else { return }
headerView.layoutIfNeeded()
let header = self.tableHeaderView
self.tableHeaderView = header
}
}

个人信息

工作经历

猎聘网 (2018.2 ~ 至今)

Swift重构猎聘App中应聘记录模块,推进项目组使用AutoLayout技术,后期负责开发迭代乐班班项目,参与每版需求分析和主要代码编写,解决复杂功能页面卡顿,内存优化,BUG修复及iOS新特性适配等。

北京万朝科技有限公司(2015.5 ~ 2018.2)

在该项目中担任iOS开发⼯作,参与项目需求分析、核⼼代码编写以及常用工具类的封装,BUG修复,版本迭代更新,推进组内swift的使用。

中盎炬华科技发展有限公司(2014.4月 ~ 2015.5)

参与iOS客户端的开发维护完成开发工作,实现一些交互动画和效果;研究最新技术,优化程序代码,集成第三方SDK。

项⽬经历

1. 猎聘APP (2018.2 ~ 至今)

项⽬目描述: 为中高端人才和经理人提供招聘的平台

  • 使用 swift 重构应聘记录模块中相关页面及功能
  • 推进整个项目组使用 AutoLayout 技术

2. 乐班班

项⽬目描述: 音/视频播放、直播,内购,即时通信等功能,由OC、Swift,RN 技术混编,项⽬应⽤技术

  • 使用ReactNative技术重构和开发部分页面
  • 使用Xcode、Instruments工具查找、分析、解决项目内存泄露,修复BUG
  • Swift重构通信模块,用collectionView构建消息页面,自定义emoj Keyboard,封装常用UI控件,自定义控制器转场动画
  • 使用AVFoundation 实现视频录制,上传,播放功能
  • 通过GCD dispatch semaphors 解决并发网络请求问题
  • 使用collectionView构建复杂的视频播放页面,企业大学首页

3. e蜂通信 (2015.5 ~ 2018.2)

项⽬目描述:整合了办公OA、CRM系统,新增即时通信、分享、远程监控、解密外发、红包、私聊等功能;由OC和swift混编, 项⽬目应⽤用技术:

  • 使⽤CocoaAsyncSocket自定义通信协议,实现聊天,特定业务消息触达功能
  • 使⽤cocoaPod实现组件化,
  • 封装swift基础库,完善swift和Objective-C混编
  • 实现分享⼩视频、图片、点赞、评论功能
  • ⾃定义相机实现人脸识别打卡、扫描二维码功能
  • 封装常⽤UI控件,使用Core Animation实现动画效果,集成百度地图SDK
  • 利⽤JavaScriptCore完成H5和Native之间的交互

4. 四万公里 (2014.4 ~ 2015.5)

项目描述:针对境外旅游群体提供便捷服务,项⽬目应⽤用技术:

  • 封装 TableViewController基类,对接服务端接口等
  • 封装基础UI控件
  • 集成科大讯飞SDK, 实现了实时翻译功能
  • 集成滴滴打车SDK, 实现用户可在国外打车功能

专业技能

  1. 熟练使用Objective-C、Swift,及Swift与Objective-C混编,有良好的编程习惯
  2. 熟练掌握iOS内存管理机制以及ARC技术,结合Instruments对项目进行内存优化
  3. 熟练掌握NSThread/GCD/NSOperation等并发编程技术
  4. 熟练掌握iOS中事件链和响应链的传递方式
  5. 熟练使⽤AFNetworking,Moya,FMDB,SDWebImage,Masonry等第三⽅开源框架
  6. 熟练掌握各种UI控件封装,熟悉多视图开发,能实现复杂的界⾯面交互
  7. 掌握KVO原理理、KVC原理理、SDWebImage内部实现过程、RunLoop的应⽤
  8. 有团队开发经验,同时能够独立完成APP开发,代码编写,调试和发布

自我介绍

工作之余了解使⽤ JavaScript,RxSwift 函数响应式编程,小程序开发等知识,经常关注iOS博客和Github 上优秀代码,以及在raywenderlich上学习新知识,在个人博客做些技术积累。 计划在iOS领域深⼊同时也扩展⾃己的知识⾯,了解使⽤后端和前端技术。

Xcode中使用Debug Memory Graph检查内存泄漏

勾选如下图所示选项

运行程序后点击

内存泄露定位

flutter安装

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
// 导入头文件
import 'package:flutter/material.dart';

void main() => (MyApp());

// 定义类, 继承

class MyApp extends StatelessWidget {
// 重写
@override

Widget build(BuildContext context) {

// 返回一窗口
return MaterialApp(
title:'Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('flutter 初体验')
),

body: Center(
child: Text('flutter 学习之路'),
),
),
);
}
}
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

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.

// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}

@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'你可以点击按钮来修改文字,我是热重载的结果',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

Swift已有项目中集成ReactNative

本教程针对于已经有的Swift项目集成ReactNative

创建新的空目录

为了不影响原有的swift项目目录结构,建议先创建一个空的文件夹,例如SwiftRN

创建package.json文件

终端进入创建的SwiftRN文件目录下,创建package.json文件

1
touch package.json
修改package.json文件,指定各个版本
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "SwiftRN",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "16.3.1",
"react-native": "^0.56.0"
}
}
执行npm install

在创建package.json所在的文件目录下执行,在此过程中可以遇到的错误可能较多

1
npm install
添加index.js文件
1
touch index.js
修改文件内容
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
import {
AppRegistry,
Text,
View,
} from 'react-native';

import React, { Component } from 'react';

// 用于最后在项目中测试
class SwiftRNHomeView extends Component {
render() {
return(
<View style={{backgroundColor:'white'}}>
<Text style={{fontSize: 20, marginTop: 200}}>Hellow world</Text>
</View>
);
}
}


class MyApp extends Component {
render() {
return <SwiftRNHomeView></SwiftRNHomeView>;
}
}

AppRegistry.registerComponent('SwiftRN', () => MyApp);

配置swift项目

将新创建的Swift文件目录下的所有文件拷贝到Swift项目所在的根目录下

使用Pod集成RN

swift项目采用了pod管理第三方库,在Podfile文件中加入下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pod 'React', :path => './node_modules/react-native', :subspecs => [
'Core',
'RCTText',
'RCTNetwork',
'RCTWebSocket', # 这个模块是用于调试功能的
'CxxBridge', # Include this for RN >= 0.47,不引入的话会报错误或者警告
'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43,不引入的话会报错误或者警告
'RCTAnimation', 不引入的话会报错误或者警告
]


# 如果你的RN版本 >= 0.42.0,请加入下面这行,不引入的话会报错误或者警告
pod "yoga", :path => "./node_modules/react-native/ReactCommon/yoga"
pod 'DoubleConversion', :podspec => './node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => './node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => './node_modules/react-native/third-party-podspecs/Folly.podspec'

创建桥接文件

  • 如果原项目中有SwiftDemo-Bridging-Header.h类似这个文件可以跳过。
  • 如果从来没有创建过桥接文件,创建一个OC的类会自动弹出是否需要创建桥接文件,直接创建即可,最后把无用的OC文件删掉

引用RN头文件

在桥接文件中导入头文件

1
2
#import <React/RCTRootView.h>
#import <React/RCTBundleURLProvider.h>

使用RN页面

在模拟器中使用RN的页面, 确保在 info.plist 文件中添加了Allow Arbitrary Loads=YES

  • moduleName 对应的名字和index.js中 AppRegistry.registerComponent(‘SwiftRN’, () => MyApp)注册的名字必须相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import UIKit

class RNTestController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://localhost:8081/index.bundle?platform=ios")!
let rootView = RCTRootView(
bundleURL: url,
moduleName: "SwiftRN", //这里的名字必须和AppRegistry.registerComponent('MyApp', () => MyApp)中注册的名字一样
initialProperties: nil,
launchOptions: nil
)
view = rootView
}
}