你真的懂Swift中的String吗?

96
月半的瘦子
2017.10.27 18:32* 字数 1938

概述

String是Swift中的一个基本数据类型,String提供了多种方便操作比如字符串拼接 + , 字符串比较的 <= , == 等运算符。同时它桥接了NSString,也就是说String与NSString可以很好的进行转换使用as即可。但是二者还是有区别的,最大区别就是:

  • String 值类型

  • NSString 引用类型

    Strings in Swift 2

swift提供characters属性暴露字符集合视图,但是string与arr、set、dictonary是不同的

Different Than the Sum of Its Parts

添加combining mark character这种字符的时候,改变的是内容

var letters: [Character] = ["c", "a", "f", "e"]
var string: String = String(letters)

print(letters.count) // 4
print(string) // cafe
print(string.characters.count) // 4

此时添加上升字符U+0301 ´,字符串仍是4个字符,但是最后的字符是é

let acuteAccent: Character = "\u{0301}" // ´ COMBINING ACUTE ACCENT' (U+0301)

string.append(acuteAccent)
print(string.characters.count) // 4
print(string.characters.last!) // é

此时字符串 characters 里面不在含有e不再包含´,而是包含é

string.characters.contains("e") // false
string.characters.contains("´") // false
string.characters.contains("é") // true

Judged by the Contents of Its Characters

另一个不同是等式比较

  • 数组相等要求有相同的count、对应索引元素相等
  • 结合相等要求有相同的count、第一个集合包含在第二个里面
  • 字典相等要求二者有相同的key set,value pair

string相等是语义相等,呈现相等,而不管string是如何构造的,韩语下面是左+右=在下面的那个字

let decomposed = "\u{1100}\u{1161}" // ᄀ + ᅡ
let precomposed = "\u{AC00}" // 가

decomposed == precomposed // true

Depends on Your Point of View

string不是集合,但是他们提供了遵守集合类型的views

  • characters,字符集合
  • unicodeScalars,unicode字符集
  • utf8,utf8字符集
  • utf16,utf16字符集

如果以前面的cafe为例,那么里面的内容是这样的
[图片上传失败...(image-83f297-1513308927776)]

接下来细数用Swift重写OC版的NSString+Extention遇到的坑

Swift的数据类型可以说都是对象类型,对应最简单的Int类型如果你查看它的源码的话你会发现它是struct类型,当然String也不例外

首先我们来谈一下Swift中的方法

Swift的String有compare方法,但是你却不能再doc文档中找到

用过OC的同学都知道OC里面的NSString是有compare方法的,方法的原名是- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange locale:(nullable id)locale,你可以和我说String有运算符来表示啊,但是有时候我们在写方法封装的时候不想通过判断去辨别用哪个运算符,我们也想用option啊。。。

回想一下,Swift中的string类是与OC类桥接的,那么Swift本身又对Fundation进行了重塑,所以可以猜测String可以调用NSString的方法并且是以Swift的语法格式(.语法格式),所以在String 的 Doc 中看不到compare的语法声明当时却可以调用!!!

同样的,你可以通过调用地方通过定义找到这个方式,那么这点给我们的启示就是桥接的类都可以这么调用,包括Array、Set、Dictionary

目测用好Swift还要懂OC。。。

带你走入正确遍历所有字符的姿势

Swift中的String是UTF-16编码, 也就是16位的unichar字符的序列。但是, 我们平常书写的字符, 并不全部都是用唯一的一个16位字符来表示, 而是有一部分用两个16位字符来表示, 这就是surrogate pairs的概念. 如果还是用上面的方法遍历字符串, 就会出现”断字”. 例如图中这个Apple Color Emoji的”THUMBS UP SIGN”字符, 其实是用2个16位unichar来表示, 它的Unicode是U+1F44D, 用(U+D83D U+DC4D)两个字符来表示.

这就涉及到的字符序列的一个概念,即用多少个字符来表示你所看到的字符视图,具体实现依靠rangeOfComposedCharacterSequencesForRange和rangeOfComposedCharacterSequenceAtIndex来解决

NSRange range;
for(int i=0; i<str.length; i+=range.length){
    range = [str rangeOfComposedCharacterSequenceAtIndex:i];
    NSString *s = [str attributedSubstringFromRange:range];
}
一次遍历一个子串, 而不是遍历一个unichar了

说到字符这里,顺带提一下,Swift中String是分字符视图的,也就是说你可以以unicode view也可以utf8 view来对其进行描述,查看相关的内容,比如

 let x = self.unicodeScalars.count

提到count我们再说下获取count的方法,一般字符都是unicode,这里不再对特殊字符进行说明,获取count也就是NSString中对应的length,方式是string.characters.count,也可以是string.count而这是等同的,不同的是charaters默认是unicode编码,如果用别的编码可以更换中间部分,比如改用utf8String

NS中的not found对应的是Swift中的可选值

  • 如果有可选值,那么当返回失败的时候应该考虑nil而不是isEmpty等属性,这点也是让我们提升对可选值的理解

  • 函数返回值上+!,表示的是应该返回?但是函数自动帮我们解绑

再说一些额外的方法

Swift与Char之间的关系

  • appendingFormat不对原String进行修改
  • subString 方法已被弃用,现在使用的是下标脚本,同时返回的是String.subString不是String

Swift与C语言的爱恨情仇

这个地方是我遇到最难受的地方了,因为有些函数你遇到会要求你使用UnsafePointer之类的东东,而这些东西其实就是C语言的东西,下面我们先简单说下UnsafePointer相关的两个常用类

UnsafeRawPointer

原始指针,用于访问非指定类型的数据。它不提供自动的内存管理、内存对齐、类型安全,这些都需要你自己来做同时你需要管理它的声明周期,避免野指针和内存泄漏

指针的内存状态

  • 分配空间未绑定类型未初始化 bindMemory绑定类型不初始化
  • 绑定类型未初始化 UnsafePointer UnsafeMutablePointer 可以访问,重新绑定类型需要先deinitialized在绑定,除非是trival 类型
  • 绑定类型初始化
  • 回收内存deallocated,指针指向未分配的内存

Raw Pointer 是byte级别的,+ - 都是指针的偏移

UnsafeRawPointer
作为函数的时候可以隐式转换

  • UnsafeRawPointer
  • SafePointer
  • Swift基本类型 采用inout语法即地址&
  • 对象类型指针 采用inout语法即地址&

不做函数的时候需要显示桥接

var number = 5
let numberPointer = UnsafeRawPointer(&number)

此处是实践MD5加密的写法

let chars = self.components(separatedBy: "")
       let results:[UInt8] = Array.init(repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
       let result = UnsafeMutablePointer(mutating: results)
       CC_MD5(UnsafeRawPointer(chars),(CC_LONG)(chars.count),result)    
这里顺带说一下如何在swift工程中导入CommonCrypto

talk is cheap, show me the code

#import <CommonCrypto/CommonCrypto.h>
放入OC Swift混编的.h文件中即可

UnsafePointer

用于访问固定类型的指针

访问元素

  • pointee

  • 下标访问

  • 也可以不绑定类型继续访问元素,只要目标类型与当前类型可以转换

    • 转换到UnsafeRawPointer之后再load即可
  • 绑定到其他类型

  • 临时绑定到不同类型

    • withMemoryRebound
  • 永久绑定到不同类型

    • UnsafeRawPointer(uint64Pointer)
      .bindMemory(to: UInt64.self, capacity: 1)
    • 绑定之后原类型的访问就是未定义的

只作为函数的时候可以隐式的转换,与Raw不同没有基本类型的构造函数

神秘的UnsafePointer究竟是什么

这里我们通过代码来说明,UnsafePointer<CChar>其实就是[CChar],但是这个不同于ContiguousArray<CChar> --> 元身是string.utf8String

let charArray: [CChar] = str.cString(using: String.Encoding.utf8)!
        let m = str.utf8CString
        let x = String.init(cString: charArray)//要求是UnsafePointer类型
         let y = String.init(cString: m)//报错

Swift与C语言理解

Swift中经常会涉及到C语言的东西,这个时候一般要求的就是UnsafePointer巴拉巴拉之类的,比如Data.append()方法要求传入UnsafePointer<UInt8>类型的数据,我们可以选择通过UnsafeRawPointer生成UnsafePonter(这个笨笨的方式我从API找了半天),当然也可以直接使用类似C的结构如下

let count = 1
//第一种方式UnsafeMutableRawPointer.allocate构造,记得deallocate
let bytesPointer = UnsafeMutableRawPointer.allocate(bytes: count * MemoryLayout<UInt8>.stride,alignedTo: MemoryLayout<UInt8>.alignment)
let int8Pointer = bytesPointer.initializeMemory(as: UInt8.self,  count: count, to: outbuf[i])
theData.append(bytesPointer.assumingMemoryBound(to: UInt8.self), count: 1)  
//第二种方式使用类似C的方法[UInt8](repeating:outbuf[i],count:1),其实这是Array的方法。。囧
theData.append([UInt8](repeating:outbuf[i],count:1), count: 1)
theData.append(Array.init(repeating:outbuf[i],count:1), count: 1)
 // After using 'int8Pointer':
int8Pointer.deallocate(capacity: count)
iOS
Web note ad 1