0%

当我们从Obj-C转到Swift时,会发现Swift提供的数据结构大量使用值语义,如ArrayDictionary等,今天,我们将实现Array的基本功能,来理解值语义以及写时复制。

值和引用语义


在实现之前,我们来简单讨论一下值语义和引用语义的区别。当我们使用Obj-C及大多数其他面向对象语言开发时,经常会使用对象指针或引用,而这就是常说的引用语义,我们可以赋值一个对象实例的引用给一个变量:

MyClass *a = ...;

接着,可以将该变量赋值给另一个变量:

MyClass *b = a;

此时,ab将指向同一个对象,如果指向的对象是可变的,当我们修改对象的内容时,两者对其都可见。

值语义相对比较简单,我们使用的如intstruct等都是值语义,声明为值语义的变量,其直接指向真正的值,而不是值的指针。所以当我们进行赋值时,将得到值的拷贝,如:

1
2
3
int a = 42;
int b = a;
b++;

此时,b的值为43,而a依然为42

Swift中,class类型仍然是引用语义,struct则为值语义,所以,如果对class类型进行赋值时,将得到一个指向同一个实例的引用,对实例对象内容的修改将对所有的引用可见;使用struct时,则不会,每个变量之间都是独立的。

对于使用过Obj-C开发过的人来说,转到Swift使用其提供的数组和字典时,可能会不习惯,因为Swift中两者已经变成了值语义,如:

1
2
3
var a = [1, 2, 3]
var b = a
b.append(4)

对于大多数语言,如上代码的结果一般都是ab都指向同一个数组[1, 2, 3, 4]。而在Swift中,a指向[1, 2, 3]b则指向[1, 2, 3, 4]

实现值语义


如果对象包含固定数量的数据,那么实现就比较简单:我们直接将数据放到struct中即可。比如想要一个2D Point类型且满足值语义,可以创建一个struct包含x``y的值:

1
2
3
4
struct Point {
var x: Int
var y: Int
}
阅读全文 »

RxSwift的sentMessagemethodInvoked方法


RxSwiftRxSwift版本,用来实现函数式、响应式编程。

具体RxSwift的很多用法不做介绍,接下来,只讨论sentMessagemethodInvoked这两个方法,其作用是返回一个Observable<[Any],可以作为观察者监控NSObject子类的某个selector,当执行该selector时,将在执行前、后分别执行注册了该selectorsentMessagemethodInvoked方法。

sentMessagemethodInvoked实现原理


sentMessagemethodInvoked只针对某个实例起作用,其实现首先借鉴了KVO的实现方法,通过创建监听的对象的子类,然后重写方法的实现来实现。sentMessagemethodInvoked实现分两个版本,基础版、优化版。基础版通过Swizzle forwardInvocation:respondsToSelector:methodSignatureForSelector:等函数,将所有需要观察的selector调用时进入forwardInvocation:流程,从而进行拦截,以实现通知;优化版则在基础版之上通过Type Encoding来做一个缓存优化,避免每次调用都进入转发的过程。

阅读全文 »

由于不同的Swift版本引用计数实现会有不同,该文讨论的引用计数原理都基于Swift3

弱引用


iOS开发时经常会遇到循环引用,如果处理不当会导致内存泄露,我们通常会使用weak reference弱引用来解决该问题,因为弱引用不会retain对象,当对象引用计数变为0时,弱引用指针将会被赋nil

实现过程


通常如果实现弱引用,可以让每一个对象维护所有指向该对象的一个弱引用列表,当一个弱引用指向一个对象时,该引用被添加进列表,当弱引用重新赋值或生命期结束,则将其从列表中移除,当一个对象dealloced后,列表中的所有引用会被赋nil。在多线程环境中,需要对获得弱引用和释放对象的操作进行同步,以避免竞态条件,既当一个线程在释放最后一个强引用对象的同时,另一个线程正尝试加载该对象的弱引用。

Objective-C实现的过程为,每一个弱引用是一个指向目标对象的指针,编译器会使用helper函数,来避免直接读写指针,确保读取弱引用对象时不会返回正在被释放的对象指针。

实战


接下来,我们将创建几个方法来观察弱引用的过程。
首先我们想要能够dump出一个对象的内存,如下方法将获取一块内存,将其分成指针大小的块,再将其内容转成16进制,以便于观察:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Swift version: Swift3

func contents(ptr: UnsafeRawPointer, _ length: Int) -> String {
let wordPtr = ptr.assumingMemoryBound(to: UInt.self)

let words = length / MemoryLayout<UInt>.size
let wordChars = MemoryLayout<UInt>.size * 2

let buffer = UnsafeBufferPointer<UInt>(start: wordPtr, count: words)
let wordStrings = buffer.map({ word -> String in
var wordString = String(word, radix: 16)
while wordString.characters.count < wordChars {
wordString = "0" + wordString
}
return wordString
})
return wordStrings.joined(separator: " ")
}
阅读全文 »

前言


iOS中,我们经常使用GCD来进行并发操作,我们并不需要关心线程的管理,Dispatch Queue会自动帮我们处理线程的创建和释放,在极大的简化并发操作的同时,某些情况下,Dispatch Queue的滥用可能会导致应用挂起,如向并发队列中添加阻塞的Block,阻塞的Block会导致系统创建更多的线程来处理任务,而GCD线程池的最大线程数为64个,所以一旦达到最大值,应用将挂起。
接下来,我将列出一些解决方案,来更好的使用Dispatch Queue

YYDispatchQueue


YYDispatchQueue的主要思想是使用串行队列来替换并发队列,可以为指定的NSQualityOfService创建一个队列池,由YYDispatchQueuePool对象来进行管理,每一种NSQualityOfService最多可以创建32个串行队列,通过- (dispatch_queue_t)queue;方法来获取可用队列,其采用Round Robin轮询算法。

除了可以创建队列池来管理并发外,还可以通过C的全局函数(dispatch_queue_t YYDispatchQueueGetForQOS(NSQualityOfService qos))来获取特定的NSQualityOfService串行队列,队列由全局的队列池来管理,每一种NSQualityOfService的串行队列数与核数相同,这样可以尽可能的减少线程之间的上下文切换。

阅读全文 »

一款用于网页浏览的APP(Web Browser For iOS)。Github地址

Features - 功能

  1. 多Tab页浏览(multi-tab browsing)
  2. 冷启动恢复浏览记录,包括当前页及前进后退页面(session restore, includes current page and backforward list)
  3. 书签、历史记录管理(bookmark、history manage)
  4. 页内查找(find in page)
  5. 点击标题栏进行页面访问或搜索(tap the title bar to input url for surf or key to search)
  6. 自动监控剪切板URL,可在新窗口中打开

Usage - 用法

  1. clone or download zip file.
  2. Run command carthage update --platform iOS
  3. Just run WebBrowser.xcodeproj

Requirements - 依赖

Demo

1. Home Page (主页):

home page

2. Multi-tab (多窗口):

tab

tab

tab

3. Search (搜索):

search

4. No Image Mode (无图模式)

no image mode

5. History (历史)

  1. Long Press to select options. (长按记录可弹出选项按钮)
  2. Tap to open history in current window.(点击记录会在当前窗口打开历史页面)

    history

6. Favorite (收藏)

In non-editing mode (在非编辑模式下操作)
  1. Long press on directory to edit directory name in non-editing mode.(长按目录来编辑目录名字)

    favorite

  2. Long press on bookmark item to edit bookmark’s url, name, directory in non-editing mode.(长按书签项来编辑书签的地址、名字、以及所在目录)

    favorite

In editing mode (在编辑模式下)
  1. reorder, delete directory in editing mode.(删除、排序目录)

    favorite

  2. click “新文件夹” button to add new directory in editing mode.(点击”新文件夹”按钮来创建新的目录)

    favorite

  3. reorder, delete bookmark in editing mode.(删除、排序书签)

    favorite

  4. add new bookmark.(添加新书签)

    favorite

7. find in page (页内查找)

find in page

License

The MIT License (MIT)

Copyright (c) 2017 Zhong Wu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

理论


Mach-O

Mach-O相关术语

Mach-OMach Object文件格式的缩写,它是一种用于可执行文件,目标代码,动态库,内核存储的文件格式。它包括多种文件类型:

  • Executable(可执行文件):App的主二进制文件
  • Dylib:动态库(如DSODLL)
  • Bundle:不能被链接的动态库,只能通过dlopen(),用于Mac OS

Image:可以是可执行文件、动态库或者bundle
Framework:动态库,包含资源和头文件。

Mach-O Image文件

一个Mach-O文件由3部分组成:HeaderLoad commandsRaw segment dataHeader描述了文件的目标架构等信息,如x86-64,PPC;Load commands列出了文件的逻辑结构及文件在虚拟内存中的布局;Raw segment data包含了在Load commands中指出的segment()。在Mach-O文件中,我们把HeaderLoad commands放在了__TEXT segment()的开头,即第一个segment()的开头。每一个由多个page()组成,的大小为大小的整数倍。如下图,TEXT段占3DATALINKEDIT分别占1的大小取决于硬件,在arm64下,大小为16K,其它则为4K。

我们还可以从section(节)的角度来理解,编译器对section是透明的,section仅仅是一个的子区间,它没有任何的大小约束,但是section之间不会产生重叠。

事实上,每一个二进制文件都包含TEXTDATALINKEDIT这3个通用的TEXT位于文件的开始,它包括Mach header,机器指令,代码以及只读常量如C字符串,DATA段是可读写的,其包括所有的全局变量,静态变量等。LINKEDIT包含加载程序的meta data(元数据),如符号,字符串,重定向表条目,供动态链接器使用。

阅读全文 »

前言


对于服务器,程序员们还是很熟悉的,任何一个互联网产品,背后几乎都离不开服务器,所有的服务,背后都是通过服务器来提供的,接下来,我将列出从裸机到可用的基本步骤,示例的服务器为HP ProLiant Gen9,当然,其他的服务器如IBM等,基本步骤也都是类似的。

配置管理口


通常来说,这一步是都是要走的,通过配置管理口,可以远程管理服务器,不用去机房插显示器、键盘。HP ProLiant Gen9的管理口称为iLO口,开机初始化后进入系统配置,进行配置,一般来说,服务器都是使用静态IP,所以需要禁掉DHCP,并配置IP地址、网关、子网掩码。重启后生效,这样就能够进行远程控制了。

做RAID


磁盘阵列(Redundant Arrays of Independent Disks,RAID),是由独立磁盘构成的具有冗余能力的阵列,将数据切割成许多区段,分别放在各个硬盘上,同时有冗余,数据重构等安全服务。

做RAID有很多种方案,可以根据不同的需求,使用不同的方案,如RAID0(条带)、RAID1(镜像)、RAID5、RAID10、RAID50等(不同的方法,所要求的磁盘数也不一样),通常,对于存放数据的磁盘,会使用RAID10、RAID50。做完RAID后,就形成了逻辑分区。

当然,并不是说服务器一定要做RAID,不做RAID的服务器也能装系统。

阅读全文 »

AppDelegate解耦


说到AppDelegate,大家想必都不陌生,它作为应用(UIApplication)的委托对象,在UIApplicationMain方法中被创建,当发生应用相关事件时,提供开发者响应的机会。

1
2
3
4
5
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

AppDelegateUIResponder的子类,应用将AppDelegate加入响应链中。UIApplication作为响应链中的最上层,当UIApplication任然无法处理特定事件时,会将事件转发给AppDelegate来处理。

当然,AppDelegate的主要职责还是响应应用事件,它满足UIApplicationDelegate协议,UIApplicationDelegate协议包含大量的委托方法,包括处理应用状态、状态恢复、后台下载数据、通知、URL Route等很多方面,这就会导致,随着程序的开发,AppDelegate将变得越来越臃肿,为了解决该问题,就需要进行解耦。

阅读全文 »

前言


performSelector may cause a leak because its selector is unknown

ARC环境下,我们使用-(id)performSelector:方法时,编译器会提示可能导致内存泄露的警告。大家如果一直使用ARC进行iOS开发,没有经历MRC时期的话,可能心里会有疑问,为什么Apple提供的API会报警告呢,又为什么会报内存泄露的警告?接下来,将详细讨论该问题。

原因


其实,产生该问题的原因是ARC,运行时系统需要知道调用方法的返回值类型,我们知道,方法的返回值包括:void,int,NSString *,id等,ARC通常可以通过定义实例方法对象的头中获取信息。

ARC对于返回值有3种处理情况:

  1. 忽略非对象类型(void,int等)
  2. 当新建对象值不再需要时release(如init,copy或带有ns_returns_retained属性的方法)
  3. 不做任何处理,且假设返回的对象值会在局部作用域内有效(在最里层的autorelease pool结束之前都有效)

调用-(id)performSelector:,编译器会假设调用方法的返回值是一个对象,且不会对返回值进行retain/release,所以,如果你调用如上讨论的第2种情况下的方法,将导致内存泄露,因为,调用的方法会返回一个新的对象。

如果#SEL返回值类型为void或非对象类型,是可以安全的使用-(id)performSelector:的。

阅读全文 »

Prerequisites


  • CAS Server 3.5.x.,下文以$CAS-SERVER表示。
  • CAS Client 3.3.x.,下文以$CAS-CLIENT表示。
  • Tomcat 7.x,下文以$CAS_TOMCAT_HOME表示。
  • GateIn-3.8.1.Final-tomcat-7,下文以$GATEIN_HOME表示。

最近公司需要对之前的多个Web应用系统进行整合,希望用户在登录某个应用系统时,能直接访问其他的Web应用系统,且不需要再次登录。既我们经常说的单点登录,最后,我选择了CAS,并将其整合进了GateIn portal。接下来,将整理一下原理和整合步骤。
所有项目代码示例可在github上下载。

CAS介绍


CAS(Central Authentication Service)是Yale大学发起的一个开源项目,旨在为Web应用系统提供一种可靠的单点登录。单点登录,既在多个应用系统中,用户只需登录一次就可以访问所有相互信任的应用系统。CAS Client支持非常多的客户端,包括JAVA、PHP、Ruby等。

接下来,接介绍一下CAS系统。

CAS 系统组成


CAS系统架构由两部分组成,CAS ServerCAS Clients,两者可以通过多种协议进行通信。

阅读全文 »