ios NSNumberFormatter 金额格式处理

##NSNumberFormatter
可以通过NSNumberFormatter,同样可以设置NSNumber输出的格式
比如123,456,789 或者 123 456 789

NSNumber *number = @(123456789.6789);
2017-10-27 17:20:59.064200+0800 NSNumberFormatter的使用[59895:2506616] No Style                  = 123456790
2017-10-27 17:20:59.064354+0800 NSNumberFormatter的使用[59895:2506616] Decimal Style             = 123,456,789.679
2017-10-27 17:20:59.064436+0800 NSNumberFormatter的使用[59895:2506616] Currency Style            = $123,456,789.68
2017-10-27 17:20:59.064537+0800 NSNumberFormatter的使用[59895:2506616] Percent Style             = 12,345,678,968%
2017-10-27 17:20:59.064626+0800 NSNumberFormatter的使用[59895:2506616] Scientific Style          = 1.234567896789E8
2017-10-27 17:20:59.064720+0800 NSNumberFormatter的使用[59895:2506616] Spell Out Style           = one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine point six seven eight nine
2017-10-27 17:20:59.064828+0800 NSNumberFormatter的使用[59895:2506616] Ordinal Style             = 123,456,790th
2017-10-27 17:20:59.064916+0800 NSNumberFormatter的使用[59895:2506616] Currency ISO Style        = USD123,456,789.68
2017-10-27 17:20:59.065019+0800 NSNumberFormatter的使用[59895:2506616] Currency plural Style     = 123,456,789.68 US dollars
2017-10-27 17:20:59.065107+0800 NSNumberFormatter的使用[59895:2506616] Currency accounting Style = $123,456,789.68

NSNumberFormatter类有个属性numberStyle,它是一个枚举型,设置不同的值可以输出不同的数字格式。该枚举包括

NSNumber *number = @(123456789.6789);
enum {
    NSNumberFormatterNoStyle = kCFNumberFormatterNoStyle, //无格式,四舍五入,输出123456790 
    NSNumberFormatterDecimalStyle = kCFNumberFormatterDecimalStyle, //小数型,保留小数输出123,456,789.679
    NSNumberFormatterCurrencyStyle = kCFNumberFormatterCurrencyStyle, //货币型,加上了人民币标志,原值输出$123,456,789.68
    NSNumberFormatter
    NSNumberFormatterPercentStyle = kCFNumberFormatterPercentStyle,  //百分比型,本身数值乘以100后用百分号表示,输出12,345,678,968%
    NSNumberFormatterScientificStyle = kCFNumberFormatterScientificStyle, //科学计数型,原值表示,输出1.234567896789E8
    NSNumberFormatterSpellOutStyle = kCFNumberFormatterSpellOutStyle  //全拼,原值的中文表示,
};

typedef NSUInteger NSNumberFormatterStyle;

以下是所有API的注释:

NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
numberFormatter.numberStyle.rawValue // numberStyle 0
numberFormatter.locale.localeIdentifier // 语言环境 语言环境
numberFormatter.generatesDecimalNumbers // 是否生成小数 false
numberFormatter.formatterBehavior.rawValue // formatterBehavior 1,040
numberFormatter.negativeFormat // 负格式 "#"
numberFormatter.textAttributesForNegativeValues // textAttributesForNegativeValues nil
numberFormatter.positiveFormat // 正格式 "#"
numberFormatter.textAttributesForPositiveValues // textAttributesForPositiveValues nil
numberFormatter.allowsFloats // 是否允许浮点值 true
numberFormatter.decimalSeparator // 小数分隔符 "."
numberFormatter.alwaysShowsDecimalSeparator // 是否始终显示小数分隔符 false
numberFormatter.currencyDecimalSeparator // 货币小数分隔符 "."
numberFormatter.usesGroupingSeparator // 是否采用分组分隔符 false
numberFormatter.groupingSeparator // 分组分隔符 ","
numberFormatter.zeroSymbol // 零符号 nil
numberFormatter.textAttributesForZero // 文本属性 nil
numberFormatter.nilSymbol // nil符号 ""
numberFormatter.textAttributesForNil // 文本属性 nil
numberFormatter.notANumberSymbol // 非数字符号 "NaN"
numberFormatter.textAttributesForNotANumber // 文本属性 nil
numberFormatter.positiveInfinitySymbol // 正无穷大符号 "+∞"
numberFormatter.textAttributesForPositiveInfinity // 文本属性 nil
numberFormatter.negativeInfinitySymbol // 负无穷大符号 "+∞"
numberFormatter.textAttributesForNegativeInfinity // 文本属性 nil
numberFormatter.positivePrefix // 正前缀 ""
numberFormatter.positiveSuffix // 正后缀 ""
numberFormatter.negativePrefix // 负前缀 "-"
numberFormatter.negativeSuffix // 负后缀 ""
numberFormatter.currencyCode // 货币代码 "USD"
numberFormatter.currencySymbol // 货币符号 "$"
numberFormatter.internationalCurrencySymbol // 国际货币符号 "USD"
numberFormatter.percentSymbol // 百分号符号 "%"
numberFormatter.perMillSymbol // 千分号符号 "‰"
numberFormatter.minusSign // 减号 "-"
numberFormatter.plusSign //加号 "+"
numberFormatter.exponentSymbol // 指数符号 "E"
numberFormatter.groupingSize // 分组大小 0
numberFormatter.secondaryGroupingSize // 第二分组大小 0
numberFormatter.multiplier // 乘数 nil
numberFormatter.formatWidth // 格式宽度 0
numberFormatter.paddingCharacter // 填充字符 "*"
numberFormatter.paddingPosition.rawValue // 填充位置 0
numberFormatter.roundingIncrement // 舍入增量 0
numberFormatter.minimumIntegerDigits // 最小的整数位 0
numberFormatter.maximumIntegerDigits // 最大的整数位 42
numberFormatter.minimumFractionDigits // 最小的小数位数 0
numberFormatter.maximumFractionDigits // 最大的小数位数 0
numberFormatter.minimum // 最小值 nil
numberFormatter.maximum // 最大值 nil
numberFormatter.currencyGroupingSeparator // 货币分组符号 ","
numberFormatter.lenient // false
numberFormatter.usesSignificantDigits // 是否使用有效数字 false
numberFormatter.minimumSignificantDigits // 最小有效数字 1
numberFormatter.maximumSignificantDigits // 最大有效数字 6
numberFormatter.partialStringValidationEnabled // 是否部分字符串验证启用 false

// ==================== 设置属性 ====================

NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
    
    numberFormatter.groupingSize = 4; // 数字分割的尺寸
    numberFormatter.usesGroupingSeparator = true;
    numberFormatter.groupingSeparator = @" "; //数字分割的格式
    
    NSNumber *number = @(123456789);
    
    NSString *spaceStr = [numberFormatter stringFromNumber:number];
    
    NSLog(@"空格 ---- %@",spaceStr);
    
    numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
    
    // 格式宽度
    numberFormatter.formatWidth = 15;
    
    // 填充符
    numberFormatter.paddingCharacter = @"?";
    
    // 填充位置
    numberFormatter.paddingPosition = kCFNumberFormatterPadBeforeSuffix;
    numberFormatter.positiveSuffix = @"元";
    
    NSLog(@"%@",[numberFormatter numberFromString:@"10000000元"]);  // 10000000
    
    // 貌似没什么用
    numberFormatter.allowsFloats = NO;
    numberFormatter.alwaysShowsDecimalSeparator = NO;
    numberFormatter.maximum = @1000;
    numberFormatter.minimum = @100;
    
    // 小数点样式
    numberFormatter.decimalSeparator = @".";
    
    // 零的样式
    numberFormatter.zeroSymbol       = @"-";
    
    // 前缀和后缀
    numberFormatter.positivePrefix = @"!";
    numberFormatter.positiveSuffix = @"元";
    numberFormatter.negativePrefix = @"@";
    numberFormatter.negativeSuffix = @"亏";
    
    // 指定符号,与我们在前面类方法中说明的一致
    NSLog(@"货币代码%@",numberFormatter.currencyCode);                     // 货币代码USD
    NSLog(@"货币符号%@",numberFormatter.currencySymbol);                   // 货币符号$
    NSLog(@"国际货币符号%@",numberFormatter.internationalCurrencySymbol);   // 国际货币符号USD
    NSLog(@"百分比符号%@",numberFormatter.percentSymbol);                   // 百分比符号%
    NSLog(@"千分号符号%@",numberFormatter.perMillSymbol);                   // 千分号符号‰
    NSLog(@"减号符号%@",numberFormatter.minusSign);                         // 减号符号-
    NSLog(@"加号符号%@",numberFormatter.plusSign);                          // 加号符号+
    NSLog(@"指数符号%@",numberFormatter.exponentSymbol);                    // 指数符号E
    
    // 整数最多位数
    numberFormatter.maximumIntegerDigits = 10;
    
    // 整数最少位数
    numberFormatter.minimumIntegerDigits = 2;
    
    // 小数位最多位数
    numberFormatter.maximumFractionDigits = 3;
    
    // 小数位最少位数
    numberFormatter.minimumFractionDigits = 1;
    
    // 数字分割的尺寸
    numberFormatter.groupingSize = 4;
    
    // 除了groupingSize决定的尺寸外,其他数字位分割的尺寸
    numberFormatter.secondaryGroupingSize = 2;
    
    // 最大有效数字个数
    numberFormatter.maximumSignificantDigits = 12;
    
    // 最少有效数字个数
    numberFormatter.minimumSignificantDigits = 3;
    
    NSLog(@"正数%@,负数%@",[numberFormatter stringFromNumber:@(+12135230.2346)],[numberFormatter stringFromNumber:@(-12135231.2346)]);  // 正数!12,13,5230.2346元,负数@12,13,5231.2346亏
    NSLog(@"零 = %@",[numberFormatter stringFromNumber:@(0)]); //  零 = -
    
    // 舍入值,比如以10为进位值,那么156就进位为160,154进位为150
    numberFormatter.roundingIncrement = @10;
    
    // 舍入方式
    numberFormatter.roundingMode = kCFNumberFormatterRoundHalfUp;
    NSLog(@"%@",[numberFormatter stringFromNumber:@123456.7890]);  // !12,3460元

LINKE:https://github.com/shenyuan000/NSNumberFormatter-

06/15/2018 08:52 上午 posted in  apple

ETH转账交易

##1.gasprice分级配置实例

https://ethers.io/gas-prices-v2.raw {
    details =     (
        "11 GWei",
        "12 GWei",
        "18 GWei",
        "100 GWei"
    );
    prices =     (
        11000000000,
        12000000000,
        18000000000,
        100000000000
    );
    subtitles =     (
        "completes in about 30 minutes",
        "completes in about 4 minutes",
        "completes in about 2 minutes",
        "completes in about 30 seconds"
    );
    titles =     (
        "SAFE LOW",
        AVERAGE,
        FAST,
        URGENT
    );
}

##2.交易成功返回的交易记录

{
	"jsonrpc": "2.0",
	"id": 1,
	"result": {
		"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
		"blockNumber": null,
		"from": "0xec34c8681972b22a79f641f42cfdb0e22bffcd64",
		"gas": "0x5208",
		"gasPrice": "0x12a05f200",
		"hash": "0xdc9246c3485357c5d92cdf0c745a435361f3e036e3863c5dd525834348de6f9c",
		"input": "0x",
		"nonce": "0x12",
		"to": "0x295ac792993a96a332334f2752d544a64e940015",
		"transactionIndex": "0x0",
		"value": "0x5af3107a4000",
		"v": "0x26",
		"r": "0xd384b9e2a5854a5c6e573c5ad0bae428fa7e1b6c389264412ea3003c0120e684",
		"s": "0x56cef9a313ec44ed5c1768801cf16877ff07dd8e8371c3e32b7a6b5e31eff511"
	}
}
06/06/2018 07:50 上午 posted in  Ethereum

ETH ERC20的相关信息

##pyhton script

import json
import web3
from web3 import Web3, HTTPProvider

my = "0xD551234Ae421e3BCBA99A0Da6d736074f22192FF"
eos_contract_address = "0x86Fa049857E0209aa7D9e616F7eb3b3B78ECfdb0"

contract_source_code='''
[{
  "type":"function",
  "name":"balanceOf",
  "constant":true,
  "payable":false,
  "inputs":[{"name":"","type":"address"}],
  "outputs":[{"name":"","type":"uint256","value":"0"}]
}]
'''

abi = json.loads(contract_source_code)

web3 = Web3(HTTPProvider('http://localhost:8545') )
#web3 = Web3(HTTPProvider('https://mainnet.infura.io/<key>') )
source_code = web3.eth.getCode(eos_contract_address)
contract = web3.eth.contract(abi=abi, address=eos_contract_address)
contract.call().balanceOf(my)

##erc20 contract abi

[
  {
    "constant": true,
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_from",
        "type": "address"
      },
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "name": "success",
        "type": "bool"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "name": "",
        "type": "uint8"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [],
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_spender",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      },
      {
        "name": "_extraData",
        "type": "bytes"
      }
    ],
    "name": "approveAndCall",
    "outputs": [
      {
        "name": "success",
        "type": "bool"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "",
        "type": "address"
      },
      {
        "name": "",
        "type": "address"
      }
    ],
    "name": "spentAllowance",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "",
        "type": "address"
      },
      {
        "name": "",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "type": "function"
  },
  {
    "inputs": [
      {
        "name": "initialSupply",
        "type": "uint256"
      },
      {
        "name": "tokenName",
        "type": "string"
      },
      {
        "name": "decimalUnits",
        "type": "uint8"
      },
      {
        "name": "tokenSymbol",
        "type": "string"
      }
    ],
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  }
]

##ERC20如eos转账demo

http://www.yaozihao.cn/2017/12/08/697/

from web3 import Web3, HTTPProvider
from web3.contract import ConciseContract
import json

web3 = Web3(HTTPProvider('http://localhost:8545'))
print(web3.eth.blockNumber)

tx='0xb1ea3885a25efa295837c287560fe4c137c1bcd3718720330eb68d7f96a5479e'
web3.eth.getTransaction(tx)
web3.eth.getTransactionReceipt(tx)

exit(0)

web3.eth.defaultAccount = '0x00B113795f1aA12EE361adC6D281392802d0e025'
EIP20_ABI_str = '[{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_releaseTime","type":"uint256"}],"name":"mintTimelocked","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]'
EIP20_ABI = json.loads(EIP20_ABI_str)

token_contract_address = '0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0'

token = web3.eth.contract(
token_contract_address,
abi=EIP20_ABI,
ContractFactoryClass=ConciseContract,
)

totalSupply = token.totalSupply()
balance = token.balanceOf('0x00B113795f1aA12EE361adC6D281392802d0e025')
tx = token.transfer(
'0x00845daA689b1374ecAf4CfEcfBfc86D41ec7735',
web3.toWei(2,"ether"),
transact={'from':'0x00B113795f1aA12EE361adC6D281392802d0e025'})

tx = '0xb3b82923d8d9b5d28fbab7f7281704f033362ac9f293e6638227efb44ff85499'
web3.eth.getTransaction(tx)
web3.eth.getTransactionReceipt(tx)

web3.eth.getTransactionCount(web3.eth.coinbase)
web3.eth.getTransactionCount('0x00B113795f1aA12EE361adC6D281392802d0e025')
06/04/2018 21:05 下午 posted in  Ethereum

ETH钱包Keystore格式

根据ether framework 创建钱包成功之后返回的JSON:

{
  "x-ethers" : {
    "mnemonicCiphertext" : "4ec6ff1c1307fde22f54535d8f2c49ac",
    "version" : "0.1",
    "mnemonicCounter" : "e6225f53c68204cc0590505b0b0d9cfb",
    "client" : "ethers\/iOS",
    "gethFilename" : "UTC--2018-06-02T09-15-17.0Z--ec34c8681972b22a79f641f42cfdb0e22bffcd64"
  },
  "id" : "39362BF0-1571-41C9-BA5D-1D2E96FB1F8A",
  "address" : "ec34c8681972b22a79f641f42cfdb0e22bffcd64",
  "Crypto" : {
    "ciphertext" : "02a2381e0712b4911e231688ecd84470cc75221192d70f74b5255dc59ec8ba90",
    "cipherparams" : {
      "iv" : "8a92b1f77ad3f0404fdcaf9189cb0027"
    },
    "kdf" : "scrypt",
    "kdfparams" : {
      "r" : 8,
      "p" : 1,
      "n" : 262144,
      "dklen" : 32,
      "salt" : "a6ab1e929bef72f21e1af6d12ed6bdf0296b3d224491274f1fbd1b5a4074e9b4"
    },
    "mac" : "b16fe2af2ec678ee5f08e5d4ca748778b5eaa26003d5bcee71cd08b68f76e60a",
    "cipher" : "aes-128-ctr"
  },
  "version" : 3
}

jsonString String "{"isHDWallet":true,"id":"ec14e04d-6ed1-431e-8873-a9bd069d9534","crypto":{"ciphertext":"16c7a5a56b2ee6a94f95d940b336b99c73450c4cb1f74bb2f532821381efd507f87d8c1ba71b1a35712d8eb36a75895633bf86e85d7ff16ef27380a8209d2512f91b5c4ed299f815eed94d17cc1b79e42dbc35d73f93b030489cdef510b5b687","cipherparams":{"iv":"3d65a2eb4f68012503d1808f11f8cb4b"},"kdf":"scrypt","kdfparams":{"r":6,"p":1,"n":4096,"dklen":32,"salt":"820f1b08b5ab9caf189ee922abf98435fa50b057c6aa6ebd46dd07605c08dd98"},"mac":"726aec73aca75e0704c5322683ec291e50ee861f9dfd3dd7929a159b3c364c8e","cipher":"aes-128-cbc"},"pathToAddress":{"m\/44'\/60'\/0'\/0\/0":"0xC59A230342A13eC78141B3D20215eb78C07c0a27"},"rootPath":"m\/44'\/60'\/0'\/0","version":3}"

06/02/2018 08:15 上午 posted in  Ethereum

比特币安全知识点:HD钱包

HD钱包(分层确定性钱包)
HD钱包(Hierarchical Deterministic)

HD钱包根据“种子”(种子密钥)按顺序导出未来的地址,每个新地址相当于“种子+计数器”。通过这种方式,你只用一个种子就可以恢复所有的私钥和地址。

05/25/2018 06:58 上午 posted in  BlockChain

比特币『私钥』『公钥』『钱包地址』间的关系

转自:http://www.bikeji.com/t/110#reply25
比特币交易涉及到很多密码学知识:公钥、私钥、哈希、对称加密、非对称加密、签名等等。那么哪些是需要用户认真保管不能对外泄露的,那些是需要用户公开的呢?先从钱包地址的生成说起。

首先使用随机数发生器生成一个『私钥』。一般来说这是一个256bits的数,拥有了这串数字就可以对相应『钱包地址』中的比特币进行操作,所以必须被安全地保存起来。
『私钥』经过SECP256K1算法处理生成了『公钥』。SECP256K1是一种椭圆曲线算法,通过一个已知『私钥』时可以算得『公钥』,而『公钥』已知时却无法反向计算出『私钥』。这是保障比特币安全的算法基础。
同SHA256一样,RIPEMD160也是一种Hash算法,由『公钥』可以计算得到『公钥哈希』,而反过来是行不通的。
将一个字节的地址版本号连接到『公钥哈希』头部(对于比特币网络的pubkey地址,这一字节为“0”),然后对其进行两次SHA256运算,将结果的前4字节作为『公钥哈希』的校验值,连接在其尾部。
将上一步结果使用BASE58进行编码(比特币定制版本),就得到了『钱包地址』。
比如, 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa
##『私钥』『公钥』『钱包地址』间的关系
在上述的五个步骤里只有“BASE58编码”有相应的可逆算法(“BASE58解码”),其他算法都是不可逆的,所以这些数据之间的关系可以表

可以看到:
通过『私钥』可以得到上述计算过程中所有的值。
『公钥哈希』和『钱包地址』可以通过互逆运算进行转换,所以它们是等价的。

##使用『私钥』对交易进行签名
比特币钱包间的转账是通过交易(Transaction)实现的。交易数据是由转出钱包『私钥』的所有者生成,也就是说有了『私钥』就可以花费该钱包的比特币余额。生成交易的过程如下:

交易的原始数据包括“转账数额”和“转入钱包地址”,但是仅有这些是不够的,因为无法证明交易的生成者对“转出钱包地址”余额有动用的权利。所以需要用『私钥』对原始数据进行签名。
生成“转出钱包公钥”,这一过程与生成『钱包地址』的第2步是一样的。
将“转出签名”和“转出公钥”添加到原始交易数据中,生成了正式的交易数据,这样它就可以被广播到比特币网络进行转账了。
##使用『公钥』对签名进行验证

交易数据被广播到比特币网络后,节点会对这个交易数据进行检验,其中就包括对签名的校验。如果校验正确,那么这笔余额就成功地从“转出钱包”转移到“转入钱包”了。
小结
如果一个『钱包地址』从未曾发送余额到其他『钱包地址』,那么它的『公钥』是不会暴露在比特币网络上的。而公钥生成算法(SECP256K1)是不可逆的,即使『公钥』暴露,也很难对『私钥』的安全性造成影响(难易取决于『私钥』的生成算法)。
『私钥』用来生成『公钥』和『钱包地址』,也用来对交易进行签名。拥有了『私钥』就是拥有了对这个钱包余额的一切操作权力。所以,保护『私钥』是所有比特币钱包应用最基本也是最重要的功能。

本文仅讨论标准P2PKH交易方式,P2SH不在讨论范围内。
历史上发生过校验错误的交易被打入到blockchain中的事情,这种交易中的比特币永远地消失在Cyberspace中了。

05/11/2018 08:08 上午 posted in  BlockChain

比特币的密钥(yue)、公钥、和地址之间的概念理解

随机取32个字节的【秘钥】

---》椭圆加密算法 ----》【公钥】(压缩的话,只取前面的32位。)

-----》sha256 ----》哈希160 ---》生成【公钥哈希】

然后比特币程序会加上校验码(【公钥哈希】进行两次sha256后取前4个字节)和比特币主网版本号(0x00)形成 最终的【地址】

私钥 >> 公钥 >> 两个【公钥哈希】(压缩【公钥哈希】和非压缩【公钥哈希】)

1.从2个公钥哈希(两个或者任意一个都无法),无法推导出【公钥】。
2.但是从【公钥】 公钥哈希可以推导出 【公钥哈希】
3.【私钥】可以推导出 两个【公钥哈希】和【公钥】,但是反过来,无论是【公钥】还是【公钥哈希】都无法推导出【密钥】

交易的验证理解:

  1. 交易数据的签名只用密钥的拥有者可以生成(加密),而且这个签名加上公钥运算后(解密)可以得到交易数据(明文),只需要将得到的明文跟真实的明文做对比即可知道该交易是否为私钥拥有者发起。
  2. 而一个公钥有且仅有一个【公钥哈希】(即账户地址),我们可以通过公钥哈希算出需要被扣款的比特币地址。
  3. 一个公钥只能对应一个私钥,
  4. 一个私钥允许对多个公钥,这个类似于,一个人拥有多张银行卡,但椭圆算法不存在这种情况,也就是说一个秘钥只有一个公钥和他对应。
  5. 秘钥相同,公钥也相同,所以竟可能的把秘钥设计的复杂一点,避免被别人猜中你的秘钥。
  6. 由于地址是由【公钥】经过两次sha256算出来的,所以理论上不存在哈希碰撞,因此一个【比特币账号地址】只能由一个【公钥】推导出来,而公钥是唯一的,所以【地址也是唯一的】
  7. 可以理解为【秘钥】是唯一的和【椭圆算法】生成的【公钥】也是唯一的,而假定【公钥两次sha256生成的值】也是唯一,所以【地址】是唯一的。即,一个【秘钥】对应一个【公钥】对应一个【地址】

私钥(Private Key)

比特币的私钥是由64位十六进制的字符组成,长得像这样,比如:5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss。

私钥的生成方式是完全随机的,随机生成这样的字符串就会有16的64次方种可能,即:2的256次方,这个数字已经超过了宇宙中原子的个数,用“暴力破解”的方式逐一遍历可能的私钥,幻想能碰到一个有效的且有币的私钥,可以说是不可能,就算是量子计算机也没用。

目前已存在的比特币私钥仅仅只有几百万个,在理想的随机生成方式下(用密码学安全的随机数生成器),如果说你所生成的私钥恰好跟别人的私钥一样,这种概率也是几乎为0。

私钥的本质就是个随机数。只不过这个数字是固定长度的,且是16进制的表现形式。

地址

大家都知道,公钥跟私钥是成对的,公钥由私钥计算而来,地址则通过公钥进一步计算而来。要注意:地址不是公钥,不要混淆概念。总之,有了私钥,就可以推导出它的公钥以及地址,地址一般长这样:1GczaiHaFpmucRHRe88RXCAVZ6gTZAoS57(这是我的比特币地址,打赏可以转我比特币试试:)),一般长34位,由私钥可以根据算法推导出地址,但有地址不能逆推出私钥,这是比特币所使用的密码学的根基逻辑。

所以,地址可以随便公开,而私钥则万万不能公开。

助记词(Mnemonic Phrase)

由于私钥64位,长得太难看,没有可读性,而私钥的备份在电脑上复制起来容易,手抄下来就比较麻烦,但私钥保存在联网的电脑上毕竟不安全,有被其他人看到的风险,于是有了助记词工具,利用某种算法可以将64位私钥转换成十多个常见的英文单词,这些单词都来源于一个固定词库,根据一定算法得来。私钥与助记词之间的转换是互通的,助记词只是你的私钥的另一种外貌体现。

助记词一般会在你创建新钱包的时候出现一次,后面就再也不会出现了,所以创建新钱包时最好把助记词抄下来、甚至放到保险柜里,自己想办法备份。如果是屏幕截图或保存在电脑里,只要设备联网都有被第三只眼看见的风险。别人获取了你的助记词等于获取了私钥所对应的一切财富。

简而言之:助记词就等于私钥,私钥就是钱,都千万不能见光。

05/11/2018 08:02 上午 posted in  BlockChain

NSObject Class 浅析

Objective-C中有两个NSObject,一个是NSObject类,另一个是NSObject协议。而其中NSObject类采用了NSObject协议。在本文中,我们主要整理一下NSObject类的使用。

说到NSObject类,写Objective-C的人都应该知道它。它是大部分Objective-C类继承体系的根类。这个类提供了一些通用的方法,对象通过继承NSObject,可以从其中继承访问运行时的接口,并让对象具备Objective-C对象的基本能力。以下我们就来看看NSObejct提供给我们的一些基础功能。

+load与+initialize

这两个方法可能平时用得比较少,但很有用。在我们的程序编译后,类相关的数据结构会保留在目标文件中,在程序运行后会被解析和使用,此时类的信息会经历加载和初始化两个过程。在这两个过程中,会分别调用类的load方法和initialize方法,在这两个方法中,我们可以适当地做一些定制处理。不当是类本身,类的分类也会经历这两个过程。对于一个类,我们可以在类的定义中重写这两个方法,也可以在分类中重写它们,或者同时重写。

load方法

对于load方法,当Objective-C运行时加载类或分类时,会调用这个方法;通常如果我们有一些类级别的操作需要在加载类时处理,就可以放在这里面,如为一个类执行Swizzling Method操作。

load消息会被发送到动态加载和静态链接的类和分类里面。不过,只有当我们在类或分类里面实现这个方法时,类/分类才会去调用这个方法。

在类继承体系中,load方法的调用顺序如下:

一个类的load方法会在其所有父类的load方法之后调用
分类的load方法会在对应类的load方法之后调用
在load的实现中,如果使用同一库中的另外一个类,则可能是不安全的,因为可能存在的情况是另外一个类的load方法还没有运行,即另一个类可能尚未被加载。另外,在load方法里面,我们不需要显示地去调用[super load],因为父类的load方法会自动被调用,且在子类之前。

在有依赖关系的两个库中,被依赖的库中的类其load方法会优先调用。但在库内部,各个类的load方法的调用顺序是不确定的。

initialize方法

当我们在程序中向类或其任何子类发送第一条消息前,runtime会向该类发送initialize消息。runtime会以线程安全的方式来向类发起initialize消息。父类会在子类之前收到这条消息。父类的initialize实现可能在下面两种情况下被调用:

子类没有实现initialize方法,runtime将会调用继承而来的实现
子类的实现中显示的调用了[super initialize]
如果我们不想让某个类中的initialize被调用多次,则可以像如下处理:

+ (void)initialize { if (self == [ClassName self]) { // ... do the initialization ... } }

因为initialize是以线程安全的方式调用的,且在不同的类中initialize被调用的顺序是不确定的,所以在initialize方法中,我们应该做少量的必须的工作。特别需要注意是,如果我们initialize方法中的代码使用了锁,则可能会导致死锁。因此,我们不应该在initialize方法中实现复杂的初始化工作,而应该在类的初始化方法(如-init)中来初始化。

另外,每个类的initialize只会被调用一次。所以,如果我们想要为类和类的分类实现单独的初始化操作,则应该实现load方法。

如果想详细地了解这两个方法的使用,可以查看《Effective Objective-C 2.0》的第51条,里面有非常详细的说明。如果想更深入地了解这两个方法的调用,则可以参考objc库的源码,另外,NSObject的load和initialize方法一文从源码层面为我们简单介绍了这两个方法。

对象的生命周期

一说到对象的创建,我们会立即想到[[NSObject alloc] init]这种经典的两段式构造。对于这种两段式构造,唐巧大神在他的”谈ObjC对象的两段构造模式“一文中作了详细描述,大家可以参考一下。

本小节我们主要介绍一下与对象生命周期相关的一些方法。

对象分配

NSObject提供的对象分配的方法有alloc和allocWithZone:,它们都是类方法。这两个方法负责创建对象并为其分配内存空间,返回一个新的对象实例。新的对象的isa实例变量使用一个数据结构来初始化,这个数据结构描述了对象的信息;创建完成后,对象的其它实例变量被初始化为0。

alloc方法的定义如下:

+ (instancetype)alloc

而allocWithZone:方法的存在是由历史原因造成的,它的调用基本上和alloc是一样的。既然是历史原因,我们就不说了,官方文档只给了一句话:

This method exists for historical reasons; memory zones are no longer used by Objective-C.
我们只需要知道alloc方法的实现调用了allocWithZone:方法。

对象初始化

我们一般不去自己重写alloc或allocWithZone:方法,不用去关心对象是如何创建、如何为其分配内存空间的;我们更关心的是如何去初始化这个对象。上面提到了,对象创建后,isa以外的实例变量都默认初始化为0。通常,我们希望将这些实例变量初始化为我们期望的值,这就是init方法的工作了。

NSObject类默认提供了一个init方法,其定义如下:

1

  • (instancetype)init
    正常情况下,它会初始化对象,如果由于某些原因无法完成对象的创建,则会返回nil。注意,对象在使用之前必须被初始化,否则无法使用。不过,NSObject中定义的init方法不做任何初始化操作,只是简单地返回self。

当然,我们定义自己的类时,可以提供自定义的初始化方法,以满足我们自己的初始化需求。需要注意的就是子类的初始化方法需要去调用父类的相应的初始化方法,以保证初始化的正确性。

讲完两段式构造的两个部分,有必要来讲讲NSObject类的new方法了。

new方法实际上是集alloc和init于一身,它创建了对象并初始化了对象。它的实现如下:

+ (instancetype)new {
	 return [[self alloc] init]; 
}

new方法更多的是一个历史遗留产物,它源于NeXT时代。如果我们的初始化操作只是调用[[self alloc] init]时,就可以直接用new来代替。不过如果我们需要使用自定义的初始化方法时,通常就使用两段式构造方式。

拷贝

说到拷贝,相信大家都很熟悉。拷贝可以分为“深拷贝”和“浅拷贝”。深拷贝拷贝的是对象的值,两个对象相互不影响,而浅拷贝拷贝的是对象的引用,修改一个对象时会影响到另一个对象。

在Objective-C中,如果一个类想要支持拷贝操作,则需要实现NSCopying协议,并实现copyWithZone:【注意:NSObject类本身并没有实现这个协议】。如果一个类不是直接继承自NSObject,则在实现copyWithZone:方法时需要调用父类的实现。

虽然NSObject自身没有实现拷贝协议,不过它提供了两个拷贝方法,如下:

1

  • (id)copy
    这个是拷贝操作的便捷方法。它的返回值是NSCopying协议的copyWithZone:方法的返回值。如果我们的类没有实现这个方法,则会抛出一个异常。

与copy对应的还有一个方法,即:

1

  • (id)mutableCopy
    从字面意义来讲,copy可以理解为不可变拷贝操作,而mutableCopy可以理解为可变操作。这便引出了拷贝的另一个特性,即可变性。

顾名思义,不可变拷贝即拷贝后的对象具有不可变属性,可变拷贝后的对象具有可变属性。这对于数组、字典、字符串、URL这种分可变和不可变的对象来说是很有意义的。我们来看如下示例:

NSMutableArray *mutableArray = [NSMutableArray array]; 
NSMutableArray *array = [mutableArray copy]; 
[array addObject:@"test1"];

实际上,这段代码是会崩溃的,我们来看看崩溃日志:

-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x100107070 
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x100107070'

从中可以看出,经过copy操作,我们的array实际上已经变成不可变的了,其底层元类是__NSArrayI。这个类是不支持addObject:方法的。

偶尔在代码中,也会看到类似于下面的情况:

@property (copy) NSMutableArray *array;

这种属性的声明方式是有问题的,即上面提到的可变性问题。使用self.array = **赋值后,数组其实是不可变的,所以需要特别注意。

mutableCopy的使用也挺有意思的,具体的还请大家自己去试验一下。

释放

当一个对象的引用计数为0时,系统就会将这个对象释放。此时runtime会自动调用对象的dealloc方法。在ARC环境下,我们不再需要在此方法中去调用[super dealloc]了。我们重写这个方法主要是为了释放对象中用到的一些资源,如我们通过C方法分配的内存空间。dealloc方法的定义如下:

- (void)dealloc

需要注意的是,我们不应该直接去调用这个方法。这些事都让runtime去做吧。

消息发送

Objective-C中对方法的调用并不是像C++里面那样直接调用,而是通过消息分发机制来实现的。这个机制核心的方法是objc_msgSend函数。消息机制的具体实现我们在此不做讨论,可以参考Objective-C Runtime 运行时之三:方法与消息

对于消息的发送,除了使用[obj method]这种机制之外,NSObject类还提供了一系列的performSelector**方法。这些方法可以让我们更加灵活地控制方法的调用。接下来我们就来看看这些方法的使用。

在线程中调用方法

如果我们想在当前线程中调用一个方法,则可以使用以下两个方法:

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay 
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes

这两个方法会在当前线程的Run loop中设置一个定时器,以在delay指定的时间之后执行aSelector。如果我们希望定时器运行在默认模式(NSDefaultRunLoopMode)下,可以使用前一个方法;如果想自己指定Run loop模式,则可以使用后一个方法。

当定时器启动时,线程会从Run loop的队列中获取到消息,并执行相应的selector。如果Run loop运行在指定的模式下,则方法会成功调用;否则,定时器会处于等待状态,直到Run loop运行在指定模式下。

需要注意的是,调用这些方法时,Run loop会保留方法接收者及相关的参数的引用(即对这些对象做retain操作),这样在执行时才不至于丢失这些对象。当方法调用完成后,Run loop会调用这些对象的release方法,减少对象的引用计数。

如果我们想在主线程上执行某个对象的方法,则可以使用以下两个方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait 
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array

我们都知道,iOS中所有的UI操作都需要在主线程中处理。如果想在某个二级线程的操作完成之后做UI操作,就可以使用这两个方法。

这两个方法会将消息放到主线程Run loop的队列中,前一个方法使用的是NSRunLoopCommonModes运行时模式;如果想自己指定运行模式,则使用后一个方法。方法的执行与之前的两个performSelector方法是类似的。当在一个线程中多次调用这个方法将不同的消息放入队列时,消息的分发顺序与入队顺序是一致的。

方法中的wait参数指定当前线程在指定的selector在主线程执行完成之后,是否被阻塞住。如果设置为YES,则当前线程被阻塞。如果当前线程是主线程,而该参数也被设置为YES,则消息会被立即发送并处理。

另外,这两个方法分发的消息不能被取消。

如果我们想在指定的线程中分发某个消息,则可以使用以下两个方法:

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait 
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array

这两个方法基本上与在主线程的方法差不多。在此就不再讨论。

如果想在后台线程中调用接收者的方法,可以使用以下方法:

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

这个方法会在程序中创建一个新的线程。由aSelector表示的方法必须像程序中的其它新线程一样去设置它的线程环境。

当然,我们经常看到的performSelector系列方法中还有几个方法,即:

- (id)performSelector:(SEL)aSelector 
- (id)performSelector:(SEL)aSelector withObject:(id)anObject 
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

不过这几个方法是在NSObject协议中定义的,NSObject类实现了这个协议,也就定义了相应的实现。这个我们将在NSObject协议中来介绍。

取消方法调用请求

对于使用performSelector:withObject:afterDelay:方法(仅限于此方法)注册的执行请求,在调用发生前,我们可以使用以下两个方法来取消:

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget 
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument

前一个方法会取消所以接收者为aTarget的执行请求,不过仅限于当前run loop,而不是所有的。

后一个方法则会取消由aTarget、aSelector和anArgument三个参数指定的执行请求。同样仅限于当前run loop。

消息转发及动态解析方法

当一个对象能接收一个消息时,会走正常的方法调用流程。但如果一个对象无法接收一个消息时,就会走消息转发机制。

消息转发机制基本上分为三个步骤:

动态方法解析
备用接收者
完整转发
具体流程可参考Objective-C Runtime 运行时之三:方法与消息,《Effective Objective-C 2.0》一书的第12小节也有详细描述。在此我们只介绍一下NSObject类为实现消息转发提供的方法。

首先,对于动态方法解析,NSObject提供了以下两个方法来处理:

+ (BOOL)resolveClassMethod:(SEL)name 
+ (BOOL)resolveInstanceMethod:(SEL)name

从方法名我们可以看出,resolveClassMethod:是用于动态解析一个类方法;而resolveInstanceMethod:是用于动态解析一个实例方法。

我们知道,一个Objective-C方法是其实是一个C函数,它至少带有两个参数,即self和_cmd。我们使用class_addMethod函数,可以给类添加一个方法。我们以resolveInstanceMethod:为例,如果要给对象动态添加一个实例方法,则可以如下处理:

void dynamicMethodIMP(id self, SEL _cmd) { 
	// implementation .... 
} 
+ (BOOL) resolveInstanceMethod:(SEL)aSEL { 
if (aSEL == @selector(resolveThisMethodDynamically)) { 
	class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); 
	return YES; 
	} 
return [super resolveInstanceMethod:aSel]; 
}

其次,对于备用接收者,NSObject提供了以下方法来处理:

- (id)forwardingTargetForSelector:(SEL)aSelector

该方法返回未被接收消息最先被转发到的对象。如果一个对象实现了这个方法,并返回一个非空的对象(且非对象本身),则这个被返回的对象成为消息的新接收者。另外如果在非根类里面实现这个方法,如果对于给定的selector,我们没有可用的对象可以返回,则应该调用父类的方法实现,并返回其结果。

最后,对于完整转发,NSObject提供了以下方法来处理

- (void)forwardInvocation:(NSInvocation *)anInvocation

当前面两步都无法处理消息时,运行时系统便会给接收者最后一个机会,将其转发给其它代理对象来处理。这主要是通过创建一个表示消息的NSInvocation对象并将这个对象当作参数传递给forwardInvocation:方法。我们在forwardInvocation:方法中可以选择将消息转发给其它对象。

在这个方法中,主要是需要做两件事:

找到一个能处理anInvocation调用的对象。
将消息以anInvocation的形式发送给对象。anInvocation将维护调用的结果,而运行时则会将这个结果返回给消息的原始发送者。
这一过程如下所示:

- (void)forwardInvocation:(NSInvocation *)invocation { 
	SEL aSelector = [invocation selector]; 
	if ([friend respondsToSelector:aSelector]) 
		[invocation invokeWithTarget:friend]; 
	else 
	[super forwardInvocation:invocation]; 
}

当然,对于一个非根类,如果还是无法处理消息,则应该调用父类的实现。而NSObject类对于这个方法的实现,只是简单地调用了doesNotRecognizeSelector:。它不再转发任何消息,而是抛出一个异常。doesNotRecognizeSelector:的声明如下:

- (void)doesNotRecognizeSelector:(SEL)aSelector

运行时系统在对象无法处理或转发一个消息时会调用这个方法。这个方法引发一个NSInvalidArgumentException异常并生成一个错误消息。

任何doesNotRecognizeSelector:消息通常都是由运行时系统来发送的。不过,它们可以用于阻止一个方法被继承。例如,一个NSObject的子类可以按以下方式来重写copy或init方法以阻止继承:

- (id)copy { 
	[self doesNotRecognizeSelector:_cmd]; 
}

这段代码阻止子类的实例响应copy消息或阻止父类转发copy消息—虽然respondsToSelector:仍然报告接收者可以访问copy方法。

当然,如果我们要重写doesNotRecognizeSelector:方法,必须调用super的实现,或者在实现的最后引发一个NSInvalidArgumentException异常。它代表对象不能响应消息,所以总是应该引发一个异常。

获取方法信息

在消息转发的最后一步中,forwardInvocation:参数是一个NSInvocation对象,这个对象需要获取方法签名的信息,而这个签名信息就是从methodSignatureForSelector:方法中获取的。

该方法的声明如下:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

这个方法返回包含方法描述信息的NSMethodSignature对象,如果找不到方法,则返回nil。如果我们的对象包含一个代理或者对象能够处理它没有直接实现的消息,则我们需要重写这个方法来返回一个合适的方法签名。

对应于实例方法,当然还有一个处理类方法的相应方法,其声明如下:

+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector

另外,NSObject类提供了两个方法来获取一个selector对应的方法实现的地址,如下所示:

- (IMP)methodForSelector:(SEL)aSelector 
+ (IMP)instanceMethodForSelector:(SEL)aSelector

获取到了方法实现的地址,我们就可以直接将IMP以函数形式来调用。

对于methodForSelector:方法,如果接收者是一个对象,则aSelector应该是一个实例方法;如果接收者是一个类,则aSelector应该是一个类方法。

对于instanceMethodForSelector:方法,其只是向类对象索取实例方法的实现。如果接收者的实例无法响应aSelector消息,则产生一个错误。

测试类

对于类的测试,在NSObject类中定义了两个方法,其中类方法instancesRespondToSelector:用于测试接收者的实例是否响应指定的消息,其声明如下:

1

  • (BOOL)instancesRespondToSelector:(SEL)aSelector
    如果aSelector消息被转发到其它对象,则类的实例可以接收这个消息而不会引发错误,即使该方法返回NO。

为了询问类是否能响应特定消息(注意:不是类的实例),则使用这个方法,而不使用NSObject协议的实例方法respondsToSelector:。

NSObject还提供了一个方法来查看类是否采用了某个协议,其声明如下:

+ (BOOL)conformsToProtocol:(Protocol *)aProtocol

如果一个类直接或间接地采用了一个协议,则我们可以说这个类实现了该协议。我们可以看看以下这个例子:

@protocol AffiliationRequests <Joining> 
@interface MyClass : NSObject <AffiliationRequests, Normalization> 
BOOL canJoin = [MyClass conformsToProtocol:@protocol(Joining)];

通过继承体系,MyClass类实现了Joining协议。

不过,这个方法并不检查类是否实现了协议的方法,这应该是程序员自己的职责了。

识别类

NSObject类提供了几个类方法来识别一个类,首先是我们常用的class类方法,该方法声明如下:

+ (Class)class

该方法返回类对象。当类是消息的接收者时,我们只通过类的名称来引用一个类。在其它情况下,类的对象必须通过这个方法类似的方法(-class实例方法)来获取。如下所示:

BOOL test = [self isKindOfClass:[SomeClass class]];

NSObject还提供了superclass类方法来获取接收者的父类,其声明如下:

+ (Class)superclass

另外,我们还可以使用isSubclassOfClass:类方法查看一个类是否是另一个类的子类,其声明如下:

1

  • (BOOL)isSubclassOfClass:(Class)aClass
    描述类

描述类是使用description方法,它返回一个表示类的内容的字符串。其声明如下:

1

  • (NSString *)description
    我们在LLDB调试器中打印类的信息时,使用的就是这个方法。

当然,如果想打印类的实例的描述时,使用的是NSObject协议中的实例方法description,我们在此不多描述。

归档操作

一说到归档操作,你会首先想到什么呢?我想到的是NSCoding协议以及它的两个方法:

initWithCoder:和encodeWithCoder:。如果我们的对象需要支持归档操作,则应该采用这个协议并提供两个方法的具体实现。

在编码与解码的过程中,一个编码器会调用一些方法,这些方法允许将对象编码以替代一个更换类或实例本身。这样,就可以使得归档在不同类层次结构或类的不同版本的实现中被共享。例如,类簇能有效地利用这一特性。这一特性也允许每个类在解码时应该只维护单一的实例来执行这一策略。

NSObject类虽然没有采用NSCoding协议,但却提供了一些替代方法,以支持上述策略。这些方法分为两类,即通用和专用的。

通用方法由NSCoder对象调用,主要有如下几个方法和属性:

@property(readonly) Class classForCoder 
- (id)replacementObjectForCoder:(NSCoder *)aCoder 
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder

专用的方法主要是针对NSKeyedArchiver对象的,主要有如下几个方法和属性:

@property(readonly) Class classForKeyedArchiver 
+ (NSArray *)classFallbacksForKeyedArchiver 
+ (Class)classForKeyedUnarchiver 
- (id)replacementObjectForKeyedArchiver:(NSKeyedArchiver *)archiver

子类在归档的过程中如果有特殊的需求,可以重写这些方法。这些方法的具体描述,可以参考官方文档。

在解码或解档过程中,有一点需要考虑的就是对象所属类的版本号,这样能确保老版本的对象能被正确地解析。NSObject类对此提供了两个方法,如下所示:

+ (void)setVersion:(NSInteger)aVersion 
+ (NSInteger)version

它们都是类方法。默认情况下,如果没有设置版本号,则默认是0.

总结

NSObject类是Objective-C中大部分类层次结构中的根类,并为我们提供了很多功能。了解这些功能更让我们更好地发挥Objective-C的特性。

04/20/2018 19:39 下午 posted in  apple

Block的复制时机以及几种类型

什么时候栈上的Block会被复制到堆呢?

  1. 调用block的copy函数时;
  2. Block作为函数返回值返回时;
  3. 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时;
  4. 方法中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时;
    在 ARC 中,捕获外部了变量的 block 的类会是 NSMallocBlock 或者 NSStackBlock,如果 block 被赋值给了某个变量在这个过程中会执行 _Block_copy 将原有的 NSStackBlock 变成 NSMallocBlock;但是如果 block 没有被赋值给某个变量,那它的类型就是 NSStackBlock;没有捕获外部变量的 block 的类会是 NSGlobalBlock 即不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。
    在非 ARC 中,捕获了外部变量的 block 的类会是 NSStackBlock,放置在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。

block中的isa指向的是该block的Class。在block runtime中,定义了6种类:
_NSConcreteStackBlock 栈上创建的block
_NSConcreteMallocBlock 堆上创建的block
_NSConcreteGlobalBlock 作为全局变量的block
_NSConcreteWeakBlockVariable
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock

其中我们能接触到的主要是前3种,后三种用于GC不再讨论..
当struct第一次被创建时,它是存在于该函数的栈帧上的,其Class是固定的_NSConcreteStackBlock。其捕获的变量是会赋值到结构体的成员上,所以当block初始化完成后,捕获到的变量不能更改。

当函数返回时,函数的栈帧被销毁,这个block的内存也会被清除。所以在函数结束后仍然需要这个block时,就必须用Block_copy()方法将它拷贝到堆上。这个方法的核心动作很简单:申请内存,将栈数据复制过去,将Class改一下,最后向捕获到的对象发送retain,增加block的引用计数。

04/12/2018 09:34 上午 posted in  GCD

关于CLASS , SEL, IMP的说明

cocoa当中的函数调用,是一种以消息的方式进行的函数调用,这一点与C++,java是有很大差别的。因此该类型的理解,会涉及到三个重要的概念,class,sel,IMP。

##class
每个NSObject的第一个成员变量都是class类型的成员,isa,这个isa的对象可以访问到本类的父类,也可以访问到本类的所有方法的列表。

##SEL
这个是方法名称的描述。

##IMP
这个是具体的方法的地址。

##Class 的含义

Class 被定义为一个指向 objc_class的结构体指针,这个结构体表示每一个类的类结构。而 objc_class 在objc/objc_class.h中定义如下:

struct objc_class {
    struct objc_class super_class;  /*父类*/
    const char *name;                 /*类名字*/
    long version;                   /*版本信息*/
    long info;                        /*类信息*/
    long instance_size;               /*实例大小*/
    struct objc_ivar_list *ivars;     /*实例参数链表*/
    struct objc_method_list **methodLists;  /*方法链表*/
    struct objc_cache *cache;               /*方法缓存*/
    struct objc_protocol_list *protocols;   /*协议链表*/
};

由此可见,Class 是指向类结构体的指针,该类结构体含有一个指向其父类类结构的指针,该类方法的链表,该类方法的缓存以及其他必要信息。

NSObject 的class 方法就返回这样一个指向其类结构的指针。每一个类实例对象的第一个实例变量是一个指向该对象的类结构的指针,叫做isa。通过该指针,对象可以访问它对应的类以及相应的父类。如图一所示:

如图一所示,圆形所代表的实例对象的第一个实例变量为 isa,它指向该类的类结构 The object’s class。而该类结构有一个指向其父类类结构的指针superclass, 以及自身消息名称(selector)/实现地址(address)的方法链表。

##方法的含义:

注意这里所说的方法链表里面存储的是Method 类型的。图一中selector 就是指 Method的 SEL, address就是指Method的 IMP。 Method 在头文件 objc_class.h中定义如下:

typedef struct objc_method *Method;
typedef struct objc_ method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
};

一个方法 Method,其包含一个方法选标 SEL – 表示该方法的名称,一个types – 表示该方法参数的类型,一个 IMP - 指向该方法的具体实现的函数指针。

##SEL 的含义:

在前面我们看到方法选标 SEL 的定义为:

typedef struct objc_selector   *SEL;   

它是一个指向 objc_selector 指针,表示方法的名字/签名。如下所示,打印出 selector。

-(NSInteger)maxIn:(NSInteger)a theOther:(NSInteger)b{
    return (a > b) ? a : b;
}
NSLog(@"SEL=%s", @selector(maxIn:theOther:));

输出:SEL=maxIn:theOther:

不同的类可以拥有相同的 selector,这个没有问题,因为不同类的实例对象performSelector相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector去查询,我们才能确定具体的执行代码。

##IMP 的含义:

在前面我们也看到 IMP 的定义为:

typedef id (*IMP)(id, SEL, ...);

根据前面id 的定义,我们知道 id是一个指向 objc_object 结构体的指针,该结构体只有一个成员isa,所以任何继承自 NSObject 的类对象都可以用id 来指代,因为 NSObject 的第一个成员实例就是isa。

至此,我们就很清楚地知道 IMP 的含义:IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。

NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。

下面的例子展示了怎么使用指针来调用setFilled:的方法实现:

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
 
for (i = 0; i < 1000; i++)
    setter(targetList[i], @selector(setFilled:), YES);

使用methodForSelector:来避免动态绑定将减少大部分消息的开销,但是这只有在指定的消息被重复发送很多次时才有意义,例如上面的for循环。

注意,methodForSelector:是Cocoa运行时系统的提供的功能,而不是Objective-C语言本身的功能。

几个重要的辅助函数,可以在使用过程中起到很好的辅助作用,尤其是在动态编译等起到了比较大的作用。

我们可以通过NSObject的一些方法获取运行时信息或动态执行一些消息:

class 返回对象的类;

isKindOfClass 和 isMemberOfClass检查对象是否在指定的类继承体系中;

respondsToSelector 检查对象能否相应指定的消息;

conformsToProtocol 检查对象是否实现了指定协议类的方法;

methodForSelector 返回指定方法实现的地址。

performSelector:withObject 执行SEL 所指代的方法。

04/12/2018 09:07 上午 posted in  apple