Question: Just like the question title

for say I have code like

```
func receieveNotification(notification : NSNotification) {

    ......verify notification
    ......retrieve userInfo

}

```

Do I still need to add observer to NSNotificationCenter.defaultCenter() ?
If I do. How to?

Answer: 
You are terminating your program too early. To test this out, add a harmless `time.sleep(10)` in the `cracker` method and observe your program still terminating within a second.

Call [`join`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool.join) to wait for the pool to finish:

```
pool = mp.Pool()
for i in open(sys.argv[2], 'r', errors='ignore'):
    pswd = bytes(i.strip('\n'), 'utf-8')
    pool.apply_async(cracker, (pswd,))

pool.close()  # Indicate that no more data is coming
pool.join()   # Wait for pool to finish processing

runtime =  round(time.time() - startime, 5)
print ("[*] Runtime:", runtime, 'seconds')
sys.exit(0)

```

Additionally, once you find the right password, calling `close` just indicates that no more future tasks are coming - all tasks already submitted will still be done. Instead, call [`terminate`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool.terminate) to kill the pool without processing any more tasks.

Furthermore, depending on the implementation details of `multiprocessing.Pool`, the global variable `pool` may not be available when you need it (and its value isn't serializable anyways). To solve this problem, you can use a callback, as in

```
def cracker(password):
    try:
        zFile.extractall(pwd=password)
    except RuntimeError:
        return
    return password

 def callback(found):
     if found:
         pool.terminate()
 ...
 pool.apply_async(cracker, (pswd,), callback=cb)

```

Of course, since you now look at the result all the time, `apply` is not the right way to go. Instead, you can write your code using [`imap_unordered`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool.imap_unordered):

```
with open(sys.argv[2], 'r', errors='ignore') as passf, \
         multiprocessing.Pool() as pool:
     passwords = (line.strip('\n').encode('utf-8') for line in passf)
     for found in pool.imap_unordered(cracker, passwords):
         if found:
             break

```

Instead of using globals, you may also want to open the zip file (and create a `ZipFile` object) in each process, by using an [`initializer`](https://docs.python.org/dev/library/multiprocessing.html#multiprocessing.pool.Pool) for the pool. Even better (and way faster), forgo all of the I/O in the first place and read just the bytes you need once and then pass them on to the children.
Here's an implementation of the advice from [@phihag's](https://stackoverflow.com/a/31350837/4279) and [@Equality 7-2521's answers](https://stackoverflow.com/a/31351538/4279):

```
#!/usr/bin/env python3
"""Brute force zip password.

Usage: brute-force-zip-password <zip archive> <passwords>
"""
import sys
from multiprocessing import Pool
from time import monotonic as timer
from zipfile import ZipFile

def init(archive): # run at the start of a worker process
    global zfile
    zfile = ZipFile(open(archive, 'rb')) # open file in each process once

def check(password):
    assert password
    try:
        with zfile.open(zfile.infolist()[0], pwd=password):
            return password # assume success
    except Exception as e:
        if e.args[0] != 'Bad password for file':
            # assume all other errors happen after the password was accepted
            raise RuntimeError(password) from e

def main():
    if len(sys.argv) != 3:
        sys.exit(__doc__) # print usage

    start = timer()
    # decode passwords using the preferred locale encoding
    with open(sys.argv[2], errors='ignore') as file, \
         Pool(initializer=init, initargs=[sys.argv[1]]) as pool: # use all CPUs
        # check passwords encoded using utf-8
        passwords = (line.rstrip('\n').encode('utf-8') for line in file)
        passwords = filter(None, passwords) # filter empty passwords
        for password in pool.imap_unordered(check, passwords, chunksize=100):
            if password is not None:  # found
                print("Password: '{}'".format(password.decode('utf-8')))
                break
        else:
            sys.exit('Unable to find password')
    print('Runtime: %.5f seconds' % (timer() - start,))

if __name__=="__main__":
    main()

```

Note:

* each worker process has its own `ZipFile` object and the zip file is opened once per process: it should make it more portable (Windows support) and improve time performance
* the content is not extracted: `check(password)` tries to open and immediately closes an archive member on success: it is safer and it should improve time performance (no need to create directories, etc)
* all errors except `'Bad password for file'` while decrypting the archive member are assumed to happen *after* the password is accepted: the rational is to avoid silencing unexpected errors -- each exception should be considered individually
* `check(password)` expects nonempty passwords
* `chunksize` parameter may drastically improve performance
* a rare `for`/`else` syntax is used, to report cases when the password is not found
* the `with`-statement calls `pool.terminate()` for you