为什么已经存在Struct 还是需要用到Class?

Struct 可以理解为一个很大很大的值,只能是值传递,并不占用同一份内存。

但是有时候需要引用同一个东西,(你的代码可以引用一个类的同一个实例)就需要用到Class,而且Class可以被继承,可以有子类,而且具有struct的所有特性,属性,方法,扩展,初始化initalize等等。

2018/9/14 posted in  iOS

CAMediaTimingFunction获取控制点:

//create timing function
CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];

我得到的controlPoint值似乎是错误的:

CGPoint controlPoint1, controlPoint2;
[function getControlPointAtIndex:1 values:(float *)&controlPoint1]; 
[function getControlPointAtIndex:2 values:(float *)&controlPoint2];
The controlPoint value that I get seems to be wrong:

controlPoint1   CGPoint (x = 5.2635442471208903E-315, y = 4.9406564584124654E-324)  
controlPoint2   CGPoint (x = 0.0078125018408172764, y = 2.8421186829703262E-314)    

但是,如果我使用float数组作为参数的类型values,我得到controlPoint的正确值:

float c1p[2], c2p[2];
[function getControlPointAtIndex:1 values:c1p];
[function getControlPointAtIndex:2 values:c2p];
The values are:

controlPoint1   CGPoint (x = 1, y = 0)  
controlPoint2   CGPoint (x = 0.75, y = 1) 

32位设备可以使用第一种方法 但是在64位设备就行不通了

2018/9/11 posted in  iOS

PromiseKit

then and done

一个典型的链条⛓️

firstly {
    login()
}.then { creds in
    fetch(avatar: creds.user)
}.done { image in
    self.imageView = image
}

如果这段代码使用之前的方式可能会是这样

login { creds, error in
    if let creds = creds {
        fetch(avatar: creds.user) { image, error in
            if let image = image {
                self.imageView = image
            }
        }
    }
}

其中重要的语法有then 是另一种方式的回调方法,但是承诺链比较容易理解,一个异步操作之后接着去执行下一个操作。

done是一样的,但是done不返回promise,它通常是链条成功部分的终结。

还有就是错误捕捉,比如我们以前的方法会有成功和错误的两个回调,但是现在使用了promise只需要返回一个promise就行了。如下所示

func login() -> Promise<Creds>
    
// Compared with:

func login(completion: (Creds?, Error?) -> Void)
                        // ^^ ugh. Optionals. Double optionals.

catch

那么错误是怎么捕捉到的呢,那么就需要用到promise的另一个重要的方法catch

firstly {
    login()
}.then { creds in
    fetch(avatar: creds.user)
}.done { image in
    self.imageView = image
}.catch {
    // any errors in the whole chain land here
}

每个promise都代表一个异步任务的对象,如果任务失败,诺言会失败,执行reject状态,如果诺言执行成功就是fulfilled状态,如果执行失败会跳过后面的所有then操作进入到catch当中。

ensure and finally

有时候我们需要执行一些必须要执行的操作,比如无论网络请求失败还是成功都隐藏请求动画,那么就需要用到ensure语句了。
无论你的promise执行结果如何,ensure当中的代码块都将被执行。

firstly {
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
    return login()
}.then {
    fetch(avatar: $0.user)
}.done {
    self.imageView = $0
}.ensure {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch {
    //…
}

另外你也可以使用finally方法在链条的最底部执行,无须返回任何值。

spinner(visible: true)

firstly {
    foo()
}.done {
    //…
}.catch {
    //…
}.finally {
    self.spinner(visible: false)
}

when

使用完成处理程序,对多个异步操作的响应要么很慢,要么很难。慢意味着连续执行:

when 有点像GCD当中的DispatchGroup 做完之后再通知到接下来的操作。

firstly {
    when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
    //…
}

when 接受承诺,等待他们解决并返回包含结果的承诺。

与任何承诺链一样,如果任何组件承诺失败,链将调用下一个catch

Making Promises

那么如何使自己的项目支持promise?
标准扩展程序将带您走很长的路,但有时您仍然需要启动自己的链。也许您正在使用不提供承诺的第三方API,或者您可能编写了自己的异步系统。无论哪种方式,添加承诺都很容易。如果您查看标准扩展的代码,您会看到它使用下面描述的相同方法。

假设我们有以下方法:

func fetch(completion: (String?, Error?) -> Void){

    }
我们可以使用下面方法变成promise
func fetch() -> Promise<String> {
        return Promise{
            seal in
            fetch(completion: seal.resolve)
        }

//      或者  return Promise { fetch(completion: $0.resolve) }
}

或者    
func fetch() -> Promise<String> {
        return Promise<String>{ seal in
            fetch{ (result, error) in
                seal.resolve(result, error)
            }
        }
}

Promise的初始化程序为您提供的seal对象定义了许多处理平常的完成处理程序的方法。它甚至涵盖了各种罕见的情况,因此您可以轻松地将promises添加到现有代码库中。

注意:在PMK 4中,此初始化程序为闭包提供了两个参数: fulfill和reject。PMK 5和6为您提供了具有两种方法fulfill和 reject方法的对象,但也提供了该方法的许多变体resolve。您通常只需将完成处理程序参数传递给,resolve并让Swift找出适用于您的特定情况的变体(如上例所示)。

还可以使用fulfill 和 reject方法,比如上面的代码可以改成下面的样子。

 func fetch() -> Promise<String> {

        return Promise<String>{ seal in
            
            fetch{ (result, error) in
                if let error = error {
                    seal.reject(error)
                }else if let success = result{
                    seal.fulfill(success)
                }else{
                    seal.reject(PMKError.invalidCallingConvention)
                }
            }
        }
    }

Guarantee

自从PromiseKit 5,提供了Guarantee作为补充 Promise。这样做是为了补充Swift强大的错误处理系统。

Guarantee永不失败,所以不能拒绝。一个很好的例子是after:

firstly {
    after(seconds: 0.1)
}.done {
    // there is no way to add a `catch` because after cannot fail.
}

APIs That Use Promises

class MyRestAPI {
    func user() -> Promise<User> {
        return firstly {
            URLSession.shared.dataTask(.promise, with: url)
        }.compactMap {
            try JSONSerialization.jsonObject(with: $0.data) as? [String: Any]
        }.map { dict in
            User(dict: dict)
        }
    }

    func avatar() -> Promise<UIImage> {
        return user().then { user in
            URLSession.shared.dataTask(.promise, with: user.imageUrl)
        }.compactMap {
            UIImage(data: $0.data)
        }
    }
}

通过这种方式,异步链可以干净地无缝地整合来自整个应用程序的代码,而不会违反架构边界。

Background Work

class MyRestAPI {
    func avatar() -> Promise<UIImage> {
        let bgq = DispatchQueue.global(qos: .userInitiated)

        return firstly {
            user()
        }.then(on: bgq) { user in
            URLSession.shared.dataTask(.promise, with: user.imageUrl)
        }.compactMap(on: bgq) {
            UIImage(data: $0)
        }
    }
}

所有PromiseKit处理程序都带有一个on参数,允许您指定运行处理程序的调度队列。默认值始终是主队列。

PromiseKit 完全是线程安全的。

Failing Chains

如果错误发生在链条中间,只需抛出一个错误。

firstly {
    foo()
}.then { baz in
    bar(baz)
}.then { result in
    guard !result.isBad else { throw MyError.myIssue }
    //…
    return doOtherThing()
}

错误将在下一个catch处理程序中显示。

由于promises处理抛出的错误,除非你真的想在本地处理错误,否则你不必将调用函数包装到块中:

foo().then { baz in
    bar(baz)
}.then { result in
    try doOtherThing()
}.catch { error in
    // if doOtherThing() throws, we end up here
}

抽象异步性 Abstracting Away Asynchronicity

var fetch = API.fetch()

override func viewDidAppear() {
    fetch.then { items in
        //…
    }
}

func buttonPressed() {
    fetch.then { items in
        //…
    }
}

func refresh() -> Promise {
    // ensure only one fetch operation happens at a time

    if fetch.isResolved {
        startSpinner()
        fetch = API.fetch().ensure {
            stopSpinner()
        }
    }
    return fetch
}

使用promises,您无需担心异步操作何时完成。就像现在一样。

在上面,我们看到您可以根据自己的喜好多次调用then。所有块将按添加顺序执行。

Minimum Duration

有时您需要一项任务至少花费一定的时间。(例如,您希望显示进度条,但如果它显示的时间少于0.3秒,则UI对用户显示为断开。)

let waitAtLeast = after(seconds: 0.3)

firstly {
    foo()
}.then {
    waitAtLeast
}.done {
    //…
}

上面的代码有效,因为我们在工作之前创建了延迟foo()。当我们等待这个承诺时,要么它已经超时,要么我们将等待0.3秒的任何剩余时间,然后继续链。

Cancellation

Promise没有cancel函数,但它们通过符合CancellableError协议的特殊错误类型支持取消。

func foo() -> (Promise<Void>, cancel: () -> Void) {
    let task = Task(…)
    var cancelme = false

    let promise = Promise<Void> { seal in
        task.completion = { value in
            guard !cancelme else { return reject(PMKError.cancelled) }
            seal.fulfill(value)
        }
        task.start()
    }

    let cancel = {
        cancelme = true
        task.cancel()
    }

    return (promise, cancel)
}

被取消的链条默认不会进入到catch方法,然后也可以接收取消中断,如下

foo.then {
    //…
}.catch(policy: .allErrors) {
    // cancelled errors are handled *as well*
}

Saving Previous Results

举个例子

login().then { username in
    fetch(avatar: username)
}.done { image in
    //…
}

如果想在done在同时访问username和image呢
可以使用下面的方法

login().then { username in
    fetch(avatar: username).done { image in
        // we have access to both `image` and `username`
    }
}.done {
    // the chain still continues as you'd expect
}

然而,这种嵌套降低了链条的清晰度。相反,我们可以使用Swift元组:

//简化版
login().then { username in
    fetch(avatar: username).map { ($0, username) }
}.then { image, username in
    //…
}

Chaining Sequences

当你在一个数组中有一系列的任务要反应的时候

// fade all visible table cells one by one in a “cascading” effect

var fade = Guarantee.value(Bool())

for cell in tableView.visibleCells {

    fade = fade.then {_ in
        UIView.animate(.promise, duration: 1, animations: {
            cell.alpha = 0

        })

    }
}
fade.done {_ in


}
        

或者你有一个包含promise的数组

var foo = Promise()
for nextPromise in arrayOfPromises {
    foo = foo.then { nextPromise }
}
foo.done {
    // finish
}

之前我们知道可以使用when同时异步执行多个promise,这种方式完成的速度更快,但是有时候我们需要promise顺序一个一个的执行,比如上面的动画。

Promise采用关联对象进行保存对象

oc中运行时关联对象 宏定义写法

extern void *PMKManualReferenceAssociatedObject;
#define PMKRetain(obj)  objc_setAssociatedObject(obj, PMKManualReferenceAssociatedObject, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
#define PMKRelease(obj) objc_setAssociatedObject(obj, PMKManualReferenceAssociatedObject, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
2018/9/6 posted in  iOS

OC以及Swift中的闭包

因为在实际开发中闭包会经常用到,几乎和代理实现同样的功能,所以我们来探究一下OC和 swift中的闭包。

Objecticve-C

①声明Block

OC中有两种声明方式

  1. 方式一

    typedef void(^BlockName)(NSString * name);
    @property(nonatomic,copy)BlockName hello;
    
  2. 方式一

    @property(nonatomic,copy)void(^Test)(NSString * name);
    

②实现Block

self.Test = ^(NSString *name) {
        NSLog(@"%@",name);
    };

self.hello = ^(NSString *name) {
        NSLog(@"%@",name);
    };

③调用Block

self.Test(@"hello");
self.hello(@"hello");

One more thing

另外类似于AFN的网络请求,我们也可以在方法当中把 block 当成参数传递。

.h文件
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController

-(void)sayHello:(void(^)(NSString * name))hello;

@end

.m 文件
#import "ViewController.h"

@implementation ViewController

-(void)sayHello:(void (^)(NSString *))hello{
    
    hello(@"name");   
}

Swift

①使用别名声明 block

typealias HelloBlock = (_ str:String) -> ()

然后使其成为属性
var block:HelloBlock?

②实现block

self.block = {
    str in
    print(str)        
}

③调用Block

block!("hello")

One more thing

和OC类似,我们在 swift 当中也可以在方法中传递闭包,把闭包当成是参数。

声明方法

func loadRequest(callBack : (_ str:String)->()){
        callBack("hello")
}

调用方法

self.loadRequest { (str) in
    print(str)
}

避免循环引用的问题

另外还需要注意循环引用,有三种方法

一、在闭包外写 weak var weakSelf = self
在内部需要调用 self 的地方使用 weakSelf代替。
二、在闭包内部写[weak self]

self.loadRequest {[weak self] (str) in
    
    self.view.backgroundColor = UIColor.redColor()

    print(str)
}

三、在闭包中写 [unowned self]

self.loadRequest {[unowned self] (str) in
    
    self.view.backgroundColor = UIColor.redColor()

    print(str)
}     
2018/9/5 posted in  iOS

一个实例对象可以通过4种方式调用其方法。

- (void)test{
    
//type1
    [self printStr1:@"hello world 1"];
    
//type2
    [self performSelector:@selector(printStr1:) withObject:@"hello world 2"];
    
//type3
    //获取方法签名
    NSMethodSignature *sigOfPrintStr = [self methodSignatureForSelector:@selector(printStr1:)];
    
    //获取方法签名对应的invocation
    NSInvocation *invocationOfPrintStr = [NSInvocation invocationWithMethodSignature:sigOfPrintStr];
    
    /**
    设置消息接受者,与[invocationOfPrintStr setArgument:(__bridge void * _Nonnull)(self) atIndex:0]等价
    */
    [invocationOfPrintStr setTarget:self];
    
    /**设置要执行的selector。与[invocationOfPrintStr setArgument:@selector(printStr1:) atIndex:1] 等价*/
    [invocationOfPrintStr setSelector:@selector(printStr1:)];
    
    //设置参数 
    NSString *str = @"hello world 3";
    [invocationOfPrintStr setArgument:&str atIndex:2];
    
    //开始执行
    [invocationOfPrintStr invoke];
    
    //type4
    ((void (*)(id,SEL,NSString *, NSArray *, NSInteger))objc_msgSend)(self, @selector(textFunctionWithParam:param2:param3:),@"111",@[@2,@3],123);

}
-(void)textFunctionWithParam:(NSString *)param1 param2:(NSArray *)param2 param3:(NSInteger)param3 {
    NSLog(@"param1:%@, param2:%@, param3:%ld",param1, param2, param3);
}
- (void)printStr1:(NSString*)str{
    NSLog(@"printStr1  %@",str);
}


下面展示block 的两种调用方式

- (void)test{

    void (^block1)(int) = ^(int a){
         NSLog(@"block1 %d",a);
    };
    
    //type1
    block1(1);
    
    //type2
    //获取block类型对应的方法签名。
    NSMethodSignature *signature = aspect_blockMethodSignature(block1);
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    [invocation setTarget:block1];
    int a=2;
    [invocation setArgument:&a atIndex:1];
    [invocation invoke];
}

2018/8/30 posted in  iOS

静态分析

随着业务开发迭代速度越来越快,完全依赖人工保证工程质量也变得越来越不牢靠。所以,静态分析,这种可以帮助我们在编写代码的阶段就能及时发现代码错误,从而在根儿上保证工程质量的技术,就成为了 iOS 开发者最常用到的一种代码调试技术。

Xcode 自带的静态分析工具 Analyze,通过静态语法分析能够找出在代码层面就能发现的内存泄露问题,还可以通过上下文分析出是否存在变量无用等问题。但是,Analyze 的功能还是有限,还是无法帮助我们在编写代码的阶段发现更多的问题。所以,这才诞生出了功能更全、定制化高、效率高的第三方静态检查工具。比如,OCLint、Infer、Clang 静态分析器等。

一款优秀的静态分析器,能够帮助我们更加全面的发现人工测试中的盲点,提高检查问题的效率,寻找潜在的可用性问题,比如空指针访问、资源和内存泄露等等。

OClint

OCLint 是基于 Clang Tooling 开发的静态分析工具,主要用来发现编译器检查不到的那些潜在的关键技术问题。2017 年 9 月份新发布的 OCLint 0.13 版本中,包含了 71 条规则。

这些规则已经基本覆盖了具有通用性的规则,主要包括语法上的基础规则、Cocoa 库相关规则、一些约定俗成的规则、各种空语句检查、是否按新语法改写的检查、命名上长变量名短变量名检查、无用的语句变量和参数的检查。

oclint Hello.m//分析单个文件

oclint -report-type html -o report.html Hello.m//分析单个文件 并生成html 文件

OCLint 的规则索引点击下面的链接。规则索引

Clang

http://clang-analyzer.llvm.org/scan-build

Infer

Infer 是 Facebook 开源的、使用 OCaml 语言编写的静态分析工具,可以对 C、Java 和 Objective-C 代码进行静态分析,可以检查出空指针访问、资源泄露以及内存泄露。

使用 homebrew 安装
brew install infer

分析代码的指令如下所示
infer -- clang -c Hello.m

2018/8/20 posted in  iOS

accessoryView

open var accessoryView: UIView? // if set, use custom view. ignore accessoryType. tracks if enabled can calls accessory action

屏幕快照 2019-04-15 下午10.55.00

如果要实现上面类似的效果,可以不使用自定义的cell去添加subview可以试着使用自带的方法给UITableViewCell的accessoryView赋值。

2018/8/20 posted in  iOS

typealias 类型别名

组合协议

有时,你会遇到一种情况,你有多个协议,而且需要使用一个特定类型来把这些协议都实现。这种情况通常发生在当你定义了一个协议层来提高灵活性时。

protocol CanRead {
    func read()
}
protocol CanWrite {}
protocol CanAuthorize {}
protocol CanCreateUser {}

typealias Administrator = CanRead & CanWrite & CanAuthorize & CanCreateUser

typealias User = CanRead & CanWrite

typealias Consumer = CanRead

class Boss: Administrator{
    func read() {
        
    }
    
}

在这里,我们定义了权限层。管理员可以做所有事情,用户可以读写,而消费者只能读。

关联类型

这超出了本文的范围,但是协议的关联类型也可以通过类型别名来定义:

protocol Example {
 associatedtype Payload: Numeric
}

struct Implementation: Example {
  typealias Payload = Int
}
2018/8/18 posted in  iOS